from typing import Any
import chess
import chipiron.environments.chess_env.board as boards
from chipiron.players.boardevaluators.evaluation_scale import (
EvaluationScale,
ValueOverEnum,
get_value_over_enum,
)
from chipiron.players.boardevaluators.over_event import HowOver, OverEvent, Winner
from chipiron.players.boardevaluators.table_base.syzygy_table import SyzygyTable
from .board_evaluator import BoardEvaluator
[docs]class MasterBoardEvaluator:
"""
The MasterBoardEvaluator class is responsible for evaluating the value of nodes in a tree structure.
It uses a board evaluator and a syzygy evaluator to calculate the value of the nodes.
"""
# The board evaluator used to evaluate the chess board.
board_evaluator: BoardEvaluator
# The Syzygy table used for endgame tablebase evaluations, or None if not available.
syzygy_evaluator: SyzygyTable[Any] | None
# The value over enum used to determine the value of the node when it is over.
value_over_enum: ValueOverEnum
def __init__(
self,
board_evaluator: BoardEvaluator,
syzygy: SyzygyTable[Any] | None,
value_over_enum: ValueOverEnum,
) -> None:
"""
Initializes a NodeEvaluator object.
Args:
board_evaluator (board_evals.BoardEvaluator): The board evaluator used to evaluate the chess board.
syzygy (SyzygyTable | None): The Syzygy table used for endgame tablebase evaluations, or None if not available.
value_over_enum (ValueOverEnum): The value over enum used to determine the value of the node when it is over.
"""
self.board_evaluator = board_evaluator
self.syzygy_evaluator = syzygy
self.value_over_enum = value_over_enum
[docs] def value_white(self, board: boards.IBoard) -> float:
"""
Calculates the value for the white player of a given node.
If the value can be obtained from the syzygy evaluator, it is used.
Otherwise, the board evaluator is used.
"""
value_white: float | None = self.syzygy_value_white(board)
value_white_float: float
if value_white is None:
value_white_float = self.board_evaluator.value_white(board)
else:
value_white_float = value_white
return value_white_float
[docs] def syzygy_value_white(self, board: boards.IBoard) -> float | None:
"""
Calculates the value for the white player of a given board using the syzygy evaluator.
If the syzygy evaluator is not available or the board is not in the syzygy table, None is returned.
"""
if self.syzygy_evaluator is None or not self.syzygy_evaluator.fast_in_table(
board
):
return None
else:
val: int = self.syzygy_evaluator.val(board)
return val
[docs] def check_obvious_over_events(
self, board: boards.IBoard
) -> tuple[OverEvent | None, float | None]:
"""
Checks if the given board is in an obvious game-over state and returns the corresponding OverEvent and evaluation.
Args:
board (boards.IBoard): The board to evaluate for game-over conditions.
Raises:
ValueError: If the board result string is not recognized.
Returns:
tuple[OverEvent | None, float]: A tuple containing the OverEvent
(if the game is over or can be determined from Syzygy tables, otherwise None) and the evaluation score from White's perspective.
The evaluation is especially useful when training models.
"""
game_over: bool = board.is_game_over()
over_event: OverEvent | None = None
evaluation: float | None = None
if game_over:
value_as_string: str = board.result(claim_draw=True)
how_over_: HowOver
who_is_winner_: Winner
match value_as_string:
case "0-1":
how_over_ = HowOver.WIN
who_is_winner_ = Winner.BLACK
case "1-0":
how_over_ = HowOver.WIN
who_is_winner_ = Winner.WHITE
case "1/2-1/2":
how_over_ = HowOver.DRAW
who_is_winner_ = Winner.NO_KNOWN_WINNER
case other:
raise ValueError(f"value {other} not expected in {__name__}")
over_event = OverEvent(
how_over=how_over_,
who_is_winner=who_is_winner_,
termination=board.termination(),
)
elif self.syzygy_evaluator and self.syzygy_evaluator.fast_in_table(board):
who_is_winner_, how_over_ = self.syzygy_evaluator.get_over_event(
board=board
)
over_event = OverEvent(
how_over=how_over_,
who_is_winner=who_is_winner_,
termination=None, # not sure how to retrieve this info more precisely atm
)
if over_event is not None:
evaluation = self.value_white_from_over_event(over_event=over_event)
return over_event, evaluation
[docs] def value_white_from_over_event(self, over_event: OverEvent) -> float:
"""
Returns the value white given an over event.
"""
assert over_event.is_over()
white_value: Any
if over_event.is_win():
assert not over_event.is_draw()
if over_event.is_winner(chess.WHITE):
white_value = self.value_over_enum.VALUE_WHITE_WHEN_OVER_WHITE_WINS
else:
white_value = self.value_over_enum.VALUE_WHITE_WHEN_OVER_BLACK_WINS
else: # draw
assert over_event.is_draw()
white_value = self.value_over_enum.VALUE_WHITE_WHEN_OVER_DRAW
assert isinstance(white_value, float)
return white_value
def create_master_board_evaluator(
board_evaluator: BoardEvaluator,
syzygy: SyzygyTable[Any] | None,
evaluation_scale: EvaluationScale,
) -> "MasterBoardEvaluator":
"""
Factory function to create a MasterBoardEvaluator instance.
Args:
board_evaluator (BoardEvaluator): The board evaluator to use.
syzygy (SyzygyTable | None): The syzygy table for endgame evaluations.
value_over_enum (ValueOverEnum): The value over enum for evaluation.
Returns:
MasterBoardEvaluator: An instance of MasterBoardEvaluator.
"""
value_over_enum: ValueOverEnum = get_value_over_enum(
evaluation_scale=evaluation_scale
)
return MasterBoardEvaluator(
board_evaluator=board_evaluator, syzygy=syzygy, value_over_enum=value_over_enum
)