-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathAI.py
More file actions
214 lines (166 loc) · 8.1 KB
/
AI.py
File metadata and controls
214 lines (166 loc) · 8.1 KB
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
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
# AI.py
from copy import deepcopy
from game import Game
from board import Piece
class MinimaxAI:
def __init__(self, color, max_depth=1): # Reduced default depth for better performance
self.color = color
self.max_depth = max_depth
def choose_move(self, game):
"""Choose the best move using minimax algorithm with alpha-beta pruning."""
# Work on a clone of the board so we never touch the real one
board_copy = deepcopy(game.board)
def minimax(state, depth, alpha, beta, maximizing):
# Base case: reached max depth or no pieces left
if depth == 0 or state.is_terminal():
return self.evaluate(state), None
best_move = None
if maximizing:
max_eval = float('-inf')
for move in self.get_valid_moves_with_pieces(state, self.color):
p_clone, (tr, tc), captured = move
# Skip invalid moves
if not isinstance(p_clone, Piece):
continue
# Create a deep copy of the board to simulate the move
next_state = deepcopy(state)
# Get the copy of the piece from the copied board
from_r, from_c = p_clone.row, p_clone.col
piece_copy = next_state.board[from_r][from_c]
# Make the move on the copied board
next_state.make_move((piece_copy, (tr, tc), captured))
# Evaluate the resulting position
eval_score, _ = minimax(
next_state, depth-1, alpha, beta, False)
# Update the best move if this is better
if eval_score > max_eval:
max_eval = eval_score
best_move = move
# Alpha-beta pruning
alpha = max(alpha, eval_score)
if beta <= alpha:
break
return max_eval, best_move
else:
min_eval = float('inf')
opponent = 'r' if self.color == 'b' else 'b'
for move in self.get_valid_moves_with_pieces(state, opponent):
p_clone, (tr, tc), captured = move
# Skip invalid moves
if not isinstance(p_clone, Piece):
continue
# Create a deep copy of the board to simulate the move
next_state = deepcopy(state)
# Get the copy of the piece from the copied board
from_r, from_c = p_clone.row, p_clone.col
piece_copy = next_state.board[from_r][from_c]
# Make the move on the copied board
next_state.make_move((piece_copy, (tr, tc), captured))
# Evaluate the resulting position
eval_score, _ = minimax(
next_state, depth-1, alpha, beta, True)
# Update the best move if this is better
if eval_score < min_eval:
min_eval = eval_score
best_move = move
# Alpha-beta pruning
beta = min(beta, eval_score)
if beta <= alpha:
break
return min_eval, best_move
# Run minimax on the board copy
_, best_move = minimax(board_copy, self.max_depth,
float('-inf'), float('inf'), True)
# If no valid moves, return None
if best_move is None:
return None
# Convert the best move to the format expected by the UI
piece, (to_r, to_c), jumped = best_move
from_r, from_c = piece.row, piece.col
# Find the corresponding real piece on the actual game board
real_piece = game.board.board[from_r][from_c]
return real_piece, (to_r, to_c), jumped
def get_valid_moves_with_pieces(self, board, color):
"""Get all valid moves for a given color, with the associated pieces."""
moves = []
for piece in board.get_all_pieces(color):
# Get all valid moves for this piece
for (to_r, to_c), captured in self.get_piece_moves(board, piece).items():
moves.append((piece, (to_r, to_c), captured))
return moves
def get_piece_moves(self, board, piece):
"""Get all valid moves for a specific piece."""
moves = {}
# Check normal moves (non-jumps)
directions = self.get_directions(piece)
for dr, dc in directions:
row, col = piece.row + dr, piece.col + dc
if 0 <= row < 8 and 0 <= col < 8 and board.board[row][col] == 0:
moves[(row, col)] = []
# Check for jumps
self.check_jumps(board, piece, piece.row, piece.col, [], moves, set())
return moves
def get_directions(self, piece):
"""Get valid movement directions based on piece type."""
if piece.king:
return [(-1, -1), (-1, 1), (1, -1), (1, 1)]
elif piece.color == 'r': # Red moves down
return [(1, -1), (1, 1)]
else: # Black moves up
return [(-1, -1), (-1, 1)]
def check_jumps(self, board, piece, row, col, captured, moves, visited):
"""Recursively check for jump moves."""
directions = self.get_directions(piece) if not captured else [
(-1, -1), (-1, 1), (1, -1), (1, 1)]
for dr, dc in directions:
mid_row, mid_col = row + dr, col + dc
jump_row, jump_col = row + 2*dr, col + 2*dc
# Check if jump is valid
if (0 <= mid_row < 8 and 0 <= mid_col < 8 and
0 <= jump_row < 8 and 0 <= jump_col < 8 and
board.board[mid_row][mid_col] != 0 and
board.board[mid_row][mid_col].color != piece.color and
board.board[jump_row][jump_col] == 0 and
(mid_row, mid_col) not in captured and
(jump_row, jump_col) not in visited):
# Add this jump to moves
new_captured = captured + [(mid_row, mid_col)]
moves[(jump_row, jump_col)] = new_captured
# Check for additional jumps
visited_with_jump = visited.union({(jump_row, jump_col)})
self.check_jumps(board, piece, jump_row, jump_col,
new_captured, moves, visited_with_jump)
def evaluate(self, state):
"""Evaluate the board position for the AI player."""
# Count material
# Kings are worth more than regular pieces
material_value = 0
position_value = 0
king_value = 1.5
for row in range(8):
for col in range(8):
piece = state.board[row][col]
if piece != 0:
# Material value
piece_value = king_value if piece.king else 1
if piece.color == self.color:
material_value += piece_value
# Positional bonuses
if piece.color == 'b': # Black moves up
# Closer to promotion
position_value += (7 - row) * 0.1
else: # Red moves down
position_value += row * 0.1 # Closer to promotion
# Edge bonus
if col == 0 or col == 7:
position_value += 0.2
else:
material_value -= piece_value
# Same positional considerations for opponent
if piece.color == 'b':
position_value -= (7 - row) * 0.1
else:
position_value -= row * 0.1
if col == 0 or col == 7:
position_value -= 0.2
return material_value + position_value