-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathchessnut_submission.py
174 lines (153 loc) · 6.22 KB
/
chessnut_submission.py
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
"""
FIDE-Google Efficient Chess AI Challenge Agent
This agent implements a resource-efficient chess AI within the competition constraints:
- 5 MiB RAM
- 2.20GHz CPU core
- 64KiB compressed size limit
"""
import numpy as np
import time
from typing import Dict, List, Optional, Tuple
from chessnut import Game, Move, InvalidMove
class ChessAgent:
def __init__(self):
# Piece values for basic material evaluation
self.piece_values = {
'p': 100, # pawn
'n': 320, # knight
'b': 330, # bishop
'r': 500, # rook
'q': 900, # queen
'k': 20000 # king
}
# Simple piece-square tables for positional evaluation
self.pst = self._initialize_piece_square_tables()
def _initialize_piece_square_tables(self) -> Dict:
"""Initialize basic piece-square tables for positional evaluation."""
pst = {
'p': np.array([ # Pawn
0, 0, 0, 0, 0, 0, 0, 0,
50, 50, 50, 50, 50, 50, 50, 50,
10, 10, 20, 30, 30, 20, 10, 10,
5, 5, 10, 25, 25, 10, 5, 5,
0, 0, 0, 20, 20, 0, 0, 0,
5, -5,-10, 0, 0,-10, -5, 5,
5, 10, 10,-20,-20, 10, 10, 5,
0, 0, 0, 0, 0, 0, 0, 0
]),
'n': np.array([ # Knight
-50,-40,-30,-30,-30,-30,-40,-50,
-40,-20, 0, 0, 0, 0,-20,-40,
-30, 0, 10, 15, 15, 10, 0,-30,
-30, 5, 15, 20, 20, 15, 5,-30,
-30, 0, 15, 20, 20, 15, 0,-30,
-30, 5, 10, 15, 15, 10, 5,-30,
-40,-20, 0, 5, 5, 0,-20,-40,
-50,-40,-30,-30,-30,-30,-40,-50
])
}
return pst
def evaluate_position(self, game: Game) -> float:
"""
Evaluate the current position.
Returns a score from white's perspective.
"""
board = game.board
score = 0
# Material evaluation
for i, piece in enumerate(board):
if piece != ' ':
is_white = piece.isupper()
piece_type = piece.lower()
value = self.piece_values[piece_type]
score += value if is_white else -value
# Add piece-square table bonus for pawns and knights
if piece_type in self.pst:
square_value = self.pst[piece_type][i if is_white else 63-i]
score += square_value if is_white else -square_value
# Basic mobility evaluation (simplified)
moves = len(list(game.get_moves()))
score += moves if game.state.player == 'w' else -moves
# Penalize blocked center pawns and knights
center_squares = [27, 28, 35, 36] # e4, d4, e5, d5
for square in center_squares:
piece = board[square]
if piece.lower() in ['p', 'n']:
is_white = piece.isupper()
score -= 10 if is_white else -10
return score
def get_best_move(self, game: Game, depth: int = 3) -> Optional[str]:
"""Find the best move using minimax with alpha-beta pruning."""
def minimax(game: Game, depth: int, alpha: float, beta: float, maximizing: bool) -> Tuple[float, Optional[str]]:
if depth == 0:
return self.evaluate_position(game), None
moves = list(game.get_moves())
if not moves:
return -20000 if maximizing else 20000, None
best_move = moves[0]
if maximizing:
max_eval = float('-inf')
for move in moves:
try:
new_game = Game(game.get_fen())
new_game.apply_move(move)
eval_score, _ = minimax(new_game, depth - 1, alpha, beta, False)
if eval_score > max_eval:
max_eval = eval_score
best_move = move
alpha = max(alpha, eval_score)
if beta <= alpha:
break
except InvalidMove:
continue
return max_eval, best_move
else:
min_eval = float('inf')
for move in moves:
try:
new_game = Game(game.get_fen())
new_game.apply_move(move)
eval_score, _ = minimax(new_game, depth - 1, alpha, beta, True)
if eval_score < min_eval:
min_eval = eval_score
best_move = move
beta = min(beta, eval_score)
if beta <= alpha:
break
except InvalidMove:
continue
return min_eval, best_move
_, best_move = minimax(game, depth, float('-inf'), float('inf'), True)
return best_move
def agent(obs, config):
"""
Main agent function that will be called by the competition framework.
Args:
obs: Observation from the environment
config: Configuration for the game
Returns:
move: A chess move in UCI format (e.g., 'e2e4')
"""
# Initialize game from FEN if provided
game = Game(obs.get('fen', Game.START_POS))
# Create agent instance
chess_agent = ChessAgent()
# Get the best move with iterative deepening
start_time = time.time()
time_limit = 0.1 # Conservative time limit to ensure we don't timeout
depth = 1
best_move = None
while time.time() - start_time < time_limit and depth <= 4:
try:
move = chess_agent.get_best_move(game, depth)
if move:
best_move = move
depth += 1
except Exception:
break
# Return the best move found or a random legal move
if best_move:
return best_move
else:
moves = list(game.get_moves())
return moves[0] if moves else None