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


      1 /* app.c - sudoku para la PicoCalc.
      2  *
      3  * Flechas mueven el cursor, 1-9 ponen un numero (o una nota en modo notas, N),
      4  * 0/espacio/Retroceso borran, P da una pista, U deshace, Esc pausa.
      5  * Los sudokus se generan en el aparato (gen.c) con solucion unica. La partida en
      6  * curso se guarda en la SD despues de cada jugada. */
      7 #include <string.h>
      8 #include "mini.h"
      9 #include "gen.h"
     10 
     11 enum { CELL = 30, OX = 18, OY = 25, GRID = 284, UNDO_MAX = 400 };
     12 
     13 static const char *const LEVEL_NAMES[] = { "Fácil", "Medio", "Difícil", "Experto" };
     14 static const char *const YESNO[] = { "No", "Sí" };
     15 enum { NLEVELS = 4 };
     16 
     17 enum { PLAYING, WON };
     18 
     19 /* Una jugada para deshacer: la celda, lo que tenia y las notas del digito
     20  * puesto que se borraron en sus 20 vecinas. */
     21 typedef struct { uint8_t cell, old; uint16_t notes; uint32_t peers; } Undo;
     22 
     23 typedef struct {
     24     int lvl;
     25     uint8_t given[81], sol[81], val[81];
     26     uint8_t fixed[81];            /* 1 pista original, 2 llenada con P */
     27     uint16_t notes[81];
     28     int cx, cy;
     29     bool notes_mode, mark;
     30     int state, errors, hints;
     31     uint32_t t0, elapsed;
     32     bool record;
     33     Undo undo[UNDO_MAX];
     34     int nundo;
     35 } Game;
     36 
     37 static Game G;
     38 
     39 /* ------------------------------------------------------------ guardado de la partida */
     40 
     41 /* sg: pistas (81 digitos), sv: valores (1-9 del jugador, a-i llenados con P),
     42  * sn: notas, 9 bits por celda de a 6 bits por caracter ('0'+x), si: nivel,
     43  * tiempo, errores, pistas usadas. */
     44 static void save_game(void)
     45 {
     46     static char b[140];
     47     for (int i = 0; i < 81; i++) b[i] = (char)('0' + G.given[i]);
     48     b[81] = 0;
     49     mini_set_str("sg", b);
     50     for (int i = 0; i < 81; i++)
     51         b[i] = G.fixed[i] == 2 ? (char)('a' + G.val[i] - 1) : (char)('0' + (G.fixed[i] ? 0 : G.val[i]));
     52     mini_set_str("sv", b);
     53     uint32_t acc = 0;
     54     int bits = 0, n = 0;
     55     for (int i = 0; i < 81; i++) {
     56         acc |= (uint32_t)G.notes[i] << bits;
     57         bits += 9;
     58         while (bits >= 6) { b[n++] = (char)('0' + (acc & 63)); acc >>= 6; bits -= 6; }
     59     }
     60     if (bits) b[n++] = (char)('0' + (acc & 63));
     61     b[n] = 0;
     62     mini_set_str("sn", b);
     63     mini_set("sl", G.lvl);
     64     mini_set("st", (long)(hal_ms() - G.t0));
     65     mini_set("se", G.errors);
     66     mini_set("sh", G.hints);
     67     mini_save();
     68 }
     69 
     70 static void clear_saved(void)
     71 {
     72     mini_set_str("sg", "-");
     73     mini_save();
     74 }
     75 
     76 static bool has_saved(void)
     77 {
     78     const char *s = mini_get_str("sg");
     79     return s && strlen(s) == 81;
     80 }
     81 
     82 static bool load_game(void)
     83 {
     84     const char *s = mini_get_str("sg");
     85     if (!s || strlen(s) != 81) return false;
     86     memset(&G, 0, sizeof G);
     87     for (int i = 0; i < 81; i++) {
     88         if (s[i] < '0' || s[i] > '9') return false;
     89         G.given[i] = (uint8_t)(s[i] - '0');
     90         G.val[i] = G.given[i];
     91         G.fixed[i] = G.given[i] ? 1 : 0;
     92     }
     93     if (su_count(G.given, 2, G.sol, 0, 1000000) != 1) return false;
     94     s = mini_get_str("sv");
     95     if (s && strlen(s) == 81)
     96         for (int i = 0; i < 81; i++) {
     97             if (G.fixed[i]) continue;
     98             if (s[i] >= 'a' && s[i] <= 'i') { G.val[i] = (uint8_t)(s[i] - 'a' + 1); G.fixed[i] = 2; }
     99             else if (s[i] >= '1' && s[i] <= '9') G.val[i] = (uint8_t)(s[i] - '0');
    100         }
    101     s = mini_get_str("sn");
    102     if (s) {
    103         uint32_t acc = 0;
    104         int bits = 0, i = 0;
    105         for (; *s && i < 81; s++) {
    106             acc |= (uint32_t)((*s - '0') & 63) << bits;
    107             bits += 6;
    108             if (bits >= 9) { G.notes[i++] = (uint16_t)(acc & 0x1FF); acc >>= 9; bits -= 9; }
    109         }
    110     }
    111     G.lvl = (int)mini_get("sl", 0);
    112     if (G.lvl < 0 || G.lvl >= NLEVELS) G.lvl = 0;
    113     G.t0 = hal_ms() - (uint32_t)mini_get("st", 0);
    114     G.errors = (int)mini_get("se", 0);
    115     G.hints = (int)mini_get("sh", 0);
    116     G.cx = G.cy = 4;
    117     return true;
    118 }
    119 
    120 /* ------------------------------------------------------------ reglas */
    121 
    122 static void paint_wait(Surface *s, void *ctx)
    123 {
    124     gfx_rect(s, 0, 0, 320, 320, RGB(16, 20, 30));
    125     gfx_box(s, 80, 140, 160, 40, C_PANEL, C_ACCENT);
    126     gfx_text_center(s, &font_bold, 80, 154, 160, "Generando...", C_TEXT);
    127 }
    128 
    129 static void new_game(int lvl)
    130 {
    131     bool mark = G.mark;
    132     hal_draw(paint_wait, 0);
    133     memset(&G, 0, sizeof G);
    134     G.mark = mark;
    135     G.lvl = lvl;
    136     su_generate(lvl, G.given, G.sol);
    137     for (int i = 0; i < 81; i++) { G.val[i] = G.given[i]; G.fixed[i] = G.given[i] ? 1 : 0; }
    138     G.cx = G.cy = 4;
    139     G.t0 = hal_ms();
    140     save_game();
    141 }
    142 
    143 static uint32_t elapsed(void) { return G.state == PLAYING ? hal_ms() - G.t0 : G.elapsed; }
    144 
    145 static void level_key(char *k, int n, const char *pre, int lvl)
    146 {
    147     int p = str_put(k, n, 0, pre);
    148     str_int(k, n, p, lvl);
    149 }
    150 
    151 static void check_win(void)
    152 {
    153     for (int i = 0; i < 81; i++) if (G.val[i] != G.sol[i]) return;
    154     G.state = WON;
    155     G.elapsed = hal_ms() - G.t0;
    156     char k[8];
    157     level_key(k, sizeof k, "res", G.lvl);
    158     mini_set(k, mini_get(k, 0) + 1);
    159     if (!G.hints) {                             /* el record vale sin pistas */
    160         level_key(k, sizeof k, "rec", G.lvl);
    161         long best = mini_get(k, 0);
    162         if (!best || (long)G.elapsed < best) { mini_set(k, (long)G.elapsed); G.record = true; }
    163     }
    164     clear_saved();
    165 }
    166 
    167 static void push_undo(int c, uint32_t peers)
    168 {
    169     if (G.nundo == UNDO_MAX) {                  /* se olvida la mas vieja */
    170         memmove(G.undo, G.undo + 1, sizeof G.undo[0] * (UNDO_MAX - 1));
    171         G.nundo--;
    172     }
    173     Undo *u = &G.undo[G.nundo++];
    174     u->cell = (uint8_t)c; u->old = G.val[c]; u->notes = G.notes[c]; u->peers = peers;
    175 }
    176 
    177 /* Pone v en c y borra esa nota de las vecinas; devuelve que vecinas la tenian. */
    178 static uint32_t set_value(int c, int v)
    179 {
    180     uint32_t peers = 0;
    181     G.val[c] = (uint8_t)v;
    182     G.notes[c] = 0;
    183     for (int i = 0; i < 20; i++) {
    184         uint16_t *n = &G.notes[su_peer[c][i]];
    185         if (*n >> (v - 1) & 1) { *n &= (uint16_t)~(1u << (v - 1)); peers |= 1u << i; }
    186     }
    187     return peers;
    188 }
    189 
    190 static void act_digit(int v)
    191 {
    192     int c = G.cy * 9 + G.cx;
    193     if (G.fixed[c]) return;
    194     if (G.notes_mode) {
    195         if (G.val[c]) return;
    196         push_undo(c, 0);
    197         G.notes[c] ^= (uint16_t)(1u << (v - 1));
    198     } else if (G.val[c] == v) {                 /* el mismo numero lo borra */
    199         push_undo(c, 0);
    200         G.val[c] = 0;
    201     } else {
    202         push_undo(c, 0);
    203         G.undo[G.nundo - 1].peers = set_value(c, v);
    204         if (v != G.sol[c]) G.errors++;
    205         check_win();
    206     }
    207     if (G.state == PLAYING) save_game();
    208 }
    209 
    210 static void act_clear(void)
    211 {
    212     int c = G.cy * 9 + G.cx;
    213     if (G.fixed[c] || (!G.val[c] && !G.notes[c])) return;
    214     push_undo(c, 0);
    215     G.val[c] = 0;
    216     G.notes[c] = 0;
    217     save_game();
    218 }
    219 
    220 static void act_undo(void)
    221 {
    222     while (G.nundo) {
    223         Undo *u = &G.undo[--G.nundo];
    224         int c = u->cell;
    225         if (G.fixed[c]) continue;               /* la lleno una pista: no se toca */
    226         int v = G.val[c];
    227         if (v && u->peers)
    228             for (int i = 0; i < 20; i++)
    229                 if (u->peers >> i & 1) G.notes[su_peer[c][i]] |= (uint16_t)(1u << (v - 1));
    230         G.val[c] = u->old;
    231         G.notes[c] = u->notes;
    232         G.cx = c % 9; G.cy = c / 9;
    233         save_game();
    234         return;
    235     }
    236 }
    237 
    238 static void act_hint(void)
    239 {
    240     int c = G.cy * 9 + G.cx;
    241     if (G.val[c] == G.sol[c]) {                 /* la celda actual esta bien: otra al azar */
    242         static uint8_t cand[81];
    243         int n = 0;
    244         for (int i = 0; i < 81; i++) if (G.val[i] != G.sol[i]) cand[n++] = (uint8_t)i;
    245         if (!n) return;
    246         c = cand[mini_rand(n)];
    247         G.cx = c % 9; G.cy = c / 9;
    248     }
    249     set_value(c, G.sol[c]);
    250     G.fixed[c] = 2;
    251     G.hints++;
    252     check_win();
    253     if (G.state == PLAYING) save_game();
    254 }
    255 
    256 /* ------------------------------------------------------------ dibujo */
    257 
    258 #define C_BG     RGB(16, 20, 30)
    259 #define C_THIN   RGB(58, 66, 86)
    260 #define C_THICK  RGB(160, 170, 192)
    261 #define C_CELL   RGB(30, 36, 50)
    262 #define C_ZONE   RGB(42, 52, 72)       /* fila/columna/bloque del cursor */
    263 #define C_SAME   RGB(66, 84, 124)      /* mismo numero que el del cursor */
    264 #define C_CUR    RGB(92, 112, 160)
    265 #define C_GIVEN  RGB(235, 235, 240)
    266 #define C_MINE   RGB(110, 180, 255)    /* numeros del jugador */
    267 #define C_HINT   RGB(120, 220, 140)
    268 #define C_BAD    RGB(255, 80, 70)
    269 
    270 static int pos(int i) { return 31 * i + 2 + i / 3; }
    271 
    272 static void paint_grid(Surface *s)
    273 {
    274     int cc = G.cy * 9 + G.cx, cur = G.val[cc];
    275     bool playing = G.state == PLAYING;
    276     gfx_rect(s, OX, OY, GRID, GRID, C_THIN);
    277     for (int k = 0; k <= 3; k++) {
    278         int p = k == 3 ? GRID - 2 : pos(3 * k) - 2;
    279         gfx_rect(s, OX + p, OY, 2, GRID, C_THICK);
    280         gfx_rect(s, OX, OY + p, GRID, 2, C_THICK);
    281     }
    282     for (int y = 0; y < 9; y++)
    283         for (int x = 0; x < 9; x++) {
    284             int c = y * 9 + x, px = OX + pos(x), py = OY + pos(y), v = G.val[c];
    285             Color bg = C_CELL;
    286             if (playing) {
    287                 if (c == cc) bg = C_CUR;
    288                 else if (cur && v == cur) bg = C_SAME;
    289                 else if (x == G.cx || y == G.cy || (x / 3 == G.cx / 3 && y / 3 == G.cy / 3)) bg = C_ZONE;
    290             }
    291             gfx_rect(s, px, py, CELL, CELL, bg);
    292             if (v) {
    293                 char t[2] = { (char)('0' + v), 0 };
    294                 Color col = G.fixed[c] == 1 ? C_GIVEN : G.fixed[c] == 2 ? C_HINT : C_MINE;
    295                 if (!G.fixed[c] && G.mark && v != G.sol[c]) col = C_BAD;
    296                 mini_big_text(s, &font_bold, px + 9, py + 2, 2, t, col);
    297             } else if (G.notes[c]) {
    298                 for (int d = 0; d < 9; d++) {
    299                     if (!(G.notes[c] >> d & 1)) continue;
    300                     char t[2] = { (char)('1' + d), 0 };
    301                     int nx = px + 2 + (d % 3) * 9, ny = py + 2 + (d / 3) * 9;
    302                     bool hl = playing && cur == d + 1;
    303                     if (hl) gfx_rect(s, nx - 1, ny - 1, 8, 9, C_ACCENT);
    304                     gfx_text(s, &font_small, nx + 1, ny, t, hl ? C_BLACK : RGB(170, 178, 196));
    305                 }
    306             }
    307         }
    308     if (playing) {
    309         int px = OX + pos(G.cx), py = OY + pos(G.cy);
    310         gfx_frame(s, px, py, CELL, CELL, C_CURSOR);
    311         gfx_frame(s, px + 1, py + 1, CELL - 2, CELL - 2, C_CURSOR);
    312     }
    313 }
    314 
    315 static void paint(Surface *s, void *ctx)
    316 {
    317     char buf[40];
    318     int p;
    319 
    320     gfx_rect(s, 0, 0, 320, 320, C_BG);
    321     gfx_rect(s, 0, 0, 320, 24, C_PANEL);
    322     if (G.mark) {
    323         p = str_put(buf, sizeof buf, 0, "Errores ");
    324         str_int(buf, sizeof buf, p, G.errors);
    325         gfx_text(s, &font_normal, 6, 6, buf, G.errors ? C_ERR : C_TEXT);
    326     } else {
    327         p = str_put(buf, sizeof buf, 0, "Pistas ");
    328         str_int(buf, sizeof buf, p, G.hints);
    329         gfx_text(s, &font_normal, 6, 6, buf, C_TEXT);
    330     }
    331     str_time(buf, sizeof buf, 0, elapsed());
    332     gfx_text(s, &font_bold, 314 - gfx_text_w(&font_bold, buf), 6, buf, C_TEXT);
    333     if (G.notes_mode && G.state == PLAYING) {
    334         gfx_rect(s, 130, 4, 60, 16, C_ACCENT);
    335         gfx_text_center(s, &font_bold, 130, 6, 60, "NOTAS", C_BLACK);
    336     } else gfx_text_center(s, &font_normal, 100, 6, 120, LEVEL_NAMES[G.lvl], C_ACCENT);
    337 
    338     paint_grid(s);
    339     if (G.state == PLAYING) {
    340         gfx_text_center(s, &font_small, 0, 311, 320,
    341                         G.notes_mode ? "1-9 nota  N números  P pista  U deshacer  Esc menú"
    342                                      : "1-9 poner  N notas  P pista  U deshacer  Esc menú", C_DIM);
    343         return;
    344     }
    345     /* cartel de fin */
    346     int bx = 40, by = 122, w = 240, h = 76;
    347     gfx_box(s, bx, by, w, h, C_PANEL, C_OK);
    348     gfx_box(s, bx + 1, by + 1, w - 2, h - 2, C_PANEL, C_OK);
    349     gfx_text_center(s, &font_bold, bx, by + 8, w, "¡Resuelto!", C_OK);
    350     p = str_put(buf, sizeof buf, 0, "Tiempo ");
    351     p = str_time(buf, sizeof buf, p, G.elapsed);
    352     if (G.record) str_put(buf, sizeof buf, p, "  ¡récord!");
    353     gfx_text_center(s, &font_normal, bx, by + 26, w, buf, G.record ? C_ACCENT : C_TEXT);
    354     p = str_put(buf, sizeof buf, 0, "Errores ");
    355     p = str_int(buf, sizeof buf, p, G.errors);
    356     p = str_put(buf, sizeof buf, p, "   Pistas ");
    357     str_int(buf, sizeof buf, p, G.hints);
    358     gfx_text_center(s, &font_normal, bx, by + 42, w, buf, C_TEXT);
    359     gfx_text_center(s, &font_small, bx, by + 61, w, "Enter otro   Esc menú", C_DIM);
    360 }
    361 
    362 /* ------------------------------------------------------------ menus y bucle */
    363 
    364 static void art(Surface *s)
    365 {
    366     /* una tirita de celdas: pistas, del jugador y una con notas */
    367     static const int8_t demo[] = { 5, 3, -1, 7, 9, 0, 1 };
    368     int c = 22, x0 = (320 - 7 * c) / 2, y = 58;
    369     gfx_rect(s, x0 - 2, y - 2, 7 * c + 4, c + 4, C_THICK);
    370     for (int i = 0; i < 7; i++) {
    371         int x = x0 + i * c;
    372         gfx_rect(s, x + 1, y, c - 2, c, i == 3 ? C_CUR : C_CELL);
    373         if (demo[i] > 0) {
    374             char t[2] = { (char)('0' + demo[i]), 0 };
    375             gfx_text(s, &font_bold, x + 8, y + 5, t, i == 1 || i == 4 ? C_MINE : C_GIVEN);
    376         } else if (demo[i] < 0) {
    377             static const char *const nn[] = { "2", "4", "6", "8" };
    378             for (int k = 0; k < 4; k++)
    379                 gfx_text(s, &font_small, x + 4 + (k % 2) * 9, y + 3 + (k / 2) * 9, nn[k], RGB(170, 178, 196));
    380         }
    381     }
    382 }
    383 
    384 enum { M_CONT = 1, M_PLAY, M_LEVEL, M_MARK };
    385 
    386 /* Devuelve M_CONT o M_PLAY; el nivel y "marcar errores" quedan en *lvl y G.mark. */
    387 static int main_menu(int *lvl)
    388 {
    389     static char lines[NLEVELS][48];
    390     Form f;
    391     form_begin(&f, "Sudoku");
    392     if (has_saved()) form_button(&f, M_CONT, "Continuar");
    393     form_button(&f, M_PLAY, "Juego nuevo");
    394     form_choice(&f, M_LEVEL, "Nivel", LEVEL_NAMES, NLEVELS, *lvl);
    395     form_choice(&f, M_MARK, "Marcar errores", YESNO, 2, G.mark);
    396     form_label(&f, "");
    397     for (int i = 0; i < NLEVELS; i++) {
    398         char k[8];
    399         level_key(k, sizeof k, "rec", i);
    400         long rec = mini_get(k, 0);
    401         int p = str_put(lines[i], sizeof lines[i], 0, LEVEL_NAMES[i]);
    402         p = str_put(lines[i], sizeof lines[i], p, ": récord ");
    403         if (rec) p = str_time(lines[i], sizeof lines[i], p, (uint32_t)rec);
    404         else p = str_put(lines[i], sizeof lines[i], p, "-");
    405         level_key(k, sizeof k, "res", i);
    406         p = str_put(lines[i], sizeof lines[i], p, ", resueltos ");
    407         str_int(lines[i], sizeof lines[i], p, mini_get(k, 0));
    408         form_label(&f, lines[i]);
    409     }
    410     for (;;) {
    411         int r = mini_form(&f);
    412         if (r != M_CONT && r != M_PLAY) continue;
    413         int nl = form_get(&f, M_LEVEL)->value;
    414         bool mark = form_get(&f, M_MARK)->value;
    415         if (nl != *lvl || mark != G.mark) {
    416             *lvl = nl; G.mark = mark;
    417             mini_set("nivel", nl); mini_set("marca", mark);
    418             mini_save();
    419         }
    420         return r;
    421     }
    422 }
    423 
    424 /* Devuelve true si hay que volver al menu principal. */
    425 static bool pause_menu(void)
    426 {
    427     uint32_t t = hal_ms();
    428     Form f;
    429     form_begin(&f, "Pausa");
    430     form_button(&f, 1, "Seguir");
    431     form_button(&f, 2, "Empezar de nuevo");
    432     form_button(&f, 3, "Menú principal");
    433     int r = mini_form(&f);
    434     G.t0 += hal_ms() - t;                       /* la pausa no cuenta */
    435     if (r == 2) {                               /* el mismo sudoku, desde cero */
    436         for (int i = 0; i < 81; i++)
    437             if (G.fixed[i] != 1) { G.val[i] = 0; G.fixed[i] = 0; }
    438         memset(G.notes, 0, sizeof G.notes);
    439         G.errors = G.hints = G.nundo = 0;
    440         G.notes_mode = false;
    441         G.t0 = hal_ms();
    442     }
    443     save_game();
    444     return r == 3;
    445 }
    446 
    447 void app_main(void)
    448 {
    449     mini_start("Sudoku", RGB(120, 200, 255));
    450     mini_art = art;
    451     su_init();
    452     int lvl = (int)mini_get("nivel", 0);
    453     if (lvl < 0 || lvl >= NLEVELS) lvl = 0;
    454     G.mark = mini_get("marca", 1) != 0;
    455     for (;;) {
    456         int r = main_menu(&lvl);
    457         bool mark = G.mark;
    458         if (r != M_CONT || !load_game()) new_game(lvl);
    459         G.mark = mark;
    460         bool dirty = true;
    461         uint32_t shown_sec = 0;
    462         for (;;) {
    463             int k = hal_key();
    464             if (k == 0) dirty = true;
    465             else if (k > 0) {
    466                 dirty = true;
    467                 if (G.state != PLAYING) {
    468                     if (k == K_OK || k == ' ') new_game(lvl);
    469                     else if (k == K_BACK) break;
    470                     continue;
    471                 }
    472                 switch (k) {
    473                 case K_LEFT:  G.cx = (G.cx + 8) % 9; break;
    474                 case K_RIGHT: G.cx = (G.cx + 1) % 9; break;
    475                 case K_UP:    G.cy = (G.cy + 8) % 9; break;
    476                 case K_DOWN:  G.cy = (G.cy + 1) % 9; break;
    477                 case '0': case ' ': case K_BKSP: act_clear(); break;
    478                 case 'n': case 'N': case K_TAB: G.notes_mode = !G.notes_mode; break;
    479                 case 'p': case 'P': act_hint(); break;
    480                 case 'u': case 'U': act_undo(); break;
    481                 case K_BACK: if (pause_menu()) goto menu; break;
    482                 default: if (k >= '1' && k <= '9') act_digit(k - '0'); break;
    483                 }
    484             }
    485             uint32_t sec = elapsed() / 1000;
    486             if (sec != shown_sec) { shown_sec = sec; dirty = true; }
    487             if (dirty) { hal_draw(paint, 0); dirty = false; }
    488             else hal_sleep(10);
    489         }
    490     menu:;
    491     }
    492 }