juegos

Juegos de terminal de Pancho: Catan (TUI, GUI, web, servidor, PicoCalc), ajedrez, calculadora y minijuegos
git clone https://git.lu3dhn.xyz/juegos.git
Log | Files | Refs

app.c (13465B)


      1 /* app.c - buscaminas para la PicoCalc.
      2  *
      3  * Flechas mueven el cursor, Enter/espacio destapa (sobre un numero con sus banderas
      4  * completas destapa los vecinos), F o Tab pone bandera, Esc pausa.
      5  * La primera casilla nunca es mina: las minas se reparten despues del primer toque. */
      6 #include <string.h>
      7 #include "mini.h"
      8 
      9 enum { MAXW = 20, MAXH = 18, TOP = 28 };
     10 
     11 typedef struct { const char *name; int w, h, mines, cell; } Level;
     12 static const Level LEVELS[] = {
     13     { "Fácil",   9,  9, 10, 30 },
     14     { "Medio",  16, 16, 40, 17 },
     15     { "Difícil", 20, 18, 72, 15 },
     16 };
     17 enum { NLEVELS = 3 };
     18 
     19 enum { PLAYING, WON, LOST };
     20 
     21 typedef struct {
     22     const Level *lv;
     23     int lvl;
     24     uint8_t mine[MAXH][MAXW], adj[MAXH][MAXW], open[MAXH][MAXW], flag[MAXH][MAXW];
     25     int cx, cy;
     26     int state, opened, flags;
     27     bool placed;                  /* ya se repartieron las minas */
     28     int boom_x, boom_y;
     29     uint32_t t0, elapsed;         /* reloj: corre desde el primer toque */
     30     bool record;
     31 } Game;
     32 
     33 static Game G;
     34 
     35 /* ------------------------------------------------------------ reglas */
     36 
     37 static bool inside(int x, int y) { return x >= 0 && y >= 0 && x < G.lv->w && y < G.lv->h; }
     38 
     39 static void place_mines(int sx, int sy)
     40 {
     41     static uint8_t idx[MAXW * MAXH];
     42     int n = 0;
     43     for (int y = 0; y < G.lv->h; y++)
     44         for (int x = 0; x < G.lv->w; x++)
     45             if (x < sx - 1 || x > sx + 1 || y < sy - 1 || y > sy + 1) idx[n++] = (uint8_t)(y * MAXW + x);
     46     /* mezcla parcial: las primeras "mines" posiciones */
     47     for (int i = 0; i < G.lv->mines; i++) {
     48         int j = i + mini_rand(n - i);
     49         uint8_t t = idx[i]; idx[i] = idx[j]; idx[j] = t;
     50         G.mine[idx[i] / MAXW][idx[i] % MAXW] = 1;
     51     }
     52     for (int y = 0; y < G.lv->h; y++)
     53         for (int x = 0; x < G.lv->w; x++) {
     54             int c = 0;
     55             for (int dy = -1; dy <= 1; dy++)
     56                 for (int dx = -1; dx <= 1; dx++)
     57                     if (inside(x + dx, y + dy) && G.mine[y + dy][x + dx]) c++;
     58             G.adj[y][x] = (uint8_t)c;
     59         }
     60     G.placed = true;
     61     G.t0 = hal_ms();
     62 }
     63 
     64 static void new_game(int lvl)
     65 {
     66     memset(&G, 0, sizeof G);
     67     G.lvl = lvl;
     68     G.lv = &LEVELS[lvl];
     69     G.cx = G.lv->w / 2;
     70     G.cy = G.lv->h / 2;
     71 }
     72 
     73 static void end_game(bool won)
     74 {
     75     G.state = won ? WON : LOST;
     76     G.elapsed = hal_ms() - G.t0;
     77     char k[16];
     78     int p = str_put(k, sizeof k, 0, "jug");
     79     str_int(k, sizeof k, p, G.lvl);
     80     mini_set(k, mini_get(k, 0) + 1);
     81     if (won) {
     82         k[0] = 'g'; k[1] = 'a'; k[2] = 'n';
     83         mini_set(k, mini_get(k, 0) + 1);
     84         p = str_put(k, sizeof k, 0, "rec");
     85         str_int(k, sizeof k, p, G.lvl);
     86         long best = mini_get(k, 0);
     87         if (!best || (long)G.elapsed < best) { mini_set(k, (long)G.elapsed); G.record = true; }
     88         for (int y = 0; y < G.lv->h; y++)            /* banderas en las que quedaron */
     89             for (int x = 0; x < G.lv->w; x++)
     90                 if (G.mine[y][x]) G.flag[y][x] = 1;
     91         G.flags = G.lv->mines;
     92     }
     93     mini_save();
     94 }
     95 
     96 /* Destapa (x, y) y, si es un cero, todo su alrededor (con una pila, sin recursion). */
     97 static void reveal(int x, int y)
     98 {
     99     static uint16_t stack[MAXW * MAXH];
    100     int sp = 0;
    101     if (G.open[y][x] || G.flag[y][x]) return;
    102     if (G.mine[y][x]) {
    103         G.open[y][x] = 1;
    104         G.boom_x = x; G.boom_y = y;
    105         end_game(false);
    106         return;
    107     }
    108     G.open[y][x] = 1; G.opened++;
    109     stack[sp++] = (uint16_t)(y * MAXW + x);
    110     while (sp) {
    111         int v = stack[--sp], px = v % MAXW, py = v / MAXW;
    112         if (G.adj[py][px]) continue;
    113         for (int dy = -1; dy <= 1; dy++)
    114             for (int dx = -1; dx <= 1; dx++) {
    115                 int nx = px + dx, ny = py + dy;
    116                 if (!inside(nx, ny) || G.open[ny][nx] || G.flag[ny][nx]) continue;
    117                 G.open[ny][nx] = 1; G.opened++;
    118                 stack[sp++] = (uint16_t)(ny * MAXW + nx);
    119             }
    120     }
    121 }
    122 
    123 static void act_open(void)
    124 {
    125     int x = G.cx, y = G.cy;
    126     if (!G.placed) place_mines(x, y);
    127     if (G.open[y][x] && G.adj[y][x]) {           /* "acorde": destapar los vecinos */
    128         int f = 0;
    129         for (int dy = -1; dy <= 1; dy++)
    130             for (int dx = -1; dx <= 1; dx++)
    131                 if (inside(x + dx, y + dy) && G.flag[y + dy][x + dx]) f++;
    132         if (f != G.adj[y][x]) return;
    133         for (int dy = -1; dy <= 1 && G.state == PLAYING; dy++)
    134             for (int dx = -1; dx <= 1 && G.state == PLAYING; dx++)
    135                 if (inside(x + dx, y + dy)) reveal(x + dx, y + dy);
    136     } else reveal(x, y);
    137     if (G.state == PLAYING && G.opened == G.lv->w * G.lv->h - G.lv->mines) end_game(true);
    138 }
    139 
    140 static void act_flag(void)
    141 {
    142     if (G.open[G.cy][G.cx]) return;
    143     G.flag[G.cy][G.cx] ^= 1;
    144     G.flags += G.flag[G.cy][G.cx] ? 1 : -1;
    145 }
    146 
    147 /* ------------------------------------------------------------ dibujo */
    148 
    149 static const Color NUMC[9] = {
    150     0, RGB(80, 150, 255), RGB(90, 200, 90), RGB(240, 80, 70), RGB(170, 110, 255),
    151     RGB(230, 150, 40), RGB(60, 200, 200), RGB(230, 230, 230), RGB(150, 150, 150)
    152 };
    153 
    154 static void draw_flag(Surface *s, int x, int y, int c)
    155 {
    156     int px = x + c * 2 / 5, pw = c >= 20 ? 2 : 1;
    157     gfx_rect(s, px, y + c / 5, pw, c * 3 / 5, C_TEXT);                        /* palo */
    158     gfx_rect(s, x + c / 4, y + c * 4 / 5 - pw, c / 2, pw, C_TEXT);               /* base */
    159     int32_t xs[3] = { (px + pw) * 16, (px + pw) * 16, (x + c * 4 / 5) * 16 };
    160     int32_t ys[3] = { (y + c / 5) * 16, (y + c * 11 / 20) * 16, (y + c * 3 / 8) * 16 };
    161     gfx_poly16(s, xs, ys, 3, RGB(240, 60, 40));
    162 }
    163 
    164 static void draw_mine(Surface *s, int x, int y, int c)
    165 {
    166     int r = c / 4;
    167     gfx_line(s, x + c / 2, y + c / 5, x + c / 2, y + c - c / 5, 1, C_BLACK);
    168     gfx_line(s, x + c / 5, y + c / 2, x + c - c / 5, y + c / 2, 1, C_BLACK);
    169     gfx_disc(s, x + c / 2, y + c / 2, r, C_BLACK);
    170     gfx_rect(s, x + c / 2 - r / 2, y + c / 2 - r / 2, r / 2 > 1 ? r / 2 : 1, r / 2 > 1 ? r / 2 : 1, C_WHITE);
    171 }
    172 
    173 static void draw_cell(Surface *s, int x, int y, int px, int py, int c)
    174 {
    175     bool open = G.open[y][x] || (G.state == LOST && G.mine[y][x]);
    176     if (!open) {
    177         gfx_rect(s, px, py, c, c, RGB(90, 104, 132));
    178         gfx_rect(s, px, py, c, 2, RGB(130, 146, 178));
    179         gfx_rect(s, px, py, 2, c, RGB(130, 146, 178));
    180         gfx_rect(s, px, py + c - 2, c, 2, RGB(52, 60, 80));
    181         gfx_rect(s, px + c - 2, py, 2, c, RGB(52, 60, 80));
    182         if (G.flag[y][x]) {
    183             draw_flag(s, px, py, c);
    184             if (G.state == LOST && !G.mine[y][x]) {           /* bandera equivocada */
    185                 gfx_line(s, px + 3, py + 3, px + c - 4, py + c - 4, 2, C_BLACK);
    186                 gfx_line(s, px + c - 4, py + 3, px + 3, py + c - 4, 2, C_BLACK);
    187             }
    188         }
    189         return;
    190     }
    191     bool boom = G.state == LOST && x == G.boom_x && y == G.boom_y;
    192     gfx_rect(s, px, py, c, c, boom ? RGB(220, 40, 30) : RGB(200, 204, 212));
    193     gfx_frame(s, px, py, c, c, RGB(160, 164, 176));
    194     if (G.mine[y][x]) { draw_mine(s, px, py, c); return; }
    195     int n = G.adj[y][x];
    196     if (!n) return;
    197     char t[2] = { (char)('0' + n), 0 };
    198     if (c >= 26) mini_big_text(s, &font_bold, px + (c - 12) / 2, py + (c - 26) / 2, 2, t, NUMC[n]);
    199     else gfx_text(s, &font_bold, px + (c - 6) / 2, py + (c - 13) / 2, t, NUMC[n]);
    200 }
    201 
    202 static uint32_t elapsed(void)
    203 {
    204     if (!G.placed) return 0;
    205     return G.state == PLAYING ? hal_ms() - G.t0 : G.elapsed;
    206 }
    207 
    208 static void paint(Surface *s, void *ctx)
    209 {
    210     const Level *lv = G.lv;
    211     int c = lv->cell, bw = lv->w * c, bh = lv->h * c;
    212     int ox = (320 - bw) / 2, oy = TOP + (280 - bh) / 2;
    213     char buf[40];
    214     int p;
    215 
    216     gfx_rect(s, 0, 0, 320, 320, RGB(16, 20, 30));
    217     gfx_rect(s, 0, 0, 320, 24, C_PANEL);
    218     draw_mine(s, 6, 4, 16);
    219     p = str_int(buf, sizeof buf, 0, lv->mines - G.flags);
    220     gfx_text(s, &font_bold, 26, 6, buf, C_TEXT);
    221     str_time(buf, sizeof buf, 0, elapsed());
    222     gfx_text(s, &font_bold, 314 - gfx_text_w(&font_bold, buf), 6, buf, C_TEXT);
    223     gfx_text_center(s, &font_normal, 60, 6, 200, lv->name, C_ACCENT);
    224 
    225     gfx_rect(s, ox - 3, oy - 3, bw + 6, bh + 6, RGB(40, 46, 60));
    226     for (int y = 0; y < lv->h; y++)
    227         for (int x = 0; x < lv->w; x++)
    228             draw_cell(s, x, y, ox + x * c, oy + y * c, c);
    229     if (G.state == PLAYING) {
    230         gfx_frame(s, ox + G.cx * c, oy + G.cy * c, c, c, C_CURSOR);
    231         gfx_frame(s, ox + G.cx * c + 1, oy + G.cy * c + 1, c - 2, c - 2, C_CURSOR);
    232         gfx_text_center(s, &font_small, 0, 311, 320, "Enter destapar  F bandera  Esc menú", C_DIM);
    233         return;
    234     }
    235     /* cartel de fin */
    236     int bx = 40, by = 130, w = 240, h = 60;
    237     gfx_box(s, bx, by, w, h, C_PANEL, G.state == WON ? C_OK : C_ERR);
    238     gfx_box(s, bx + 1, by + 1, w - 2, h - 2, C_PANEL, G.state == WON ? C_OK : C_ERR);
    239     gfx_text_center(s, &font_bold, bx, by + 8, w, G.state == WON ? "¡Ganaste!" : "¡Boom!",
    240                     G.state == WON ? C_OK : C_ERR);
    241     if (G.state == WON) {
    242         p = str_put(buf, sizeof buf, 0, "Tiempo ");
    243         p = str_time(buf, sizeof buf, p, G.elapsed);
    244         if (G.record) str_put(buf, sizeof buf, p, "  ¡récord!");
    245         gfx_text_center(s, &font_normal, bx, by + 26, w, buf, G.record ? C_ACCENT : C_TEXT);
    246     } else gfx_text_center(s, &font_normal, bx, by + 26, w, "Pisaste una mina", C_TEXT);
    247     gfx_text_center(s, &font_small, bx, by + 45, w, "Enter otra   Esc menú", C_DIM);
    248 }
    249 
    250 /* ------------------------------------------------------------ menus y bucle */
    251 
    252 static void art(Surface *s)
    253 {
    254     /* una tirita de casillas de adorno */
    255     static const int8_t demo[] = { 1, 2, -1, 3, -2, 1, 0 };
    256     int c = 22, x0 = (320 - 7 * c) / 2, y = 60;
    257     for (int i = 0; i < 7; i++) {
    258         int x = x0 + i * c;
    259         if (demo[i] == -1) {
    260             gfx_rect(s, x, y, c, c, RGB(90, 104, 132));
    261             gfx_rect(s, x, y, c, 2, RGB(130, 146, 178));
    262             draw_flag(s, x, y, c);
    263         } else {
    264             gfx_rect(s, x, y, c, c, demo[i] == -2 ? RGB(220, 40, 30) : RGB(200, 204, 212));
    265             gfx_frame(s, x, y, c, c, RGB(160, 164, 176));
    266             if (demo[i] == -2) draw_mine(s, x, y, c);
    267             else if (demo[i]) { char t[2] = { (char)('0' + demo[i]), 0 }; gfx_text(s, &font_bold, x + 8, y + 5, t, NUMC[demo[i]]); }
    268         }
    269     }
    270 }
    271 
    272 static const char *const LEVEL_NAMES[] = { "Fácil", "Medio", "Difícil" };
    273 
    274 static int main_menu(int lvl)
    275 {
    276     static char lines[NLEVELS][48];
    277     Form f;
    278     form_begin(&f, "Buscaminas");
    279     form_button(&f, 1, "Jugar");
    280     form_choice(&f, 2, "Nivel", LEVEL_NAMES, NLEVELS, lvl);
    281     form_label(&f, "");
    282     for (int i = 0; i < NLEVELS; i++) {
    283         char k[8];
    284         int p = str_put(k, sizeof k, 0, "rec");
    285         str_int(k, sizeof k, p, i);
    286         long rec = mini_get(k, 0);
    287         p = str_put(lines[i], sizeof lines[i], 0, LEVEL_NAMES[i]);
    288         p = str_put(lines[i], sizeof lines[i], p, ": récord ");
    289         if (rec) p = str_time(lines[i], sizeof lines[i], p, (uint32_t)rec);
    290         else p = str_put(lines[i], sizeof lines[i], p, "-");
    291         k[0] = 'g'; k[1] = 'a'; k[2] = 'n';
    292         p = str_put(lines[i], sizeof lines[i], p, ", ganadas ");
    293         p = str_int(lines[i], sizeof lines[i], p, mini_get(k, 0));
    294         k[0] = 'j'; k[1] = 'u'; k[2] = 'g';
    295         p = str_put(lines[i], sizeof lines[i], p, " de ");
    296         str_int(lines[i], sizeof lines[i], p, mini_get(k, 0));
    297         form_label(&f, lines[i]);
    298     }
    299     for (;;) {
    300         int r = mini_form(&f);
    301         if (r == 1) return form_get(&f, 2)->value;
    302     }
    303 }
    304 
    305 /* Devuelve true si hay que volver al menu principal. */
    306 static bool pause_menu(void)
    307 {
    308     uint32_t t = hal_ms();
    309     Form f;
    310     form_begin(&f, "Pausa");
    311     form_button(&f, 1, "Seguir");
    312     form_button(&f, 2, "Empezar de nuevo");
    313     form_button(&f, 3, "Menú principal");
    314     int r = mini_form(&f);
    315     G.t0 += hal_ms() - t;                       /* la pausa no cuenta */
    316     if (r == 2) new_game(G.lvl);
    317     return r == 3;
    318 }
    319 
    320 void app_main(void)
    321 {
    322     mini_start("Buscaminas", RGB(120, 170, 255));
    323     mini_art = art;
    324     int lvl = (int)mini_get("nivel", 0);
    325     if (lvl < 0 || lvl >= NLEVELS) lvl = 0;
    326     for (;;) {
    327         int nl = main_menu(lvl);
    328         if (nl != lvl) { lvl = nl; mini_set("nivel", lvl); mini_save(); }
    329         new_game(lvl);
    330         bool dirty = true;
    331         uint32_t shown_sec = 0;
    332         for (;;) {
    333             int k = hal_key();
    334             if (k == 0) dirty = true;
    335             else if (k > 0) {
    336                 dirty = true;
    337                 if (G.state != PLAYING) {
    338                     if (k == K_OK || k == ' ') new_game(lvl);
    339                     else if (k == K_BACK) break;
    340                     continue;
    341                 }
    342                 int w = G.lv->w, h = G.lv->h;
    343                 switch (k) {
    344                 case K_LEFT:  G.cx = (G.cx + w - 1) % w; break;
    345                 case K_RIGHT: G.cx = (G.cx + 1) % w; break;
    346                 case K_UP:    G.cy = (G.cy + h - 1) % h; break;
    347                 case K_DOWN:  G.cy = (G.cy + 1) % h; break;
    348                 case K_OK: case ' ': act_open(); break;
    349                 case 'f': case 'F': case K_TAB: case 'm': case 'M': act_flag(); break;
    350                 case K_BACK: if (pause_menu()) goto menu; break;
    351                 }
    352             }
    353             uint32_t sec = elapsed() / 1000;
    354             if (sec != shown_sec) { shown_sec = sec; dirty = true; }
    355             if (dirty) { hal_draw(paint, 0); dirty = false; }
    356             else hal_sleep(10);
    357         }
    358     menu:;
    359     }
    360 }