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

screen.c (18848B)


      1 /* screen.c - pantalla del ajedrez en pixeles: layout ancho (PC 640x480) y
      2  * compacto (PicoCalc 320x320). Registra las zonas clickeables en un HitList. */
      3 #include <string.h>
      4 #include "chess_gfx.h"
      5 
      6 const Theme THEMES[NTHEMES] = {
      7     { "classic", { RGB(215, 175, 135), RGB(135, 135, 95), RGB(0, 135, 255), RGB(215, 0, 0), RGB(95, 175, 95), RGB(135, 175, 175) }, { 180, 101, 33, 160, 71, 109 } },
      8     { "ocean", { RGB(175, 215, 255), RGB(0, 95, 175), RGB(255, 215, 0), RGB(255, 0, 0), RGB(0, 255, 135), RGB(95, 255, 255) }, { 153, 25, 220, 196, 48, 87 } },
      9     { "night", { RGB(88, 88, 88), RGB(38, 38, 38), RGB(0, 95, 135), RGB(135, 0, 0), RGB(0, 95, 0), RGB(0, 95, 95) }, { 240, 235, 24, 88, 22, 23 } },
     10     { "contrast", { RGB(255, 255, 255), RGB(0, 0, 0), RGB(0, 0, 255), RGB(255, 0, 0), RGB(0, 255, 0), RGB(0, 255, 255) }, { 231, 16, 21, 196, 46, 51 } },
     11     { "colorblind", { RGB(255, 175, 95), RGB(0, 95, 175), RGB(255, 255, 0), RGB(255, 95, 0), RGB(95, 175, 255), RGB(135, 215, 255) }, { 215, 25, 226, 202, 75, 117 } },
     12     { "sunset", { RGB(255, 175, 135), RGB(135, 95, 175), RGB(0, 135, 255), RGB(215, 0, 0), RGB(0, 175, 95), RGB(135, 175, 175) }, { 216, 97, 33, 160, 35, 109 } },
     13 };
     14 
     15 #define C_WPIECE  RGB(242, 240, 230)
     16 #define C_WLINE   RGB(24, 22, 20)
     17 #define C_BPIECE  RGB(36, 34, 38)
     18 #define C_BLINE   RGB(205, 205, 200)
     19 #define C_BG      RGB(28, 32, 40)
     20 
     21 static void cat(char *b, int n, const char *s) { int p = (int)strlen(b); str_put(b, n, p, s); }
     22 static void cati(char *b, int n, long v) { int p = (int)strlen(b); str_int(b, n, p, v); }
     23 
     24 void fmt_clock(int32_t ms, char *buf, int n)
     25 {
     26     buf[0] = 0;
     27     if (ms < 0) ms = 0;
     28     int32_t s = (ms + 999) / 1000;
     29     if (s >= 86400) {
     30         cati(buf, n, s / 86400); cat(buf, n, "d ");
     31         if ((s % 86400) / 3600 < 10) cat(buf, n, "0");
     32         cati(buf, n, (s % 86400) / 3600); cat(buf, n, "h");
     33         return;
     34     }
     35     if (s >= 3600) { cati(buf, n, s / 3600); cat(buf, n, ":"); s %= 3600; if (s / 60 < 10) cat(buf, n, "0"); }
     36     cati(buf, n, s / 60);
     37     cat(buf, n, s % 60 < 10 ? ":0" : ":");
     38     cati(buf, n, s % 60);
     39 }
     40 
     41 void captured_pieces(const Game *g, int out[2][7])
     42 {
     43     int start[2][7], now[2][7];
     44     memset(start, 0, sizeof start);
     45     memset(now, 0, sizeof now);
     46     for (int s = 0; s < 64; s++) {
     47         if (g->start.sq[s]) start[PCOLOR(g->start.sq[s])][PTYPE(g->start.sq[s])]++;
     48         if (g->pos.sq[s]) now[PCOLOR(g->pos.sq[s])][PTYPE(g->pos.sq[s])]++;
     49     }
     50     for (int c = 0; c < 2; c++)
     51         for (int t = 0; t < 7; t++) out[c][t] = start[c][t] > now[c][t] ? start[c][t] - now[c][t] : 0;
     52 }
     53 
     54 /* ------------------------------------------------------------ piezas */
     55 
     56 static const PieceSet *set_for(int sq)
     57 {
     58     const PieceSet *best = &PIECES[NPIECESETS - 1];
     59     for (int i = 0; i < NPIECESETS; i++)
     60         if (PIECES[i].size <= sq - 2 && PIECES[i].size > best->size) best = &PIECES[i];
     61     return best;
     62 }
     63 
     64 static void draw_piece(Surface *s, const PieceSet *ps, int x, int y, int pc)
     65 {
     66     int t = PTYPE(pc), size = ps->size;
     67     if (!t) return;
     68     const uint8_t *img = ps->img[t];
     69     Color body = PCOLOR(pc) == WHITE ? C_WPIECE : C_BPIECE, line = PCOLOR(pc) == WHITE ? C_WLINE : C_BLINE;
     70     int y0 = y < s->cy0 ? s->cy0 - y : 0, y1 = y + size > s->cy1 ? s->cy1 - y : size;
     71     for (int j = y0; j < y1; j++) {
     72         int i = 0;
     73         while (i < size) {
     74             int k = j * size + i;
     75             int v = (img[k >> 2] >> ((k & 3) * 2)) & 3;
     76             int run = 1;
     77             while (i + run < size) {
     78                 int k2 = k + run;
     79                 if (((img[k2 >> 2] >> ((k2 & 3) * 2)) & 3) != v) break;
     80                 run++;
     81             }
     82             if (v) gfx_rect(s, x + i, y + j, run, 1, v == 1 ? body : line);
     83             i += run;
     84         }
     85     }
     86 }
     87 
     88 /* ------------------------------------------------------------ tablero */
     89 
     90 static void sq_xy(const View *v, int sq, int bx, int by, int ss, int *x, int *y)
     91 {
     92     int f = FILE_OF(sq), r = RANK_OF(sq);
     93     if (v->c && v->c->flipped) { f = 7 - f; r = 7 - r; }
     94     *x = bx + f * ss;
     95     *y = by + (7 - r) * ss;
     96 }
     97 
     98 static void board(Surface *s, const View *v, int bx, int by, int ss, HitList *hits)
     99 {
    100     const Theme *th = &THEMES[v->theme % NTHEMES];
    101     const Game *g = v->g;
    102     const Ctrl *c = v->c;
    103     int lf = -1, lt = -1;
    104     if (g->nply) { lf = MFROM(g->mv[g->nply - 1]); lt = MTO(g->mv[g->nply - 1]); }
    105     int check_sq = g->result == R_NONE && pos_in_check(&g->pos) ? g->pos.king[g->pos.side] : -1;
    106     if (g->result == R_WHITE || g->result == R_BLACK)
    107         if (g->reason == RS_MATE) check_sq = g->pos.king[g->pos.side];
    108     const PieceSet *ps = set_for(ss);
    109     int off = (ss - ps->size) / 2;
    110     for (int sq = 0; sq < 64; sq++) {
    111         int x, y;
    112         sq_xy(v, sq, bx, by, ss, &x, &y);
    113         bool light = (FILE_OF(sq) + RANK_OF(sq)) & 1;
    114         Color bg = light ? th->c[TH_LIGHT] : th->c[TH_DARK];
    115         if (sq == lf || sq == lt) bg = th->c[TH_PATH];
    116         if (c && sq == c->sel) bg = th->c[TH_FROM];
    117         if (sq == check_sq) bg = th->c[TH_CAPTURE];
    118         gfx_rect(s, x, y, ss, ss, bg);
    119         if (c && ctrl_is_target(c, sq)) {
    120             if (g->pos.sq[sq] || (c->sel >= 0 && PTYPE(g->pos.sq[c->sel]) == PAWN && sq == g->pos.ep)) {
    121                 gfx_ring(s, x + ss / 2, y + ss / 2, ss / 2 - 1, ss > 40 ? 4 : 3, th->c[TH_CAPTURE]);
    122             } else gfx_disc(s, x + ss / 2, y + ss / 2, ss / 7 + 1, th->c[TH_TARGET]);
    123         }
    124         draw_piece(s, ps, x + off, y + off, g->pos.sq[sq]);
    125         if (c && sq == c->cursor && c->g && c->g->result == R_NONE) {
    126             gfx_frame(s, x, y, ss, ss, C_CURSOR);
    127             gfx_frame(s, x + 1, y + 1, ss - 2, ss - 2, C_CURSOR);
    128             if (ss > 40) gfx_frame(s, x + 2, y + 2, ss - 4, ss - 4, C_OUTLINE);
    129         }
    130         hit_add(hits, x, y, ss, ss, HIT_SQUARE, sq);
    131     }
    132     /* coordenadas en los bordes: abajo-izquierda siempre es oscura, arriba-izquierda clara */
    133     bool fl = c && c->flipped;
    134     for (int i = 0; i < 8; i++) {
    135         char fc[2] = { (char)('a' + (fl ? 7 - i : i)), 0 }, rc[2] = { (char)('8' - (fl ? 7 - i : i)), 0 };
    136         gfx_text(s, &font_small, bx + i * ss + ss - 7, by + 8 * ss - 9, fc, (i & 1) ? th->c[TH_DARK] : th->c[TH_LIGHT]);
    137         gfx_text(s, &font_small, bx + 2, by + i * ss + 2, rc, (i & 1) ? th->c[TH_LIGHT] : th->c[TH_DARK]);
    138     }
    139     /* coronacion: cuatro piezas al lado del destino */
    140     if (c && c->mode == CM_PROMO) {
    141         int to = MTO(c->promo[0]), x, y;
    142         sq_xy(v, to, bx, by, ss, &x, &y);
    143         int col = g->pos.side;
    144         int dy = y < by + 4 * ss ? ss : -ss;
    145         gfx_frame(s, x - 2, (dy > 0 ? y : y - 3 * ss) - 2, ss + 4, 4 * ss + 4, C_ACCENT);
    146         for (int k = 0; k < 4; k++) {
    147             int yy = y + k * dy;
    148             gfx_rect(s, x, yy, ss, ss, k == c->promo_cur ? C_ACCENT : C_PANEL2);
    149             draw_piece(s, ps, x + off, yy + off, PIECE(col, MPROMO(c->promo[k])));
    150             hit_add(hits, x, yy, ss, ss, HIT_PROMO, k);
    151         }
    152     }
    153 }
    154 
    155 /* ------------------------------------------------------------ textos */
    156 
    157 static void status_line(const View *v, char *buf, int n)
    158 {
    159     const Game *g = v->g;
    160     buf[0] = 0;
    161     if (g->result == R_DRAW) { cat(buf, n, tr(S_DRAW)); cat(buf, n, " · "); cat(buf, n, reason_text(g->reason)); }
    162     else if (g->result != R_NONE) {
    163         cat(buf, n, color_name(g->result == R_WHITE ? WHITE : BLACK));
    164         cat(buf, n, " ");
    165         cat(buf, n, tr(S_WINS));
    166         cat(buf, n, " · ");
    167         cat(buf, n, reason_text(g->reason));
    168     } else {
    169         cat(buf, n, g_lang ? "" : "Juegan ");
    170         cat(buf, n, v->names[g->pos.side] && *v->names[g->pos.side] ? v->names[g->pos.side] : color_name(g->pos.side));
    171         if (g_lang) cat(buf, n, " to move");
    172         if (pos_in_check(&g->pos)) { cat(buf, n, " · "); cat(buf, n, tr(S_CHECK)); }
    173     }
    174 }
    175 
    176 /* SAN de las ultimas jugadas: se reproduce la partida hasta ahi */
    177 static int recent_san(const Game *g, int first, char san[][12], int max)
    178 {
    179     Pos p = g->start;
    180     int k = 0;
    181     for (int i = 0; i < g->nply; i++) {
    182         if (i >= first && k < max) move_san(&p, g->mv[i], san[k++], 12);
    183         Undo u;
    184         pos_make(&p, g->mv[i], &u);
    185     }
    186     return k;
    187 }
    188 
    189 static void player_box(Surface *s, const View *v, int color, int x, int y, int w, bool compact)
    190 {
    191     const Game *g = v->g;
    192     bool active = g->result == R_NONE && g->pos.side == color;
    193     gfx_box(s, x, y, w, compact ? 14 : 22, active ? C_PANEL2 : C_PANEL, active ? C_ACCENT : C_DIM);
    194     int px = x + 4;
    195     gfx_disc(s, px + 4, y + (compact ? 7 : 11), compact ? 3 : 5, color == WHITE ? C_WPIECE : C_BPIECE);
    196     gfx_ring(s, px + 4, y + (compact ? 7 : 11), compact ? 3 : 5, 1, C_DIM);
    197     const char *name = v->names[color] && *v->names[color] ? v->names[color] : color_name(color);
    198     int cw = 0;
    199     if (v->has_clock) {
    200         char clk[16];
    201         fmt_clock(v->clock[color], clk, sizeof clk);
    202         const Font *f = compact ? &font_normal : &font_bold;
    203         cw = gfx_text_w(f, clk) + 6;
    204         Color cc = v->clock[color] < 20000 && active ? C_ERR : active ? C_ACCENT : C_TEXT;
    205         gfx_text(s, f, x + w - cw, y + (compact ? 2 : 5), clk, cc);
    206     }
    207     gfx_text_fit(s, compact ? &font_small : &font_normal, px + 12, y + (compact ? 3 : 5), w - 20 - cw, name, active ? C_WHITE : C_TEXT);
    208 }
    209 
    210 static void eval_bar_v(Surface *s, const View *v, int x, int y, int w, int h)
    211 {
    212     int cp = v->eval;
    213     if (cp > 800) cp = 800;
    214     if (cp < -800) cp = -800;
    215     int white_h = h / 2 + cp * (h / 2) / 800;
    216     if (MATE_IN(v->eval)) white_h = v->eval > 0 ? h : 0;
    217     bool fl = v->c && v->c->flipped;
    218     gfx_rect(s, x, y, w, h, C_BPIECE);
    219     if (fl) gfx_rect(s, x, y, w, white_h, C_WPIECE);
    220     else gfx_rect(s, x, y + h - white_h, w, white_h, C_WPIECE);
    221     gfx_frame(s, x, y, w, h, C_DIM);
    222 }
    223 
    224 static void eval_text(const View *v, char *buf, int n)
    225 {
    226     buf[0] = 0;
    227     if (MATE_IN(v->eval)) { cat(buf, n, v->eval > 0 ? "+M" : "-M"); cati(buf, n, (MATE - (v->eval > 0 ? v->eval : -v->eval) + 1) / 2); return; }
    228     cat(buf, n, v->eval >= 0 ? "+" : "-");
    229     int a = v->eval >= 0 ? v->eval : -v->eval;
    230     cati(buf, n, a / 100);
    231     cat(buf, n, ".");
    232     if (a % 100 < 10) cat(buf, n, "0");
    233     cati(buf, n, a % 100);
    234 }
    235 
    236 static void captured_row(Surface *s, int x, int y, int w, const int cap[7], int color)
    237 {
    238     static const char SYM[] = " PNBRQK";
    239     char buf[48] = "";
    240     for (int t = QUEEN; t >= PAWN; t--)
    241         for (int k = 0; k < cap[t] && (int)strlen(buf) < 40; k++) { char c1[2] = { SYM[t], 0 }; cat(buf, sizeof buf, c1); }
    242     gfx_text_fit(s, &font_small, x, y, w, buf, color == WHITE ? C_WPIECE : C_DIM);
    243 }
    244 
    245 static void help_overlay(Surface *s, int x, int y, int w, int h)
    246 {
    247     gfx_box(s, x, y, w, h, C_PANEL, C_ACCENT);
    248     const char *t = tr(S_HELP_KEYS);
    249     int ly = y + 6;
    250     char line[96];
    251     while (*t && ly < y + h - 10) {
    252         int k = 0;
    253         while (*t && !(t[0] == ' ' && t[1] == '\xc2')) { if (k < (int)sizeof line - 1) line[k++] = *t; t++; }
    254         line[k] = 0;
    255         if (*t) t += 4;     /* " · " */
    256         while (*t == ' ') t++;
    257         gfx_text_fit(s, &font_normal, x + 8, ly, w - 16, line, C_TEXT);
    258         ly += 13;
    259     }
    260 }
    261 
    262 static void modal(Surface *s, const View *v, int bx, int by, int bw, int bh)
    263 {
    264     int w = bw * 5 / 6, h = v->modal2 ? 48 : 34;
    265     int x = bx + (bw - w) / 2, y = by + (bh - h) / 2;
    266     gfx_box(s, x, y, w, h, C_PANEL, C_ACCENT);
    267     gfx_text_center(s, &font_bold, x + 4, y + 10, w - 8, v->modal, C_ACCENT);
    268     if (v->modal2) gfx_text_center(s, &font_normal, x + 4, y + 28, w - 8, v->modal2, C_TEXT);
    269 }
    270 
    271 /* ------------------------------------------------------------ layouts */
    272 
    273 static void prompt_line(Surface *s, const View *v, int x, int y, int w, const Font *f)
    274 {
    275     const Ctrl *c = v->hide_prompt ? 0 : v->c;
    276     char buf[128] = "";
    277     if (c && c->mode == CM_ENTRY) { cat(buf, sizeof buf, tr(S_P_ENTRY)); cat(buf, sizeof buf, " "); cat(buf, sizeof buf, c->entry); cat(buf, sizeof buf, "_"); }
    278     else if (c && c->msg[0]) { gfx_text_fit(s, f, x, y, w, c->msg, c->msg_err ? C_ERR : C_OK); return; }
    279     else if (c) {
    280         cat(buf, sizeof buf, ctrl_prompt(c));
    281         if (c->g && c->g->result == R_NONE && !ctrl_my_turn(c)) {
    282             int side = c->g->pos.side;
    283             cat(buf, sizeof buf, " ");
    284             cat(buf, sizeof buf, v->names[side] && *v->names[side] ? v->names[side] : color_name(side));
    285         }
    286     }
    287     gfx_text_fit(s, f, x, y, w, buf, C_ACCENT);
    288 }
    289 
    290 static void screen_wide(Surface *s, const View *v, HitList *hits)
    291 {
    292     const Game *g = v->g;
    293     int ss = 56, bx = 8, by = 8;
    294     gfx_rect(s, 0, 0, s->w, s->h, C_BG);
    295     board(s, v, bx, by, ss, hits);
    296     int px = bx + 8 * ss + 10, pw = s->w - px - 6;
    297     bool fl = v->c && v->c->flipped;
    298     int top = fl ? WHITE : BLACK, bot = top ^ 1;
    299     int cap[2][7];
    300     captured_pieces(g, cap);
    301     player_box(s, v, top, px, by, pw, false);
    302     captured_row(s, px + 2, by + 25, pw - 4, cap[bot], bot);     /* lo que le capturo el de arriba */
    303     int y = by + 40;
    304     char buf[160];
    305     status_line(v, buf, sizeof buf);
    306     gfx_text_fit(s, &font_bold, px, y, pw, buf, g->result != R_NONE ? C_ACCENT : C_TEXT);
    307     y += 15;
    308     if (v->status && *v->status) { gfx_text_fit(s, &font_small, px, y, pw, v->status, C_DIM); y += 11; }
    309     if (v->opening) { gfx_text_fit(s, &font_small, px, y, pw, v->opening, C_OK); y += 11; }
    310     if (v->show_eval && v->eval_valid) {
    311         char e[16];
    312         eval_text(v, e, sizeof e);
    313         gfx_text(s, &font_small, px, y + 1, e, C_TEXT);
    314         int bw = pw - 40;
    315         int wx = bw * (v->eval > 800 ? 800 : v->eval < -800 ? -800 : v->eval) / 1600 + bw / 2;
    316         if (MATE_IN(v->eval)) wx = v->eval > 0 ? bw : 0;
    317         gfx_rect(s, px + 38, y, bw, 9, C_BPIECE);
    318         gfx_rect(s, px + 38, y, wx, 9, C_WPIECE);
    319         gfx_frame(s, px + 38, y, bw, 9, C_DIM);
    320         y += 13;
    321     }
    322     if (v->banner) {
    323         gfx_box(s, px, y, pw, 30, C_PANEL2, C_ACCENT);
    324         gfx_text_fit(s, &font_normal, px + 4, y + 3, pw - 8, v->banner, C_ACCENT);
    325         y += 34;
    326     }
    327     /* jugadas: las ultimas que entran, de a dos por renglon */
    328     int lh = 12, list_bot = s->h - 64 - 20;
    329     int rows = (list_bot - y - 14) / lh;
    330     if (rows < 1) rows = 1;
    331     gfx_text(s, &font_small, px, y, tr(S_MOVES), C_DIM);
    332     y += 12;
    333     int first_side = g->start.side;
    334     int nrows_total = (g->nply + first_side + 1) / 2;
    335     int first_row = nrows_total > rows ? nrows_total - rows : 0;
    336     int first_ply = first_row * 2 - first_side;
    337     if (first_ply < 0) first_ply = 0;
    338     char san[128][12];
    339     int ns = recent_san(g, first_ply, san, 128);
    340     int ply = first_ply;
    341     for (int r = first_row; r < nrows_total && ply < g->nply; r++) {
    342         char num[12] = "";
    343         cati(num, sizeof num, g->start.fullmove + r);
    344         cat(num, sizeof num, ".");
    345         gfx_text(s, &font_small, px, y, num, C_DIM);
    346         for (int col = 0; col < 2; col++) {
    347             if (r == 0 && col == 0 && first_side == BLACK) { gfx_text(s, &font_small, px + 30, y, "...", C_DIM); continue; }
    348             if (ply >= g->nply) break;
    349             int idx = ply - first_ply;
    350             bool last = ply == g->nply - 1 || ply == v->view_ply - 1;
    351             if (idx < ns) gfx_text(s, &font_normal, px + 30 + col * 64, y - 1, san[idx], last ? C_ACCENT : C_TEXT);
    352             ply++;
    353         }
    354         y += lh;
    355     }
    356     captured_row(s, px + 2, s->h - 64 - 12, pw - 4, cap[top], top);
    357     player_box(s, v, bot, px, s->h - 64, pw, false);
    358     gfx_text_fit(s, &font_small, px, s->h - 38, pw, g_lang ? "? help  m menu" : "? ayuda  m menú", C_DIM);
    359     prompt_line(s, v, bx, by + 8 * ss + 4, 8 * ss, &font_normal);
    360     if (v->show_help) help_overlay(s, bx + 20, by + 40, 8 * ss - 40, 8 * ss - 80);
    361     if (v->modal) modal(s, v, bx, by, 8 * ss, 8 * ss);
    362 }
    363 
    364 static void screen_compact(Surface *s, const View *v, HitList *hits)
    365 {
    366     const Game *g = v->g;
    367     int ss = 36, bx = 0, by = 16;
    368     gfx_rect(s, 0, 0, s->w, s->h, C_BG);
    369     bool fl = v->c && v->c->flipped;
    370     int top = fl ? WHITE : BLACK, bot = top ^ 1;
    371     player_box(s, v, top, 0, 1, 160, true);
    372     player_box(s, v, bot, 160, 1, 160, true);
    373     board(s, v, bx, by, ss, hits);
    374     /* columna derecha: evaluacion y capturadas */
    375     int cx = bx + 8 * ss + 2, cw = s->w - cx - 2;
    376     if (v->show_eval && v->eval_valid) eval_bar_v(s, v, cx + cw - 8, by, 8, 8 * ss);
    377     int cap[2][7];
    378     captured_pieces(g, cap);
    379     static const char SYM[] = " PNBRQK";
    380     for (int side = 0; side < 2; side++) {
    381         int y = side == 0 ? by + 2 : by + 8 * ss - 60;
    382         int who = side == 0 ? bot : top;       /* arriba: lo que perdio el de abajo */
    383         for (int t = QUEEN; t >= PAWN; t--)
    384             if (cap[who][t]) {
    385                 char b[6] = { SYM[t], 0 };
    386                 if (cap[who][t] > 1) cati(b, sizeof b, cap[who][t]);
    387                 gfx_text(s, &font_small, cx, y, b, who == WHITE ? C_WPIECE : C_DIM);
    388                 y += 10;
    389             }
    390     }
    391     char buf[160];
    392     int ly = by + 8 * ss + 1;
    393     if (!v->hide_prompt && v->c && (v->c->msg[0] || v->c->mode == CM_ENTRY || v->c->mode == CM_PROMO)) prompt_line(s, v, 2, ly, s->w - 4, &font_small);
    394     else if (v->banner) gfx_text_fit(s, &font_small, 2, ly, s->w - 4, v->banner, C_ACCENT);
    395     else {
    396         status_line(v, buf, sizeof buf);
    397         if (v->status && *v->status && g->result == R_NONE) { cat(buf, sizeof buf, " · "); cat(buf, sizeof buf, v->status); }
    398         else if (v->opening && g->result == R_NONE) { cat(buf, sizeof buf, " · "); cat(buf, sizeof buf, v->opening); }
    399         gfx_text_fit(s, &font_small, 2, ly, s->w - 4, buf, g->result != R_NONE ? C_ACCENT : C_TEXT);
    400     }
    401     if (v->show_help) help_overlay(s, 8, by + 8, s->w - 16, 8 * ss - 16);
    402     if (v->modal) modal(s, v, 0, by, s->w, 8 * ss);
    403 }
    404 
    405 void chess_screen(Surface *s, const View *v, HitList *hits)
    406 {
    407     if (hits) hits->n = 0;
    408     if (v->layout == LAYOUT_COMPACT) screen_compact(s, v, hits);
    409     else screen_wide(s, v, hits);
    410 }
    411 
    412 /* ------------------------------------------------------------ menus */
    413 
    414 /* fondo de tablero y una fila de piezas como logo */
    415 int chess_form_backdrop(Surface *s, bool compact)
    416 {
    417     const Theme *th = &THEMES[0];
    418     int W = s->w, H = s->h, ss = compact ? 20 : 40;
    419     for (int y = 0; y * ss < H; y++)
    420         for (int x = 0; x * ss < W; x++)
    421             gfx_rect(s, x * ss, y * ss, ss, ss, ((x + y) & 1) ? gfx_dim(th->c[TH_DARK]) : gfx_dim(gfx_dim(th->c[TH_LIGHT])));
    422     const PieceSet *ps = &PIECES[NPIECESETS - 1];
    423     static const uint8_t ROW[] = { ROOK, KNIGHT, BISHOP, QUEEN, KING, BISHOP, KNIGHT, ROOK };
    424     int n = compact ? 5 : 8, pw = ps->size + 2;
    425     int x0 = (W - n * pw) / 2;
    426     for (int i = 0; i < n; i++) draw_piece(s, ps, x0 + i * pw, compact ? 2 : 8, PIECE(i & 1, ROW[compact ? i + 2 : i]));
    427     return compact ? ps->size + 6 : ps->size + 20;
    428 }