Spaces:
Build error
Build error
| class Configuration: | |
| def __init__(self, config_dict): | |
| self.rows = config_dict["rows"] | |
| self.columns = config_dict["columns"] | |
| self.inarow = config_dict["inarow"] | |
| class Observation: | |
| def __init__(self, obs_dict): | |
| self.board = obs_dict["board"] | |
| self.mark = obs_dict["mark"] | |
| def my_agent(observation, configuration): | |
| """ | |
| ConnectX agent using Minimax algorithm with alpha-beta pruning | |
| Args: | |
| observation: Current game state | |
| configuration: Game configuration | |
| Returns: | |
| Column number (0-based) where to drop the piece | |
| """ | |
| import numpy as np | |
| # Constants | |
| EMPTY = 0 | |
| MAX_DEPTH = 6 # Search depth limit | |
| INFINITY = float('inf') | |
| def make_board(obs): | |
| """Convert observation to 2D numpy array""" | |
| return np.asarray(obs.board).reshape(configuration.rows, configuration.columns) | |
| def get_valid_moves(board): | |
| """Get list of valid moves (columns that aren't full)""" | |
| return [col for col in range(configuration.columns) if board[0][col] == EMPTY] | |
| def drop_piece(board, col, piece): | |
| """Drop piece in specified column and return row position""" | |
| row = np.where(board[:, col] == EMPTY)[0][-1] | |
| board[row, col] = piece | |
| return row | |
| def check_window(window, piece, inarow): | |
| """ | |
| Score a window of positions | |
| Higher scores for more pieces in a row and potential winning moves | |
| Negative scores for opponent's threatening positions | |
| """ | |
| score = 0 | |
| opp_piece = 1 if piece == 2 else 2 | |
| # Winning position | |
| if np.count_nonzero(window == piece) == inarow: | |
| score += 100 | |
| # One move away from winning | |
| elif np.count_nonzero(window == piece) == (inarow - 1) and np.count_nonzero(window == EMPTY) == 1: | |
| score += 10 | |
| # Two moves away from winning | |
| elif np.count_nonzero(window == piece) == (inarow - 2) and np.count_nonzero(window == EMPTY) == 2: | |
| score += 5 | |
| # Opponent one move away from winning - defensive move needed | |
| if np.count_nonzero(window == opp_piece) == (inarow - 1) and np.count_nonzero(window == EMPTY) == 1: | |
| score -= 80 | |
| return score | |
| def score_position(board, piece): | |
| """ | |
| Score entire board position | |
| Considers horizontal, vertical, and diagonal possibilities | |
| Extra weight for center column control | |
| """ | |
| score = 0 | |
| # Horizontal windows | |
| for row in range(configuration.rows): | |
| for col in range(configuration.columns - (configuration.inarow - 1)): | |
| window = board[row, col:col + configuration.inarow] | |
| score += check_window(window, piece, configuration.inarow) | |
| # Vertical windows | |
| for row in range(configuration.rows - (configuration.inarow - 1)): | |
| for col in range(configuration.columns): | |
| window = board[row:row + configuration.inarow, col] | |
| score += check_window(window, piece, configuration.inarow) | |
| # Positive diagonal windows | |
| for row in range(configuration.rows - (configuration.inarow - 1)): | |
| for col in range(configuration.columns - (configuration.inarow - 1)): | |
| window = [board[row + i][col + i] for i in range(configuration.inarow)] | |
| score += check_window(window, piece, configuration.inarow) | |
| # Negative diagonal windows | |
| for row in range(configuration.inarow - 1, configuration.rows): | |
| for col in range(configuration.columns - (configuration.inarow - 1)): | |
| window = [board[row - i][col + i] for i in range(configuration.inarow)] | |
| score += check_window(window, piece, configuration.inarow) | |
| # Center column control bonus | |
| center_array = board[:, configuration.columns//2] | |
| center_count = np.count_nonzero(center_array == piece) | |
| score += center_count * 6 | |
| return score | |
| def is_terminal_node(board): | |
| """Check if current position is terminal (game over)""" | |
| # Check horizontal wins | |
| for row in range(configuration.rows): | |
| for col in range(configuration.columns - (configuration.inarow - 1)): | |
| window = list(board[row, col:col + configuration.inarow]) | |
| if window.count(1) == configuration.inarow or window.count(2) == configuration.inarow: | |
| return True | |
| # Check vertical wins | |
| for row in range(configuration.rows - (configuration.inarow - 1)): | |
| for col in range(configuration.columns): | |
| window = list(board[row:row + configuration.inarow, col]) | |
| if window.count(1) == configuration.inarow or window.count(2) == configuration.inarow: | |
| return True | |
| # Check if board is full | |
| return len(get_valid_moves(board)) == 0 | |
| def minimax(board, depth, alpha, beta, maximizing_player): | |
| """ | |
| Minimax algorithm with alpha-beta pruning | |
| Returns best move and its score | |
| """ | |
| valid_moves = get_valid_moves(board) | |
| is_terminal = is_terminal_node(board) | |
| # Base cases: max depth reached or terminal position | |
| if depth == 0 or is_terminal: | |
| if is_terminal: | |
| return (None, -INFINITY if maximizing_player else INFINITY) | |
| else: | |
| return (None, score_position(board, observation.mark)) | |
| if maximizing_player: | |
| value = -INFINITY | |
| column = np.random.choice(valid_moves) | |
| for col in valid_moves: | |
| board_copy = board.copy() | |
| drop_piece(board_copy, col, observation.mark) | |
| new_score = minimax(board_copy, depth-1, alpha, beta, False)[1] | |
| if new_score > value: | |
| value = new_score | |
| column = col | |
| alpha = max(alpha, value) | |
| if alpha >= beta: | |
| break | |
| return column, value | |
| else: | |
| value = INFINITY | |
| column = np.random.choice(valid_moves) | |
| opponent_piece = 1 if observation.mark == 2 else 2 | |
| for col in valid_moves: | |
| board_copy = board.copy() | |
| drop_piece(board_copy, col, opponent_piece) | |
| new_score = minimax(board_copy, depth-1, alpha, beta, True)[1] | |
| if new_score < value: | |
| value = new_score | |
| column = col | |
| beta = min(beta, value) | |
| if alpha >= beta: | |
| break | |
| return column, value | |
| # Main game logic | |
| board = make_board(observation) | |
| valid_moves = get_valid_moves(board) | |
| # First move: take center column | |
| if len(np.where(board != 0)[0]) == 0: | |
| return configuration.columns // 2 | |
| # Check for immediate winning moves | |
| for col in valid_moves: | |
| board_copy = board.copy() | |
| drop_piece(board_copy, col, observation.mark) | |
| if is_terminal_node(board_copy): | |
| return col | |
| # Use minimax to find best move | |
| column, minimax_score = minimax(board, MAX_DEPTH, -INFINITY, INFINITY, True) | |
| return column |