Source code for haive.games.tic_tac_toe.state_manager

"""Tic Tac Toe game state management module.

This module provides comprehensive state management functionality for the classic
Tic Tac Toe game, including board initialization, move validation, win detection,
and game status tracking.

Tic Tac Toe is a classic strategy game played on a 3×3 grid where players take
turns placing their marks (X or O) in empty squares. The first player to get
three marks in a row (horizontally, vertically, or diagonally) wins.

Classes:
    TicTacToeStateManager: Main state management class for Tic Tac Toe operations.

Example:
    Basic Tic Tac Toe game setup and play:

        >>> from haive.games.tic_tac_toe.state_manager import TicTacToeStateManager
        >>> from haive.games.tic_tac_toe.models import TicTacToeMove
        >>>
        >>> # Initialize game
        >>> state = TicTacToeStateManager.initialize()
        >>> print(f"Current player: {state.turn}")
        >>>
        >>> # Make a move in the center
        >>> move = TicTacToeMove(row=1, col=1, player="X")
        >>> new_state = TicTacToeStateManager.apply_move(state, move)
        >>>
        >>> # Check for winners
        >>> winner = TicTacToeStateManager.get_winner(new_state)
        >>> print(f"Winner: {winner}")

Note:
    The board uses 0-based indexing where (0,0) is top-left and (2,2) is bottom-right.
    Players are represented as "X" and "O" strings in the game state.
"""

from haive.games.framework.base.state_manager import GameStateManager
from haive.games.tic_tac_toe.models import TicTacToeAnalysis, TicTacToeMove
from haive.games.tic_tac_toe.state import TicTacToeState


[docs] class TicTacToeStateManager(GameStateManager[TicTacToeState]): """Manager for Tic Tac Toe game state."""
[docs] @classmethod def initialize(cls, **kwargs) -> TicTacToeState: """Initialize a new Tic Tac Toe game. Args: **kwargs: Keyword arguments for game initialization. first_player: Which player goes first ('X' or 'O'). Default is 'X'. player_X: Which player is X ('player1' or 'player2'). Default is 'player1'. player_O: Which player is O ('player1' or 'player2'). Default is 'player2'. Returns: TicTacToeState: A new Tic Tac Toe game state. """ first_player = kwargs.get("first_player", "X") player_X = kwargs.get("player_X", "player1") player_O = kwargs.get("player_O", "player2") # Create an empty 3x3 board board = [[None for _ in range(3)] for _ in range(3)] return TicTacToeState( players=["player1", "player2"], board=board, turn=first_player, game_status="ongoing", move_history=[], error_message=None, winner=None, player_X=player_X, player_O=player_O, player1_analysis=[], player2_analysis=[], )
[docs] @classmethod def apply_move(cls, state: TicTacToeState, move: TicTacToeMove) -> TicTacToeState: """Apply a move to the current state and return the new state. Args: state: The current game state. move: The move to apply. Returns: TicTacToeState: A new game state after applying the move. Raises: ValueError: If the move is invalid. """ # Validate player's turn if move.player != state.turn: raise ValueError(f"Not {move.player}'s turn") # Validate move is to an empty cell if state.board[move.row][move.col] is not None: raise ValueError(f"Cell ({move.row}, {move.col}) is already occupied") # Create a deep copy of the state new_state = state.model_copy(deep=True) # Apply the move new_state.board[move.row][move.col] = move.player new_state.move_history.append(move) # Switch turns new_state.turn = "O" if move.player == "X" else "X" # Check game status return cls.check_game_status(new_state)
[docs] @classmethod def check_game_status(cls, state: TicTacToeState) -> TicTacToeState: """Check and update the game status. Args: state: The current game state. Returns: TicTacToeState: The game state with updated status. """ # Check rows for a win for row in range(3): if ( state.board[row][0] is not None and state.board[row][0] == state.board[row][1] == state.board[row][2] ): winner = state.board[row][0] state.game_status = f"{winner}_win" state.winner = winner return state # Check columns for a win for col in range(3): if ( state.board[0][col] is not None and state.board[0][col] == state.board[1][col] == state.board[2][col] ): winner = state.board[0][col] state.game_status = f"{winner}_win" state.winner = winner return state # Check diagonals for a win if ( state.board[0][0] is not None and state.board[0][0] == state.board[1][1] == state.board[2][2] ): winner = state.board[0][0] state.game_status = f"{winner}_win" state.winner = winner return state if ( state.board[0][2] is not None and state.board[0][2] == state.board[1][1] == state.board[2][0] ): winner = state.board[0][2] state.game_status = f"{winner}_win" state.winner = winner return state # Check for a draw if state.is_board_full: state.game_status = "draw" state.winner = None return state # Game is still ongoing return state
[docs] @classmethod def get_winner(cls, state: TicTacToeState) -> str | None: """Get the winner of the game, if any. Args: state: The current game state. Returns: Optional[str]: The winner ('X' or 'O'), or None if the game is ongoing or a draw. """ return state.winner
[docs] @classmethod def add_analysis( cls, state: TicTacToeState, player: str, analysis: TicTacToeAnalysis ) -> TicTacToeState: """Add an analysis to the state. Args: state: The current game state. player: The player who performed the analysis. analysis: The analysis to add. Returns: TicTacToeState: Updated state with the analysis added. """ new_state = state.model_copy() if player == "player1": new_state.player1_analysis.append(analysis) else: new_state.player2_analysis.append(analysis) return new_state
[docs] @classmethod def find_winning_move( cls, state: TicTacToeState, player: str ) -> list[tuple[int, int]]: """Find a winning move for the specified player, if any. Args: state: The current game state. player: The player to find a winning move for ('X' or 'O'). Returns: List[Tuple[int, int]]: List of winning move coordinates (row, col), or empty list if none. """ winning_moves = [] # Try each empty cell for row, col in state.empty_cells: # Create a temporary board with the move temp_board = [row[:] for row in state.board] temp_board[row][col] = player # Check if this move would win if cls._is_winning_board(temp_board, player): winning_moves.append((row, col)) return winning_moves
@classmethod def _is_winning_board(cls, board: list[list[str | None]], player: str) -> bool: """Check if the board is a win for the specified player. Args: board: The board to check. player: The player to check for ('X' or 'O'). Returns: bool: True if the player has won, False otherwise. """ # Check rows for row in range(3): if board[row][0] == board[row][1] == board[row][2] == player: return True # Check columns for col in range(3): if board[0][col] == board[1][col] == board[2][col] == player: return True # Check diagonals if board[0][0] == board[1][1] == board[2][2] == player: return True if board[0][2] == board[1][1] == board[2][0] == player: return True return False