Source code for haive.games.reversi.state

"""Reversi (Othello) game state model.

Defines board layout, current game status, turn tracking, move history, analysis
storage, and rendering utilities for the Reversi agent system.

"""

from typing import TYPE_CHECKING, Any, Literal

from pydantic import Field, field_validator

from haive.games.framework.base.state import GameState
from haive.games.reversi.models import ReversiMove

if TYPE_CHECKING:
    pass


[docs] class ReversiState(GameState): """State model for a game of Reversi/Othello. Attributes: board (List[List[Optional[str]]]): 8x8 grid representing the game board. turn (str): The current player's turn ('B' or 'W'). game_status (str): Overall game status (ongoing, draw, B_win, W_win). move_history (List[ReversiMove]): History of all moves made. winner (Optional[str]): Winner symbol ('B' or 'W'), or None. player_B (str): Identifier for the player using black discs. player_W (str): Identifier for the player using white discs. player1_analysis (List[Dict[str, any]]): Analysis history by player1. player2_analysis (List[Dict[str, any]]): Analysis history by player2. skip_count (int): Number of consecutive turns skipped (used for endgame). """ board: list[list[str | None]] = Field( ..., description="8x8 game board, each cell can be None, 'B', or 'W'" ) turn: Literal["B", "W"] = Field( ..., description="Current player's turn (B=Black, W=White)" ) game_status: Literal["ongoing", "B_win", "W_win", "draw"] = Field( default="ongoing", description="Status of the game" ) move_history: list[ReversiMove] = Field( default_factory=list, description="History of moves" ) winner: str | None = Field(default=None, description="Winner of the game, if any") player_B: Literal["player1", "player2"] = Field( default="player1", description="Which player is Black" ) player_W: Literal["player1", "player2"] = Field( default="player2", description="Which player is White" ) player1_analysis: list[dict[str, Any]] = Field( default_factory=list, description="Analyses by player1" ) player2_analysis: list[dict[str, Any]] = Field( default_factory=list, description="Analyses by player2" ) skip_count: int = Field( default=0, description="Number of consecutive skipped turns" )
[docs] @field_validator("board") @classmethod def validate_board(cls, board: list[list[str | None]]) -> list[list[str | None]]: """Validate that the board is an 8x8 grid with only valid values. Args: board (List[List[Optional[str]]]): Input board state. Returns: List[List[Optional[str]]]: The validated board. Raises: ValueError: If the board structure or contents are invalid. """ if len(board) != 8: raise ValueError("Board must have 8 rows") for row in board: if len(row) != 8: raise ValueError("Each row must have 8 columns") for cell in row: if cell is not None and cell not in ["B", "W"]: raise ValueError( f"Cell values must be None, 'B', or 'W', got {cell}" ) return board
@property def current_player_name(self) -> str: """Get the current player's identifier. Returns: str: Either 'player1' or 'player2'. """ return self.player_B if self.turn == "B" else self.player_W @property def disc_count(self) -> dict[str, int]: """Count the number of discs of each color on the board. Returns: Dict[str, int]: Dictionary with counts of 'B' and 'W'. """ black_count = sum(1 for row in self.board for cell in row if cell == "B") white_count = sum(1 for row in self.board for cell in row if cell == "W") return {"B": black_count, "W": white_count} @property def board_string(self) -> str: """Get a human-readable string of the current board layout. Returns: str: Formatted board as text. """ result = [] result.append(" 1 2 3 4 5 6 7 8") result.append(" +-----------------+") for i, row in enumerate(self.board): row_str = f"{chr(65 + i)} |" for cell in row: if cell is None: row_str += " |" else: row_str += f"{cell}|" result.append(row_str) result.append(" +-----------------+") counts = self.disc_count result.append(f"Black: {counts['B']} discs, White: {counts['W']} discs") return "\n".join(result)
[docs] @classmethod def initialize( cls, first_player: str = "B", player_B: str = "player1", player_W: str = "player2", ) -> "ReversiState": """Class-level initializer for ReversiState. Args: first_player (str): 'B' or 'W'. Who plays first. player_B (str): Which player controls black. player_W (str): Which player controls white. Returns: ReversiState: Initialized state. """ from haive.games.reversi.state_manager import ReversiStateManager return ReversiStateManager.initialize( first_player=first_player, player_B=player_B, player_W=player_W )