Spaces:
Runtime error
Runtime error
File size: 7,446 Bytes
f5067fa |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 |
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 |