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 }