app.c (13465B)
1 /* app.c - buscaminas para la PicoCalc. 2 * 3 * Flechas mueven el cursor, Enter/espacio destapa (sobre un numero con sus banderas 4 * completas destapa los vecinos), F o Tab pone bandera, Esc pausa. 5 * La primera casilla nunca es mina: las minas se reparten despues del primer toque. */ 6 #include <string.h> 7 #include "mini.h" 8 9 enum { MAXW = 20, MAXH = 18, TOP = 28 }; 10 11 typedef struct { const char *name; int w, h, mines, cell; } Level; 12 static const Level LEVELS[] = { 13 { "Fácil", 9, 9, 10, 30 }, 14 { "Medio", 16, 16, 40, 17 }, 15 { "Difícil", 20, 18, 72, 15 }, 16 }; 17 enum { NLEVELS = 3 }; 18 19 enum { PLAYING, WON, LOST }; 20 21 typedef struct { 22 const Level *lv; 23 int lvl; 24 uint8_t mine[MAXH][MAXW], adj[MAXH][MAXW], open[MAXH][MAXW], flag[MAXH][MAXW]; 25 int cx, cy; 26 int state, opened, flags; 27 bool placed; /* ya se repartieron las minas */ 28 int boom_x, boom_y; 29 uint32_t t0, elapsed; /* reloj: corre desde el primer toque */ 30 bool record; 31 } Game; 32 33 static Game G; 34 35 /* ------------------------------------------------------------ reglas */ 36 37 static bool inside(int x, int y) { return x >= 0 && y >= 0 && x < G.lv->w && y < G.lv->h; } 38 39 static void place_mines(int sx, int sy) 40 { 41 static uint8_t idx[MAXW * MAXH]; 42 int n = 0; 43 for (int y = 0; y < G.lv->h; y++) 44 for (int x = 0; x < G.lv->w; x++) 45 if (x < sx - 1 || x > sx + 1 || y < sy - 1 || y > sy + 1) idx[n++] = (uint8_t)(y * MAXW + x); 46 /* mezcla parcial: las primeras "mines" posiciones */ 47 for (int i = 0; i < G.lv->mines; i++) { 48 int j = i + mini_rand(n - i); 49 uint8_t t = idx[i]; idx[i] = idx[j]; idx[j] = t; 50 G.mine[idx[i] / MAXW][idx[i] % MAXW] = 1; 51 } 52 for (int y = 0; y < G.lv->h; y++) 53 for (int x = 0; x < G.lv->w; x++) { 54 int c = 0; 55 for (int dy = -1; dy <= 1; dy++) 56 for (int dx = -1; dx <= 1; dx++) 57 if (inside(x + dx, y + dy) && G.mine[y + dy][x + dx]) c++; 58 G.adj[y][x] = (uint8_t)c; 59 } 60 G.placed = true; 61 G.t0 = hal_ms(); 62 } 63 64 static void new_game(int lvl) 65 { 66 memset(&G, 0, sizeof G); 67 G.lvl = lvl; 68 G.lv = &LEVELS[lvl]; 69 G.cx = G.lv->w / 2; 70 G.cy = G.lv->h / 2; 71 } 72 73 static void end_game(bool won) 74 { 75 G.state = won ? WON : LOST; 76 G.elapsed = hal_ms() - G.t0; 77 char k[16]; 78 int p = str_put(k, sizeof k, 0, "jug"); 79 str_int(k, sizeof k, p, G.lvl); 80 mini_set(k, mini_get(k, 0) + 1); 81 if (won) { 82 k[0] = 'g'; k[1] = 'a'; k[2] = 'n'; 83 mini_set(k, mini_get(k, 0) + 1); 84 p = str_put(k, sizeof k, 0, "rec"); 85 str_int(k, sizeof k, p, G.lvl); 86 long best = mini_get(k, 0); 87 if (!best || (long)G.elapsed < best) { mini_set(k, (long)G.elapsed); G.record = true; } 88 for (int y = 0; y < G.lv->h; y++) /* banderas en las que quedaron */ 89 for (int x = 0; x < G.lv->w; x++) 90 if (G.mine[y][x]) G.flag[y][x] = 1; 91 G.flags = G.lv->mines; 92 } 93 mini_save(); 94 } 95 96 /* Destapa (x, y) y, si es un cero, todo su alrededor (con una pila, sin recursion). */ 97 static void reveal(int x, int y) 98 { 99 static uint16_t stack[MAXW * MAXH]; 100 int sp = 0; 101 if (G.open[y][x] || G.flag[y][x]) return; 102 if (G.mine[y][x]) { 103 G.open[y][x] = 1; 104 G.boom_x = x; G.boom_y = y; 105 end_game(false); 106 return; 107 } 108 G.open[y][x] = 1; G.opened++; 109 stack[sp++] = (uint16_t)(y * MAXW + x); 110 while (sp) { 111 int v = stack[--sp], px = v % MAXW, py = v / MAXW; 112 if (G.adj[py][px]) continue; 113 for (int dy = -1; dy <= 1; dy++) 114 for (int dx = -1; dx <= 1; dx++) { 115 int nx = px + dx, ny = py + dy; 116 if (!inside(nx, ny) || G.open[ny][nx] || G.flag[ny][nx]) continue; 117 G.open[ny][nx] = 1; G.opened++; 118 stack[sp++] = (uint16_t)(ny * MAXW + nx); 119 } 120 } 121 } 122 123 static void act_open(void) 124 { 125 int x = G.cx, y = G.cy; 126 if (!G.placed) place_mines(x, y); 127 if (G.open[y][x] && G.adj[y][x]) { /* "acorde": destapar los vecinos */ 128 int f = 0; 129 for (int dy = -1; dy <= 1; dy++) 130 for (int dx = -1; dx <= 1; dx++) 131 if (inside(x + dx, y + dy) && G.flag[y + dy][x + dx]) f++; 132 if (f != G.adj[y][x]) return; 133 for (int dy = -1; dy <= 1 && G.state == PLAYING; dy++) 134 for (int dx = -1; dx <= 1 && G.state == PLAYING; dx++) 135 if (inside(x + dx, y + dy)) reveal(x + dx, y + dy); 136 } else reveal(x, y); 137 if (G.state == PLAYING && G.opened == G.lv->w * G.lv->h - G.lv->mines) end_game(true); 138 } 139 140 static void act_flag(void) 141 { 142 if (G.open[G.cy][G.cx]) return; 143 G.flag[G.cy][G.cx] ^= 1; 144 G.flags += G.flag[G.cy][G.cx] ? 1 : -1; 145 } 146 147 /* ------------------------------------------------------------ dibujo */ 148 149 static const Color NUMC[9] = { 150 0, RGB(80, 150, 255), RGB(90, 200, 90), RGB(240, 80, 70), RGB(170, 110, 255), 151 RGB(230, 150, 40), RGB(60, 200, 200), RGB(230, 230, 230), RGB(150, 150, 150) 152 }; 153 154 static void draw_flag(Surface *s, int x, int y, int c) 155 { 156 int px = x + c * 2 / 5, pw = c >= 20 ? 2 : 1; 157 gfx_rect(s, px, y + c / 5, pw, c * 3 / 5, C_TEXT); /* palo */ 158 gfx_rect(s, x + c / 4, y + c * 4 / 5 - pw, c / 2, pw, C_TEXT); /* base */ 159 int32_t xs[3] = { (px + pw) * 16, (px + pw) * 16, (x + c * 4 / 5) * 16 }; 160 int32_t ys[3] = { (y + c / 5) * 16, (y + c * 11 / 20) * 16, (y + c * 3 / 8) * 16 }; 161 gfx_poly16(s, xs, ys, 3, RGB(240, 60, 40)); 162 } 163 164 static void draw_mine(Surface *s, int x, int y, int c) 165 { 166 int r = c / 4; 167 gfx_line(s, x + c / 2, y + c / 5, x + c / 2, y + c - c / 5, 1, C_BLACK); 168 gfx_line(s, x + c / 5, y + c / 2, x + c - c / 5, y + c / 2, 1, C_BLACK); 169 gfx_disc(s, x + c / 2, y + c / 2, r, C_BLACK); 170 gfx_rect(s, x + c / 2 - r / 2, y + c / 2 - r / 2, r / 2 > 1 ? r / 2 : 1, r / 2 > 1 ? r / 2 : 1, C_WHITE); 171 } 172 173 static void draw_cell(Surface *s, int x, int y, int px, int py, int c) 174 { 175 bool open = G.open[y][x] || (G.state == LOST && G.mine[y][x]); 176 if (!open) { 177 gfx_rect(s, px, py, c, c, RGB(90, 104, 132)); 178 gfx_rect(s, px, py, c, 2, RGB(130, 146, 178)); 179 gfx_rect(s, px, py, 2, c, RGB(130, 146, 178)); 180 gfx_rect(s, px, py + c - 2, c, 2, RGB(52, 60, 80)); 181 gfx_rect(s, px + c - 2, py, 2, c, RGB(52, 60, 80)); 182 if (G.flag[y][x]) { 183 draw_flag(s, px, py, c); 184 if (G.state == LOST && !G.mine[y][x]) { /* bandera equivocada */ 185 gfx_line(s, px + 3, py + 3, px + c - 4, py + c - 4, 2, C_BLACK); 186 gfx_line(s, px + c - 4, py + 3, px + 3, py + c - 4, 2, C_BLACK); 187 } 188 } 189 return; 190 } 191 bool boom = G.state == LOST && x == G.boom_x && y == G.boom_y; 192 gfx_rect(s, px, py, c, c, boom ? RGB(220, 40, 30) : RGB(200, 204, 212)); 193 gfx_frame(s, px, py, c, c, RGB(160, 164, 176)); 194 if (G.mine[y][x]) { draw_mine(s, px, py, c); return; } 195 int n = G.adj[y][x]; 196 if (!n) return; 197 char t[2] = { (char)('0' + n), 0 }; 198 if (c >= 26) mini_big_text(s, &font_bold, px + (c - 12) / 2, py + (c - 26) / 2, 2, t, NUMC[n]); 199 else gfx_text(s, &font_bold, px + (c - 6) / 2, py + (c - 13) / 2, t, NUMC[n]); 200 } 201 202 static uint32_t elapsed(void) 203 { 204 if (!G.placed) return 0; 205 return G.state == PLAYING ? hal_ms() - G.t0 : G.elapsed; 206 } 207 208 static void paint(Surface *s, void *ctx) 209 { 210 const Level *lv = G.lv; 211 int c = lv->cell, bw = lv->w * c, bh = lv->h * c; 212 int ox = (320 - bw) / 2, oy = TOP + (280 - bh) / 2; 213 char buf[40]; 214 int p; 215 216 gfx_rect(s, 0, 0, 320, 320, RGB(16, 20, 30)); 217 gfx_rect(s, 0, 0, 320, 24, C_PANEL); 218 draw_mine(s, 6, 4, 16); 219 p = str_int(buf, sizeof buf, 0, lv->mines - G.flags); 220 gfx_text(s, &font_bold, 26, 6, buf, C_TEXT); 221 str_time(buf, sizeof buf, 0, elapsed()); 222 gfx_text(s, &font_bold, 314 - gfx_text_w(&font_bold, buf), 6, buf, C_TEXT); 223 gfx_text_center(s, &font_normal, 60, 6, 200, lv->name, C_ACCENT); 224 225 gfx_rect(s, ox - 3, oy - 3, bw + 6, bh + 6, RGB(40, 46, 60)); 226 for (int y = 0; y < lv->h; y++) 227 for (int x = 0; x < lv->w; x++) 228 draw_cell(s, x, y, ox + x * c, oy + y * c, c); 229 if (G.state == PLAYING) { 230 gfx_frame(s, ox + G.cx * c, oy + G.cy * c, c, c, C_CURSOR); 231 gfx_frame(s, ox + G.cx * c + 1, oy + G.cy * c + 1, c - 2, c - 2, C_CURSOR); 232 gfx_text_center(s, &font_small, 0, 311, 320, "Enter destapar F bandera Esc menú", C_DIM); 233 return; 234 } 235 /* cartel de fin */ 236 int bx = 40, by = 130, w = 240, h = 60; 237 gfx_box(s, bx, by, w, h, C_PANEL, G.state == WON ? C_OK : C_ERR); 238 gfx_box(s, bx + 1, by + 1, w - 2, h - 2, C_PANEL, G.state == WON ? C_OK : C_ERR); 239 gfx_text_center(s, &font_bold, bx, by + 8, w, G.state == WON ? "¡Ganaste!" : "¡Boom!", 240 G.state == WON ? C_OK : C_ERR); 241 if (G.state == WON) { 242 p = str_put(buf, sizeof buf, 0, "Tiempo "); 243 p = str_time(buf, sizeof buf, p, G.elapsed); 244 if (G.record) str_put(buf, sizeof buf, p, " ¡récord!"); 245 gfx_text_center(s, &font_normal, bx, by + 26, w, buf, G.record ? C_ACCENT : C_TEXT); 246 } else gfx_text_center(s, &font_normal, bx, by + 26, w, "Pisaste una mina", C_TEXT); 247 gfx_text_center(s, &font_small, bx, by + 45, w, "Enter otra Esc menú", C_DIM); 248 } 249 250 /* ------------------------------------------------------------ menus y bucle */ 251 252 static void art(Surface *s) 253 { 254 /* una tirita de casillas de adorno */ 255 static const int8_t demo[] = { 1, 2, -1, 3, -2, 1, 0 }; 256 int c = 22, x0 = (320 - 7 * c) / 2, y = 60; 257 for (int i = 0; i < 7; i++) { 258 int x = x0 + i * c; 259 if (demo[i] == -1) { 260 gfx_rect(s, x, y, c, c, RGB(90, 104, 132)); 261 gfx_rect(s, x, y, c, 2, RGB(130, 146, 178)); 262 draw_flag(s, x, y, c); 263 } else { 264 gfx_rect(s, x, y, c, c, demo[i] == -2 ? RGB(220, 40, 30) : RGB(200, 204, 212)); 265 gfx_frame(s, x, y, c, c, RGB(160, 164, 176)); 266 if (demo[i] == -2) draw_mine(s, x, y, c); 267 else if (demo[i]) { char t[2] = { (char)('0' + demo[i]), 0 }; gfx_text(s, &font_bold, x + 8, y + 5, t, NUMC[demo[i]]); } 268 } 269 } 270 } 271 272 static const char *const LEVEL_NAMES[] = { "Fácil", "Medio", "Difícil" }; 273 274 static int main_menu(int lvl) 275 { 276 static char lines[NLEVELS][48]; 277 Form f; 278 form_begin(&f, "Buscaminas"); 279 form_button(&f, 1, "Jugar"); 280 form_choice(&f, 2, "Nivel", LEVEL_NAMES, NLEVELS, lvl); 281 form_label(&f, ""); 282 for (int i = 0; i < NLEVELS; i++) { 283 char k[8]; 284 int p = str_put(k, sizeof k, 0, "rec"); 285 str_int(k, sizeof k, p, i); 286 long rec = mini_get(k, 0); 287 p = str_put(lines[i], sizeof lines[i], 0, LEVEL_NAMES[i]); 288 p = str_put(lines[i], sizeof lines[i], p, ": récord "); 289 if (rec) p = str_time(lines[i], sizeof lines[i], p, (uint32_t)rec); 290 else p = str_put(lines[i], sizeof lines[i], p, "-"); 291 k[0] = 'g'; k[1] = 'a'; k[2] = 'n'; 292 p = str_put(lines[i], sizeof lines[i], p, ", ganadas "); 293 p = str_int(lines[i], sizeof lines[i], p, mini_get(k, 0)); 294 k[0] = 'j'; k[1] = 'u'; k[2] = 'g'; 295 p = str_put(lines[i], sizeof lines[i], p, " de "); 296 str_int(lines[i], sizeof lines[i], p, mini_get(k, 0)); 297 form_label(&f, lines[i]); 298 } 299 for (;;) { 300 int r = mini_form(&f); 301 if (r == 1) return form_get(&f, 2)->value; 302 } 303 } 304 305 /* Devuelve true si hay que volver al menu principal. */ 306 static bool pause_menu(void) 307 { 308 uint32_t t = hal_ms(); 309 Form f; 310 form_begin(&f, "Pausa"); 311 form_button(&f, 1, "Seguir"); 312 form_button(&f, 2, "Empezar de nuevo"); 313 form_button(&f, 3, "Menú principal"); 314 int r = mini_form(&f); 315 G.t0 += hal_ms() - t; /* la pausa no cuenta */ 316 if (r == 2) new_game(G.lvl); 317 return r == 3; 318 } 319 320 void app_main(void) 321 { 322 mini_start("Buscaminas", RGB(120, 170, 255)); 323 mini_art = art; 324 int lvl = (int)mini_get("nivel", 0); 325 if (lvl < 0 || lvl >= NLEVELS) lvl = 0; 326 for (;;) { 327 int nl = main_menu(lvl); 328 if (nl != lvl) { lvl = nl; mini_set("nivel", lvl); mini_save(); } 329 new_game(lvl); 330 bool dirty = true; 331 uint32_t shown_sec = 0; 332 for (;;) { 333 int k = hal_key(); 334 if (k == 0) dirty = true; 335 else if (k > 0) { 336 dirty = true; 337 if (G.state != PLAYING) { 338 if (k == K_OK || k == ' ') new_game(lvl); 339 else if (k == K_BACK) break; 340 continue; 341 } 342 int w = G.lv->w, h = G.lv->h; 343 switch (k) { 344 case K_LEFT: G.cx = (G.cx + w - 1) % w; break; 345 case K_RIGHT: G.cx = (G.cx + 1) % w; break; 346 case K_UP: G.cy = (G.cy + h - 1) % h; break; 347 case K_DOWN: G.cy = (G.cy + 1) % h; break; 348 case K_OK: case ' ': act_open(); break; 349 case 'f': case 'F': case K_TAB: case 'm': case 'M': act_flag(); break; 350 case K_BACK: if (pause_menu()) goto menu; break; 351 } 352 } 353 uint32_t sec = elapsed() / 1000; 354 if (sec != shown_sec) { shown_sec = sec; dirty = true; } 355 if (dirty) { hal_draw(paint, 0); dirty = false; } 356 else hal_sleep(10); 357 } 358 menu:; 359 } 360 }