app.c (20340B)
1 /* app.c - Bloques: piezas que caen, para la PicoCalc. 2 * 3 * Flechas izq/der mueven, arriba o X rota horario, Z antihorario, abajo baja una fila 4 * (el teclado solo manda "apretada": cada toque es una fila), espacio/Enter la tira 5 * hasta el fondo, C guarda la pieza, Esc pausa. 6 * Bolsa de 7, rotacion SRS con wall kicks, lock delay de 500 ms con tope de reinicios, 7 * gravedad y puntaje segun la guideline. Los tiempos se miden con hal_ms. */ 8 #include <string.h> 9 #include "mini.h" 10 11 enum { 12 W = 10, H = 22, HID = 2, /* 2 filas ocultas arriba del pozo */ 13 CELL = 14, TOP = 26, 14 OX = 14, OY = TOP, /* esquina del pozo visible */ 15 PX = 166, PW = 148, /* panel lateral */ 16 LOCK_MS = 500, LOCK_RESETS = 15, FLASH_MS = 150, 17 NLEVELS = 15, MAXLEVEL = 20, NQUEUE = 3, 18 }; 19 20 enum { PLAYING, FLASH, OVER }; 21 22 /* ------------------------------------------------------------ piezas */ 23 24 enum { P_I, P_O, P_T, P_S, P_Z, P_J, P_L }; 25 26 /* estado 0 de cada pieza (x, y con y hacia abajo) y lado de su caja de rotacion */ 27 static const int8_t SHAPE0[7][4][2] = { 28 { {0,1}, {1,1}, {2,1}, {3,1} }, /* I */ 29 { {1,0}, {2,0}, {1,1}, {2,1} }, /* O */ 30 { {1,0}, {0,1}, {1,1}, {2,1} }, /* T */ 31 { {1,0}, {2,0}, {0,1}, {1,1} }, /* S */ 32 { {0,0}, {1,0}, {1,1}, {2,1} }, /* Z */ 33 { {0,0}, {0,1}, {1,1}, {2,1} }, /* J */ 34 { {2,0}, {0,1}, {1,1}, {2,1} }, /* L */ 35 }; 36 static const int8_t BOX[7] = { 4, 4, 3, 3, 3, 3, 3 }; 37 38 static int8_t shape[7][4][4][2]; /* [pieza][rotacion][celda][x,y] */ 39 40 /* Wall kicks SRS (y hacia arriba, como en la tabla oficial). Indice: 0->R, R->0, 41 * R->2, 2->R, 2->L, L->2, L->0, 0->L; horario desde s = 2s, antihorario = (2s+7)%8. */ 42 static const int8_t KICK_JLSTZ[8][5][2] = { 43 { {0,0}, {-1,0}, {-1, 1}, {0,-2}, {-1,-2} }, 44 { {0,0}, { 1,0}, { 1,-1}, {0, 2}, { 1, 2} }, 45 { {0,0}, { 1,0}, { 1,-1}, {0, 2}, { 1, 2} }, 46 { {0,0}, {-1,0}, {-1, 1}, {0,-2}, {-1,-2} }, 47 { {0,0}, { 1,0}, { 1, 1}, {0,-2}, { 1,-2} }, 48 { {0,0}, {-1,0}, {-1,-1}, {0, 2}, {-1, 2} }, 49 { {0,0}, {-1,0}, {-1,-1}, {0, 2}, {-1, 2} }, 50 { {0,0}, { 1,0}, { 1, 1}, {0,-2}, { 1,-2} }, 51 }; 52 static const int8_t KICK_I[8][5][2] = { 53 { {0,0}, {-2,0}, { 1,0}, {-2,-1}, { 1, 2} }, 54 { {0,0}, { 2,0}, {-1,0}, { 2, 1}, {-1,-2} }, 55 { {0,0}, {-1,0}, { 2,0}, {-1, 2}, { 2,-1} }, 56 { {0,0}, { 1,0}, {-2,0}, { 1,-2}, {-2, 1} }, 57 { {0,0}, { 2,0}, {-1,0}, { 2, 1}, {-1,-2} }, 58 { {0,0}, {-2,0}, { 1,0}, {-2,-1}, { 1, 2} }, 59 { {0,0}, { 1,0}, {-2,0}, { 1,-2}, {-2, 1} }, 60 { {0,0}, {-1,0}, { 2,0}, {-1, 2}, { 2,-1} }, 61 }; 62 63 /* ms por fila segun nivel (guideline: (0.8 - (n-1)*0.007)^(n-1) s) */ 64 static const uint16_t GRAVITY[MAXLEVEL] = { 65 1000, 793, 618, 473, 355, 262, 190, 135, 94, 64, 43, 28, 18, 11, 7, 5, 4, 3, 2, 1 66 }; 67 68 static const Color COL[7] = { RGB(40, 200, 230), RGB(240, 210, 40), RGB(170, 80, 220), RGB(80, 200, 80), 69 RGB(230, 60, 60), RGB(60, 110, 235), RGB(240, 140, 40) }; 70 static const Color LIGHT[7] = { RGB(150, 235, 250), RGB(255, 240, 150), RGB(210, 160, 245), RGB(160, 235, 160), 71 RGB(250, 150, 150), RGB(150, 180, 250), RGB(255, 195, 130) }; 72 static const Color DARK[7] = { RGB(20, 110, 130), RGB(150, 125, 20), RGB(95, 40, 130), RGB(40, 115, 40), 73 RGB(135, 30, 30), RGB(30, 55, 140), RGB(145, 80, 20) }; 74 75 static void init_shapes(void) 76 { 77 for (int p = 0; p < 7; p++) { 78 int n = BOX[p]; 79 for (int c = 0; c < 4; c++) { shape[p][0][c][0] = SHAPE0[p][c][0]; shape[p][0][c][1] = SHAPE0[p][c][1]; } 80 for (int r = 1; r < 4; r++) 81 for (int c = 0; c < 4; c++) { 82 int x = shape[p][r - 1][c][0], y = shape[p][r - 1][c][1]; 83 if (p == P_O) { shape[p][r][c][0] = (int8_t)x; shape[p][r][c][1] = (int8_t)y; } 84 else { shape[p][r][c][0] = (int8_t)(n - 1 - y); shape[p][r][c][1] = (int8_t)x; } 85 } 86 } 87 } 88 89 /* ------------------------------------------------------------ estado */ 90 91 typedef struct { 92 uint8_t well[H][W]; /* 0 vacio, 1..7 pieza+1 */ 93 int cur, rot, x, y; /* pieza en juego */ 94 int hold; /* -1 nada */ 95 bool held; /* ya se guardo con esta pieza */ 96 uint8_t bag[7]; int bag_n; 97 uint8_t queue[NQUEUE]; 98 int state; 99 int start_level, level; 100 long score, lines, best; 101 bool b2b; /* el ultimo borrado fue un tetris */ 102 uint32_t fall_t, lock_t, flash_t, t0, elapsed, over_t; 103 bool locking; 104 int resets, lowest; 105 uint8_t full[H]; /* filas que destellan */ 106 int queued[4], nq; /* teclas apretadas durante el destello */ 107 bool record; 108 const char *msg; /* "¡Tetris!", "Doble"... */ 109 uint32_t msg_t; 110 } Game; 111 112 static Game G; 113 114 /* ------------------------------------------------------------ reglas */ 115 116 static bool fits(int p, int r, int x, int y) 117 { 118 for (int c = 0; c < 4; c++) { 119 int cx = x + shape[p][r][c][0], cy = y + shape[p][r][c][1]; 120 if (cx < 0 || cx >= W || cy < 0 || cy >= H || G.well[cy][cx]) return false; 121 } 122 return true; 123 } 124 125 static int next_from_bag(void) 126 { 127 if (G.bag_n == 0) { 128 for (int i = 0; i < 7; i++) G.bag[i] = (uint8_t)i; 129 mini_shuffle(G.bag, 7); 130 G.bag_n = 7; 131 } 132 return G.bag[--G.bag_n]; 133 } 134 135 static int take_next(void) 136 { 137 int p = G.queue[0]; 138 for (int i = 0; i < NQUEUE - 1; i++) G.queue[i] = G.queue[i + 1]; 139 G.queue[NQUEUE - 1] = (uint8_t)next_from_bag(); 140 return p; 141 } 142 143 static void end_game(void); 144 145 static void spawn(int p) 146 { 147 G.cur = p; G.rot = 0; 148 G.x = 3; G.y = 0; 149 G.locking = false; G.resets = 0; 150 G.fall_t = hal_ms(); 151 if (!fits(p, 0, G.x, G.y)) { end_game(); return; } 152 if (fits(p, 0, G.x, G.y + 1)) G.y++; 153 G.lowest = G.y; 154 } 155 156 static int level_now(void) 157 { 158 int l = G.start_level + (int)(G.lines / 10); 159 return l > MAXLEVEL ? MAXLEVEL : l; 160 } 161 162 static void new_game(int start_level) 163 { 164 memset(&G, 0, sizeof G); 165 G.start_level = G.level = start_level; 166 G.hold = -1; 167 G.best = mini_get("rec", 0); 168 for (int i = 0; i < NQUEUE; i++) G.queue[i] = (uint8_t)next_from_bag(); 169 G.t0 = hal_ms(); 170 spawn(take_next()); 171 } 172 173 /* Records y estadisticas; tambien al abandonar desde la pausa. */ 174 static void save_stats(void) 175 { 176 mini_set("jug", mini_get("jug", 0) + 1); 177 if (G.score > mini_get("rec", 0)) { mini_set("rec", G.score); G.record = true; } 178 if (G.lines > mini_get("lin", 0)) mini_set("lin", G.lines); 179 mini_save(); 180 } 181 182 static void end_game(void) 183 { 184 G.state = OVER; 185 G.elapsed = hal_ms() - G.t0; 186 G.over_t = hal_ms(); 187 save_stats(); 188 } 189 190 /* Despues de un movimiento o giro que salio bien: reinicia el lock delay (con tope). */ 191 static void moved(void) 192 { 193 if (G.y > G.lowest) { G.lowest = G.y; G.resets = 0; } 194 if (G.locking && G.resets < LOCK_RESETS) { G.resets++; G.lock_t = hal_ms(); } 195 if (fits(G.cur, G.rot, G.x, G.y + 1)) G.locking = false; 196 } 197 198 static bool shift(int dx) 199 { 200 if (!fits(G.cur, G.rot, G.x + dx, G.y)) return false; 201 G.x += dx; 202 moved(); 203 return true; 204 } 205 206 static bool rotate(int dir) /* 1 horario, -1 antihorario */ 207 { 208 if (G.cur == P_O) return false; 209 int to = (G.rot + (dir > 0 ? 1 : 3)) & 3; 210 int k = dir > 0 ? 2 * G.rot : (2 * G.rot + 7) % 8; 211 const int8_t (*kick)[2] = G.cur == P_I ? KICK_I[k] : KICK_JLSTZ[k]; 212 for (int t = 0; t < 5; t++) { 213 int nx = G.x + kick[t][0], ny = G.y - kick[t][1]; 214 if (fits(G.cur, to, nx, ny)) { 215 G.x = nx; G.y = ny; G.rot = to; 216 moved(); 217 return true; 218 } 219 } 220 return false; 221 } 222 223 static void lock_piece(void) 224 { 225 bool visible = false; 226 for (int c = 0; c < 4; c++) { 227 int cy = G.y + shape[G.cur][G.rot][c][1]; 228 G.well[cy][G.x + shape[G.cur][G.rot][c][0]] = (uint8_t)(G.cur + 1); 229 if (cy >= HID) visible = true; 230 } 231 int n = 0; 232 for (int y = 0; y < H; y++) { 233 int k = 0; 234 for (int x = 0; x < W; x++) k += G.well[y][x] != 0; 235 G.full[y] = k == W; 236 n += G.full[y]; 237 } 238 G.held = false; 239 if (n) { 240 static const int PTS[5] = { 0, 100, 300, 500, 800 }; 241 static const char *const MSG[5] = { 0, "Simple", "Doble", "Triple", "¡Tetris!" }; 242 long pts = (long)PTS[n] * G.level; 243 if (n == 4 && G.b2b) pts += pts / 2; 244 G.b2b = n == 4; 245 G.score += pts; 246 G.lines += n; 247 G.level = level_now(); 248 G.msg = MSG[n]; G.msg_t = hal_ms(); 249 G.state = FLASH; 250 G.flash_t = hal_ms(); 251 G.cur = -1; 252 return; 253 } 254 if (!visible) { G.cur = -1; end_game(); return; } /* se trabo entera arriba del pozo */ 255 spawn(take_next()); 256 } 257 258 static void finish_flash(void) 259 { 260 int dst = H - 1; 261 for (int y = H - 1; y >= 0; y--) 262 if (!G.full[y]) { if (dst != y) memcpy(G.well[dst], G.well[y], W); dst--; } 263 for (; dst >= 0; dst--) memset(G.well[dst], 0, W); 264 memset(G.full, 0, sizeof G.full); 265 G.state = PLAYING; 266 spawn(take_next()); 267 } 268 269 static int drop_dist(void) 270 { 271 int d = 0; 272 while (fits(G.cur, G.rot, G.x, G.y + d + 1)) d++; 273 return d; 274 } 275 276 static void hard_drop(void) 277 { 278 int d = drop_dist(); 279 G.y += d; 280 G.score += 2 * d; 281 lock_piece(); 282 } 283 284 static void soft_drop(void) 285 { 286 if (!fits(G.cur, G.rot, G.x, G.y + 1)) return; 287 G.y++; 288 G.score++; 289 G.fall_t = hal_ms(); 290 moved(); 291 } 292 293 static void do_hold(void) 294 { 295 if (G.held) return; 296 int p = G.cur; 297 if (G.hold < 0) { G.hold = p; spawn(take_next()); } 298 else { int h = G.hold; G.hold = p; spawn(h); } 299 G.held = true; 300 } 301 302 /* Gravedad y lock delay; devuelve true si algo cambio. */ 303 static bool tick(uint32_t now) 304 { 305 if (G.state == FLASH) { 306 if (now - G.flash_t < FLASH_MS) return false; 307 finish_flash(); 308 return true; 309 } 310 if (G.state != PLAYING) return false; 311 bool ch = false; 312 uint32_t g = GRAVITY[G.level - 1]; 313 int steps = 0; 314 while (now - G.fall_t >= g && steps < H) { 315 if (!fits(G.cur, G.rot, G.x, G.y + 1)) { G.fall_t = now; break; } 316 G.y++; G.fall_t += g; steps++; ch = true; 317 if (G.y > G.lowest) { G.lowest = G.y; G.resets = 0; } 318 } 319 if (!fits(G.cur, G.rot, G.x, G.y + 1)) { 320 if (!G.locking) { G.locking = true; G.lock_t = now; } 321 else if (now - G.lock_t >= LOCK_MS) { lock_piece(); ch = true; } 322 } else G.locking = false; 323 return ch; 324 } 325 326 static void game_key(int k) 327 { 328 switch (k) { 329 case K_LEFT: shift(-1); break; 330 case K_RIGHT: shift(1); break; 331 case K_UP: case 'x': case 'X': rotate(1); break; 332 case 'z': case 'Z': rotate(-1); break; 333 case K_DOWN: soft_drop(); break; 334 case ' ': case K_OK: hard_drop(); break; 335 case 'c': case 'C': do_hold(); break; 336 } 337 } 338 339 /* ------------------------------------------------------------ dibujo */ 340 341 static void draw_block(Surface *s, int x, int y, int c, int p) 342 { 343 gfx_rect(s, x, y, c, c, COL[p]); 344 gfx_rect(s, x, y, c, 2, LIGHT[p]); 345 gfx_rect(s, x, y, 2, c, LIGHT[p]); 346 gfx_rect(s, x, y + c - 2, c, 2, DARK[p]); 347 gfx_rect(s, x + c - 2, y + 2, 2, c - 2, DARK[p]); 348 } 349 350 /* pieza suelta centrada en la caja (x, y, w, h) */ 351 static void draw_mini(Surface *s, int p, int x, int y, int w, int h, int c, bool dim) 352 { 353 int minx = 9, maxx = -1, miny = 9, maxy = -1; 354 for (int i = 0; i < 4; i++) { 355 int cx = shape[p][0][i][0], cy = shape[p][0][i][1]; 356 if (cx < minx) minx = cx; 357 if (cx > maxx) maxx = cx; 358 if (cy < miny) miny = cy; 359 if (cy > maxy) maxy = cy; 360 } 361 int ox = x + (w - (maxx - minx + 1) * c) / 2, oy = y + (h - (maxy - miny + 1) * c) / 2; 362 for (int i = 0; i < 4; i++) { 363 int bx = ox + (shape[p][0][i][0] - minx) * c, by = oy + (shape[p][0][i][1] - miny) * c; 364 if (dim) { gfx_rect(s, bx, by, c, c, RGB(70, 76, 92)); gfx_frame(s, bx, by, c, c, RGB(50, 55, 68)); } 365 else draw_block(s, bx, by, c, p); 366 } 367 } 368 369 static uint32_t elapsed(void) { return G.state == OVER ? G.elapsed : hal_ms() - G.t0; } 370 371 static void stat_row(Surface *s, int y, const char *label, long v, Color c) 372 { 373 char buf[16]; 374 str_int(buf, sizeof buf, 0, v); 375 gfx_text(s, &font_normal, PX + 8, y, label, C_DIM); 376 gfx_text(s, &font_bold, PX + PW - 8 - gfx_text_w(&font_bold, buf), y, buf, c); 377 } 378 379 static void paint(Surface *s, void *ctx) 380 { 381 char buf[40]; 382 gfx_rect(s, 0, 0, 320, 320, RGB(16, 20, 30)); 383 384 /* barra de arriba */ 385 gfx_rect(s, 0, 0, 320, 24, C_PANEL); 386 gfx_text(s, &font_bold, 8, 6, "Bloques", C_ACCENT); 387 int p = str_put(buf, sizeof buf, 0, "Nivel "); 388 str_int(buf, sizeof buf, p, G.level); 389 gfx_text_center(s, &font_normal, 80, 6, 160, buf, C_TEXT); 390 str_time(buf, sizeof buf, 0, elapsed()); 391 gfx_text(s, &font_bold, 312 - gfx_text_w(&font_bold, buf), 6, buf, C_TEXT); 392 393 /* pozo: fondo con grilla fija */ 394 int bw = W * CELL, bh = (H - HID) * CELL; 395 gfx_rect(s, OX - 3, OY - 3, bw + 6, bh + 6, RGB(60, 68, 88)); 396 gfx_rect(s, OX - 1, OY - 1, bw + 2, bh + 2, RGB(30, 34, 46)); 397 gfx_rect(s, OX, OY, bw, bh, RGB(8, 10, 16)); 398 for (int i = 1; i < W; i++) gfx_rect(s, OX + i * CELL, OY, 1, bh, RGB(20, 24, 34)); 399 for (int i = 1; i < H - HID; i++) gfx_rect(s, OX, OY + i * CELL, bw, 1, RGB(20, 24, 34)); 400 401 for (int y = HID; y < H; y++) { 402 int py = OY + (y - HID) * CELL; 403 if (G.full[y]) { 404 gfx_rect(s, OX, py, bw, CELL, RGB(250, 250, 250)); 405 continue; 406 } 407 for (int x = 0; x < W; x++) 408 if (G.well[y][x]) draw_block(s, OX + x * CELL, py, CELL, G.well[y][x] - 1); 409 } 410 if (G.state == PLAYING && G.cur >= 0) { 411 int gy = G.y + drop_dist(); 412 for (int c = 0; c < 4; c++) { /* fantasma */ 413 int cy = gy + shape[G.cur][G.rot][c][1]; 414 if (cy < HID) continue; 415 int bx = OX + (G.x + shape[G.cur][G.rot][c][0]) * CELL, by = OY + (cy - HID) * CELL; 416 gfx_frame(s, bx + 1, by + 1, CELL - 2, CELL - 2, DARK[G.cur]); 417 gfx_frame(s, bx + 2, by + 2, CELL - 4, CELL - 4, gfx_dim(DARK[G.cur])); 418 } 419 for (int c = 0; c < 4; c++) { 420 int cy = G.y + shape[G.cur][G.rot][c][1]; 421 if (cy < HID) continue; 422 draw_block(s, OX + (G.x + shape[G.cur][G.rot][c][0]) * CELL, OY + (cy - HID) * CELL, CELL, G.cur); 423 } 424 } 425 426 /* panel lateral */ 427 gfx_text(s, &font_small, PX + 2, 30, "GUARDADA (C)", C_DIM); 428 gfx_box(s, PX, 40, PW, 40, C_PANEL, C_PANEL2); 429 if (G.hold >= 0) draw_mini(s, G.hold, PX, 40, PW, 40, 12, G.held); 430 431 gfx_text(s, &font_small, PX + 2, 88, "SIGUIENTES", C_DIM); 432 gfx_box(s, PX, 98, PW, 96, C_PANEL, C_PANEL2); 433 draw_mini(s, G.queue[0], PX, 100, PW, 36, 12, false); 434 draw_mini(s, G.queue[1], PX, 138, PW, 26, 9, false); 435 draw_mini(s, G.queue[2], PX, 165, PW, 26, 9, false); 436 437 gfx_box(s, PX, 202, PW, 84, C_PANEL, C_PANEL2); 438 long best = G.score > G.best ? G.score : G.best; 439 stat_row(s, 208, "Puntos", G.score, C_TEXT); 440 stat_row(s, 227, "Líneas", G.lines, C_TEXT); 441 stat_row(s, 246, "Nivel", G.level, C_TEXT); 442 stat_row(s, 265, "Récord", best, G.score > G.best && G.best ? C_ACCENT : C_TEXT); 443 444 if (G.msg) gfx_text_center(s, &font_bold, PX, 292, PW, G.msg, G.msg[0] == '\xc2' ? C_ACCENT : C_OK); 445 446 if (G.state != OVER) { 447 gfx_text_center(s, &font_small, 0, 311, 320, "X/Z gira Abajo baja Espacio tira C guarda Esc pausa", C_DIM); 448 return; 449 } 450 /* cartel de fin */ 451 int bx = 40, by = 110, w = 240, h = 86; 452 gfx_box(s, bx, by, w, h, C_PANEL, C_ERR); 453 gfx_box(s, bx + 1, by + 1, w - 2, h - 2, C_PANEL, C_ERR); 454 gfx_text_center(s, &font_bold, bx, by + 8, w, "Fin de la partida", C_ERR); 455 p = str_put(buf, sizeof buf, 0, "Puntos "); 456 p = str_int(buf, sizeof buf, p, G.score); 457 p = str_put(buf, sizeof buf, p, " Líneas "); 458 str_int(buf, sizeof buf, p, G.lines); 459 gfx_text_center(s, &font_normal, bx, by + 28, w, buf, C_TEXT); 460 if (G.record) p = str_put(buf, sizeof buf, 0, "¡Récord nuevo!"); 461 else { 462 p = str_put(buf, sizeof buf, 0, "Récord "); 463 str_int(buf, sizeof buf, p, G.best); 464 } 465 gfx_text_center(s, &font_normal, bx, by + 46, w, buf, G.record ? C_ACCENT : C_DIM); 466 gfx_text_center(s, &font_small, bx, by + 70, w, "Enter otra Esc menú", C_DIM); 467 } 468 469 /* ------------------------------------------------------------ menus y bucle */ 470 471 static void art(Surface *s) 472 { 473 /* las siete piezas en fila */ 474 static const int8_t order[7] = { P_T, P_S, P_I, P_O, P_L, P_Z, P_J }; 475 int c = 8, x = 40; /* 168 px de piezas + 6 huecos de 12 */ 476 for (int i = 0; i < 7; i++) { 477 int p = order[i], w = (p == P_I ? 4 : p == P_O ? 2 : 3) * c; 478 draw_mini(s, p, x, 58, w, 20, c, false); 479 x += w + 12; 480 } 481 } 482 483 static const char *const LEVEL_NAMES[NLEVELS] = { 484 "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15" 485 }; 486 487 static int main_menu(int lvl) 488 { 489 static char lines[3][48]; 490 Form f; 491 form_begin(&f, "Bloques"); 492 form_button(&f, 1, "Jugar"); 493 form_choice(&f, 2, "Nivel inicial", LEVEL_NAMES, NLEVELS, lvl - 1); 494 form_label(&f, ""); 495 int p = str_put(lines[0], sizeof lines[0], 0, "Récord: "); 496 str_int(lines[0], sizeof lines[0], p, mini_get("rec", 0)); 497 p = str_put(lines[1], sizeof lines[1], 0, "Más líneas: "); 498 str_int(lines[1], sizeof lines[1], p, mini_get("lin", 0)); 499 p = str_put(lines[2], sizeof lines[2], 0, "Partidas: "); 500 str_int(lines[2], sizeof lines[2], p, mini_get("jug", 0)); 501 for (int i = 0; i < 3; i++) form_label(&f, lines[i]); 502 for (;;) { 503 int r = mini_form(&f); 504 if (r == 1) return form_get(&f, 2)->value + 1; 505 } 506 } 507 508 /* Devuelve true si hay que volver al menu principal. */ 509 static bool pause_menu(void) 510 { 511 uint32_t t = hal_ms(); 512 Form f; 513 form_begin(&f, "Pausa"); 514 form_button(&f, 1, "Seguir"); 515 form_button(&f, 2, "Empezar de nuevo"); 516 form_button(&f, 3, "Menú principal"); 517 int r = mini_form(&f); 518 uint32_t d = hal_ms() - t; /* la pausa no cuenta */ 519 G.t0 += d; G.fall_t += d; G.lock_t += d; G.flash_t += d; G.msg_t += d; 520 if (r == 2 || r == 3) save_stats(); 521 if (r == 2) new_game(G.start_level); 522 return r == 3; 523 } 524 525 void app_main(void) 526 { 527 mini_start("Bloques", RGB(240, 140, 40)); 528 mini_art = art; 529 init_shapes(); 530 int lvl = (int)mini_get("nivel", 1); 531 if (lvl < 1 || lvl > NLEVELS) lvl = 1; 532 for (;;) { 533 int nl = main_menu(lvl); 534 if (nl != lvl) { lvl = nl; mini_set("nivel", lvl); mini_save(); } 535 new_game(lvl); 536 bool dirty = true; 537 uint32_t shown_sec = 0; 538 for (;;) { 539 int k = hal_key(); 540 if (k == 0) dirty = true; 541 else if (k > 0) { 542 dirty = true; 543 if (G.state == OVER) { 544 if (hal_ms() - G.over_t < 400) continue; /* que un espacio de mas no reinicie */ 545 if (k == K_OK || k == ' ') new_game(lvl); 546 else if (k == K_BACK) break; 547 continue; 548 } 549 if (k == K_BACK) { 550 if (pause_menu()) break; 551 continue; 552 } 553 if (G.state == FLASH) { /* se aplican al salir la pieza */ 554 if (G.nq < 4) G.queued[G.nq++] = k; 555 continue; 556 } 557 game_key(k); 558 } 559 uint32_t now = hal_ms(); 560 bool was_flash = G.state == FLASH; 561 if (tick(now)) dirty = true; 562 if (was_flash && G.state == PLAYING) { 563 for (int i = 0; i < G.nq && G.state == PLAYING; i++) game_key(G.queued[i]); 564 G.nq = 0; 565 } 566 if (G.msg && now - G.msg_t > 1500) { G.msg = 0; dirty = true; } 567 uint32_t sec = elapsed() / 1000; 568 if (sec != shown_sec) { shown_sec = sec; dirty = true; } 569 if (dirty) { hal_draw(paint, 0); dirty = false; } 570 else hal_sleep(5); 571 } 572 } 573 }