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 (17168B)


      1 /* app.c - 2048 para la PicoCalc.
      2  *
      3  * Flechas deslizan todas las fichas; dos iguales que chocan se funden (una vez por
      4  * jugada). Despues de cada jugada aparece un 2 (90%) o un 4 (10%) en un hueco.
      5  * U o Retroceso deshace la ultima jugada (una sola vez seguida). Esc pausa.
      6  * La partida en curso se guarda en la SD despues de cada jugada. */
      7 #include <string.h>
      8 #include "mini.h"
      9 
     10 enum { MAXN = 5, TOP = 28, FLASH_MS = 90 };
     11 
     12 typedef struct { const char *name; int n, cell, gap; } Size;
     13 static const Size SIZES[] = {
     14     { "4x4", 4, 64, 4 },
     15     { "5x5", 5, 50, 4 },
     16 };
     17 enum { NSIZES = 2 };
     18 
     19 enum { PLAYING, WINBOX, LOST };
     20 
     21 typedef struct {
     22     int sz, n;
     23     uint8_t b[MAXN][MAXN];            /* exponente: 0 vacio, 1 = 2, 2 = 4, ... */
     24     uint8_t ub[MAXN][MAXN];           /* tablero antes de la ultima jugada */
     25     uint8_t merged[MAXN][MAXN];       /* fusionadas en la ultima jugada (destello) */
     26     long score, uscore;
     27     bool can_undo;
     28     bool won;                         /* ya se mostro el cartel de 2048 */
     29     int state, sel;                   /* sel: boton del cartel de 2048 */
     30     int new_x, new_y;                 /* ficha recien aparecida (-1 ninguna) */
     31     bool flash;                       /* dibujar el destello */
     32     bool record;                      /* supero el record en esta partida */
     33 } Game;
     34 
     35 static Game G;
     36 static bool g_flash_on = true;        /* opcion del menu */
     37 
     38 /* ------------------------------------------------------------ records y guardado */
     39 
     40 static void key_for(char *k, int n, const char *pre, int sz)
     41 {
     42     int p = str_put(k, n, 0, pre);
     43     str_int(k, n, p, SIZES[sz].n);
     44 }
     45 
     46 static long best_score(int sz) { char k[8]; key_for(k, sizeof k, "r", sz); return mini_get(k, 0); }
     47 static long best_tile(int sz)  { char k[8]; key_for(k, sizeof k, "t", sz); return mini_get(k, 0); }
     48 
     49 /* Partida en curso: "p <n><fichas a..><:puntos:ganada>", o "-" si no hay. */
     50 static void save_game(void)
     51 {
     52     char v[64];
     53     int p = 0;
     54     v[p++] = (char)('0' + G.n);
     55     for (int y = 0; y < G.n; y++)
     56         for (int x = 0; x < G.n; x++) v[p++] = (char)('a' + G.b[y][x]);
     57     v[p] = 0;
     58     p = str_put(v, sizeof v, p, ":");
     59     p = str_int(v, sizeof v, p, G.score);
     60     str_put(v, sizeof v, p, G.won ? ":1" : ":0");
     61     mini_set_str("p", v);
     62 }
     63 
     64 static bool has_saved(void)
     65 {
     66     const char *v = mini_get_str("p");
     67     return v && (v[0] == '4' || v[0] == '5');
     68 }
     69 
     70 static bool load_game(void)
     71 {
     72     const char *v = mini_get_str("p");
     73     if (!v) return false;
     74     int n = v[0] - '0', sz = -1;
     75     for (int i = 0; i < NSIZES; i++) if (SIZES[i].n == n) sz = i;
     76     if (sz < 0 || (int)strlen(v) < 1 + n * n + 2) return false;
     77     memset(&G, 0, sizeof G);
     78     G.sz = sz; G.n = n;
     79     for (int i = 0; i < n * n; i++) {
     80         int e = v[1 + i] - 'a';
     81         if (e < 0 || e > 20) return false;
     82         G.b[i / n][i % n] = (uint8_t)e;
     83     }
     84     const char *s = v + 1 + n * n;
     85     if (*s++ != ':' || !(s = str_scan_int(s, &G.score))) return false;
     86     G.won = s[0] == ':' && s[1] == '1';
     87     G.new_x = G.new_y = -1;
     88     return true;
     89 }
     90 
     91 static void clear_saved(void) { mini_set_str("p", "-"); }
     92 
     93 /* ------------------------------------------------------------ reglas */
     94 
     95 static void spawn(void)
     96 {
     97     int empty[MAXN * MAXN], ne = 0;
     98     for (int y = 0; y < G.n; y++)
     99         for (int x = 0; x < G.n; x++)
    100             if (!G.b[y][x]) empty[ne++] = y * MAXN + x;
    101     if (!ne) { G.new_x = G.new_y = -1; return; }
    102     int c = empty[mini_rand(ne)];
    103     G.new_x = c % MAXN; G.new_y = c / MAXN;
    104     G.b[G.new_y][G.new_x] = mini_rand(10) ? 1 : 2;
    105 }
    106 
    107 static void new_game(int sz)
    108 {
    109     memset(&G, 0, sizeof G);
    110     G.sz = sz;
    111     G.n = SIZES[sz].n;
    112     spawn();
    113     spawn();
    114     G.new_x = G.new_y = -1;
    115 }
    116 
    117 static bool can_move(void)
    118 {
    119     for (int y = 0; y < G.n; y++)
    120         for (int x = 0; x < G.n; x++) {
    121             if (!G.b[y][x]) return true;
    122             if (x + 1 < G.n && G.b[y][x + 1] == G.b[y][x]) return true;
    123             if (y + 1 < G.n && G.b[y + 1][x] == G.b[y][x]) return true;
    124         }
    125     return false;
    126 }
    127 
    128 static int max_exp(void)
    129 {
    130     int m = 0;
    131     for (int y = 0; y < G.n; y++)
    132         for (int x = 0; x < G.n; x++)
    133             if (G.b[y][x] > m) m = G.b[y][x];
    134     return m;
    135 }
    136 
    137 /* Desliza hacia (dx, dy). Devuelve true si algo se movio. */
    138 static bool slide(int dx, int dy)
    139 {
    140     uint8_t old[MAXN][MAXN];
    141     long old_score = G.score;
    142     bool moved = false;
    143     memcpy(old, G.b, sizeof old);
    144     memset(G.merged, 0, sizeof G.merged);
    145     for (int line = 0; line < G.n; line++) {
    146         /* celdas de la linea, empezando por la del borde hacia donde se desliza */
    147         int cx[MAXN], cy[MAXN];
    148         for (int i = 0; i < G.n; i++) {
    149             int j = (dx > 0 || dy > 0) ? G.n - 1 - i : i;
    150             if (dx) { cx[i] = j; cy[i] = line; }
    151             else    { cx[i] = line; cy[i] = j; }
    152         }
    153         int out = 0;
    154         bool last_merged = false;
    155         for (int i = 0; i < G.n; i++) {
    156             int e = G.b[cy[i]][cx[i]];
    157             if (!e) continue;
    158             G.b[cy[i]][cx[i]] = 0;
    159             if (out > 0 && !last_merged && G.b[cy[out - 1]][cx[out - 1]] == e) {
    160                 G.b[cy[out - 1]][cx[out - 1]] = (uint8_t)(e + 1);
    161                 G.merged[cy[out - 1]][cx[out - 1]] = 1;
    162                 G.score += 1L << (e + 1);
    163                 last_merged = true;
    164             } else {
    165                 G.b[cy[out]][cx[out]] = (uint8_t)e;
    166                 out++;
    167                 last_merged = false;
    168             }
    169         }
    170     }
    171     for (int y = 0; y < G.n && !moved; y++)
    172         for (int x = 0; x < G.n; x++)
    173             if (G.b[y][x] != old[y][x]) { moved = true; break; }
    174     if (moved) {
    175         memcpy(G.ub, old, sizeof old);
    176         G.uscore = old_score;
    177         G.can_undo = true;
    178     }
    179     return moved;
    180 }
    181 
    182 static void update_records(void)
    183 {
    184     char k[8];
    185     key_for(k, sizeof k, "r", G.sz);
    186     if (G.score > mini_get(k, 0)) { mini_set(k, G.score); G.record = G.score > 0; }
    187     key_for(k, sizeof k, "t", G.sz);
    188     long tile = 1L << max_exp();
    189     if (tile > mini_get(k, 0)) mini_set(k, tile);
    190 }
    191 
    192 /* Si no quedan jugadas termina la partida; si no, la guarda. No escribe la SD. */
    193 static void check_lost(void)
    194 {
    195     if (can_move()) { save_game(); return; }
    196     G.state = LOST;
    197     char k[8];
    198     key_for(k, sizeof k, "j", G.sz);
    199     mini_set(k, mini_get(k, 0) + 1);
    200     clear_saved();
    201 }
    202 
    203 static void act_move(int dx, int dy)
    204 {
    205     if (!slide(dx, dy)) return;
    206     spawn();
    207     G.flash = g_flash_on;
    208     update_records();
    209     if (!G.won && max_exp() >= 11) { G.won = true; G.state = WINBOX; G.sel = 0; save_game(); }
    210     else check_lost();
    211     mini_save();
    212 }
    213 
    214 static void act_undo(void)
    215 {
    216     if (!G.can_undo) return;
    217     memcpy(G.b, G.ub, sizeof G.b);
    218     G.score = G.uscore;
    219     G.can_undo = false;
    220     G.flash = false;
    221     G.new_x = G.new_y = -1;
    222     memset(G.merged, 0, sizeof G.merged);
    223     if (G.state == LOST) {                        /* deshacer la jugada perdedora */
    224         char k[8];
    225         key_for(k, sizeof k, "j", G.sz);
    226         mini_set(k, mini_get(k, 0) - 1);
    227     }
    228     G.state = PLAYING;
    229     save_game();
    230     mini_save();
    231 }
    232 
    233 /* ------------------------------------------------------------ dibujo */
    234 
    235 static Color tile_bg(int e)
    236 {
    237     static const Color C[] = {
    238         RGB(44, 50, 66),                                   /* vacia */
    239         RGB(238, 228, 218), RGB(237, 224, 200), RGB(242, 177, 121), RGB(245, 149, 99),
    240         RGB(246, 124, 95), RGB(246, 94, 59), RGB(237, 207, 114), RGB(237, 204, 97),
    241         RGB(237, 200, 80), RGB(237, 197, 63), RGB(237, 194, 46),               /* 2048 */
    242         RGB(178, 110, 240), RGB(140, 90, 230), RGB(96, 110, 235), RGB(60, 150, 230),
    243         RGB(40, 180, 190), RGB(40, 180, 120),
    244     };
    245     return C[e < (int)(sizeof C / sizeof C[0]) ? e : (int)(sizeof C / sizeof C[0]) - 1];
    246 }
    247 
    248 static Color tile_fg(int e) { return e <= 2 ? RGB(110, 100, 90) : RGB(250, 248, 242); }
    249 
    250 /* Rectangulo con las esquinas recortadas (r = 1 o 2). */
    251 static void round_rect(Surface *s, int x, int y, int w, int h, Color c)
    252 {
    253     gfx_rect(s, x + 2, y, w - 4, h, c);
    254     gfx_rect(s, x, y + 2, w, h - 4, c);
    255     gfx_rect(s, x + 1, y + 1, w - 2, h - 2, c);
    256 }
    257 
    258 static void num_text(char *t, int n, int e)
    259 {
    260     str_int(t, n, 0, 1L << e);
    261 }
    262 
    263 /* Numero lo mas grande posible que entre en la ficha. */
    264 static void draw_num(Surface *s, int x, int y, int c, const char *t, Color col)
    265 {
    266     static const struct { const Font *f; int sc; } OPT[] = {
    267         { &font_bold, 3 }, { &font_bold, 2 }, { &font_small, 3 }, { &font_small, 2 }, { &font_bold, 1 },
    268     };
    269     int nopt = (int)(sizeof OPT / sizeof OPT[0]);
    270     for (int i = 0; i < nopt; i++) {
    271         const Font *f = OPT[i].f;
    272         int sc = OPT[i].sc, w = mini_big_w(f, sc, t), h = f->h * sc;
    273         if (i < nopt - 1 && (w > c - 8 || h > c - 8)) continue;
    274         /* los digitos de font_bold ocupan las filas 2..11: centrar eso */
    275         int gy = f == &font_bold ? (c - 10 * sc) / 2 - 2 * sc : (c - h) / 2;
    276         mini_big_text(s, f, x + (c - w) / 2 + 1, y + gy, sc, t, col);
    277         return;
    278     }
    279 }
    280 
    281 static void draw_tile(Surface *s, int px, int py, int c, int e, bool is_new, bool merged)
    282 {
    283     if (!e) { round_rect(s, px, py, c, c, tile_bg(0)); return; }
    284     if (is_new) {                                 /* aparece: mas chica y palida */
    285         int in = c / 6;
    286         round_rect(s, px, py, c, c, tile_bg(0));
    287         round_rect(s, px + in, py + in, c - 2 * in, c - 2 * in, tile_bg(e));
    288         return;
    289     }
    290     round_rect(s, px, py, c, c, tile_bg(e));
    291     if (merged) {                                 /* destello: borde blanco */
    292         gfx_frame(s, px + 1, py + 1, c - 2, c - 2, C_WHITE);
    293         gfx_frame(s, px + 2, py + 2, c - 4, c - 4, C_WHITE);
    294     }
    295     char t[12];
    296     num_text(t, sizeof t, e);
    297     draw_num(s, px, py, c, t, tile_fg(e));
    298 }
    299 
    300 static void draw_banner(Surface *s, int bx, int by, int w, int h, Color border)
    301 {
    302     gfx_box(s, bx, by, w, h, C_PANEL, border);
    303     gfx_box(s, bx + 1, by + 1, w - 2, h - 2, C_PANEL, border);
    304 }
    305 
    306 static void paint(Surface *s, void *ctx)
    307 {
    308     const Size *sz = &SIZES[G.sz];
    309     int n = G.n, c = sz->cell, g = sz->gap, bw = n * c + (n + 1) * g;
    310     int ox = (320 - bw) / 2, oy = TOP + (278 - bw) / 2;
    311     char buf[48];
    312     int p;
    313 
    314     gfx_rect(s, 0, 0, 320, 320, RGB(16, 20, 30));
    315     gfx_rect(s, 0, 0, 320, 24, C_PANEL);
    316     p = str_put(buf, sizeof buf, 0, "Puntos ");
    317     str_int(buf, sizeof buf, p, G.score);
    318     gfx_text(s, &font_bold, 6, 6, buf, C_TEXT);
    319     p = str_put(buf, sizeof buf, 0, "Récord ");
    320     long best = best_score(G.sz);
    321     str_int(buf, sizeof buf, p, best > G.score ? best : G.score);
    322     gfx_text(s, &font_bold, 314 - gfx_text_w(&font_bold, buf), 6, buf, G.record ? C_ACCENT : C_TEXT);
    323     gfx_text_center(s, &font_normal, 130, 6, 60, sz->name, C_ACCENT);
    324 
    325     round_rect(s, ox, oy, bw, bw, RGB(30, 35, 48));
    326     for (int y = 0; y < n; y++)
    327         for (int x = 0; x < n; x++)
    328             draw_tile(s, ox + g + x * (c + g), oy + g + y * (c + g), c, G.b[y][x],
    329                       G.flash && x == G.new_x && y == G.new_y, G.flash && G.merged[y][x]);
    330 
    331     if (G.state == PLAYING) {
    332         gfx_text_center(s, &font_small, 0, 311, 320,
    333                         G.can_undo ? "Flechas mover  U deshacer  Esc menú" : "Flechas mover  Esc menú", C_DIM);
    334         return;
    335     }
    336     int bx = 40, w = 240;
    337     if (G.state == WINBOX) {
    338         int by = 120, h = 80;
    339         draw_banner(s, bx, by, w, h, C_ACCENT);
    340         gfx_text_center(s, &font_bold, bx, by + 10, w, "¡Llegaste a 2048!", C_ACCENT);
    341         p = str_put(buf, sizeof buf, 0, "Puntos ");
    342         str_int(buf, sizeof buf, p, G.score);
    343         gfx_text_center(s, &font_normal, bx, by + 28, w, buf, C_TEXT);
    344         static const char *const BTN[2] = { "Seguir jugando", "Menú" };
    345         int bxs[2] = { bx + 16, bx + 148 }, bws[2] = { 120, 76 };
    346         for (int i = 0; i < 2; i++) {
    347             bool on = G.sel == i;
    348             gfx_box(s, bxs[i], by + 50, bws[i], 20, on ? C_PANEL2 : C_PANEL, on ? C_CURSOR : C_DIM);
    349             gfx_text_center(s, &font_normal, bxs[i], by + 54, bws[i], BTN[i], on ? C_CURSOR : C_TEXT);
    350         }
    351         return;
    352     }
    353     /* sin movimientos */
    354     int by = 125, h = 70;
    355     draw_banner(s, bx, by, w, h, C_ERR);
    356     gfx_text_center(s, &font_bold, bx, by + 8, w, "Sin movimientos", C_ERR);
    357     p = str_put(buf, sizeof buf, 0, "Puntos ");
    358     p = str_int(buf, sizeof buf, p, G.score);
    359     if (G.record) str_put(buf, sizeof buf, p, "  ¡récord!");
    360     gfx_text_center(s, &font_normal, bx, by + 26, w, buf, G.record ? C_ACCENT : C_TEXT);
    361     gfx_text_center(s, &font_small, bx, by + 46, w,
    362                     G.can_undo ? "Enter otra  U deshacer  Esc menú" : "Enter otra   Esc menú", C_DIM);
    363 }
    364 
    365 /* ------------------------------------------------------------ menus y bucle */
    366 
    367 static void art(Surface *s)
    368 {
    369     /* una tirita de fichas de adorno: 2 4 8 ... 128 */
    370     int c = 22, gap = 3, nt = 7, x0 = (320 - nt * c - (nt - 1) * gap) / 2, y = 56;
    371     for (int i = 0; i < nt; i++) {
    372         int x = x0 + i * (c + gap), e = i + 1;
    373         char t[8];
    374         round_rect(s, x, y, c, c, tile_bg(e));
    375         num_text(t, sizeof t, e);
    376         const Font *f = e < 4 ? &font_bold : &font_small;
    377         gfx_text(s, f, x + (c - gfx_text_w(f, t)) / 2 + 1, y + (c - f->h) / 2 + 1, t, tile_fg(e));
    378     }
    379 }
    380 
    381 static const char *const SIZE_NAMES[] = { "4x4", "5x5" };
    382 static const char *const YESNO[] = { "No", "Sí" };
    383 
    384 /* Devuelve 1 jugar, 2 continuar; cambia *sz y la opcion de destello. */
    385 static int main_menu(int *sz)
    386 {
    387     static char lines[NSIZES][48];
    388     Form f;
    389     form_begin(&f, "2048");
    390     if (has_saved()) form_button(&f, 2, mini_get_str("p")[0] == '5' ? "Continuar (5x5)" : "Continuar (4x4)");
    391     form_button(&f, 1, "Jugar");
    392     form_choice(&f, 3, "Tamaño", SIZE_NAMES, NSIZES, *sz);
    393     form_choice(&f, 4, "Destello", YESNO, 2, g_flash_on);
    394     form_label(&f, "");
    395     for (int i = 0; i < NSIZES; i++) {
    396         int p = str_put(lines[i], sizeof lines[i], 0, SIZE_NAMES[i]);
    397         p = str_put(lines[i], sizeof lines[i], p, ": récord ");
    398         p = str_int(lines[i], sizeof lines[i], p, best_score(i));
    399         long t = best_tile(i);
    400         if (t) {
    401             p = str_put(lines[i], sizeof lines[i], p, ", ficha ");
    402             str_int(lines[i], sizeof lines[i], p, t);
    403         }
    404         form_label(&f, lines[i]);
    405     }
    406     for (;;) {
    407         int r = mini_form(&f);
    408         if (r == 1 || r == 2) {
    409             int nsz = form_get(&f, 3)->value;
    410             bool fl = form_get(&f, 4)->value != 0;
    411             if (nsz != *sz || fl != g_flash_on) {
    412                 *sz = nsz; g_flash_on = fl;
    413                 mini_set("tam", nsz); mini_set("fl", fl); mini_save();
    414             }
    415             return r;
    416         }
    417     }
    418 }
    419 
    420 /* Devuelve true si hay que volver al menu principal. */
    421 static bool pause_menu(void)
    422 {
    423     Form f;
    424     form_begin(&f, "Pausa");
    425     form_button(&f, 1, "Seguir");
    426     form_button(&f, 2, "Empezar de nuevo");
    427     form_button(&f, 3, "Menú principal");
    428     int r = mini_form(&f);
    429     if (r == 2) {
    430         new_game(G.sz);
    431         save_game();
    432         mini_save();
    433     }
    434     return r == 3;
    435 }
    436 
    437 void app_main(void)
    438 {
    439     mini_start("2048", RGB(237, 194, 46));
    440     mini_art = art;
    441     int sz = (int)mini_get("tam", 0);
    442     if (sz < 0 || sz >= NSIZES) sz = 0;
    443     g_flash_on = mini_get("fl", 1) != 0;
    444     for (;;) {
    445         int r = main_menu(&sz);
    446         if (r != 2 || !load_game()) {
    447             new_game(sz);
    448             save_game();
    449             mini_save();
    450         }
    451         bool dirty = true;
    452         uint32_t flash_t = 0;
    453         for (;;) {
    454             int k = hal_key();
    455             if (k == 0) dirty = true;
    456             else if (k > 0) {
    457                 dirty = true;
    458                 G.flash = false;
    459                 if (G.state == WINBOX) {
    460                     if (k == K_LEFT || k == K_RIGHT || k == K_TAB) G.sel ^= 1;
    461                     else if (k == K_OK || k == ' ') {
    462                         if (G.sel == 1) break;
    463                         G.state = PLAYING;
    464                         check_lost();
    465                         mini_save();
    466                     } else if (k == K_BACK) break;
    467                     continue;
    468                 }
    469                 if (G.state == LOST) {
    470                     if (k == 'u' || k == 'U' || k == K_BKSP) act_undo();
    471                     else if (k == K_OK || k == ' ') { new_game(G.sz); save_game(); mini_save(); }
    472                     else if (k == K_BACK) break;
    473                     continue;
    474                 }
    475                 switch (k) {
    476                 case K_LEFT:  act_move(-1, 0); break;
    477                 case K_RIGHT: act_move(1, 0); break;
    478                 case K_UP:    act_move(0, -1); break;
    479                 case K_DOWN:  act_move(0, 1); break;
    480                 case 'u': case 'U': case K_BKSP: act_undo(); break;
    481                 case K_BACK: if (pause_menu()) goto menu; break;
    482                 }
    483                 if (G.flash) flash_t = hal_ms();
    484             }
    485             if (G.flash && hal_ms() - flash_t >= FLASH_MS) { G.flash = false; dirty = true; }
    486             if (dirty) { hal_draw(paint, 0); dirty = false; }
    487             else hal_sleep(10);
    488         }
    489     menu:;
    490     }
    491 }