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

game.c (10217B)


      1 /* game.c - la partida: log de acciones, reloj, tablas, abandono, deshacer.
      2  *
      3  * El estado se puede reconstruir siempre reproduciendo el log desde la posicion
      4  * inicial; deshacer lo usa (vuelve a jugar todo menos las ultimas jugadas). */
      5 #include <string.h>
      6 #include "chess.h"
      7 
      8 void game_init(Game *g, const Pos *start, const TimeCtl *tc, bool allow_undo)
      9 {
     10     chess_init();
     11     memset(g, 0, sizeof *g);
     12     if (start) g->start = *start;
     13     else pos_start(&g->start);
     14     if (tc) g->tc = *tc;
     15     g->allow_undo = allow_undo;
     16     g->pos = g->start;
     17     g->hist[0] = g->pos.hash;
     18 }
     19 
     20 bool game_has_clock(const Game *g) { return g->tc.base > 0 || g->tc.per_move > 0; }
     21 
     22 int32_t game_clock(const Game *g, int color)
     23 {
     24     if (g->tc.per_move > 0) {
     25         /* correspondencia: el que mueve tiene per_move entero para esta jugada */
     26         return g->tc.per_move;
     27     }
     28     int64_t t = g->tc.base;
     29     /* jugada i la hizo el color (lado inicial + i) % 2 */
     30     for (int i = 0; i < g->nply; i++)
     31         if (((g->start.side + i) & 1) == color) t += (int64_t)g->tc.inc - g->spent[i];
     32     if (t > 2000000000) t = 2000000000;
     33     if (t < -2000000000) t = -2000000000;
     34     return (int32_t)t;
     35 }
     36 
     37 int game_repetitions(const Game *g)
     38 {
     39     int n = 1;
     40     uint64_t h = g->hist[g->nply];
     41     int lim = g->nply - g->pos.halfmove;
     42     if (lim < 0) lim = 0;
     43     for (int i = g->nply - 2; i >= lim; i -= 2)
     44         if (g->hist[i] == h) n++;
     45     return n;
     46 }
     47 
     48 bool game_can_claim(const Game *g) { return g->pos.halfmove >= 100 || game_repetitions(g) >= 3; }
     49 
     50 int game_undo_plies(const Game *g, int requester)
     51 {
     52     /* si ya le toca al que pide, el rival contesto: se deshacen las dos */
     53     int n = g->pos.side == requester ? 2 : 1;
     54     return n <= g->nply ? n : g->nply;
     55 }
     56 
     57 /* termina sola despues de una jugada: mate, ahogado, 75 jugadas, 5 repeticiones, material */
     58 static void check_end(Game *g)
     59 {
     60     MoveList l;
     61     pos_legal(&g->pos, &l);
     62     if (!l.n) {
     63         if (pos_in_check(&g->pos)) { g->result = g->pos.side == WHITE ? R_BLACK : R_WHITE; g->reason = RS_MATE; }
     64         else { g->result = R_DRAW; g->reason = RS_STALEMATE; }
     65     } else if (game_repetitions(g) >= 5) { g->result = R_DRAW; g->reason = RS_REP5; }
     66     else if (g->pos.halfmove >= 150) { g->result = R_DRAW; g->reason = RS_MOVE75; }
     67     else if (pos_insufficient(&g->pos)) { g->result = R_DRAW; g->reason = RS_INSUFFICIENT; }
     68 }
     69 
     70 /* terminada por el tablero (no por abandono, tiempo o acuerdo): se puede deshacer */
     71 static bool ended_on_board(const Game *g)
     72 {
     73     switch (g->reason) {
     74     case RS_MATE: case RS_STALEMATE: case RS_REP5: case RS_MOVE75: case RS_INSUFFICIENT: return true;
     75     }
     76     return false;
     77 }
     78 
     79 int game_check(const Game *g, const Act *a)
     80 {
     81     if (a->c != WHITE && a->c != BLACK) return E_ARGS;
     82     if (g->result != R_NONE) {
     83         bool undo = a->type == ACT_UNDO || a->type == ACT_UNDOOK || a->type == ACT_UNDONO;
     84         if (!undo || !ended_on_board(g)) return E_OVER;
     85     }
     86     switch (a->type) {
     87     case ACT_MOVE:
     88         if (a->c != g->pos.side) return E_TURN;
     89         if (g->nply >= MAX_PLY) return E_ARGS;
     90         return pos_is_legal(&g->pos, a->m) ? E_OK : E_ILLEGAL;
     91     case ACT_RESIGN:
     92         return E_OK;
     93     case ACT_DRAW:
     94         return g->draw_offer ? E_NOTHING : E_OK;
     95     case ACT_DRAWOK: case ACT_DRAWNO:
     96         return g->draw_offer && g->draw_offer - 1 != a->c ? E_OK : E_NOTHING;
     97     case ACT_CLAIM:
     98         return game_can_claim(g) ? E_OK : E_NOCLAIM;
     99     case ACT_UNDO:
    100         if (!g->allow_undo) return E_NOUNDO;
    101         if (g->undo_req) return E_NOTHING;
    102         /* hay que tener al menos una jugada propia para deshacer */
    103         for (int i = g->nply - 1; i >= 0 && i >= g->nply - 2; i--)
    104             if (((g->start.side + i) & 1) == a->c) return E_OK;
    105         return E_NOTHING;
    106     case ACT_UNDOOK: case ACT_UNDONO:
    107         return g->undo_req && g->undo_req - 1 != a->c ? E_OK : E_NOTHING;
    108     case ACT_FLAG:
    109         /* la autoridad la emite cuando al que mueve se le acabo el tiempo */
    110         return game_has_clock(g) && a->c == g->pos.side ? E_OK : E_ARGS;
    111     }
    112     return E_ARGS;
    113 }
    114 
    115 static void replay_to(Game *g, int nply)
    116 {
    117     g->result = R_NONE;             /* las posiciones anteriores no terminaban la partida */
    118     g->reason = RS_NONE;
    119     g->pos = g->start;
    120     for (int i = 0; i < nply; i++) {
    121         Undo u;
    122         pos_make(&g->pos, g->mv[i], &u);
    123         g->hist[i + 1] = g->pos.hash;
    124     }
    125     g->nply = nply;
    126 }
    127 
    128 /* con material para dar mate? (si al rival se le cae la bandera y no, es tablas) */
    129 static bool can_mate(const Pos *p, int color)
    130 {
    131     int minors = 0;
    132     for (int s = 0; s < 64; s++) {
    133         uint8_t pc = p->sq[s];
    134         if (!pc || PCOLOR(pc) != color) continue;
    135         int t = PTYPE(pc);
    136         if (t == PAWN || t == ROOK || t == QUEEN) return true;
    137         if (t == KNIGHT || t == BISHOP) minors++;
    138     }
    139     return minors >= 2;
    140 }
    141 
    142 void game_apply(Game *g, const Act *a)
    143 {
    144     switch (a->type) {
    145     case ACT_MOVE: {
    146         Undo u;
    147         g->mv[g->nply] = a->m;
    148         g->spent[g->nply] = a->ms;
    149         pos_make(&g->pos, a->m, &u);
    150         g->nply++;
    151         g->hist[g->nply] = g->pos.hash;
    152         g->draw_offer = 0;          /* jugar rechaza la oferta pendiente */
    153         g->undo_req = 0;
    154         /* reloj: si se paso de tiempo con la jugada, pierde igual */
    155         if (game_has_clock(g)) {
    156             bool late = g->tc.per_move > 0 ? a->ms > g->tc.per_move
    157                                             : game_clock(g, a->c) - g->tc.inc < 0;
    158             if (late) {
    159                 g->result = can_mate(&g->pos, a->c ^ 1) ? (a->c == WHITE ? R_BLACK : R_WHITE) : R_DRAW;
    160                 g->reason = g->result == R_DRAW ? RS_TIMEOUT_DRAW : RS_TIMEOUT;
    161                 break;
    162             }
    163         }
    164         check_end(g);
    165         break;
    166     }
    167     case ACT_RESIGN:
    168         g->result = a->c == WHITE ? R_BLACK : R_WHITE;
    169         g->reason = RS_RESIGN;
    170         break;
    171     case ACT_DRAW: g->draw_offer = (uint8_t)(a->c + 1); break;
    172     case ACT_DRAWOK: g->result = R_DRAW; g->reason = RS_AGREED; g->draw_offer = 0; break;
    173     case ACT_DRAWNO: g->draw_offer = 0; break;
    174     case ACT_CLAIM:
    175         g->result = R_DRAW;
    176         g->reason = game_repetitions(g) >= 3 ? RS_REP3 : RS_MOVE50;
    177         break;
    178     case ACT_UNDO: g->undo_req = (uint8_t)(a->c + 1); break;
    179     case ACT_UNDOOK:
    180         replay_to(g, g->nply - game_undo_plies(g, g->undo_req - 1));
    181         g->undo_req = 0;
    182         g->draw_offer = 0;
    183         break;
    184     case ACT_UNDONO: g->undo_req = 0; break;
    185     case ACT_FLAG:
    186         g->result = can_mate(&g->pos, a->c ^ 1) ? (a->c == WHITE ? R_BLACK : R_WHITE) : R_DRAW;
    187         g->reason = g->result == R_DRAW ? RS_TIMEOUT_DRAW : RS_TIMEOUT;
    188         break;
    189     }
    190 }
    191 
    192 /* ------------------------------------------------------------------ texto */
    193 
    194 static const char *const ACT_NAME[ACT__COUNT] = {
    195     "MOVE", "RESIGN", "DRAW", "DRAWOK", "DRAWNO", "CLAIM", "UNDO", "UNDOOK", "UNDONO", "FLAG"
    196 };
    197 
    198 int act_fmt(const Act *a, char *buf, int n)
    199 {
    200     if (a->type >= ACT__COUNT) return -1;
    201     int k = str_int(buf, n, 0, a->c);
    202     k = str_put(buf, n, k, " ");
    203     k = str_put(buf, n, k, ACT_NAME[a->type]);
    204     if (a->type == ACT_MOVE) {
    205         char u[8];
    206         move_uci(a->m, u, sizeof u);
    207         k = str_put(buf, n, k, " ");
    208         k = str_put(buf, n, k, u);
    209     }
    210     if (a->type == ACT_MOVE || a->type == ACT_FLAG) {
    211         k = str_put(buf, n, k, " ");
    212         k = str_int(buf, n, k, a->ms);
    213     }
    214     return k;
    215 }
    216 
    217 /* Parsea solo la forma; la jugada queda en a->m como desde/hasta/coronacion
    218  * sin banderas: act_resolve() la completa contra la posicion. */
    219 bool act_parse(const char *s, Act *a)
    220 {
    221     memset(a, 0, sizeof *a);
    222     long v;
    223     const char *q = str_scan_int(s, &v);
    224     if (!q || v < 0 || v > 1) return false;
    225     a->c = (int8_t)v;
    226     while (*q == ' ') q++;
    227     int t = -1;
    228     for (int i = 0; i < ACT__COUNT; i++) {
    229         size_t l = strlen(ACT_NAME[i]);
    230         if (!strncmp(q, ACT_NAME[i], l) && (q[l] == ' ' || !q[l])) { t = i; q += l; break; }
    231     }
    232     if (t < 0) return false;
    233     a->type = (uint8_t)t;
    234     while (*q == ' ') q++;
    235     if (t == ACT_MOVE) {
    236         if (q[0] < 'a' || q[0] > 'h' || q[1] < '1' || q[1] > '8' || q[2] < 'a' || q[2] > 'h' || q[3] < '1' || q[3] > '8')
    237             return false;
    238         int from = SQ(q[0] - 'a', q[1] - '1'), to = SQ(q[2] - 'a', q[3] - '1');
    239         int flag = 0;
    240         q += 4;
    241         if (*q && *q != ' ') {
    242             const char *pc = strchr("nbrq", *q);
    243             if (!pc) return false;
    244             flag = MF_PROMO + (int)(pc - "nbrq");
    245             q++;
    246         }
    247         a->m = MOVE(from, to, flag);
    248         while (*q == ' ') q++;
    249     }
    250     if (t == ACT_MOVE || t == ACT_FLAG) {
    251         if (*q) {
    252             q = str_scan_int(q, &v);
    253             if (!q || v < 0) return false;
    254             a->ms = (int32_t)v;
    255         }
    256     }
    257     return true;
    258 }
    259 
    260 /* completa las banderas de una jugada parseada (captura, enroque...) */
    261 bool act_resolve(const Game *g, Act *a)
    262 {
    263     if (a->type != ACT_MOVE) return true;
    264     char u[8];
    265     move_uci(a->m, u, sizeof u);
    266     Move m = move_from_uci(&g->pos, u);
    267     if (!m) return false;
    268     a->m = m;
    269     return true;
    270 }
    271 
    272 int game_hdr_fmt(const Game *g, char *buf, int n)
    273 {
    274     char fen[100];
    275     pos_fen(&g->start, fen, sizeof fen);
    276     int k = str_put(buf, n, 0, "C ");
    277     k = str_int(buf, n, k, g->tc.base);
    278     k = str_put(buf, n, k, " ");
    279     k = str_int(buf, n, k, g->tc.inc);
    280     k = str_put(buf, n, k, " ");
    281     k = str_int(buf, n, k, g->tc.per_move);
    282     k = str_put(buf, n, k, g->allow_undo ? " 1 " : " 0 ");
    283     return str_put(buf, n, k, fen);
    284 }
    285 
    286 bool game_hdr_parse(const char *s, Game *g)
    287 {
    288     if (s[0] != 'C' || s[1] != ' ') return false;
    289     long v[4];
    290     const char *q = s + 2;
    291     for (int i = 0; i < 4; i++) if (!(q = str_scan_int(q, &v[i])) || v[i] < 0) return false;
    292     TimeCtl tc = { (int32_t)v[0], (int32_t)v[1], (int32_t)v[2] };
    293     Pos p;
    294     if (!pos_from_fen(&p, q)) return false;
    295     game_init(g, &p, &tc, v[3] != 0);
    296     return true;
    297 }
    298 
    299 const char *result_text(int result)
    300 {
    301     switch (result) {
    302     case R_WHITE: return "1-0";
    303     case R_BLACK: return "0-1";
    304     case R_DRAW: return "1/2-1/2";
    305     }
    306     return "*";
    307 }