from __future__ import annotations from collections.abc import Callable, Iterable from dataclasses import dataclass import chess from zero960.engine.search import EvalFn, select_move @dataclass(slots=True) class MatchResult: candidate_points: float total_games: int finished_games: int def _new_board(chess960_index: int) -> chess.Board: board = chess.Board.from_chess960_pos(chess960_index) board.chess960 = True return board def play_game( chess960_index: int, white_eval: EvalFn, black_eval: EvalFn, depth: int = 2, max_plies: int = 120, ) -> float: board = _new_board(chess960_index) for _ in range(max_plies): if board.is_game_over(claim_draw=True): break eval_fn = white_eval if board.turn == chess.WHITE else black_eval move = select_move(board, depth=depth, eval_fn=eval_fn) board.push(move) result = board.result(claim_draw=True) if result == "1-0": return 1.0 if result == "0-1": return 0.0 return 0.5 def play_match_series( candidate_eval: EvalFn, baseline_eval: EvalFn, start_positions: Iterable[int], depth: int = 2, ) -> MatchResult: points = 0.0 total_games = 0 for chess960_index in start_positions: total_games += 2 points += play_game(chess960_index, candidate_eval, baseline_eval, depth=depth) points += 1.0 - play_game(chess960_index, baseline_eval, candidate_eval, depth=depth) return MatchResult( candidate_points=points, total_games=total_games, finished_games=total_games, )