juegos

Juegos de terminal de Pancho: Catan (TUI, GUI, web, servidor, PicoCalc), ajedrez, calculadora y minijuegos
git clone https://git.lu3dhn.xyz/juegos.git
Log | Files | Refs

app.c (16042B)


      1 /* app.c - la vibora (snake) para la PicoCalc.
      2  *
      3  * Flechas cambian la direccion (se guardan hasta 2 giros para las vueltas en U,
      4  * el giro de 180 grados se ignora), Esc pausa. La partida arranca con la primera
      5  * flecha. Cada manzana suma puntos, alarga la vibora y la acelera un poco.
      6  * Con paredes, chocar el borde mata; sin paredes se sale por el lado opuesto.
      7  *
      8  * Dibujo: la cancha tiene un patron fijo y cada paso cambia pocas celdas (cabeza,
      9  * cola, manzana), asi en el aparato solo se retransmiten 2-4 franjas. */
     10 #include <string.h>
     11 #include "mini.h"
     12 
     13 enum { W = 20, H = 18, CELL = 15, OX = 10, OY = 32, MAXN = W * H };
     14 
     15 enum { UP, RIGHT, DOWN, LEFT };
     16 static const int8_t DX[4] = { 0, 1, 0, -1 }, DY[4] = { -1, 0, 1, 0 };
     17 
     18 static const char *const SPEED_NAMES[] = { "Lenta", "Normal", "Rápida" };
     19 static const char *const WALL_NAMES[] = { "Sí", "No" };
     20 static const int SPEED_MS[] = { 190, 140, 95 };    /* intervalo inicial por paso */
     21 static const int SPEED_MIN[] = { 90, 70, 50 };     /* tope de aceleracion */
     22 enum { NSPEEDS = 3, POINTS = 10 };
     23 
     24 enum { READY, PLAYING, DEAD, FULL };
     25 
     26 typedef struct {
     27     int speed, walls;                  /* opciones (walls 0: si hay paredes) */
     28     uint16_t pos[MAXN];                /* anillo de celdas y*W+x, de la cola a la cabeza */
     29     uint8_t dir[MAXN];                 /* direccion con la que se entro a esa celda */
     30     int tail, len;                     /* indice de la cola en el anillo y largo */
     31     uint8_t occ[H][W];
     32     int grow;                          /* segmentos que faltan crecer */
     33     int food;                          /* celda de la manzana, -1 si no hay */
     34     int d;                             /* direccion actual */
     35     uint8_t q[2]; int nq;              /* giros pendientes */
     36     int state, score, eaten;
     37     int crash;                         /* celda contra la que choco (-1: ninguna) */
     38     int interval;                      /* ms por paso */
     39     uint32_t next;                     /* hal_ms del proximo paso */
     40     long best;
     41     bool record;
     42 } Game;
     43 
     44 static Game G;
     45 
     46 /* ------------------------------------------------------------ reglas */
     47 
     48 static int seg(int i) { return (G.tail + i) % MAXN; }         /* i-esimo desde la cola */
     49 static int head_idx(void) { return seg(G.len - 1); }
     50 
     51 static void rec_key(char *k, int speed, int walls)
     52 {
     53     k[0] = 'r'; k[1] = (char)('0' + speed); k[2] = (char)('0' + walls); k[3] = 0;
     54 }
     55 
     56 static void place_food(void)
     57 {
     58     int nfree = W * H - G.len;
     59     G.food = -1;
     60     if (nfree <= 0) return;
     61     int n = mini_rand(nfree);
     62     for (int c = 0; c < W * H; c++)
     63         if (!G.occ[c / W][c % W] && n-- == 0) { G.food = c; return; }
     64 }
     65 
     66 static void new_game(int speed, int walls)
     67 {
     68     memset(&G, 0, sizeof G);
     69     G.speed = speed;
     70     G.walls = walls;
     71     G.len = 4;
     72     for (int i = 0; i < G.len; i++) {
     73         int x = W / 2 - 3 + i, y = H / 2;
     74         G.pos[i] = (uint16_t)(y * W + x);
     75         G.dir[i] = RIGHT;
     76         G.occ[y][x] = 1;
     77     }
     78     G.d = RIGHT;
     79     G.crash = -1;
     80     G.interval = SPEED_MS[speed];
     81     char k[4];
     82     rec_key(k, speed, walls);
     83     G.best = mini_get(k, 0);
     84     place_food();
     85 }
     86 
     87 static void end_game(int state)
     88 {
     89     G.state = state;
     90     char k[8];
     91     rec_key(k, G.speed, G.walls);
     92     if (G.score > mini_get(k, 0)) { mini_set(k, G.score); G.best = G.score; G.record = true; }
     93     k[0] = 'j';
     94     mini_set(k, mini_get(k, 0) + 1);
     95     mini_save();
     96 }
     97 
     98 /* Flecha: se encola si no repite ni invierte el ultimo giro pendiente. */
     99 static void turn(int nd)
    100 {
    101     int last = G.nq ? G.q[G.nq - 1] : G.d;
    102     if (nd == last || nd == (last + 2) % 4 || G.nq >= 2) return;
    103     G.q[G.nq++] = (uint8_t)nd;
    104 }
    105 
    106 static void step(void)
    107 {
    108     if (G.nq) {
    109         G.d = G.q[0];
    110         G.q[0] = G.q[1];
    111         G.nq--;
    112     }
    113     int h = G.pos[head_idx()], x = h % W + DX[G.d], y = h / W + DY[G.d];
    114     if (x < 0 || y < 0 || x >= W || y >= H) {
    115         if (!G.walls) { end_game(DEAD); return; }
    116         x = (x + W) % W;
    117         y = (y + H) % H;
    118     }
    119     int nc = y * W + x;
    120     bool eat = nc == G.food;
    121     if (eat) G.grow++;
    122     /* la cola se corre en este mismo paso, asi que se puede entrar donde estaba */
    123     if (G.occ[y][x] && (G.grow || nc != G.pos[G.tail])) { G.crash = nc; end_game(DEAD); return; }
    124     if (G.grow) G.grow--;
    125     else {
    126         int t = G.pos[G.tail];
    127         G.occ[t / W][t % W] = 0;
    128         G.tail = (G.tail + 1) % MAXN;
    129         G.len--;
    130     }
    131     int i = seg(G.len);
    132     G.pos[i] = (uint16_t)nc;
    133     G.dir[i] = (uint8_t)G.d;
    134     G.occ[y][x] = 1;
    135     G.len++;
    136     if (eat) {
    137         G.eaten++;
    138         G.score += POINTS + G.speed * 5;
    139         G.interval -= 3;
    140         if (G.interval < SPEED_MIN[G.speed]) G.interval = SPEED_MIN[G.speed];
    141         place_food();
    142         if (G.food < 0) end_game(FULL);
    143     }
    144 }
    145 
    146 /* ------------------------------------------------------------ dibujo */
    147 
    148 #define C_BG      RGB(16, 20, 30)
    149 #define C_FIELD1  RGB(26, 40, 32)
    150 #define C_FIELD2  RGB(30, 46, 37)
    151 #define C_SKIN    RGB(90, 200, 80)
    152 #define C_SKIN2   RGB(70, 176, 64)
    153 #define C_EDGE    RGB(20, 70, 30)
    154 #define C_HEAD    RGB(120, 220, 96)
    155 
    156 static void cell_xy(int c, int *px, int *py) { *px = OX + (c % W) * CELL; *py = OY + (c / W) * CELL; }
    157 
    158 static void draw_apple(Surface *s, int px, int py)
    159 {
    160     int cx = px + 7, cy = py + 8;
    161     gfx_disc(s, cx, cy, 5, RGB(210, 36, 40));
    162     gfx_disc(s, cx - 2, cy - 2, 1, RGB(255, 150, 140));
    163     gfx_rect(s, cx, py + 1, 1, 3, RGB(110, 70, 30));
    164     int32_t xs[3] = { (cx + 1) * 16, (cx + 5) * 16, (cx + 2) * 16 };
    165     int32_t ys[3] = { (py + 3) * 16, (py + 1) * 16, (py + 5) * 16 };
    166     gfx_poly16(s, xs, ys, 3, RGB(80, 190, 70));
    167 }
    168 
    169 /* Tramo del cuerpo de media celda que sale del centro (cx, cy) hacia d. */
    170 static void bridge(Surface *s, int cx, int cy, int d, int r, Color c)
    171 {
    172     int h = CELL / 2;
    173     switch (d) {
    174     case UP:    gfx_rect(s, cx - r, cy - h, 2 * r + 1, h, c); break;
    175     case DOWN:  gfx_rect(s, cx - r, cy + 1, 2 * r + 1, h, c); break;
    176     case LEFT:  gfx_rect(s, cx - h, cy - r, h, 2 * r + 1, c); break;
    177     case RIGHT: gfx_rect(s, cx + 1, cy - r, h, 2 * r + 1, c); break;
    178     }
    179 }
    180 
    181 /* Radio del segmento i (desde la cola): la cola se afina. */
    182 static int seg_r(int i)
    183 {
    184     if (i == G.len - 1) return 6;
    185     return i == 0 ? 4 : i == 1 ? 5 : 6;
    186 }
    187 
    188 /* Un segmento: disco en el centro y puentes hacia sus vecinos. pass 0: borde oscuro. */
    189 static void draw_seg(Surface *s, int i, int pass)
    190 {
    191     int k = seg(i), px, py;
    192     cell_xy(G.pos[k], &px, &py);
    193     int cx = px + 7, cy = py + 7, r = seg_r(i) + (pass ? 0 : 1);
    194     /* puentes un poco mas finos y oscuros que los discos: se ven los segmentos */
    195     int rb = pass ? r - 1 : r;
    196     Color cb = pass ? C_SKIN2 : C_EDGE;
    197     if (i < G.len - 1) bridge(s, cx, cy, G.dir[seg(i + 1)], rb, cb);   /* hacia la cabeza */
    198     if (i > 0) bridge(s, cx, cy, (G.dir[k] + 2) % 4, rb, cb);          /* hacia la cola */
    199     Color c = pass == 0 ? C_EDGE : C_SKIN;
    200     if (i == G.len - 1 && pass) c = G.state == DEAD ? RGB(230, 100, 70) : C_HEAD;
    201     gfx_disc(s, cx, cy, r, c);
    202     if (pass && r >= 5 && i < G.len - 1) gfx_disc(s, cx - 2, cy - 2, 1, RGB(140, 230, 120));
    203 }
    204 
    205 static void draw_eyes(Surface *s)
    206 {
    207     int px, py;
    208     cell_xy(G.pos[head_idx()], &px, &py);
    209     int cx = px + 7, cy = py + 7, d = G.d;
    210     /* ojos adelante y a los costados de la direccion */
    211     int fx = DX[d] * 2, fy = DY[d] * 2, sx = -DY[d] * 3, sy = DX[d] * 3;
    212     for (int e = -1; e <= 1; e += 2) {
    213         int ex = cx + fx + e * sx, ey = cy + fy + e * sy;
    214         if (G.state == DEAD) {
    215             gfx_line(s, ex - 1, ey - 1, ex + 1, ey + 1, 1, C_BLACK);
    216             gfx_line(s, ex + 1, ey - 1, ex - 1, ey + 1, 1, C_BLACK);
    217         } else {
    218             gfx_disc(s, ex, ey, 2, C_WHITE);
    219             gfx_rect(s, ex + DX[d] - (DX[d] < 0), ey + DY[d] - (DY[d] < 0), 1 + !DX[d], 1 + !DY[d], C_BLACK);
    220         }
    221     }
    222 }
    223 
    224 static bool rows_hit(Surface *s, int c)
    225 {
    226     int py = OY + (c / W) * CELL;
    227     return py + CELL + 1 >= s->hy0 && py - 1 < s->hy1;
    228 }
    229 
    230 static void paint_field(Surface *s)
    231 {
    232     Color frame = G.walls ? RGB(70, 80, 100) : RGB(150, 110, 60);
    233     gfx_rect(s, OX - 3, OY - 3, W * CELL + 6, H * CELL + 6, frame);
    234     if (G.walls) {                       /* sin paredes: borde punteado (se sale por el otro lado) */
    235         for (int x = OX; x < OX + W * CELL; x += 10) {
    236             gfx_rect(s, x + 5, OY - 3, 5, 3, C_BG);
    237             gfx_rect(s, x + 5, OY + H * CELL, 5, 3, C_BG);
    238         }
    239         for (int y = OY; y < OY + H * CELL; y += 10) {
    240             gfx_rect(s, OX - 3, y + 5, 3, 5, C_BG);
    241             gfx_rect(s, OX + W * CELL, y + 5, 3, 5, C_BG);
    242         }
    243     }
    244     int y0 = (s->hy0 - OY) / CELL, y1 = (s->hy1 - OY) / CELL;
    245     if (y0 < 0) y0 = 0;
    246     if (y1 > H - 1) y1 = H - 1;
    247     for (int y = y0; y <= y1; y++)
    248         for (int x = 0; x < W; x++)
    249             gfx_rect(s, OX + x * CELL, OY + y * CELL, CELL, CELL, (x + y) & 1 ? C_FIELD2 : C_FIELD1);
    250 }
    251 
    252 static void paint(Surface *s, void *ctx)
    253 {
    254     char buf[40];
    255     gfx_rect(s, 0, 0, 320, 320, C_BG);
    256     if (s->hy0 < 24) {
    257         gfx_rect(s, 0, 0, 320, 24, C_PANEL);
    258         draw_apple(s, 6, 4);
    259         str_int(buf, sizeof buf, 0, G.score);
    260         gfx_text(s, &font_bold, 26, 6, buf, C_TEXT);
    261         int p = str_put(buf, sizeof buf, 0, SPEED_NAMES[G.speed]);
    262         str_put(buf, sizeof buf, p, G.walls ? ", sin paredes" : ", con paredes");
    263         gfx_text_center(s, &font_normal, 70, 6, 180, buf, C_ACCENT);
    264         p = str_put(buf, sizeof buf, 0, "Récord ");
    265         str_int(buf, sizeof buf, p, G.best > G.score ? G.best : G.score);
    266         gfx_text(s, &font_normal, 314 - gfx_text_w(&font_normal, buf), 6, buf, C_DIM);
    267     }
    268     paint_field(s);
    269 
    270     if (G.food >= 0 && rows_hit(s, G.food)) {
    271         int px, py;
    272         cell_xy(G.food, &px, &py);
    273         draw_apple(s, px, py);
    274     }
    275     if (G.crash >= 0 && rows_hit(s, G.crash)) {
    276         int px, py;
    277         cell_xy(G.crash, &px, &py);
    278         gfx_rect(s, px, py, CELL, CELL, RGB(150, 40, 36));
    279     }
    280     for (int pass = 0; pass < 2; pass++)
    281         for (int i = 0; i < G.len; i++)
    282             if (rows_hit(s, G.pos[seg(i)])) draw_seg(s, i, pass);
    283     draw_eyes(s);
    284 
    285     if (G.state == READY) {
    286         gfx_box(s, 70, 196, 180, 22, C_PANEL, C_ACCENT);
    287         gfx_text_center(s, &font_normal, 70, 201, 180, "Apretá una flecha", C_TEXT);
    288     }
    289     if (G.state == READY || G.state == PLAYING) {
    290         gfx_text_center(s, &font_small, 0, 311, 320, "Flechas girar  Esc pausa", C_DIM);
    291         return;
    292     }
    293     /* cartel de fin */
    294     bool won = G.state == FULL;
    295     Color bc = won || G.record ? C_OK : C_ERR;
    296     int bx = 40, by = 130, w = 240, h = 60;
    297     gfx_box(s, bx, by, w, h, C_PANEL, bc);
    298     gfx_box(s, bx + 1, by + 1, w - 2, h - 2, C_PANEL, bc);
    299     gfx_text_center(s, &font_bold, bx, by + 8, w, won ? "¡Llenaste la cancha!" : "¡Chocaste!", bc);
    300     int p = str_put(buf, sizeof buf, 0, "Puntos ");
    301     p = str_int(buf, sizeof buf, p, G.score);
    302     if (G.record) str_put(buf, sizeof buf, p, "  ¡récord!");
    303     else {
    304         p = str_put(buf, sizeof buf, p, "   récord ");
    305         str_int(buf, sizeof buf, p, G.best);
    306     }
    307     gfx_text_center(s, &font_normal, bx, by + 26, w, buf, G.record ? C_ACCENT : C_TEXT);
    308     gfx_text_center(s, &font_small, bx, by + 45, w, "Enter otra   Esc menú", C_DIM);
    309 }
    310 
    311 /* ------------------------------------------------------------ menus y bucle */
    312 
    313 static void art(Surface *s)
    314 {
    315     /* una viborita yendo hacia una manzana */
    316     static const uint8_t body[] = { 0, 1, 2, 3, 4, 5 };
    317     int y = 64, x0 = 110;
    318     for (int pass = 0; pass < 2; pass++)
    319         for (unsigned i = 0; i < sizeof body; i++) {
    320             int cx = x0 + body[i] * CELL + 7, cy = y + 7 + (i == 1 || i == 2 ? -3 : i == 4 ? 3 : 0);
    321             int r = (i == 0 ? 4 : i == 1 ? 5 : 6) + (pass ? 0 : 1);
    322             Color c = pass == 0 ? C_EDGE : i == sizeof body - 1 ? C_HEAD : (i & 1) ? C_SKIN : C_SKIN2;
    323             gfx_disc(s, cx, cy, r, c);
    324             if (i + 1 < sizeof body) {
    325                 int ny = y + 7 + (i + 1 == 1 || i + 1 == 2 ? -3 : i + 1 == 4 ? 3 : 0);
    326                 gfx_line(s, cx, cy, cx + CELL, ny, 2 * r, c);
    327             }
    328         }
    329     int hx = x0 + 5 * CELL + 7, hy = y + 7;
    330     for (int e = -1; e <= 1; e += 2) {
    331         gfx_disc(s, hx + 2, hy + e * 3, 2, C_WHITE);
    332         gfx_rect(s, hx + 3, hy + e * 3, 1, 1, C_BLACK);
    333     }
    334     draw_apple(s, x0 + 7 * CELL, y);
    335 }
    336 
    337 /* Tabla de records: una fila por paredes, una columna por velocidad (letra fija de 6 px). */
    338 static void menu_labels(Form *f)
    339 {
    340     static char lines[3][48];
    341     str_put(lines[0], sizeof lines[0], 0, "Récords       Lenta Normal Rápida");
    342     for (int w = 0; w < 2; w++) {
    343         char *l = lines[w + 1];
    344         int p = str_put(l, sizeof lines[0], 0, w ? "Sin paredes" : "Con paredes");
    345         for (int i = 0; i < NSPEEDS; i++) {
    346             char k[4], n[12];
    347             rec_key(k, i, w);
    348             int nl = str_int(n, sizeof n, 0, mini_get(k, 0));
    349             for (int pad = (i ? 7 : 8) - nl; pad > 0; pad--) p = str_put(l, sizeof lines[0], p, " ");
    350             p = str_put(l, sizeof lines[0], p, n);
    351         }
    352     }
    353     for (int i = 0; i < 3; i++) form_label(f, lines[i]);
    354 }
    355 
    356 static void main_menu(int *speed, int *walls)
    357 {
    358     Form f;
    359     form_begin(&f, "Víbora");
    360     form_button(&f, 1, "Jugar");
    361     form_choice(&f, 2, "Velocidad", SPEED_NAMES, NSPEEDS, *speed);
    362     form_choice(&f, 3, "Paredes", WALL_NAMES, 2, *walls);
    363     form_label(&f, "");
    364     menu_labels(&f);
    365     while (mini_form(&f) != 1) {}
    366     *speed = form_get(&f, 2)->value;
    367     *walls = form_get(&f, 3)->value;
    368 }
    369 
    370 /* Devuelve true si hay que volver al menu principal. */
    371 static bool pause_menu(void)
    372 {
    373     Form f;
    374     form_begin(&f, "Pausa");
    375     form_button(&f, 1, "Seguir");
    376     form_button(&f, 2, "Empezar de nuevo");
    377     form_button(&f, 3, "Menú principal");
    378     int r = mini_form(&f);
    379     G.next = hal_ms() + (uint32_t)G.interval;        /* la pausa no cuenta */
    380     if (r == 2) new_game(G.speed, G.walls);
    381     return r == 3;
    382 }
    383 
    384 void app_main(void)
    385 {
    386     mini_start("Víbora", RGB(110, 220, 90));
    387     mini_art = art;
    388     int speed = (int)mini_get("vel", 1), walls = (int)mini_get("par", 0);
    389     if (speed < 0 || speed >= NSPEEDS) speed = 1;
    390     if (walls < 0 || walls > 1) walls = 0;
    391     for (;;) {
    392         int os = speed, ow = walls;
    393         main_menu(&speed, &walls);
    394         if (os != speed || ow != walls) { mini_set("vel", speed); mini_set("par", walls); mini_save(); }
    395         new_game(speed, walls);
    396         bool dirty = true;
    397         for (;;) {
    398             int k = hal_key();
    399             if (k == 0) dirty = true;
    400             else if (k > 0) {
    401                 if (G.state == DEAD || G.state == FULL) {
    402                     if (k == K_OK || k == ' ') { new_game(speed, walls); dirty = true; }
    403                     else if (k == K_BACK) break;
    404                     continue;
    405                 }
    406                 int nd = k == K_UP ? UP : k == K_RIGHT ? RIGHT : k == K_DOWN ? DOWN : k == K_LEFT ? LEFT : -1;
    407                 if (nd >= 0) {
    408                     if (G.state == READY) {            /* la primera flecha arranca */
    409                         if (nd == (G.d + 2) % 4) nd = G.d;
    410                         G.state = PLAYING;
    411                         G.next = hal_ms();
    412                         dirty = true;
    413                     }
    414                     turn(nd);
    415                 } else if (k == K_BACK) {
    416                     if (pause_menu()) break;
    417                     dirty = true;
    418                 }
    419             }
    420             if (G.state == PLAYING) {
    421                 uint32_t now = hal_ms();
    422                 if ((int32_t)(now - G.next) >= 0) {
    423                     step();
    424                     dirty = true;
    425                     G.next += (uint32_t)G.interval;
    426                     /* si el dibujo nos atraso mas de un paso, no acumular deuda */
    427                     if ((int32_t)(now - G.next) > G.interval) G.next = now + (uint32_t)G.interval;
    428                 }
    429             }
    430             if (dirty) { hal_draw(paint, 0); dirty = false; }
    431             else hal_sleep(5);
    432         }
    433     }
    434 }