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 }