Source code for haive.games.connect4.state_manager
"""Connect Four game state management module.This module provides comprehensive state management functionality for the Connect Fourgame, including board management, move validation, win detection, and game status tracking.Connect Four is a strategy game played on a 7×6 grid where players take turns droppingcolored pieces into columns. The pieces fall to the lowest available position in thechosen column. The first player to get four pieces in a row (horizontally, vertically,or diagonally) wins the game.Classes: Connect4StateManager: Main state management class for Connect Four operations.Example: Basic Connect Four game setup and play: >>> from haive.games.connect4.state_manager import Connect4StateManager >>> from haive.games.connect4.models import Connect4Move >>> >>> # Initialize game (red player starts) >>> state = Connect4StateManager.initialize() >>> print(f"Current player: {state.current_player}") # "red" >>> print(f"Board size: {len(state.board)}x{len(state.board[0])}") # "6x7" >>> >>> # Drop piece in center column >>> move = Connect4Move(column=3, explanation="Center play") >>> new_state = Connect4StateManager.apply_move(state, move) >>> >>> # Check if column is full >>> legal_moves = Connect4StateManager.get_legal_moves(new_state) >>> print(f"Available columns: {[m.column for m in legal_moves]}")Note: - Columns are 0-indexed (0-6 for a standard 7-column board) - Players alternate between "red" and "yellow" - Pieces are placed at the bottom-most available position in each column - The game ends when a player gets 4 in a row or the board is full (draw)"""importcopyfromtypingimportAnyfromhaive.games.connect4.modelsimportConnect4Movefromhaive.games.connect4.stateimportConnect4Statefromhaive.games.framework.base.state_managerimportGameStateManager
[docs]classConnect4StateManager(GameStateManager[Connect4State]):"""Manager for Connect4 game state. This class provides methods for managing Connect4 game state, including: - Game initialization - Move application and validation - Win condition checking - Game state conversion The state manager follows the immutable state pattern, creating new state instances rather than modifying existing ones. """
[docs]@classmethoddefinitialize(cls)->Connect4State:"""Initialize a new Connect4 game. Creates a fresh Connect4 game state with an empty board, red player starting, and game status set to ongoing. Returns: Connect4State: A new game state with default settings Example: >>> state = Connect4StateManager.initialize() >>> state.turn 'red' >>> state.game_status 'ongoing' """board=[[Nonefor_inrange(7)]for_inrange(6)]returnConnect4State(board=board,turn="red",game_status="ongoing",move_history=[])
[docs]@classmethoddefapply_move(cls,state:Connect4State,move:Connect4Move)->Connect4State:"""Apply a move to the Connect4 state. Applies the given move to the game state, updating the board, checking win conditions, and switching turns as appropriate. Args: state: Current game state move: Move to apply Returns: Connect4State: Updated game state after applying the move Raises: ValueError: If the move is invalid (column full or out of range) Example: >>> state = Connect4StateManager.initialize() >>> move = Connect4Move(column=3) >>> new_state = Connect4StateManager.apply_move(state, move) >>> new_state.turn 'yellow' """new_state=copy.deepcopy(state)board=new_state.boardcolumn=move.column# Validate columnifnot(0<=column<7):raiseValueError(f"Column {column} is out of range (0-6)")# Find next available rowrow=new_state.get_next_row(column)ifrowisNone:raiseValueError(f"Column {column} is full")# Place pieceboard[row][column]=new_state.turnnew_state.move_history.append(move)# Check win conditionifcls._check_win(new_state,row,column):new_state.game_status=f"{new_state.turn}_win"new_state.winner=new_state.turnelifall(cellisnotNoneforrowinboardforcellinrow):new_state.game_status="draw"else:new_state.turn="yellow"ifnew_state.turn=="red"else"red"returnnew_state
[docs]@classmethoddefget_legal_moves(cls,state:Connect4State)->list[Connect4Move]:"""Get all legal moves for the current game state. Returns a list of all valid moves (non-full columns) for the current player. Args: state: Current game state Returns: list[Connect4Move]: List of legal moves Example: >>> state = Connect4StateManager.initialize() >>> legal_moves = Connect4StateManager.get_legal_moves(state) >>> len(legal_moves) 7 # All columns are empty in a new game """return[Connect4Move(column=col)forcolinrange(7)ifnotstate.is_column_full(col)]
[docs]@classmethoddefcheck_game_over(cls,state:Connect4State)->bool:"""Check if the game is over (win or draw). Args: state: Current game state Returns: bool: True if the game is over, False otherwise Example: >>> state = Connect4StateManager.initialize() >>> Connect4StateManager.check_game_over(state) False """returnstate.game_statusin["red_win","yellow_win","draw"]
[docs]@classmethoddefensure_state(cls,state:dict[str,Any]|Connect4State)->Connect4State:"""Ensure the input is a proper Connect4State object. Args: state: State object or dictionary Returns: Connect4State: Properly typed state object Example: >>> state_dict = {"board": [[None for _ in range(7)] for _ in range(6)], ... "turn": "red", "game_status": "ongoing"} >>> state = Connect4StateManager.ensure_state(state_dict) >>> isinstance(state, Connect4State) True """ifisinstance(state,dict):returnConnect4State(**state)returnstate
@classmethoddef_check_win(cls,state:Connect4State,row:int,col:int)->bool:"""Check if there's a win at the specified position. Checks for four in a row horizontally, vertically, and diagonally after a piece is placed at the specified position. Args: state: Current game state row: Row index where the piece was placed col: Column index where the piece was placed Returns: bool: True if there's a win, False otherwise """player=state.turnboard=state.board# Check horizontalforcinrange(max(0,col-3),min(4,col+1)):ifall(board[row][c+i]==playerforiinrange(4)):returnTrue# Check verticalifrow<=2:# Only check if there are enough rows belowifall(board[row+i][col]==playerforiinrange(4)):returnTrue# Check diagonal /foroffsetinrange(-3,1):if0<=row+offset<=2and0<=col+offset<=3:ifall(board[row+offset+i][col+offset+i]==playerforiinrange(4)):returnTrue# Check diagonal \foroffsetinrange(-3,1):if0<=row+offset<=2and3<=col-offset<=6:ifall(board[row+offset+i][col-offset-i]==playerforiinrange(4)):returnTruereturnFalse