app.c (21085B)
1 /* app.c - Palabra: el Wordle en castellano para la PicoCalc. 2 * 3 * Adivinar una palabra de 5 letras en 6 intentos. Letras del teclado, ';' = Ñ, 4 * Retroceso borra, Enter prueba, Esc pausa. Las tildes no cuentan (se muestran al 5 * final). En modo difícil las pistas reveladas se tienen que usar. 6 * La partida en curso se guarda despues de cada intento ("Continuar"). */ 7 #include <string.h> 8 #include "mini.h" 9 #include "palabras.h" 10 11 /* 1: muestra la solucion en la barra y la fija en PALABRA_WORD (para probar). */ 12 #ifndef PALABRA_DEBUG 13 #define PALABRA_DEBUG 0 14 #endif 15 #ifndef PALABRA_WORD 16 #define PALABRA_WORD "perro" 17 #endif 18 19 enum { ROWS = 6, LEN = 5, NLET = 27, ENE = 26 }; 20 enum { PLAYING, WON, LOST }; 21 enum { UNK, ABSENT, PRESENT, CORRECT }; /* estado de letra / casilla */ 22 23 /* tablero y teclado en pantalla */ 24 enum { CELL = 30, GAP = 3, GX = (320 - (LEN * CELL + (LEN - 1) * GAP)) / 2, GY = 30, 25 KW = 28, KH = 19, KGAP = 3, KY = 243 }; 26 enum { STEP_MS = 180, SHAKE_MS = 40, MSG_MS = 1800 }; 27 28 typedef struct { 29 int sol; /* indice en SOLUCIONES */ 30 uint8_t ans[LEN]; 31 bool hard; 32 uint8_t guess[ROWS][LEN], res[ROWS][LEN]; 33 int rows; /* intentos hechos */ 34 uint8_t cur[LEN]; 35 int ncur; 36 uint8_t keys[NLET]; /* mejor estado conocido de cada letra */ 37 int state; 38 char msg[48]; 39 uint32_t msg_t, shake_t, reveal_t; 40 bool shaking, revealing; 41 bool card; /* cartel de fin a la vista (Tab lo esconde) */ 42 } Game; 43 44 static Game G; 45 46 /* ------------------------------------------------------------ palabras */ 47 48 static void unpack(uint32_t w, uint8_t *l) 49 { 50 for (int i = LEN - 1; i >= 0; i--) { l[i] = (uint8_t)(w & 31); w >>= 5; } 51 } 52 53 static uint32_t pack(const uint8_t *l) 54 { 55 uint32_t w = 0; 56 for (int i = 0; i < LEN; i++) w = w << 5 | l[i]; 57 return w; 58 } 59 60 static bool is_valid(const uint8_t *l) 61 { 62 uint32_t w = pack(l); 63 int lo = 0, hi = NVALIDAS - 1; 64 while (lo <= hi) { 65 int m = (lo + hi) / 2; 66 if (VALIDAS[m] == w) return true; 67 if (VALIDAS[m] < w) lo = m + 1; else hi = m - 1; 68 } 69 return false; 70 } 71 72 /* Letra en UTF-8 (mayuscula). */ 73 static int put_letter(char *b, int n, int p, int l) 74 { 75 char t[3]; 76 if (l == ENE) { t[0] = (char)0xC3; t[1] = (char)0x91; t[2] = 0; } 77 else { t[0] = (char)('A' + l); t[1] = 0; } 78 return str_put(b, n, p, t); 79 } 80 81 /* La solucion con su tilde, en mayusculas. */ 82 static int put_solution(char *b, int n, int p) 83 { 84 uint32_t w = SOLUCIONES[G.sol]; 85 int acc = (int)(w >> 25 & 7), dier = (int)(w >> 28 & 1); 86 for (int i = 0; i < LEN; i++) { 87 int l = G.ans[i]; 88 if (acc == i + 1 && l < 26) { 89 static const char V[] = "AEIOU"; 90 static const uint8_t TILDE[] = { 0xC1, 0xC9, 0xCD, 0xD3, 0xDA }; /* Latin-1 */ 91 const char *v = strchr(V, 'A' + l); 92 if (v) { 93 int c = dier ? 0xDC : TILDE[v - V]; /* Ü */ 94 char t[3] = { (char)0xC3, (char)(c - 0x40), 0 }; 95 p = str_put(b, n, p, t); 96 continue; 97 } 98 } 99 p = put_letter(b, n, p, l); 100 } 101 return p; 102 } 103 104 /* Colores con la regla de las letras repetidas: primero las exactas, despues las 105 * que estan en otro lado mientras queden sin usar en la solucion. */ 106 static void score(const uint8_t *g, uint8_t *r) 107 { 108 int left[NLET] = { 0 }; 109 for (int i = 0; i < LEN; i++) { 110 if (g[i] == G.ans[i]) r[i] = CORRECT; 111 else { r[i] = ABSENT; left[G.ans[i]]++; } 112 } 113 for (int i = 0; i < LEN; i++) 114 if (r[i] != CORRECT && left[g[i]] > 0) { r[i] = PRESENT; left[g[i]]--; } 115 } 116 117 /* Modo dificil: los verdes en su lugar y las letras reveladas, al menos tantas veces. */ 118 static bool hard_ok(const uint8_t *g, char *why, int n) 119 { 120 for (int r = 0; r < G.rows; r++) { 121 for (int i = 0; i < LEN; i++) 122 if (G.res[r][i] == CORRECT && g[i] != G.guess[r][i]) { 123 int p = str_put(why, n, 0, "La "); 124 p = str_int(why, n, p, i + 1); 125 p = str_put(why, n, p, "ª letra tiene que ser "); 126 put_letter(why, n, p, G.guess[r][i]); 127 return false; 128 } 129 for (int i = 0; i < LEN; i++) { 130 if (G.res[r][i] != PRESENT) continue; 131 int l = G.guess[r][i], need = 0, have = 0; 132 for (int j = 0; j < LEN; j++) { 133 if (G.guess[r][j] == l && G.res[r][j] != ABSENT) need++; 134 if (g[j] == l) have++; 135 } 136 if (have < need) { 137 int p = str_put(why, n, 0, "Tiene que tener la "); 138 put_letter(why, n, p, l); 139 return false; 140 } 141 } 142 } 143 return true; 144 } 145 146 static void update_keys(int r) 147 { 148 for (int i = 0; i < LEN; i++) { 149 int l = G.guess[r][i]; 150 if (G.res[r][i] > G.keys[l]) G.keys[l] = G.res[r][i]; 151 } 152 } 153 154 /* ------------------------------------------------------------ partida y guardado */ 155 156 static void new_game(bool hard) 157 { 158 memset(&G, 0, sizeof G); 159 G.hard = hard; 160 G.sol = mini_rand(NSOLUCIONES); 161 #if PALABRA_DEBUG 162 uint8_t dw[LEN]; 163 for (int i = 0; i < LEN; i++) dw[i] = (uint8_t)(PALABRA_WORD[i] - 'a'); 164 for (int i = 0; i < NSOLUCIONES; i++) 165 if ((SOLUCIONES[i] & PAL_MASK) == pack(dw)) G.sol = i; 166 #endif 167 unpack(SOLUCIONES[G.sol] & PAL_MASK, G.ans); 168 } 169 170 /* "p": modo (0/1), indice de la solucion, '.', los intentos (a..z, Ñ = '_'). */ 171 static void save_progress(void) 172 { 173 char b[64]; 174 int p = str_put(b, sizeof b, 0, G.hard ? "1" : "0"); 175 p = str_int(b, sizeof b, p, G.sol); 176 p = str_put(b, sizeof b, p, "."); 177 for (int r = 0; r < G.rows; r++) 178 for (int i = 0; i < LEN; i++) { 179 char t[2] = { G.guess[r][i] == ENE ? '_' : (char)('a' + G.guess[r][i]), 0 }; 180 p = str_put(b, sizeof b, p, t); 181 } 182 mini_set_str("p", b); 183 mini_save(); 184 } 185 186 static bool has_progress(void) 187 { 188 const char *s = mini_get_str("p"); 189 return s && s[0]; 190 } 191 192 static bool load_progress(void) 193 { 194 const char *s = mini_get_str("p"); 195 if (!s || (s[0] != '0' && s[0] != '1')) return false; 196 long idx = 0; 197 int i = 1; 198 while (s[i] >= '0' && s[i] <= '9') idx = idx * 10 + (s[i++] - '0'); 199 if (s[i] != '.' || idx >= NSOLUCIONES) return false; 200 memset(&G, 0, sizeof G); 201 G.hard = s[0] == '1'; 202 G.sol = (int)idx; 203 unpack(SOLUCIONES[G.sol] & PAL_MASK, G.ans); 204 const char *g = s + i + 1; 205 while (G.rows < ROWS - 1 && strlen(g) >= LEN) { 206 for (int k = 0; k < LEN; k++) { 207 if (g[k] == '_') G.guess[G.rows][k] = ENE; 208 else if (g[k] >= 'a' && g[k] <= 'z') G.guess[G.rows][k] = (uint8_t)(g[k] - 'a'); 209 else return false; 210 } 211 score(G.guess[G.rows], G.res[G.rows]); 212 update_keys(G.rows); 213 G.rows++; 214 g += LEN; 215 } 216 return true; 217 } 218 219 static void add_stat(const char *k, long d) { mini_set(k, mini_get(k, 0) + d); } 220 221 static void record(bool won) 222 { 223 add_stat("jug", 1); 224 if (won) { 225 add_stat("gan", 1); 226 add_stat("rac", 1); 227 if (mini_get("rac", 0) > mini_get("max", 0)) mini_set("max", mini_get("rac", 0)); 228 char k[4] = { 'd', (char)('0' + G.rows), 0 }; 229 add_stat(k, 1); 230 } else mini_set("rac", 0); 231 mini_set_str("p", ""); 232 mini_save(); 233 } 234 235 /* Dejar una partida empezada cuenta como perdida (si no, se podria sortear la palabra). */ 236 static void abandon(void) 237 { 238 if (has_progress()) { 239 const char *s = mini_get_str("p"); 240 const char *dot = strchr(s, '.'); 241 if (dot && dot[1]) record(false); 242 else { mini_set_str("p", ""); mini_save(); } 243 } 244 } 245 246 static void set_msg(const char *m) 247 { 248 str_put(G.msg, sizeof G.msg, 0, m); 249 G.msg_t = hal_ms(); 250 } 251 252 static void submit(void) 253 { 254 if (G.ncur < LEN) { set_msg("Faltan letras"); G.shaking = true; G.shake_t = hal_ms(); return; } 255 if (!is_valid(G.cur)) { set_msg("No está en la lista"); G.shaking = true; G.shake_t = hal_ms(); return; } 256 char why[48]; 257 if (G.hard && !hard_ok(G.cur, why, sizeof why)) { set_msg(why); G.shaking = true; G.shake_t = hal_ms(); return; } 258 memcpy(G.guess[G.rows], G.cur, LEN); 259 score(G.cur, G.res[G.rows]); 260 G.rows++; 261 G.ncur = 0; 262 G.msg[0] = 0; 263 G.revealing = true; 264 G.reveal_t = hal_ms(); 265 bool won = !memcmp(G.guess[G.rows - 1], G.ans, LEN); 266 if (won || G.rows == ROWS) { 267 G.state = won ? WON : LOST; 268 G.card = true; 269 record(won); 270 } else save_progress(); 271 } 272 273 /* Termina la animacion de revelado (el teclado se actualiza recien ahi). */ 274 static void finish_reveal(void) 275 { 276 if (!G.revealing) return; 277 G.revealing = false; 278 update_keys(G.rows - 1); 279 } 280 281 static void type_letter(int l) 282 { 283 if (G.ncur < LEN) G.cur[G.ncur++] = (uint8_t)l; 284 } 285 286 /* ------------------------------------------------------------ dibujo */ 287 288 #define BG RGB(16, 20, 30) 289 #define C_GREEN RGB(70, 150, 70) 290 #define C_YELLOW RGB(196, 164, 48) 291 #define C_GRAY RGB(62, 66, 76) 292 #define C_EMPTY RGB(50, 56, 72) 293 #define C_TYPED RGB(130, 140, 162) 294 #define C_KEY RGB(104, 114, 136) 295 296 static Color tile_color(int st) 297 { 298 return st == CORRECT ? C_GREEN : st == PRESENT ? C_YELLOW : C_GRAY; 299 } 300 301 static void draw_tile(Surface *s, int x, int y, int l, int st, bool typed) 302 { 303 char t[4]; 304 if (st == UNK) { 305 gfx_rect(s, x, y, CELL, CELL, BG); 306 gfx_frame(s, x, y, CELL, CELL, typed ? C_TYPED : C_EMPTY); 307 gfx_frame(s, x + 1, y + 1, CELL - 2, CELL - 2, typed ? C_TYPED : C_EMPTY); 308 } else gfx_rect(s, x, y, CELL, CELL, tile_color(st)); 309 if (l < 0) return; 310 put_letter(t, sizeof t, 0, l); 311 mini_big_text(s, &font_bold, x + (CELL - 12) / 2, y + 3, 2, t, C_WHITE); 312 } 313 314 /* Cuantas casillas de la ultima fila ya se revelaron. */ 315 static int revealed(void) 316 { 317 if (!G.revealing) return LEN; 318 int n = (int)((hal_ms() - G.reveal_t) / STEP_MS); 319 return n > LEN ? LEN : n; 320 } 321 322 static int shake_dx(void) 323 { 324 static const int8_t D[] = { -6, 6, -5, 5, -3, 3, -1, 0 }; 325 if (!G.shaking) return 0; 326 uint32_t i = (hal_ms() - G.shake_t) / SHAKE_MS; 327 return i < sizeof D ? D[i] : 0; 328 } 329 330 static void draw_board(Surface *s) 331 { 332 int rv = revealed(); 333 for (int r = 0; r < ROWS; r++) { 334 int y = GY + r * (CELL + GAP), dx = r == G.rows && G.state == PLAYING ? shake_dx() : 0; 335 for (int i = 0; i < LEN; i++) { 336 int x = GX + dx + i * (CELL + GAP); 337 if (r < G.rows) { 338 bool shown = r < G.rows - 1 || i < rv; 339 draw_tile(s, x, y, G.guess[r][i], shown ? G.res[r][i] : UNK, true); 340 } else if (r == G.rows && G.state == PLAYING) 341 draw_tile(s, x, y, i < G.ncur ? G.cur[i] : -1, UNK, i < G.ncur); 342 else draw_tile(s, x, y, -1, UNK, false); 343 } 344 } 345 } 346 347 static void draw_key(Surface *s, int x, int y, int w, const char *t, Color bg) 348 { 349 gfx_rect(s, x, y, w, KH, bg); 350 gfx_text_center(s, &font_bold, x, y + 3, w, t, C_WHITE); 351 } 352 353 static const char *const KROWS[3] = { "QWERTYUIOP", "ASDFGHJKL;", "ZXCVBNM" }; 354 355 static void draw_keyboard(Surface *s) 356 { 357 for (int r = 0; r < 3; r++) { 358 const char *k = KROWS[r]; 359 int n = (int)strlen(k), side = r == 2 ? 44 : 0; 360 int w = n * KW + (n - 1) * KGAP + (side ? 2 * (side + KGAP) : 0); 361 int x = (320 - w) / 2, y = KY + r * (KH + KGAP); 362 if (side) { 363 draw_key(s, x, y, side, "", C_KEY); 364 gfx_text_center(s, &font_small, x, y + 6, side, "ENTER", C_WHITE); 365 x += side + KGAP; 366 } 367 for (int i = 0; i < n; i++, x += KW + KGAP) { 368 int l = k[i] == ';' ? ENE : k[i] - 'A'; 369 char t[3]; 370 put_letter(t, sizeof t, 0, l); 371 Color bg = G.keys[l] == UNK ? C_KEY : G.keys[l] == ABSENT ? RGB(36, 40, 50) : tile_color(G.keys[l]); 372 draw_key(s, x, y, KW, t, bg); 373 if (G.keys[l] == ABSENT) gfx_text_center(s, &font_bold, x, y + 3, KW, t, RGB(90, 96, 110)); 374 } 375 if (side) { /* borrar: una flecha */ 376 draw_key(s, x, y, side, "", C_KEY); 377 int cx = x + side / 2, cy = y + KH / 2; 378 int32_t xs[3] = { (cx - 10) * 16, (cx - 3) * 16, (cx - 3) * 16 }; 379 int32_t ys[3] = { cy * 16, (cy - 6) * 16, (cy + 6) * 16 }; 380 gfx_poly16(s, xs, ys, 3, C_WHITE); 381 gfx_rect(s, cx - 3, cy - 2, 12, 5, C_WHITE); 382 } 383 } 384 } 385 386 static void stat_col(Surface *s, int x, int y, long v, const char *label) 387 { 388 char b[12]; 389 str_int(b, sizeof b, 0, v); 390 int w = mini_big_w(&font_bold, 2, b); 391 mini_big_text(s, &font_bold, x + (60 - w) / 2, y, 2, b, C_TEXT); 392 gfx_text_center(s, &font_small, x, y + 28, 60, label, C_DIM); 393 } 394 395 static void draw_card(Surface *s) 396 { 397 int bx = 30, by = 34, w = 260, h = 232; 398 Color edge = G.state == WON ? C_OK : C_ERR; 399 char b[48]; 400 gfx_box(s, bx, by, w, h, C_PANEL, edge); 401 gfx_box(s, bx + 1, by + 1, w - 2, h - 2, C_PANEL, edge); 402 const char *title = G.state == WON ? "¡Ganaste!" : "Perdiste"; 403 mini_big_text(s, &font_bold, bx + (w - mini_big_w(&font_bold, 2, title)) / 2, by + 8, 2, title, edge); 404 int p = str_put(b, sizeof b, 0, "La palabra era "); 405 put_solution(b, sizeof b, p); 406 gfx_text_center(s, &font_normal, bx, by + 38, w, b, C_TEXT); 407 408 long jug = mini_get("jug", 0), gan = mini_get("gan", 0); 409 int y = by + 58; 410 stat_col(s, bx + 10, y, jug, "Jugadas"); 411 stat_col(s, bx + 70, y, jug ? (gan * 100 + jug / 2) / jug : 0, "% ganadas"); 412 stat_col(s, bx + 130, y, mini_get("rac", 0), "Racha"); 413 stat_col(s, bx + 190, y, mini_get("max", 0), "Máxima"); 414 415 y += 44; 416 gfx_text_center(s, &font_bold, bx, y, w, "Intentos", C_ACCENT); 417 y += 17; 418 long d[ROWS], dmax = 1; 419 for (int i = 0; i < ROWS; i++) { 420 char k[4] = { 'd', (char)('1' + i), 0 }; 421 d[i] = mini_get(k, 0); 422 if (d[i] > dmax) dmax = d[i]; 423 } 424 for (int i = 0; i < ROWS; i++, y += 15) { 425 char n[2] = { (char)('1' + i), 0 }; 426 gfx_text(s, &font_bold, bx + 16, y, n, C_TEXT); 427 str_int(b, sizeof b, 0, d[i]); 428 int tw = gfx_text_w(&font_bold, b), full = w - 44; 429 int bw = (int)(d[i] * full / dmax); 430 if (bw < tw + 8) bw = tw + 8; 431 bool mine = G.state == WON && G.rows == i + 1; 432 gfx_rect(s, bx + 28, y - 1, bw, 13, mine ? C_GREEN : C_GRAY); 433 gfx_text(s, &font_bold, bx + 28 + bw - tw - 4, y, b, C_WHITE); 434 } 435 gfx_text_center(s, &font_small, bx, by + h - 14, w, "Enter otra Tab tablero Esc menú", C_DIM); 436 } 437 438 static void paint(Surface *s, void *ctx) 439 { 440 char b[40]; 441 gfx_rect(s, 0, 0, 320, 320, BG); 442 gfx_rect(s, 0, 0, 320, 24, C_PANEL); 443 int p = str_put(b, sizeof b, 0, "Intento "); 444 p = str_int(b, sizeof b, p, G.rows + (G.state == PLAYING ? 1 : 0)); 445 str_put(b, sizeof b, p, "/6"); 446 gfx_text(s, &font_bold, 6, 6, b, C_TEXT); 447 gfx_text_center(s, &font_normal, 100, 6, 120, G.hard ? "Difícil" : "Normal", C_ACCENT); 448 #if PALABRA_DEBUG 449 put_solution(b, sizeof b, 0); 450 #else 451 p = str_put(b, sizeof b, 0, "Racha "); 452 str_int(b, sizeof b, p, mini_get("rac", 0)); 453 #endif 454 gfx_text(s, &font_bold, 314 - gfx_text_w(&font_bold, b), 6, b, C_TEXT); 455 456 draw_board(s); 457 draw_keyboard(s); 458 if (G.msg[0] && hal_ms() - G.msg_t < MSG_MS) { 459 int w = gfx_text_w(&font_bold, G.msg) + 16; 460 gfx_box(s, (320 - w) / 2, 226, w, 16, C_TEXT, C_TEXT); 461 gfx_text_center(s, &font_bold, 0, 228, 320, G.msg, BG); 462 } 463 if (G.state != PLAYING && !G.revealing && G.card) draw_card(s); 464 else if (G.state != PLAYING && !G.revealing) 465 gfx_text_center(s, &font_small, 0, 311, 320, "Enter otra Tab estadísticas Esc menú", C_DIM); 466 else gfx_text_center(s, &font_small, 0, 311, 320, "Enter probar Retroceso borrar ; = Ñ Esc menú", C_DIM); 467 } 468 469 /* ------------------------------------------------------------ menus y bucle */ 470 471 static void art(Surface *s) 472 { 473 /* PALABRA en fichas de colores */ 474 static const char W[] = "PALABRA"; 475 static const uint8_t ST[] = { CORRECT, ABSENT, PRESENT, ABSENT, CORRECT, PRESENT, CORRECT }; 476 int c = 22, g = 3, x0 = (320 - 7 * c - 6 * g) / 2, y = 58; 477 for (int i = 0; i < 7; i++) { 478 int x = x0 + i * (c + g); 479 char t[2] = { W[i], 0 }; 480 gfx_rect(s, x, y, c, c, tile_color(ST[i])); 481 gfx_text_center(s, &font_bold, x, y + 5, c, t, C_WHITE); 482 } 483 } 484 485 static const char *const MODES[] = { "Normal", "Difícil" }; 486 487 static void how_to(void) 488 { 489 Form f; 490 form_begin(&f, "Cómo se juega"); 491 form_label(&f, "Adiviná la palabra de 5 letras en 6 intentos."); 492 form_label(&f, "Verde: la letra está en ese lugar."); 493 form_label(&f, "Amarillo: está en la palabra, en otro lugar."); 494 form_label(&f, "Gris: no está (o no tantas veces)."); 495 form_label(&f, "Las tildes no cuentan. ; = Ñ"); 496 form_label(&f, "Difícil: las pistas se tienen que usar."); 497 form_button(&f, 1, "OK"); 498 f.cur = f.n - 1; 499 mini_form(&f); 500 } 501 502 /* 1 jugar nueva, 2 continuar */ 503 static int main_menu(bool *hard) 504 { 505 static char l1[64], l2[64]; 506 for (;;) { 507 bool cont = has_progress(); 508 Form f; 509 form_begin(&f, "Palabra"); 510 if (cont) form_button(&f, 2, "Continuar"); 511 form_button(&f, 1, "Palabra al azar"); 512 form_choice(&f, 3, "Modo", MODES, 2, *hard ? 1 : 0); 513 form_button(&f, 4, "Cómo se juega"); 514 form_label(&f, ""); 515 long jug = mini_get("jug", 0), gan = mini_get("gan", 0); 516 int p = str_put(l1, sizeof l1, 0, "Jugadas "); 517 p = str_int(l1, sizeof l1, p, jug); 518 p = str_put(l1, sizeof l1, p, ", ganadas "); 519 p = str_int(l1, sizeof l1, p, jug ? (gan * 100 + jug / 2) / jug : 0); 520 str_put(l1, sizeof l1, p, "%"); 521 p = str_put(l2, sizeof l2, 0, "Racha "); 522 p = str_int(l2, sizeof l2, p, mini_get("rac", 0)); 523 p = str_put(l2, sizeof l2, p, ", máxima "); 524 str_int(l2, sizeof l2, p, mini_get("max", 0)); 525 form_label(&f, l1); 526 form_label(&f, l2); 527 int r = mini_form(&f); 528 bool h = form_get(&f, 3)->value == 1; 529 if (h != *hard) { *hard = h; mini_set("modo", h); mini_save(); } 530 if (r == 4) how_to(); 531 else if (r == 1 || r == 2) return r; 532 } 533 } 534 535 /* Devuelve true si hay que volver al menu principal. */ 536 static bool pause_menu(void) 537 { 538 Form f; 539 form_begin(&f, "Pausa"); 540 form_button(&f, 1, "Seguir"); 541 form_button(&f, 2, "Empezar de nuevo"); 542 form_button(&f, 3, "Menú principal"); 543 form_label(&f, ""); 544 form_label(&f, "La partida queda guardada."); 545 form_label(&f, "Empezar de nuevo cuenta como perdida."); 546 int r = mini_form(&f); 547 if (r == 2) { 548 if (G.rows) record(false); 549 new_game(G.hard); 550 } 551 return r == 3; 552 } 553 554 static int to_letter(int k) 555 { 556 if (k >= 'a' && k <= 'z') return k - 'a'; 557 if (k >= 'A' && k <= 'Z') return k - 'A'; 558 if (k == ';' || k == 0xF1 || k == 0xD1) return ENE; /* ñ Ñ en Latin-1 */ 559 return -1; 560 } 561 562 void app_main(void) 563 { 564 mini_start("Palabra", RGB(110, 200, 110)); 565 mini_art = art; 566 bool hard = mini_get("modo", 0) == 1; 567 for (;;) { 568 int m = main_menu(&hard); 569 if (m == 2 && load_progress()) { 570 hard = G.hard; 571 } else { 572 abandon(); 573 new_game(hard); 574 } 575 bool dirty = true; 576 int shown_rv = -1, shown_dx = 0; 577 bool shown_msg = false; 578 for (;;) { 579 int k = hal_key(); 580 if (k == 0) dirty = true; 581 else if (k > 0) { 582 dirty = true; 583 if (G.revealing) finish_reveal(); 584 if (G.state != PLAYING) { 585 if (k == K_OK || k == ' ') { new_game(G.hard); } 586 else if (k == K_TAB) G.card = !G.card; 587 else if (k == K_BACK) break; 588 continue; 589 } 590 int l = to_letter(k); 591 if (l >= 0) type_letter(l); 592 else if (k == K_BKSP || k == K_LEFT) { if (G.ncur) G.ncur--; } 593 else if (k == K_OK) submit(); 594 else if (k == K_BACK && pause_menu()) break; 595 } 596 /* animaciones: revelado, sacudon y mensaje */ 597 if (G.revealing) { 598 int rv = revealed(); 599 if (rv != shown_rv) { shown_rv = rv; dirty = true; } 600 if (hal_ms() - G.reveal_t >= (uint32_t)(LEN * STEP_MS + 100)) { finish_reveal(); dirty = true; } 601 } 602 if (G.shaking) { 603 int dx = shake_dx(); 604 if (dx != shown_dx) { shown_dx = dx; dirty = true; } 605 if (hal_ms() - G.shake_t > 8 * SHAKE_MS) G.shaking = false; 606 } 607 bool msg = G.msg[0] && hal_ms() - G.msg_t < MSG_MS; 608 if (msg != shown_msg) { shown_msg = msg; dirty = true; } 609 if (dirty) { hal_draw(paint, 0); dirty = false; } 610 else hal_sleep(10); 611 } 612 } 613 }