-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest.py
More file actions
617 lines (530 loc) · 28.1 KB
/
test.py
File metadata and controls
617 lines (530 loc) · 28.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
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
import pygame
import random
import sys
import time
import copy # 导入 copy 模块用于深拷贝
# --- Constants and Initialization ---
# Game Settings
ROWS, COLS = 7, 8
TILE_SIZE = 80
SCREEN_WIDTH, SCREEN_HEIGHT = COLS * TILE_SIZE, ROWS * TILE_SIZE + 40
STATUS_BAR_HEIGHT = 40 # Added for clarity
# Colors
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (220, 50, 50)
BLUE = (50, 50, 220)
GREY = (180, 180, 180)
YELLOW = (255, 255, 0)
pygame.init()
pygame.font.init() # Ensure font module is initialized
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("简化斗兽棋")
font = pygame.font.SysFont(None, 36)
# --- Game Classes ---
class Piece:
"""Represents a single game piece with player and strength."""
def __init__(self, player, strength):
self.player = player # 0 = Red, 1 = Blue
self.strength = strength
self.revealed = False
# 为了深拷贝 Piece 对象,我们需要实现 __copy__ 或 __deepcopy__
# 对于这个简单的类,浅拷贝它的属性通常就足够了,但为了严谨,可以实现深拷贝
def __deepcopy__(self, memo):
cls = self.__class__
result = cls.__new__(cls)
memo[id(self)] = result
for k, v in self.__dict__.items():
setattr(result, k, copy.deepcopy(v, memo))
return result
class Board:
"""Manages the game board state and piece placement."""
def __init__(self):
self.board = [[None for _ in range(COLS)] for _ in range(ROWS)]
self._initialize_pieces()
def _initialize_pieces(self):
"""Initializes and shuffles all pieces on the board."""
all_pieces = [Piece(player, strength)
for player in [0, 1]
for strength in range(1, 9)]
random.shuffle(all_pieces)
# Pieces are placed only on the top and bottom two rows
valid_positions = []
for r in range(ROWS):
for c in range(COLS):
if r < 1 or r >= ROWS - 1:
valid_positions.append((r, c))
random.shuffle(valid_positions)
for piece, (r, c) in zip(all_pieces, valid_positions[:len(all_pieces)]):
self.board[r][c] = piece
def get_piece(self, row, col):
"""Returns the piece at the given coordinates."""
if 0 <= row < ROWS and 0 <= col < COLS:
return self.board[row][col]
return None
def set_piece(self, row, col, piece):
"""Sets a piece at the given coordinates."""
if 0 <= row < ROWS and 0 <= col < COLS:
self.board[row][col] = piece
def is_adjacent(self, pos1, pos2):
"""Checks if two positions are adjacent (horizontal or vertical)."""
r1, c1 = pos1
r2, c2 = pos2
return abs(r1 - r2) + abs(c1 - c2) == 1
def try_move(self, start_pos, end_pos):
"""
Attempts to move a piece from start_pos to end_pos, handling captures.
Returns True if the move was successful, False otherwise.
Note: This method modifies the board directly.
"""
sr, sc = start_pos
er, ec = end_pos
piece_moving = self.get_piece(sr, sc)
piece_at_target = self.get_piece(er, ec)
# 1. 移动棋子不存在
if not piece_moving:
return False
# 2. 目标位置不是相邻的
if not self.is_adjacent(start_pos, end_pos):
return False
# 3. 目标位置为空
if piece_at_target is None:
self.set_piece(er, ec, piece_moving)
self.set_piece(sr, sc, None)
return True
# 4. 目标位置有棋子
else:
# 4a. 目标棋子未翻开
if not piece_at_target.revealed:
piece_at_target.revealed = True # 翻开目标棋子
if piece_at_target.player == piece_moving.player:
# 自己的棋子不能移动到有自己未翻开棋子的位置,只翻开
return True # 翻开即结束本回合
else:
# 如果是对手的未翻开棋子,进行捕获判断(强度比较)
if (piece_moving.strength > piece_at_target.strength and not (piece_moving.strength == 8 and piece_at_target.strength == 1)) or \
(piece_moving.strength == 1 and piece_at_target.strength == 8): # 鼠吃象
self.set_piece(er, ec, piece_moving)
self.set_piece(sr, sc, None)
return True
elif piece_moving.strength == piece_at_target.strength: # 同强度
self.set_piece(sr, sc, None)
self.set_piece(er, ec, None)
return True
else: # 移动方被吃
self.set_piece(sr, sc, None)
return True # 完成捕获,结束本回合
# 4b. 目标棋子已翻开
else:
if piece_at_target.player == piece_moving.player:
return False # 不能吃自己的棋子,也不能移动到有自己已翻开棋子的格子
# 4b-ii. 目标棋子是对手的棋子 (已翻开)
else:
if (piece_moving.strength > piece_at_target.strength and not (piece_moving.strength == 8 and piece_at_target.strength == 1)) or \
(piece_moving.strength == 1 and piece_at_target.strength == 8): # 鼠吃象
self.set_piece(er, ec, piece_moving)
self.set_piece(sr, sc, None)
return True
elif piece_moving.strength < piece_at_target.strength or \
(piece_moving.strength == 8 and piece_at_target.strength == 1): # 象不能吃鼠
self.set_piece(sr, sc, None) # 移动方被吃
return True
else: # 同强度
self.set_piece(sr, sc, None)
self.set_piece(er, ec, None) # 两个棋子同强度
return True
return False # 默认返回 False,表示移动未成功
def get_player_pieces(self, player_id):
"""Returns a list of positions for a given player's pieces."""
return [(r, c) for r in range(ROWS) for c in range(COLS)
if self.board[r][c] and self.board[r][c].player == player_id]
### NEW CODE FOR MINIMAX ###
def get_all_possible_moves(self, player_id):
"""
Generates all legal moves and reveal actions for the given player.
Returns a list of tuples: ("reveal", (r, c)) or ("move", (sr, sc), (er, ec))
Each action represents a valid way to end a turn.
"""
possible_actions = []
for r in range(ROWS):
for c in range(COLS):
piece = self.get_piece(r, c)
if piece and piece.player == player_id:
if not piece.revealed:
# 可以翻开自己的未翻开棋子
possible_actions.append(("reveal", (r, c)))
else:
# 如果已翻开,可以尝试移动
for dr, dc in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
nr, nc = r + dr, c + dc
if 0 <= nr < ROWS and 0 <= nc < COLS:
temp_board_for_check = copy.deepcopy(self) # 模拟此动作
# 注意:这里的 try_move 会修改 temp_board_for_check
# 我们只检查 try_move 是否能成功执行并结束回合
if temp_board_for_check.try_move((r, c), (nr, nc)):
possible_actions.append(("move", (r, c), (nr, nc)))
return possible_actions
### END NEW CODE FOR MINIMAX ###
class Player:
"""Base class for players."""
def __init__(self, player_id):
self.player_id = player_id
def handle_event(self, event, board):
"""Handles Pygame events (e.g., mouse clicks for human players)."""
raise NotImplementedError
def take_turn(self, board):
"""Executes a turn for AI players."""
raise NotImplementedError
class HumanPlayer(Player):
"""Controls a player via human input."""
def __init__(self, player_id):
super().__init__(player_id)
self.selected_piece_pos = None
def handle_event(self, event, board):
if event.type == pygame.MOUSEBUTTONDOWN:
x, y = event.pos
row, col = y // TILE_SIZE, x // TILE_SIZE
if not (0 <= row < ROWS and 0 <= col < COLS):
return False # Event not handled (clicked outside board)
piece = board.get_piece(row, col)
if self.selected_piece_pos:
# A piece is already selected, try to move or deselect
sr, sc = self.selected_piece_pos
piece_moving = board.get_piece(sr, sc)
# 确保选择的棋子是自己的
if piece_moving and piece_moving.player == self.player_id:
# 如果点击了相邻位置,尝试移动
if board.is_adjacent(self.selected_piece_pos, (row, col)):
moved = board.try_move(self.selected_piece_pos, (row, col))
if moved:
self.selected_piece_pos = None
return True # Move successful, turn ends
else:
# 移动失败(例如,试图移动到自己已翻开的棋子),取消选择
self.selected_piece_pos = None
# 尝试选择新点击的棋子(如果它属于当前玩家)
if piece and piece.player == self.player_id and piece.revealed:
self.selected_piece_pos = (row, col)
else:
# 目标位置不相邻,取消选择当前棋子,并尝试选择新棋子
self.selected_piece_pos = None
if piece and piece.player == self.player_id:
if not piece.revealed:
piece.revealed = True
return True # Revealed piece, turn ends
else:
self.selected_piece_pos = (row, col) # Select new piece
else: # 如果 selected_piece_pos 指向的不是自己的棋子了(可能被AI吃掉),或者已经清空了
self.selected_piece_pos = None
if piece and piece.player == self.player_id:
if not piece.revealed:
piece.revealed = True
return True # Revealed piece, turn ends
else:
self.selected_piece_pos = (row, col) # Select new piece
elif piece:
# No piece selected, try to select one
if not piece.revealed:
piece.revealed = True
return True # Revealed piece, turn ends
elif piece.player == self.player_id: # 只能选择自己的棋子
self.selected_piece_pos = (row, col) # Select piece
return False # Event not handled or turn not ended
def get_selected_pos(self):
"""Returns the position of the currently selected piece."""
return self.selected_piece_pos
class RandomPlayer(Player):
"""An AI player that makes random valid moves."""
def __init__(self, player_id):
super().__init__(player_id)
def take_turn(self, board):
# RandomPlayer 现在使用 Board.get_all_possible_moves
possible_actions = board.get_all_possible_moves(self.player_id)
random.shuffle(possible_actions)
for action in possible_actions:
# RandomPlayer 在这里不需要深拷贝,因为它是直接在实际 board 上尝试动作
# MinimaxPlayer 才需要深拷贝进行模拟
action_type = action[0]
if action_type == "reveal":
r, c = action[1]
piece = board.get_piece(r, c)
if piece and piece.player == self.player_id and not piece.revealed:
piece.revealed = True
return True # Turn ended
elif action_type == "move":
start_pos, end_pos = action[1], action[2]
piece_to_move = board.get_piece(start_pos[0], start_pos[1])
if piece_to_move and piece_to_move.player == self.player_id:
if board.try_move(start_pos, end_pos):
return True # Turn ended
return False # No valid moves found
### NEW CODE FOR MINIMAX PLAYER ###
class MinimaxPlayer(Player):
def __init__(self, player_id, max_depth=3): # Added max_depth for search
super().__init__(player_id)
self.max_depth = max_depth
self.opponent_id = 1 - player_id
def _evaluate(self, board):
"""
Evaluates the current board state for the AI player.
Positive values are good for self.player_id, negative for opponent.
"""
score = 0
# 1. Piece count difference
self_pieces = board.get_player_pieces(self.player_id)
opponent_pieces = board.get_player_pieces(self.opponent_id)
# 优先判断游戏是否结束,避免分数计算偏差
if not opponent_pieces: # Opponent has no pieces left
return float('inf') # Win state is highly favorable
if not self_pieces: # Self has no pieces left
return float('-inf') # Loss state is highly unfavorable
score += (len(self_pieces) - len(opponent_pieces)) * 100 # Each piece is worth 100 points
# 2. Strength sum difference (prioritize stronger pieces)
# 只计算已翻开棋子的强度
self_strength_sum = sum(board.get_piece(r,c).strength for r,c in self_pieces if board.get_piece(r,c).revealed)
opponent_strength_sum = sum(board.get_piece(r,c).strength for r,c in opponent_pieces if board.get_piece(r,c).revealed)
score += (self_strength_sum - opponent_strength_sum) * 10 # Each strength point worth 10
# 3. Revealed vs Unrevealed pieces
# Incentivize revealing own pieces
for r, c in self_pieces:
if not board.get_piece(r, c).revealed:
score += 5 # Small bonus for having unrevealed pieces to reveal (potential future power)
# Penalize opponent for having unrevealed pieces (unknown threat)
for r, c in opponent_pieces:
if not board.get_piece(r, c).revealed:
score -= 10 # Small penalty for opponent having hidden pieces
# 4. Proximity to opponent's pieces for revealed pieces (threats/opportunities)
for sr, sc in self_pieces:
self_piece = board.get_piece(sr, sc)
if self_piece and self_piece.revealed:
for er, ec in opponent_pieces:
opponent_piece = board.get_piece(er, ec)
if opponent_piece and opponent_piece.revealed:
if board.is_adjacent((sr, sc), (er, ec)):
if self_piece.strength > opponent_piece.strength or \
(self_piece.strength == 1 and opponent_piece.strength == 8):
score += 20 # Strong capture opportunity
elif self_piece.strength < opponent_piece.strength and \
not (self_piece.strength == 8 and opponent_piece.strength == 1):
score -= 15 # Danger of being captured
return score
def _minimax(self, board, depth, maximizing_player_id):
# Base case: max_depth reached or game over
# 优化:在每次评估时,判断游戏是否结束,避免不必要的递归
self_pieces_count = len(board.get_player_pieces(self.player_id))
opponent_pieces_count = len(board.get_player_pieces(self.opponent_id))
if depth == 0 or self_pieces_count == 0 or opponent_pieces_count == 0:
return self._evaluate(board), None # Return score and no move
current_player = maximizing_player_id
is_maximizing_player = (current_player == self.player_id)
best_move = None
# 如果是最大化玩家(AI自己)
if is_maximizing_player:
max_eval = float('-inf')
# 获取当前玩家所有可能的动作
possible_actions = board.get_all_possible_moves(current_player)
random.shuffle(possible_actions) # 打乱顺序,增加AI行为多样性(当多个动作分数相同)
for action in possible_actions:
# 对棋盘进行深拷贝以模拟动作
temp_board = copy.deepcopy(board)
action_performed = False
action_type = action[0]
if action_type == "reveal":
r, c = action[1]
piece = temp_board.get_piece(r, c)
if piece and piece.player == current_player and not piece.revealed:
piece.revealed = True
action_performed = True
elif action_type == "move":
start_pos, end_pos = action[1], action[2]
# 在临时棋盘上尝试移动
# 这里不需要再检查 piece_to_move.revealed,因为 get_all_possible_moves 已经过滤了
action_performed = temp_board.try_move(start_pos, end_pos)
if action_performed: # 只有当动作成功执行后,才进行下一步递归
# 递归调用 Minimax,切换到对手玩家
eval, _ = self._minimax(temp_board, depth - 1, self.opponent_id)
if eval > max_eval:
max_eval = eval
best_move = action
return max_eval, best_move
# 如果是最小化玩家(对手)
else:
min_eval = float('inf')
# 获取对手所有可能的动作
possible_actions = board.get_all_possible_moves(current_player)
random.shuffle(possible_actions) # 打乱顺序
for action in possible_actions:
temp_board = copy.deepcopy(board)
action_performed = False
action_type = action[0]
if action_type == "reveal":
r, c = action[1]
piece = temp_board.get_piece(r, c)
if piece and piece.player == current_player and not piece.revealed:
piece.revealed = True
action_performed = True
elif action_type == "move":
start_pos, end_pos = action[1], action[2]
action_performed = temp_board.try_move(start_pos, end_pos)
if action_performed:
# 递归调用 Minimax,切换回 AI 玩家
eval, _ = self._minimax(temp_board, depth - 1, self.player_id)
if eval < min_eval:
min_eval = eval
best_move = action
return min_eval, best_move
def take_turn(self, board):
print(f"Minimax Player {self.player_id} thinking...")
start_time = time.time()
# 运行 Minimax 搜索
value, best_action = self._minimax(board, self.max_depth, self.player_id)
end_time = time.time()
print(f"Minimax found best action: {best_action} with value {value} in {end_time - start_time:.2f} seconds")
if best_action:
action_type = best_action[0]
if action_type == "reveal":
r, c = best_action[1]
piece = board.get_piece(r, c)
# 再次检查以确保棋子仍然存在且未被翻开(尽管在 Minimax 模拟中应该是这样)
if piece and piece.player == self.player_id and not piece.revealed:
piece.revealed = True
return True
elif action_type == "move":
start_pos, end_pos = best_action[1], best_action[2]
piece_to_move = board.get_piece(start_pos[0], start_pos[1])
# 再次检查以确保棋子仍然存在且属于当前玩家
if piece_to_move and piece_to_move.player == self.player_id:
return board.try_move(start_pos, end_pos)
return False # 如果没有找到最佳动作或动作失败,不应该发生
### END NEW CODE FOR MINIMAX PLAYER ###
class Game:
"""Main class to manage the game flow."""
def __init__(self):
self.board_manager = Board()
self.players = {
0: HumanPlayer(0), # Red
### MODIFIED CODE ###
# 1: RandomPlayer(1), # Original RandomPlayer
# 1: HumanPlayer(1) # For two human players
1: MinimaxPlayer(1, max_depth=3) # Blue AI with Minimax. Adjust max_depth for difficulty.
# 尝试不同的深度,例如 max_depth=2 会更快但可能没那么智能
}
self.current_player_id = 0
self.running = True
self._last_human_move_time = 0 # Track when human player last made a move
self.AI_DELAY_SECONDS = 0.5 # Reduced delay for Minimax as it might take longer
def _draw_board(self):
"""Draws the game board and pieces."""
screen.fill(WHITE)
for i in range(ROWS):
for j in range(COLS):
rect = pygame.Rect(j * TILE_SIZE, i * TILE_SIZE, TILE_SIZE, TILE_SIZE)
pygame.draw.rect(screen, BLACK, rect, 1) # Cell border
piece = self.board_manager.get_piece(i, j)
if piece:
if piece.revealed:
color = RED if piece.player == 0 else BLUE
pygame.draw.circle(screen, color, rect.center, TILE_SIZE // 3)
text_color = WHITE # Always white text on colored circle for better contrast
text = font.render(str(piece.strength), True, text_color)
text_rect = text.get_rect(center=rect.center)
screen.blit(text, text_rect)
else:
pygame.draw.rect(screen, GREY, rect.inflate(-10, -10)) # Unrevealed piece block
# Highlight selected piece for human player
human_player = self.players[0] # Human player is always player 0 in this setup
if isinstance(human_player, HumanPlayer) and human_player.get_selected_pos():
i, j = human_player.get_selected_pos()
rect = pygame.Rect(j * TILE_SIZE, i * TILE_SIZE, TILE_SIZE, TILE_SIZE)
pygame.draw.rect(screen, YELLOW, rect, 3)
# Draw status bar
status_bar_rect = pygame.Rect(0, SCREEN_HEIGHT - STATUS_BAR_HEIGHT, SCREEN_WIDTH, STATUS_BAR_HEIGHT)
pygame.draw.rect(screen, BLACK, status_bar_rect) # Background for status bar
player_name = "Red" if self.current_player_id == 0 else "Blue"
text_color = RED if self.current_player_id == 0 else BLUE
status_text = f"Current Turn: {player_name}"
# If it's AI's turn and waiting for delay
if self.current_player_id == 1 and (time.time() - self._last_human_move_time) < self.AI_DELAY_SECONDS:
status_text += " (AI thinking...)" # Or any other message you want to display
text_surface = font.render(status_text, True, text_color)
screen.blit(text_surface, (10, SCREEN_HEIGHT - STATUS_BAR_HEIGHT + 5))
def _check_game_over(self):
"""Checks for win/loss conditions and terminates the game if met."""
red_pieces = self.board_manager.get_player_pieces(0)
blue_pieces = self.board_manager.get_player_pieces(1)
if not red_pieces:
self._game_over("Blue wins!")
return True
elif not blue_pieces:
self._game_over("Red wins!")
return True
### MODIFIED CODE ###
# 更精确的和棋判断:只剩一颗棋子且无法互相捕获或不相邻
elif len(red_pieces) == 1 and len(blue_pieces) == 1:
rr, rc = red_pieces[0]
br, bc = blue_pieces[0]
red_piece = self.board_manager.get_piece(rr, rc)
blue_piece = self.board_manager.get_piece(br, bc)
# 只有当两个棋子都已翻开时,才能判断是否能互相捕获
if red_piece.revealed and blue_piece.revealed:
can_red_attack = (red_piece.strength > blue_piece.strength) or \
(red_piece.strength == 1 and blue_piece.strength == 8)
can_blue_attack = (blue_piece.strength > red_piece.strength) or \
(blue_piece.strength == 1 and red_piece.strength == 8)
# 如果它们相邻,且双方都无法捕获对方,则为和棋
if self.board_manager.is_adjacent((rr, rc), (br, bc)):
if not can_red_attack and not can_blue_attack:
self._game_over("Draw! (Stalemate - last pieces cannot capture each other)")
return True
else: # 如果不相邻,也无法捕获,则为和棋
self._game_over("Draw! (Last pieces not adjacent)")
return True
# 如果有一方或双方未翻开,游戏继续 (因为信息不完全,未来可能仍有变化)
return False # 游戏未结束
def _game_over(self, message):
"""Displays game over message and exits."""
print(message)
self.running = False # Stop the main game loop
# Optional: Keep the window open for a few seconds before closing
pygame.time.wait(3000)
def run(self):
"""Main game loop."""
clock = pygame.time.Clock()
while self.running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.running = False
else:
current_player_obj = self.players[self.current_player_id]
if isinstance(current_player_obj, HumanPlayer):
if current_player_obj.handle_event(event, self.board_manager):
# Only switch turn if the human player successfully made a move/reveal
# 立即检查游戏是否结束,如果是则停止循环
if self._check_game_over():
self.running = False
else:
self.current_player_id = 1 - self.current_player_id # Switch turn
self._last_human_move_time = time.time() # Record the time of human's last move
# AI Player's turn logic
current_player_obj = self.players[self.current_player_id]
### MODIFIED CODE ###
if isinstance(current_player_obj, (RandomPlayer, MinimaxPlayer)): # 现在也包含 MinimaxPlayer
# 检查是否满足 AI 延迟
if (time.time() - self._last_human_move_time) >= self.AI_DELAY_SECONDS:
# 让 AI 执行回合
if current_player_obj.take_turn(self.board_manager):
# 仅在 AI 成功执行动作后切换回合
if self._check_game_over(): # 立即检查游戏是否结束
self.running = False
else:
self.current_player_id = 1 - self.current_player_id # 切换回合
self._draw_board()
pygame.display.flip()
# _check_game_over 现在在玩家行动后立即调用,所以主循环中不需要重复调用
clock.tick(30)
pygame.quit()
sys.exit()
# --- Run the Game ---
if __name__ == "__main__":
game = Game()
game.run()