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


      1 /* app.c - generala argentina para la PicoCalc.
      2  *
      3  * De 1 a 4 jugadores (humanos o CPU) en el mismo aparato. 5 dados, hasta 3 tiradas
      4  * por turno, 11 categorias. 1-5 o flechas + espacio guardan dados, Enter/T tira,
      5  * arriba/abajo eligen categoria y Enter anota, Esc pausa.
      6  * La CPU elige que guardar por valor esperado exacto sobre los multiconjuntos de dados
      7  * (programacion dinamica sobre las 462 manos parciales), sin recursion ni float. */
      8 #include <string.h>
      9 #include "mini.h"
     10 
     11 enum { NCAT = 11, MAXP = 4, NDICE = 5 };
     12 enum { C_ESC = 6, C_FULL, C_POKER, C_GEN, C_DOBLE };
     13 
     14 static const char *const CAT_NAMES[NCAT] = {
     15     "Unos", "Doses", "Treses", "Cuatros", "Cincos", "Seises",
     16     "Escalera", "Full", "Póker", "Generala", "Gen. doble"
     17 };
     18 
     19 static const Color PCOL[MAXP] = {
     20     RGB(120, 170, 255), RGB(240, 120, 100), RGB(110, 210, 120), RGB(230, 190, 70)
     21 };
     22 
     23 enum { PLAYING, CONFIRM, OVER };
     24 
     25 typedef struct {
     26     int n;                         /* jugadores */
     27     uint8_t cpu[MAXP];
     28     char name[MAXP][10];
     29     bool esc_as;                   /* 3-4-5-6-1 tambien es escalera */
     30     bool srv_gana;                 /* generala servida gana la partida */
     31     int16_t sc[MAXP][NCAT];        /* -1: libre */
     32     int cur;
     33     uint8_t dice[NDICE], held[NDICE];
     34     int rolls;                     /* tiradas hechas en este turno (0..3) */
     35     int dcur, sel;                 /* cursor de dados; categoria elegida (-1 ninguna) */
     36     int state;
     37     int servida;                   /* jugador que gano con generala servida, o -1 */
     38     bool counted;
     39     uint32_t cpu_t;                /* cuando da el proximo paso la CPU */
     40     int cpu_step;                  /* 0 tirar, 1 anotar */
     41     char msg[48];
     42 } Game;
     43 
     44 static Game G;
     45 
     46 /* ------------------------------------------------------------ reglas */
     47 
     48 static void counts(const uint8_t *d, int *c)
     49 {
     50     for (int f = 0; f < 6; f++) c[f] = 0;
     51     for (int i = 0; i < NDICE; i++) c[d[i] - 1]++;
     52 }
     53 
     54 /* Puntos de la categoria con esos conteos (c[0] = cantidad de unos). */
     55 static int score_c(const int *c, int cat, bool servida, int player)
     56 {
     57     if (cat < 6) return c[cat] * (cat + 1);
     58     int mx = 0, pairs = 0, three = 0;
     59     for (int f = 0; f < 6; f++) {
     60         if (c[f] > mx) mx = c[f];
     61         if (c[f] == 2) pairs++;
     62         if (c[f] == 3) three++;
     63     }
     64     switch (cat) {
     65     case C_ESC: {
     66         bool mid = c[2] == 1 && c[3] == 1 && c[4] == 1;          /* 3-4-5 */
     67         bool esc = mid && ((c[0] == 1 && c[1] == 1) || (c[1] == 1 && c[5] == 1) ||
     68                            (G.esc_as && c[0] == 1 && c[5] == 1));
     69         return esc ? (servida ? 25 : 20) : 0;
     70     }
     71     case C_FULL:  return three && pairs ? (servida ? 35 : 30) : 0;
     72     case C_POKER: return mx >= 4 ? (servida ? 45 : 40) : 0;
     73     case C_GEN:   return mx == 5 ? 50 : 0;
     74     case C_DOBLE: return mx == 5 && G.sc[player][C_GEN] == 50 ? 100 : 0;
     75     }
     76     return 0;
     77 }
     78 
     79 static int score(int cat)
     80 {
     81     int c[6];
     82     counts(G.dice, c);
     83     return score_c(c, cat, G.rolls == 1, G.cur);
     84 }
     85 
     86 static int total(int p)
     87 {
     88     int t = 0;
     89     for (int c = 0; c < NCAT; c++) if (G.sc[p][c] > 0) t += G.sc[p][c];
     90     return t;
     91 }
     92 
     93 static int filled(int p)
     94 {
     95     int n = 0;
     96     for (int c = 0; c < NCAT; c++) if (G.sc[p][c] >= 0) n++;
     97     return n;
     98 }
     99 
    100 static void set_names(void)
    101 {
    102     int humans = 0, cpus = 0;
    103     for (int i = 0; i < G.n; i++) if (!G.cpu[i]) humans++;
    104     for (int i = 0; i < G.n; i++) {
    105         char *b = G.name[i];
    106         if (G.cpu[i]) { int p = str_put(b, 10, 0, "CPU "); str_int(b, 10, p, ++cpus); }
    107         else if (humans == 1) str_put(b, 10, 0, "Vos");
    108         else { int p = str_put(b, 10, 0, "J"); str_int(b, 10, p, i + 1); }
    109     }
    110 }
    111 
    112 static void start_turn(void)
    113 {
    114     memset(G.held, 0, sizeof G.held);
    115     memset(G.dice, 0, sizeof G.dice);
    116     G.rolls = 0;
    117     G.sel = -1;
    118     G.dcur = 0;
    119     G.cpu_step = 0;
    120     G.cpu_t = hal_ms() + 700;
    121     int p = str_put(G.msg, sizeof G.msg, 0, "Turno de ");
    122     str_put(G.msg, sizeof G.msg, p, G.name[G.cur]);
    123 }
    124 
    125 static void new_game(int n, const uint8_t *cpu, bool esc_as, bool srv)
    126 {
    127     memset(&G, 0, sizeof G);
    128     G.n = n;
    129     memcpy(G.cpu, cpu, MAXP);
    130     G.esc_as = esc_as;
    131     G.srv_gana = srv;
    132     memset(G.sc, 0xff, sizeof G.sc);
    133     G.servida = -1;
    134     set_names();
    135     start_turn();
    136 }
    137 
    138 /* ------------------------------------------------------------ guardado de la partida */
    139 
    140 static const char HEX[] = "0123456789abcdef";
    141 
    142 static void save_game(void)
    143 {
    144     char b[8 + MAXP * NCAT * 2 + 1];
    145     int k = 0;
    146     b[k++] = (char)('0' + G.n);
    147     for (int i = 0; i < MAXP; i++) b[k++] = G.cpu[i] ? 'C' : 'H';
    148     b[k++] = G.esc_as ? '1' : '0';
    149     b[k++] = G.srv_gana ? '1' : '0';
    150     b[k++] = (char)('0' + G.cur);
    151     for (int p = 0; p < G.n; p++)
    152         for (int c = 0; c < NCAT; c++) {
    153             int v = G.sc[p][c];
    154             if (v < 0) { b[k++] = '-'; b[k++] = '-'; }
    155             else { b[k++] = HEX[v >> 4]; b[k++] = HEX[v & 15]; }
    156         }
    157     b[k] = 0;
    158     mini_set_str("pg", b);
    159     mini_save();
    160 }
    161 
    162 static int hexv(char ch)
    163 {
    164     if (ch >= '0' && ch <= '9') return ch - '0';
    165     if (ch >= 'a' && ch <= 'f') return ch - 'a' + 10;
    166     return -1;
    167 }
    168 
    169 static bool has_saved(void)
    170 {
    171     const char *s = mini_get_str("pg");
    172     return s && s[0] >= '1' && s[0] <= '4' && (int)strlen(s) == 8 + (s[0] - '0') * NCAT * 2;
    173 }
    174 
    175 static bool load_game(void)
    176 {
    177     if (!has_saved()) return false;
    178     const char *s = mini_get_str("pg");
    179     uint8_t cpu[MAXP];
    180     int n = s[0] - '0';
    181     for (int i = 0; i < MAXP; i++) cpu[i] = s[1 + i] == 'C';
    182     new_game(n, cpu, s[5] == '1', s[6] == '1');
    183     int cur = s[7] - '0';
    184     if (cur < 0 || cur >= n) return false;
    185     G.cur = cur;
    186     const char *q = s + 8;
    187     for (int p = 0; p < n; p++)
    188         for (int c = 0; c < NCAT; c++, q += 2) {
    189             if (q[0] == '-') continue;
    190             int a = hexv(q[0]), b = hexv(q[1]);
    191             if (a < 0 || b < 0) return false;
    192             G.sc[p][c] = (int16_t)(a * 16 + b);
    193         }
    194     start_turn();
    195     return true;
    196 }
    197 
    198 /* ------------------------------------------------------------ fin de partida */
    199 
    200 static bool is_winner(int p)
    201 {
    202     if (G.servida >= 0) return p == G.servida;
    203     int best = 0;
    204     for (int i = 0; i < G.n; i++) if (total(i) > best) best = total(i);
    205     return total(p) == best;
    206 }
    207 
    208 static void end_game(void)
    209 {
    210     G.state = OVER;
    211     G.sel = -1;
    212     mini_set_str("pg", "-");
    213     bool human = false, cpu = false, hwin = false;
    214     int best = 0;
    215     for (int p = 0; p < G.n; p++) {
    216         if (G.cpu[p]) { cpu = true; continue; }
    217         human = true;
    218         if (is_winner(p)) hwin = true;
    219         if (total(p) > best) best = total(p);
    220     }
    221     if (human) {
    222         mini_set("par", mini_get("par", 0) + 1);
    223         if (cpu && hwin) mini_set("gan", mini_get("gan", 0) + 1);
    224         if (best > mini_get("mej", 0)) mini_set("mej", best);
    225     }
    226     mini_save();
    227 }
    228 
    229 static void next_turn(void)
    230 {
    231     G.cur = (G.cur + 1) % G.n;
    232     if (filled(G.cur) == NCAT) { end_game(); return; }
    233     save_game();
    234     start_turn();
    235 }
    236 
    237 static void do_score(int cat)
    238 {
    239     int v = score(cat);
    240     G.sc[G.cur][cat] = (int16_t)v;
    241     int p = str_put(G.msg, sizeof G.msg, 0, G.name[G.cur]);
    242     if (v) {
    243         p = str_put(G.msg, sizeof G.msg, p, ": ");
    244         p = str_int(G.msg, sizeof G.msg, p, v);
    245         p = str_put(G.msg, sizeof G.msg, p, " a ");
    246     } else p = str_put(G.msg, sizeof G.msg, p, " tacha ");
    247     str_put(G.msg, sizeof G.msg, p, CAT_NAMES[cat]);
    248     next_turn();
    249     if (G.state == PLAYING && G.cpu[G.cur]) G.cpu_t = hal_ms() + 1200;   /* que se lea */
    250 }
    251 
    252 /* ------------------------------------------------------------ CPU
    253  * Manos parciales = multiconjuntos de 0..5 dados, codificados por sus conteos en base 6
    254  * (clave < 6^6). E_r[m] = valor esperado de completar m tirando lo que falta, con r
    255  * tiradas mas despues; V_r[mano] = max sobre lo que se guarda de E_(r-1). */
    256 
    257 enum { NMS = 462 };
    258 static uint16_t ms_key[NMS];
    259 static uint8_t  ms_size[NMS];
    260 static int32_t  E0[NMS], E1[NMS], VV[NMS];
    261 
    262 static int ms_find(int key)
    263 {
    264     int lo = 0, hi = NMS - 1;
    265     while (lo < hi) {
    266         int m = (lo + hi) / 2;
    267         if (ms_key[m] < key) lo = m + 1; else hi = m;
    268     }
    269     return lo;
    270 }
    271 
    272 static const int POW6[7] = { 1, 6, 36, 216, 1296, 7776, 46656 };
    273 
    274 static void ms_init(void)
    275 {
    276     if (ms_key[NMS - 1]) return;
    277     int n = 0;
    278     for (int k = 0; k < POW6[6]; k++) {
    279         int s = 0;
    280         for (int f = 0, v = k; f < 6; f++, v /= 6) s += v % 6;
    281         if (s <= 5) { ms_key[n] = (uint16_t)k; ms_size[n] = (uint8_t)s; n++; }
    282     }
    283 }
    284 
    285 /* lo que "cuesta" gastar cada categoria: aproximadamente lo que se espera de ella */
    286 static const int8_t BASE[NCAT] = { 1, 3, 6, 8, 10, 12, 10, 12, 10, 5, 2 };
    287 
    288 /* Mejor valor de anotar esta mano (en milesimas); *cat = la categoria. */
    289 static int32_t best_cat(const int *c, bool servida, int *cat)
    290 {
    291     int32_t best = -1000000;
    292     int bc = -1;
    293     for (int k = 0; k < NCAT; k++) {
    294         if (G.sc[G.cur][k] >= 0) continue;
    295         int32_t v = (score_c(c, k, servida, G.cur) - BASE[k]) * 1000;
    296         if (v > best) { best = v; bc = k; }
    297     }
    298     if (cat) *cat = bc;
    299     return best;
    300 }
    301 
    302 static void decode(int key, int *c)
    303 {
    304     for (int f = 0; f < 6; f++, key /= 6) c[f] = key % 6;
    305 }
    306 
    307 /* E[m] desde V (manos completas), de las manos grandes a las chicas */
    308 static void expect(int32_t *E, const int32_t *V)
    309 {
    310     for (int sz = 5; sz >= 0; sz--)
    311         for (int i = 0; i < NMS; i++) {
    312             if (ms_size[i] != sz) continue;
    313             if (sz == 5) { E[i] = V[i]; continue; }
    314             int32_t s = 0;
    315             for (int f = 0; f < 6; f++) {
    316                 int c[6];
    317                 decode(ms_key[i], c);
    318                 if (c[f] == 5) continue;
    319                 s += E[ms_find(ms_key[i] + POW6[f])];
    320             }
    321             E[i] = s / 6;
    322         }
    323 }
    324 
    325 /* mejor sub-multiconjunto de la mano i segun E; con proper, sin contar la mano entera */
    326 static int32_t best_keep(int i, const int32_t *E, bool proper, int *keep)
    327 {
    328     int c[6], k[6];
    329     decode(ms_key[i], c);
    330     int32_t best = -2000000000;
    331     int bk = 0;
    332     for (k[0] = 0; k[0] <= c[0]; k[0]++)
    333     for (k[1] = 0; k[1] <= c[1]; k[1]++)
    334     for (k[2] = 0; k[2] <= c[2]; k[2]++)
    335     for (k[3] = 0; k[3] <= c[3]; k[3]++)
    336     for (k[4] = 0; k[4] <= c[4]; k[4]++)
    337     for (k[5] = 0; k[5] <= c[5]; k[5]++) {
    338         int key = 0, sz = 0;
    339         for (int f = 0; f < 6; f++) { key += k[f] * POW6[f]; sz += k[f]; }
    340         if (proper && sz == 5) continue;
    341         int32_t v = E[ms_find(key)];
    342         if (v > best) { best = v; bk = key; }
    343     }
    344     if (keep) *keep = bk;
    345     return best;
    346 }
    347 
    348 /* Tablas del turno (dependen de las categorias libres del jugador actual). */
    349 static void cpu_tables(void)
    350 {
    351     ms_init();
    352     for (int i = 0; i < NMS; i++) {
    353         if (ms_size[i] != 5) { VV[i] = 0; continue; }
    354         int c[6];
    355         decode(ms_key[i], c);
    356         VV[i] = best_cat(c, false, 0);
    357     }
    358     expect(E0, VV);                        /* una tirada por delante */
    359     for (int i = 0; i < NMS; i++)
    360         if (ms_size[i] == 5) VV[i] = best_keep(i, E0, false, 0);
    361     expect(E1, VV);                        /* dos tiradas por delante */
    362 }
    363 
    364 /* Decide la CPU: devuelve la categoria para anotar, o -1 y deja marcados los dados a guardar. */
    365 static int cpu_decide(void)
    366 {
    367     int c[6], cat;
    368     counts(G.dice, c);
    369     int32_t stop = best_cat(c, G.rolls == 1, &cat);
    370     if (G.rolls >= 3) return cat;
    371     if (G.rolls == 1) cpu_tables();
    372     int key = 0;
    373     for (int f = 0; f < 6; f++) key += c[f] * POW6[f];
    374     int keep;
    375     int32_t go = best_keep(ms_find(key), G.rolls == 1 ? E1 : E0, true, &keep);
    376     if (stop >= go) return cat;
    377     int k[6];
    378     decode(keep, k);
    379     for (int i = 0; i < NDICE; i++) {
    380         int f = G.dice[i] - 1;
    381         G.held[i] = k[f] > 0;
    382         if (k[f] > 0) k[f]--;
    383     }
    384     return -1;
    385 }
    386 
    387 /* ------------------------------------------------------------ dibujo */
    388 
    389 enum { DS = 42, DGAP = 12, DX0 = (320 - 5 * DS - 4 * DGAP) / 2, DY = 36, SY = 106, RH = 15 };
    390 
    391 static uint8_t anim[NDICE];        /* caras durante la animacion (0: no anima) */
    392 
    393 static void draw_die(Surface *s, int x, int y, int sz, int face, Color bg, Color pip, Color edge)
    394 {
    395     gfx_rect(s, x + 3, y, sz - 6, sz, edge);
    396     gfx_rect(s, x, y + 3, sz, sz - 6, edge);
    397     gfx_rect(s, x + 1, y + 1, sz - 2, sz - 2, edge);
    398     gfx_rect(s, x + 3, y + 1, sz - 6, sz - 2, bg);
    399     gfx_rect(s, x + 1, y + 3, sz - 2, sz - 6, bg);
    400     gfx_rect(s, x + 2, y + 2, sz - 4, sz - 4, bg);
    401     if (!face) return;
    402     static const uint16_t PIPS[7] = { 0, 0x010, 0x104, 0x111, 0x145, 0x155, 0x16d };
    403     int r = sz / 10 > 1 ? sz / 10 : 1;
    404     for (int b = 0; b < 9; b++)
    405         if (PIPS[face] & (0x100 >> b)) {
    406             int gx = b % 3, gy = b / 3;
    407             gfx_disc(s, x + sz / 2 + (gx - 1) * sz * 27 / 100, y + sz / 2 + (gy - 1) * sz * 27 / 100, r, pip);
    408         }
    409 }
    410 
    411 static void paint_dice(Surface *s)
    412 {
    413     bool human = G.state != OVER && !G.cpu[G.cur];
    414     for (int i = 0; i < NDICE; i++) {
    415         int x = DX0 + i * (DS + DGAP), y = DY;
    416         int face = anim[i] ? anim[i] : G.dice[i];
    417         if (!G.rolls && !anim[i]) {
    418             draw_die(s, x, y, DS, 0, RGB(52, 58, 74), 0, RGB(70, 78, 96));
    419         } else if (G.held[i]) {
    420             y -= 7;
    421             gfx_rect(s, x + 4, DY + DS - 3, DS - 8, 4, RGB(8, 10, 16));        /* sombra */
    422             draw_die(s, x, y, DS, face, RGB(255, 226, 140), RGB(40, 30, 10), RGB(230, 150, 40));
    423         } else draw_die(s, x, y, DS, face, RGB(240, 240, 234), RGB(20, 20, 30), RGB(150, 150, 160));
    424         if (human && G.rolls && G.rolls < 3) {
    425             char t[2] = { (char)('1' + i), 0 };
    426             bool c = i == G.dcur && G.sel < 0;
    427             gfx_text_center(s, &font_small, x, DY + DS + 4, DS, t, c ? C_CURSOR : C_DIM);
    428             if (c) gfx_rect(s, x + 4, DY + DS + 12, DS - 8, 2, C_CURSOR);
    429         }
    430     }
    431 }
    432 
    433 static void paint_sheet(Surface *s)
    434 {
    435     int cw = G.n == 1 ? 90 : G.n == 2 ? 80 : G.n == 3 ? 64 : 50;
    436     int nx = 6, nw = 100, px0 = nx + nw + 4;
    437     int tw = nw + 4 + cw * G.n + 6;
    438     int x0 = (320 - tw) / 2;
    439     nx += x0 - 6; px0 += x0 - 6;
    440     char buf[12];
    441     bool show = G.state != OVER && G.rolls > 0;
    442 
    443     gfx_rect(s, x0 - 2, SY - 2, tw + 4, RH * 13 + 4, RGB(26, 30, 42));
    444     /* columna del jugador actual */
    445     if (G.state != OVER)
    446         gfx_rect(s, px0 + G.cur * cw, SY - 2, cw, RH * 13 + 4, RGB(40, 46, 64));
    447     for (int p = 0; p < G.n; p++)
    448         gfx_text_center(s, &font_bold, px0 + p * cw, SY + 1, cw, G.name[p], PCOL[p]);
    449     gfx_rect(s, x0, SY + RH - 1, tw, 1, RGB(70, 78, 96));
    450     for (int c = 0; c < NCAT; c++) {
    451         int y = SY + RH * (c + 1);
    452         if (c == G.sel) gfx_rect(s, x0, y, tw, RH - 1, RGB(70, 64, 30));
    453         else if (c == 6) gfx_rect(s, x0, y - 1, tw, 1, RGB(50, 56, 72));
    454         gfx_text(s, &font_normal, nx, y + 1, CAT_NAMES[c], c == G.sel ? C_CURSOR : C_TEXT);
    455         for (int p = 0; p < G.n; p++) {
    456             int v = G.sc[p][c], cx = px0 + p * cw;
    457             if (v > 0) {
    458                 str_int(buf, sizeof buf, 0, v);
    459                 gfx_text_center(s, &font_normal, cx, y + 1, cw, buf, C_TEXT);
    460             } else if (v == 0) {
    461                 gfx_text_center(s, &font_normal, cx, y + 1, cw, "X", RGB(170, 80, 70));
    462             } else if (p == G.cur && show) {
    463                 int pv = score(c);
    464                 str_int(buf, sizeof buf, 0, pv);
    465                 gfx_text_center(s, &font_normal, cx, y + 1, cw, pv ? buf : "·",
    466                                 pv ? RGB(110, 200, 230) : RGB(90, 96, 112));
    467             }
    468             if (c == G.sel && p == G.cur) gfx_frame(s, cx + 2, y - 1, cw - 4, RH + 1, C_CURSOR);
    469         }
    470     }
    471     int y = SY + RH * 12;
    472     gfx_rect(s, x0, y - 1, tw, 1, RGB(70, 78, 96));
    473     gfx_text(s, &font_bold, nx, y + 2, "Total", C_ACCENT);
    474     for (int p = 0; p < G.n; p++) {
    475         str_int(buf, sizeof buf, 0, total(p));
    476         gfx_text_center(s, &font_bold, px0 + p * cw, y + 2, cw, buf, C_ACCENT);
    477     }
    478 }
    479 
    480 static void paint(Surface *s, void *ctx)
    481 {
    482     char buf[48];
    483     gfx_rect(s, 0, 0, 320, 320, RGB(16, 20, 30));
    484     gfx_rect(s, 0, 0, 320, 24, C_PANEL);
    485     if (G.state != OVER) {
    486         gfx_disc(s, 12, 12, 5, PCOL[G.cur]);
    487         gfx_text(s, &font_bold, 22, 6, G.name[G.cur], PCOL[G.cur]);
    488         int p = str_put(buf, sizeof buf, 0, "Ronda ");
    489         p = str_int(buf, sizeof buf, p, filled(G.cur) + 1);
    490         str_put(buf, sizeof buf, p, "/11");
    491         gfx_text_center(s, &font_normal, 100, 6, 120, buf, C_DIM);
    492         p = str_put(buf, sizeof buf, 0, "Tirada ");
    493         p = str_int(buf, sizeof buf, p, G.rolls);
    494         str_put(buf, sizeof buf, p, "/3");
    495         gfx_text(s, &font_bold, 314 - gfx_text_w(&font_bold, buf), 6, buf, G.rolls == 3 ? C_ERR : C_TEXT);
    496     } else gfx_text_center(s, &font_bold, 0, 6, 320, "Fin de la partida", C_ACCENT);
    497 
    498     if (G.state != OVER) {
    499         paint_dice(s);
    500         gfx_text_center(s, &font_normal, 0, 90, 320, G.msg, C_TEXT);
    501     }
    502     paint_sheet(s);
    503 
    504     const char *help;
    505     if (G.state == OVER) help = "Enter otra   Esc menú";
    506     else if (G.cpu[G.cur]) help = "Juega la CPU   Enter apura   Esc menú";
    507     else if (!G.rolls) help = "Enter tirar   Esc menú";
    508     else if (G.sel >= 0) help = G.rolls < 3 ? "Arriba/abajo elegir  Enter anotar  T tirar  Esc volver"
    509                                             : "Arriba/abajo elegir   Enter anotar   Esc menú";
    510     else help = "1-5 guardar   Enter tirar   Arriba/abajo anotar   Esc menú";
    511     gfx_text_center(s, &font_small, 0, 311, 320, help, C_DIM);
    512 
    513     if (G.state == CONFIRM) {
    514         int bx = 50, by = 140, w = 220, h = 50;
    515         gfx_box(s, bx, by, w, h, C_PANEL, C_ERR);
    516         gfx_box(s, bx + 1, by + 1, w - 2, h - 2, C_PANEL, C_ERR);
    517         int p = str_put(buf, sizeof buf, 0, "¿Tachar ");
    518         p = str_put(buf, sizeof buf, p, CAT_NAMES[G.sel]);
    519         str_put(buf, sizeof buf, p, "?");
    520         gfx_text_center(s, &font_bold, bx, by + 10, w, buf, C_ERR);
    521         gfx_text_center(s, &font_small, bx, by + 32, w, "Enter sí   Esc no", C_DIM);
    522     }
    523     if (G.state == OVER) {
    524         /* cartel de fin: ranking */
    525         int ord[MAXP] = { 0, 1, 2, 3 };
    526         for (int i = 1; i < G.n; i++)
    527             for (int j = i; j > 0 && (is_winner(ord[j]) > is_winner(ord[j - 1]) ||
    528                  (is_winner(ord[j]) == is_winner(ord[j - 1]) && total(ord[j]) > total(ord[j - 1]))); j--) {
    529                 int t = ord[j]; ord[j] = ord[j - 1]; ord[j - 1] = t;
    530             }
    531         int nw = 0;
    532         for (int i = 0; i < G.n; i++) if (is_winner(i)) nw++;
    533         int w = 220, h = 26 + G.n * 13, bx = 50, by = 28;
    534         gfx_box(s, bx, by, w, h, C_PANEL, C_OK);
    535         gfx_box(s, bx + 1, by + 1, w - 2, h - 2, C_PANEL, C_OK);
    536         int w0 = ord[0], p = 0;
    537         if (nw > 1) str_put(buf, sizeof buf, 0, "¡Empate!");
    538         else if (G.servida >= 0) {
    539             p = str_put(buf, sizeof buf, 0, "¡Generala servida de ");
    540             p = str_put(buf, sizeof buf, p, G.name[w0]);
    541             str_put(buf, sizeof buf, p, "!");
    542         } else if (!G.cpu[w0] && !strcmp(G.name[w0], "Vos")) str_put(buf, sizeof buf, 0, "¡Ganaste!");
    543         else { p = str_put(buf, sizeof buf, 0, "¡Ganó "); p = str_put(buf, sizeof buf, p, G.name[w0]); str_put(buf, sizeof buf, p, "!"); }
    544         gfx_text_center(s, &font_bold, bx, by + 6, w, buf, G.servida >= 0 ? C_ACCENT : C_OK);
    545         int y = by + 22;
    546         for (int i = 0; i < G.n; i++, y += 13) {
    547             int q = ord[i];
    548             buf[0] = (char)('1' + i); buf[1] = '.'; buf[2] = 0;
    549             gfx_text(s, &font_normal, bx + 30, y, buf, C_DIM);
    550             gfx_disc(s, bx + 56, y + 6, 4, PCOL[q]);
    551             gfx_text(s, &font_bold, bx + 66, y, G.name[q], is_winner(q) ? C_OK : C_TEXT);
    552             str_int(buf, sizeof buf, 0, total(q));
    553             gfx_text(s, &font_bold, bx + w - 30 - gfx_text_w(&font_bold, buf), y, buf, C_TEXT);
    554         }
    555     }
    556 }
    557 
    558 /* ------------------------------------------------------------ acciones */
    559 
    560 static void roll(void)
    561 {
    562     if (G.rolls >= 3) return;
    563     for (int f = 0; f < 3; f++) {             /* animacion: solo cambia la franja de los dados */
    564         for (int i = 0; i < NDICE; i++) anim[i] = G.held[i] && G.rolls ? 0 : (uint8_t)(1 + mini_rand(6));
    565         hal_draw(paint, 0);
    566         hal_sleep(60);
    567     }
    568     memset(anim, 0, sizeof anim);
    569     for (int i = 0; i < NDICE; i++)
    570         if (!G.held[i] || !G.rolls) G.dice[i] = (uint8_t)(1 + mini_rand(6));
    571     if (!G.rolls) memset(G.held, 0, sizeof G.held);
    572     G.rolls++;
    573     G.sel = -1;
    574     int c[6];
    575     counts(G.dice, c);
    576     G.msg[0] = 0;
    577     for (int f = 0; f < 6; f++)
    578         if (c[f] == 5 && G.rolls == 1) {
    579             str_put(G.msg, sizeof G.msg, 0, "¡Generala servida!");
    580             if (G.srv_gana) { G.servida = G.cur; end_game(); return; }
    581         }
    582     if (G.rolls == 1 && !G.msg[0]) {
    583         for (int k = C_ESC; k <= C_POKER; k++)
    584             if (score_c(c, k, true, G.cur)) {
    585                 int p = str_put(G.msg, sizeof G.msg, 0, CAT_NAMES[k]);
    586                 str_put(G.msg, sizeof G.msg, p, " servido");
    587                 if (k == C_ESC) G.msg[strlen(G.msg) - 1] = 'a';
    588                 break;
    589             }
    590     }
    591     if (G.rolls == 3 && !G.cpu[G.cur]) {       /* sugerir la mejor */
    592         int cat;
    593         best_cat(c, false, &cat);
    594         G.sel = cat;
    595     }
    596 }
    597 
    598 static void move_sel(int d)
    599 {
    600     for (int k = 0; k < NCAT; k++) {
    601         G.sel = G.sel < 0 ? (d > 0 ? 0 : NCAT - 1) : (G.sel + d + NCAT) % NCAT;
    602         if (G.sc[G.cur][G.sel] < 0) return;
    603     }
    604 }
    605 
    606 static void try_score(void)
    607 {
    608     if (G.sel < 0 || G.sc[G.cur][G.sel] >= 0) return;
    609     if (score(G.sel) == 0) G.state = CONFIRM;
    610     else do_score(G.sel);
    611 }
    612 
    613 static void cpu_step(void)
    614 {
    615     if (G.cpu_step == 0) {
    616         roll();
    617         if (G.state != PLAYING) return;
    618         int cat = cpu_decide();
    619         if (cat >= 0) {
    620             G.sel = cat;
    621             G.cpu_step = 1;
    622             if (!G.msg[0]) {
    623                 int p = str_put(G.msg, sizeof G.msg, 0, "Anota en ");
    624                 str_put(G.msg, sizeof G.msg, p, CAT_NAMES[cat]);
    625             }
    626         } else {
    627             int p = str_put(G.msg, sizeof G.msg, 0, "Guarda");
    628             int nh = 0;
    629             for (int i = 0; i < NDICE; i++)
    630                 if (G.held[i]) { char t[3] = { ' ', (char)('0' + G.dice[i]), 0 }; p = str_put(G.msg, sizeof G.msg, p, t); nh++; }
    631             if (!nh) str_put(G.msg, sizeof G.msg, 0, "Tira todo de nuevo");
    632         }
    633         G.cpu_t = hal_ms() + 800;
    634     } else do_score(G.sel);
    635 }
    636 
    637 /* ------------------------------------------------------------ menus y bucle */
    638 
    639 static void art(Surface *s)
    640 {
    641     static const uint8_t demo[5] = { 5, 5, 5, 5, 5 };
    642     int c = 22, x0 = (320 - 5 * c - 4 * 6) / 2, y = 58;
    643     for (int i = 0; i < 5; i++) {
    644         bool h = i != 2;
    645         draw_die(s, x0 + i * (c + 6), y - (h ? 0 : 3), c, demo[i],
    646                  h ? RGB(240, 240, 234) : RGB(255, 226, 140), RGB(20, 20, 30), RGB(150, 150, 160));
    647     }
    648 }
    649 
    650 static void paint_form(Surface *s, void *ctx) { gfx_form(s, ctx, 0); }
    651 
    652 static const char *const NPL[] = { "1", "2", "3", "4" };
    653 static const char *const TYPES[] = { "Humano", "CPU" };
    654 static const char *const YESNO[] = { "No", "Sí" };
    655 static const char *const PL_LABEL[MAXP] = { "Jugador 1", "Jugador 2", "Jugador 3", "Jugador 4" };
    656 
    657 /* 1 jugar, 2 continuar */
    658 static int main_menu(void)
    659 {
    660     static char line[64];
    661     Form f;
    662     form_begin(&f, "Generala");
    663     form_button(&f, 1, "Jugar");
    664     form_button(&f, 2, "Continuar")->hidden = !has_saved();
    665     form_choice(&f, 3, "Jugadores", NPL, 4, (int)mini_get("nj", 2) - 1);
    666     long t = mini_get("tip", 2);
    667     for (int i = 0; i < MAXP; i++) form_choice(&f, 4 + i, PL_LABEL[i], TYPES, 2, (int)((t >> i) & 1));
    668     form_choice(&f, 8, "Escalera 3456-1", YESNO, 2, (int)mini_get("esc", 0) & 1);
    669     form_choice(&f, 9, "Servida gana", YESNO, 2, (int)mini_get("srv", 1) & 1);
    670     form_label(&f, "");
    671     int p = str_put(line, sizeof line, 0, "Partidas ");
    672     p = str_int(line, sizeof line, p, mini_get("par", 0));
    673     p = str_put(line, sizeof line, p, "   ganadas vs CPU ");
    674     p = str_int(line, sizeof line, p, mini_get("gan", 0));
    675     p = str_put(line, sizeof line, p, "   mejor ");
    676     str_int(line, sizeof line, p, mini_get("mej", 0));
    677     form_label(&f, line);
    678     if (has_saved()) f.cur = 1;
    679     bool dirty = true;
    680     int r = FORM_NONE;
    681     for (;;) {
    682         int n = form_get(&f, 3)->value + 1;
    683         for (int i = 0; i < MAXP; i++) form_get(&f, 4 + i)->hidden = i >= n;
    684         if (dirty) { hal_draw(paint_form, &f); dirty = false; }
    685         int k = hal_key();
    686         if (k < 0) { hal_sleep(10); continue; }
    687         dirty = true;
    688         if (k == 0) continue;
    689         r = form_key(&f, k);
    690         if (r == 1 || r == 2) break;
    691     }
    692     long tip = 0;
    693     for (int i = 0; i < MAXP; i++) tip |= (long)form_get(&f, 4 + i)->value << i;
    694     mini_set("nj", form_get(&f, 3)->value + 1);
    695     mini_set("tip", tip);
    696     mini_set("esc", form_get(&f, 8)->value);
    697     mini_set("srv", form_get(&f, 9)->value);
    698     mini_save();
    699     return r;
    700 }
    701 
    702 static void start_from_options(void)
    703 {
    704     uint8_t cpu[MAXP];
    705     long t = mini_get("tip", 2);
    706     for (int i = 0; i < MAXP; i++) cpu[i] = (uint8_t)((t >> i) & 1);
    707     new_game((int)mini_get("nj", 2), cpu, mini_get("esc", 0) != 0, mini_get("srv", 1) != 0);
    708 }
    709 
    710 /* otra partida con la misma configuracion */
    711 static void restart(void)
    712 {
    713     uint8_t cpu[MAXP];
    714     memcpy(cpu, G.cpu, MAXP);
    715     new_game(G.n, cpu, G.esc_as, G.srv_gana);
    716 }
    717 
    718 /* Devuelve true si hay que volver al menu principal. */
    719 static bool pause_menu(void)
    720 {
    721     Form f;
    722     form_begin(&f, "Pausa");
    723     form_button(&f, 1, "Seguir");
    724     form_button(&f, 2, "Empezar de nuevo");
    725     form_button(&f, 3, "Menú principal");
    726     int r = mini_form(&f);
    727     if (r == 2) restart();
    728     G.cpu_t = hal_ms() + 700;
    729     return r == 3;
    730 }
    731 
    732 void app_main(void)
    733 {
    734     mini_start("Generala", RGB(240, 110, 80));
    735     mini_art = art;
    736     for (;;) {
    737         int r = main_menu();
    738         if (r != 2 || !load_game()) start_from_options();
    739         bool dirty = true;
    740         for (;;) {
    741             int k = hal_key();
    742             if (k == 0) dirty = true;
    743             else if (k > 0) {
    744                 dirty = true;
    745                 if (G.state == OVER) {
    746                     if (k == K_OK || k == ' ') restart();
    747                     else if (k == K_BACK) break;
    748                     continue;
    749                 }
    750                 if (G.state == CONFIRM) {
    751                     if (k == K_OK || k == 's' || k == 'S') { G.state = PLAYING; do_score(G.sel); }
    752                     else if (k == K_BACK || k == 'n' || k == 'N') G.state = PLAYING;
    753                     continue;
    754                 }
    755                 if (G.cpu[G.cur]) {
    756                     if (k != K_BACK) G.cpu_t = hal_ms();          /* cualquier tecla apura */
    757                     else if (pause_menu()) goto menu;
    758                     continue;
    759                 }
    760                 switch (k) {
    761                 case K_LEFT:  G.dcur = (G.dcur + NDICE - 1) % NDICE; G.sel = -1; break;
    762                 case K_RIGHT: G.dcur = (G.dcur + 1) % NDICE; G.sel = -1; break;
    763                 case '1': case '2': case '3': case '4': case '5':
    764                     G.dcur = k - '1';
    765                     /* fallthrough */
    766                 case ' ':
    767                     if (G.rolls && G.rolls < 3) { G.held[G.dcur] ^= 1; G.sel = -1; }
    768                     break;
    769                 case K_UP:   if (G.rolls) move_sel(-1); break;
    770                 case K_DOWN: if (G.rolls) move_sel(1); break;
    771                 case 't': case 'T': roll(); break;
    772                 case K_OK:
    773                     if (G.sel >= 0) try_score();
    774                     else if (G.rolls < 3) roll();
    775                     break;
    776                 case K_BACK:
    777                     if (G.sel >= 0 && G.rolls < 3) G.sel = -1;
    778                     else if (pause_menu()) goto menu;
    779                     break;
    780                 }
    781             }
    782             if (G.state == PLAYING && G.cpu[G.cur] && (int32_t)(hal_ms() - G.cpu_t) >= 0) {
    783                 cpu_step();
    784                 dirty = true;
    785             }
    786             if (dirty) { hal_draw(paint, 0); dirty = false; }
    787             else hal_sleep(10);
    788         }
    789     menu:;
    790     }
    791 }