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 }