selftest.sh (10272B)
1 #!/usr/bin/env bash 2 # ============================================================================ 3 # lib/selftest.sh -- Pruebas automaticas del motor (sin interfaz ni red). 4 # ============================================================================ 5 # 6 # QUE ES ESTO: un "self-test" (auto-prueba) es un conjunto de pruebas que el 7 # programa se corre a SI MISMO para verificar que las reglas funcionan bien. Es la 8 # red de seguridad: si tocas algo del motor y rompes una regla, estas pruebas lo 9 # detectan al instante. Se corren con: ./ches.sh --selftest 10 # 11 # Cada prueba arma una posicion conocida, hace unas jugadas, y compara el 12 # resultado con lo que ESPERABAMOS. Si coinciden, "ok"; si no, "FAIL". 13 # (Los textos de las pruebas quedan en ingles porque son mensajes que se imprimen.) 14 15 _ST_PASS=0 # contador de pruebas que pasaron 16 _ST_FAIL=0 # contador de pruebas que fallaron 17 18 # _assert: el corazon de las pruebas. Recibe una descripcion, el valor ESPERADO y 19 # el valor REAL. Si son iguales, cuenta un "ok"; si no, muestra el FAIL con ambos 20 # valores para poder depurar. 21 _assert() { 22 local desc=$1 expected=$2 actual=$3 23 if [[ "$expected" == "$actual" ]]; then 24 ((_ST_PASS++)) 25 printf ' \e[32mok\e[0m %s\n' "$desc" # \e[32m = verde 26 else 27 ((_ST_FAIL++)) 28 printf ' \e[31mFAIL\e[0m %s\n expected: %s\n actual: %s\n' \ 29 "$desc" "$expected" "$actual" # \e[31m = rojo 30 fi 31 } 32 33 # _play: ayuda para las pruebas. Carga una posicion (FEN), reinicia el contador de 34 # repeticiones, y aplica una lista de jugadas. "$@" son todos los argumentos 35 # (despues del FEN) y el "for" los aplica uno por uno. 36 _play() { 37 local fen=$1; shift 38 POSCOUNT=() 39 setup_fen "$fen" 40 POSCOUNT["$(position_key)"]=1 41 local m 42 for m in "$@"; do apply_move "$m"; done 43 } 44 45 # _sq: que pieza hay en una casilla (ej: _sq f7). Util para chequear resultados. 46 _sq() { echo "${BOARD[$(alg_to_idx "$1")]}"; } 47 48 # run_selftest: corre todas las pruebas, una atras de otra. 49 run_selftest() { 50 echo "Running engine self-tests..." 51 52 # 1. Mate del pastor (un mate clasico en 4 jugadas). 53 _play "$START_FEN" e2e4 e7e5 f1c4 b8c6 d1h5 g8f6 h5f7 54 _assert "scholar's mate is checkmate" checkmate "$(game_status)" 55 _assert "scholar's mate: queen on f7" Q "$(_sq f7)" 56 57 # 2. Captura al paso (en passant). 58 _play "$START_FEN" e2e4 a7a6 e4e5 d7d5 59 _assert "en passant target set to d6" d6 "$EP" 60 _play "$START_FEN" e2e4 a7a6 e4e5 d7d5 e5d6 61 _assert "en passant: white pawn on d6" P "$(_sq d6)" 62 _assert "en passant: captured pawn gone from d5" . "$(_sq d5)" 63 _assert "en passant: e5 vacated" . "$(_sq e5)" 64 65 # 3. Enroque corto. 66 _play "r3k2r/8/8/8/8/8/8/R3K2R w KQkq - 0 1" e1g1 67 _assert "castling: white king on g1" K "$(_sq g1)" 68 _assert "castling: rook moved to f1" R "$(_sq f1)" 69 _assert "castling: h1 vacated" . "$(_sq h1)" 70 _assert "castling: e1 vacated" . "$(_sq e1)" 71 _assert "castling: white rights cleared" "kq" "$CASTLE" 72 73 # 4. Coronacion de peon. 74 _play "k6K/P7/8/8/8/8/8/8 w - - 0 1" a7a8q 75 _assert "promotion: queen on a8" Q "$(_sq a8)" 76 _assert "promotion: a7 vacated" . "$(_sq a7)" 77 78 # 5. Ahogado (le toca a las negras, no estan en jaque, pero no tienen jugadas). 79 _play "7k/5Q2/6K1/8/8/8/8/8 b - - 0 1" 80 _assert "stalemate detected" stalemate "$(game_status)" 81 82 # 6. Control: la posicion inicial tiene exactamente 20 jugadas legales. 83 _play "$START_FEN" 84 _assert "start position legal move count" 20 "$(legal_moves | grep -c .)" 85 86 # 6b. Repeticion: la triple es RECLAMABLE (no automatica); la quintuple si corta. 87 _play "$START_FEN" g1f3 g8f6 f3g1 f6g8 g1f3 g8f6 f3g1 f6g8 # inicio visto 3 veces 88 _assert "threefold is claimable" yes "$(can_claim_threefold && echo yes || echo no)" 89 _assert "threefold does not auto-draw" ongoing "$(game_status)" 90 _play "$START_FEN" g1f3 g8f6 f3g1 f6g8 g1f3 g8f6 f3g1 f6g8 \ 91 g1f3 g8f6 f3g1 f6g8 g1f3 g8f6 f3g1 f6g8 # inicio visto 5 veces 92 _assert "fivefold auto-draws" draw-fivefold "$(game_status)" 93 94 # 6c. Regla de 75 movimientos: tablas automaticas a las 150 medias jugadas. 95 _play "4k3/8/8/8/8/8/8/4K2R w - - 149 200" h1h2 96 _assert "75-move rule auto-draws" draw-75move "$(game_status)" 97 98 # 7. Reloj: codificar/decodificar el control de tiempo y dar formato. 99 TC_ENABLED=1; TC_BASE=300; TC_INC=5 100 _assert "tc_encode timed control" "300:5" "$(tc_encode)" 101 TC_ENABLED=0; TC_BASE=0; TC_INC=0 102 _assert "tc_encode untimed" "off" "$(tc_encode)" 103 tc_decode "180:2" 104 _assert "tc_decode base" 180 "$TC_BASE" 105 _assert "tc_decode increment" 2 "$TC_INC" 106 _assert "tc_decode enabled" 1 "$TC_ENABLED" 107 tc_decode "off" 108 _assert "tc_decode off disables" 0 "$TC_ENABLED" 109 _assert "clock format mm:ss" "05:05" "$(_fmt_clock 305)" 110 _assert "clock format clamps negative" "00:00" "$(_fmt_clock -7)" 111 112 # 8. Guardar / recuperar: una partida guardada y vuelta a cargar restaura todo. 113 _play "$START_FEN" e2e4 e7e5 g1f3 114 TC_ENABLED=1; TC_BASE=300; TC_INC=5; CLOCK_white=271; CLOCK_black=283 115 ALLOW_TAKEBACK=1 116 HISTORY=("e2e4 e7e5" "g1f3 ..."); HIST_FULLMOVE=1; HIST_LINE="g1f3" 117 local _sv; _sv="$(mktemp)" 118 save_game "$_sv" 119 # ensuciamos el estado a proposito, despues recargamos en las variables GAME_* 120 TC_ENABLED=0; CLOCK_white=0; CLOCK_black=0; ALLOW_TAKEBACK=0 121 load_game "$_sv"; rm -f "$_sv" 122 _assert "resume restores fen" "$(get_fen)" "$GAME_FEN" 123 _assert "resume restores white clock" 271 "$GAME_CW" 124 _assert "resume restores black clock" 283 "$GAME_CB" 125 _assert "resume restores time control" 1 "$TC_ENABLED" 126 _assert "resume restores increment" 5 "$TC_INC" 127 _assert "resume restores takeback flag" 1 "$GAME_TB" 128 _assert "resume restores history len" 2 "${#GAME_HIST[@]}" 129 130 # 9. Ida y vuelta del estado por la red (lo que el host le manda al que se une). 131 local _enc; _enc="$(_state_encode)" 132 GAME_FEN=""; GAME_CW=0; GAME_CB=0; GAME_TB=0; GAME_HIST=() 133 _state_decode "$_enc" 134 _assert "state payload restores white clock" 271 "$GAME_CW" 135 _assert "state payload restores takeback flag" 1 "$GAME_TB" 136 _assert "state payload restores history len" 2 "${#GAME_HIST[@]}" 137 _assert "state payload restores last ply" "g1f3 ..." "${GAME_HIST[1]}" 138 139 # 8b. Retroceso: tomar una "foto", aplicar una jugada, deshacerla -> todo vuelve. 140 _play "$START_FEN" 141 CLOCK_white=300; CLOCK_black=300 142 HISTORY=(); HIST_FULLMOVE=0; HIST_LINE=""; UNDO_STACK=() 143 local _fen_before; _fen_before="$(get_fen)" 144 _snapshot_push 145 CLOCK_white=288 # las blancas "gastaron" 12s antes de mover 146 apply_move e2e4; record_history white e2e4 147 do_takeback 148 _assert "takeback restores position" "$_fen_before" "$(get_fen)" 149 _assert "takeback refunds white clock" 300 "$CLOCK_white" 150 _assert "takeback clears history" 0 "${#HISTORY[@]}" 151 152 # 11. Notacion SAN (move_to_san), cubriendo cada caso especial. 153 _play "$START_FEN" 154 _assert "san pawn push" "e4" "$(move_to_san e2e4)" 155 _assert "san knight move" "Nf3" "$(move_to_san g1f3)" 156 _play "rnbqkbnr/ppp1pppp/8/3p4/4P3/8/PPPP1PPP/RNBQKBNR w KQkq d6 0 2" 157 _assert "san pawn capture" "exd5" "$(move_to_san e4d5)" 158 _play "r3k2r/8/8/8/8/8/8/R3K2R w KQkq - 0 1" 159 _assert "san kingside castle" "O-O" "$(move_to_san e1g1)" 160 _assert "san queenside castle" "O-O-O" "$(move_to_san e1c1)" 161 _play "8/P7/8/4k3/8/8/8/7K w - - 0 1" 162 _assert "san promotion" "a8=Q" "$(move_to_san a7a8q)" 163 _play "7k/8/8/8/8/8/8/R6K w - - 0 1" 164 _assert "san check suffix" "Ra8+" "$(move_to_san a1a8)" 165 _play "6k1/5ppp/8/8/8/8/8/R6K w - - 0 1" 166 _assert "san mate suffix" "Ra8#" "$(move_to_san a1a8)" 167 _play "4k3/8/8/8/8/1N3N2/8/4K3 w - - 0 1" 168 _assert "san disambiguation" "Nbd4" "$(move_to_san b3d4)" 169 170 # 12. Ida y vuelta de PGN: importar y exportar dan la misma partida. 171 load_pgn_string "1. e4 e5 2. Nf3 Nc6 3. Bb5 a6 4. Bxc6 dxc6" 172 local _fen_after="$GAME_FEN" 173 _assert "pgn import ply count" 8 "${#GAME_PGN[@]}" 174 _assert "pgn import keeps capture SAN" "Bxc6" "${GAME_PGN[6]}" 175 PGN_SAN=("${GAME_PGN[@]}"); PGN_STARTFEN="$START_FEN" 176 local _pf; _pf="$(mktemp)" 177 export_pgn "$_pf" "*" 178 load_pgn_string "$(cat "$_pf")"; rm -f "$_pf" 179 _assert "pgn round-trip final position" "$_fen_after" "$GAME_FEN" 180 _assert "pgn round-trip ply count" 8 "${#GAME_PGN[@]}" 181 182 # 10. Un archivo con solo un FEN se carga como partida sin reloj. 183 local _fenfile; _fenfile="$(mktemp)" 184 printf '%s\n' "$START_FEN" > "$_fenfile" 185 TC_ENABLED=1 186 load_game "$_fenfile"; rm -f "$_fenfile" 187 _assert "bare FEN file sets position" "$START_FEN" "$GAME_FEN" 188 _assert "bare FEN file is untimed" 0 "$TC_ENABLED" 189 190 # 13. Fuente de jugadas de la IA: leer el "bestmove" (no necesita el motor) y, 191 # si Stockfish esta instalado, una prueba real (si no, se saltea, asi una 192 # copia recien bajada del proyecto sigue dando todo en verde). 193 _assert "parse bestmove + ponder" e2e4 "$(_parse_bestmove "bestmove e2e4 ponder e7e5")" 194 _assert "parse bestmove promotion" e7e8q "$(_parse_bestmove "bestmove e7e8q")" 195 if ai_available; then 196 AI_SKILL=1; AI_ELO=""; AI_MOVETIME=200 197 if ai_init; then 198 _play "$START_FEN" 199 local _aim; _aim="$(ai_move)" 200 _assert "engine returns a legal move" yes \ 201 "$(legal_moves | grep -qx "$_aim" && echo yes || echo no)" 202 ai_close 203 else 204 echo " -- ai: engine present but failed to start (live test skipped)" 205 fi 206 else 207 echo " -- ai: Stockfish not installed (live engine test skipped)" 208 fi 209 210 # Resumen final: cuantas pasaron y cuantas fallaron (en verde o rojo). 211 echo 212 local color=$'\e[32m'; (( _ST_FAIL > 0 )) && color=$'\e[31m' 213 printf 'Self-test: \e[32m%d passed\e[0m, %s%d failed\e[0m\n' \ 214 "$_ST_PASS" "$color" "$_ST_FAIL" 215 (( _ST_FAIL == 0 )) || return 1 # devolvemos error si fallo alguna 216 return 0 217 }