tại sao code của tôi mỗi nước đi rất chậm (30s đến...
Créé le : 12 mai 2026
Répondu en utilisant GPT-5.5 Thinking par Chat01
Créé le : 12 mai 2026
Répondu en utilisant GPT-5.5 Thinking par Chat01
tại sao code của tôi mỗi nước đi rất chậm (30s đến 1 phút 1 nước đi) trong khi các phần mềm khác phản hồi cực nhanh, dường như vừa ấn đã phản hồi, hướng dẫn cụ thể cách sửa code:
"""
CHƯƠNG TRÌNH CHƠI CỜ CARO - MINIMAX VÀ ALPHA-BETA PRUNING
Sinh viên: [Họ tên]
Mục tiêu: Xây dựng AI chơi Caro với Minimax và Alpha-Beta
"""
import time
import sys
from typing import List, Tuple, Dict, Optional
from copy import deepcopy
from enum import Enum
class Player(Enum):
"""Định nghĩa người chơi"""
EMPTY = 0
HUMAN = 1 # X
COMPUTER = -1 # O
class GameState(Enum):
"""Trạng thái trò chơi"""
ONGOING = 0
HUMAN_WIN = 1
COMPUTER_WIN = 2
DRAW = 3
BOARD_SIZE = 9
WIN_LENGTH = 4
HUMAN_SYMBOL = 'X'
COMPUTER_SYMBOL = 'O'
EMPTY_SYMBOL = '.'
INF = float('inf')
COMPUTER_WIN_VALUE = 10000
HUMAN_WIN_VALUE = -10000
COMPUTER_3_VALUE = 500
HUMAN_3_VALUE = -500
COMPUTER_2_VALUE = 50
HUMAN_2_VALUE = -50
class Board:
"""Lớp quản lý bàn cờ"""
textdef __init__(self, size=BOARD_SIZE): self.size = size self.grid = [[Player.EMPTY for _ in range(size)] for _ in range(size)] self.move_count = 0 def copy(self): """Tạo bản sao của bàn cờ""" new_board = Board(self.size) new_board.grid = deepcopy(self.grid) new_board.move_count = self.move_count return new_board def is_valid_move(self, row: int, col: int) -> bool: """Kiểm tra nước đi hợp lệ""" if row < 0 or row >= self.size or col < 0 or col >= self.size: return False return self.grid[row][col] == Player.EMPTY def make_move(self, row: int, col: int, player: Player) -> bool: """Thực hiện nước đi""" if not self.is_valid_move(row, col): return False self.grid[row][col] = player self.move_count += 1 return True def undo_move(self, row: int, col: int): """Hoàn tác nước đi""" self.grid[row][col] = Player.EMPTY self.move_count -= 1 def get_legal_moves(self, prioritize_center=True) -> List[Tuple[int, int]]: """Sinh danh sách nước đi hợp lệ với phạm vi thu hẹp""" if self.move_count == 0: mid = self.size // 2 return [(mid, mid)] candidates = set() range_size = 1 # TỐI ƯU 1: Giảm từ 3 xuống 1 để cắt giảm số nhánh thừa for i in range(self.size): for j in range(self.size): if self.grid[i][j] != Player.EMPTY: # Chỉ lấy các ô lân cận trực tiếp for di in range(-range_size, range_size + 1): for dj in range(-range_size, range_size + 1): ni, nj = i + di, j + dj if 0 <= ni < self.size and 0 <= nj < self.size: if self.grid[ni][nj] == Player.EMPTY: candidates.add((ni, nj)) # Nếu không có ứng viên (trường hợp hiếm), lấy toàn bộ ô trống if not candidates: candidates = {(i, j) for i in range(self.size) for j in range(self.size) if self.grid[i][j] == Player.EMPTY} moves = list(candidates) # TỐI ƯU 2: Move Ordering cơ bản (Ưu tiên các nước đi gần tâm hơn) if prioritize_center: mid = self.size // 2 # Dùng Manhattan distance thay vì bình phương để tính nhanh hơn moves.sort(key=lambda pos: abs(pos[0] - mid) + abs(pos[1] - mid)) return moves def display(self): """Hiển thị bàn cờ""" print("\n ", end="") for j in range(self.size): print(f"{j:2}", end="") print() for i in range(self.size): print(f"{i:2}", end="") for j in range(self.size): cell = self.grid[i][j] if cell == Player.HUMAN: print(f" {HUMAN_SYMBOL}", end="") elif cell == Player.COMPUTER: print(f" {COMPUTER_SYMBOL}", end="") else: print(f" {EMPTY_SYMBOL}", end="") print() print()
class GameRules:
"""Lớp xử lý luật chơi"""
text@staticmethod def count_direction(board: Board, row: int, col: int, player: Player, dr: int, dc: int) -> int: """Đếm số quân liên tiếp theo một hướng""" count = 0 r, c = row + dr, col + dc while 0 <= r < board.size and 0 <= c < board.size: if board.grid[r][c] == player: count += 1 r += dr c += dc else: break return count @staticmethod def check_win(board: Board, row: int, col: int, player: Player) -> bool: """Kiểm tra xem nước đi vừa rồi có tạo thành 4 quân liên tiếp""" directions = [ (0, 1), # Ngang (1, 0), # Dọc (1, 1), # Chéo / (1, -1) # Chéo \ ] for dr, dc in directions: # Đếm quân cùng loại ở hai bên left_count = GameRules.count_direction(board, row, col, player, -dr, -dc) right_count = GameRules.count_direction(board, row, col, player, dr, dc) total = left_count + right_count + 1 if total >= WIN_LENGTH: return True return False @staticmethod def get_game_state(board: Board, last_move: Optional[Tuple[int, int]] = None) -> GameState: """Xác định trạng thái trò chơi""" # Kiểm tra xem nước đi cuối cùng có tạo thành 4 quân if last_move: row, col = last_move if board.grid[row][col] == Player.COMPUTER: if GameRules.check_win(board, row, col, Player.COMPUTER): return GameState.COMPUTER_WIN elif board.grid[row][col] == Player.HUMAN: if GameRules.check_win(board, row, col, Player.HUMAN): return GameState.HUMAN_WIN # Kiểm tra nước đi bất kỳ để phát hiện thắng for i in range(board.size): for j in range(board.size): if board.grid[i][j] == Player.COMPUTER: if GameRules.check_win(board, i, j, Player.COMPUTER): return GameState.COMPUTER_WIN elif board.grid[i][j] == Player.HUMAN: if GameRules.check_win(board, i, j, Player.HUMAN): return GameState.HUMAN_WIN # Kiểm tra hòa (bàn cờ đầy) if board.move_count == board.size * board.size: return GameState.DRAW return GameState.ONGOING @staticmethod def is_game_over(board: Board) -> bool: """Kiểm tra trò chơi có kết thúc""" state = GameRules.get_game_state(board) return state != GameState.ONGOING
class Evaluator:
"""Lớp đánh giá trạng thái trò chơi có sử dụng Caching"""
text_cache: Dict[tuple, int] = {} # TỐI ƯU 3: Bộ nhớ cache @classmethod def clear_cache(cls): cls._cache.clear() @staticmethod def evaluate(board: Board) -> int: # Tạo key không thể thay đổi (immutable) cho bàn cờ hiện tại board_hash = tuple(tuple(row) for row in board.grid) # Nếu bàn cờ này đã từng được đánh giá, trả về luôn kết quả if board_hash in Evaluator._cache: return Evaluator._cache[board_hash] # Kiểm tra thắng/thua ngay state = GameRules.get_game_state(board) if state == GameState.COMPUTER_WIN: return COMPUTER_WIN_VALUE elif state == GameState.HUMAN_WIN: return HUMAN_WIN_VALUE elif state == GameState.DRAW: return 0 score = 0 directions = [(0, 1), (1, 0), (1, 1), (1, -1)] # Đánh giá dựa trên các chuỗi quân for i in range(board.size): for j in range(board.size): player = board.grid[i][j] if player != Player.EMPTY: for dr, dc in directions: # Tối ưu: Kiểm tra xem ô trước đó theo hướng này có cùng quân không, # nếu có tức là chuỗi này đã được đếm, bỏ qua để tránh đếm trùng. prev_r, prev_c = i - dr, j - dc if 0 <= prev_r < board.size and 0 <= prev_c < board.size and board.grid[prev_r][prev_c] == player: continue count = 1 + Evaluator._count_in_direction(board, i, j, player, dr, dc, True) if count >= WIN_LENGTH: score += (COMPUTER_WIN_VALUE // 2) if player == Player.COMPUTER else (HUMAN_WIN_VALUE // 2) elif count == 3: score += COMPUTER_3_VALUE if player == Player.COMPUTER else HUMAN_3_VALUE elif count == 2: score += COMPUTER_2_VALUE if player == Player.COMPUTER else HUMAN_2_VALUE # Lưu vào cache Evaluator._cache[board_hash] = score return score @staticmethod def _count_in_direction(board: Board, row: int, col: int, player: Player, dr: int, dc: int, one_direction=False) -> int: count = 0 r, c = row + dr, col + dc while 0 <= r < board.size and 0 <= c < board.size and board.grid[r][c] == player: count += 1 r += dr c += dc if not one_direction: r, c = row - dr, col - dc while 0 <= r < board.size and 0 <= c < board.size and board.grid[r][c] == player: count += 1 r -= dr c -= dc return count
class MinimaxAI:
"""Lớp cài đặt thuật toán Minimax"""
textdef __init__(self, max_depth=3): self.max_depth = max_depth self.nodes_explored = 0 def reset_stats(self): """Đặt lại thống kê""" self.nodes_explored = 0 def minimax(self, board: Board, depth: int, is_maximizing: bool, last_move: Optional[Tuple[int, int]] = None) -> int: """ Thuật toán Minimax - is_maximizing=True: lượt máy (MAX) - is_maximizing=False: lượt người (MIN) """ self.nodes_explored += 1 # Điều kiện dừng state = GameRules.get_game_state(board, last_move) if state == GameState.COMPUTER_WIN: return COMPUTER_WIN_VALUE elif state == GameState.HUMAN_WIN: return HUMAN_WIN_VALUE elif state == GameState.DRAW: return 0 elif depth == 0: return Evaluator.evaluate(board) # Lấy danh sách nước đi hợp lệ legal_moves = board.get_legal_moves() if not legal_moves: return Evaluator.evaluate(board) if is_maximizing: # Lượt máy: tìm giá trị lớn nhất max_eval = -INF for row, col in legal_moves: board.make_move(row, col, Player.COMPUTER) eval_score = self.minimax(board, depth - 1, False, (row, col)) board.undo_move(row, col) max_eval = max(max_eval, eval_score) return max_eval else: # Lượt người: tìm giá trị nhỏ nhất min_eval = INF for row, col in legal_moves: board.make_move(row, col, Player.HUMAN) eval_score = self.minimax(board, depth - 1, True, (row, col)) board.undo_move(row, col) min_eval = min(min_eval, eval_score) return min_eval def find_best_move(self, board: Board) -> Tuple[Tuple[int, int], int]: """ Tìm nước đi tốt nhất sử dụng Minimax Trả về: (nước đi tốt nhất, giá trị đánh giá) """ self.reset_stats() legal_moves = board.get_legal_moves() if not legal_moves: return None, 0 best_move = legal_moves[0] best_value = -INF for row, col in legal_moves: board.make_move(row, col, Player.COMPUTER) eval_score = self.minimax(board, self.max_depth - 1, False, (row, col)) board.undo_move(row, col) if eval_score > best_value: best_value = eval_score best_move = (row, col) return best_move, best_value
class AlphaBetaAI:
def init(self, max_depth=3):
self.max_depth = max_depth
self.nodes_explored = 0
textdef reset_stats(self): self.nodes_explored = 0 Evaluator.clear_cache() # Xóa cache sau mỗi nước đi để giải phóng RAM # TỐI ƯU 4: Bổ sung tham số last_move def alpha_beta(self, board: Board, depth: int, alpha: float, beta: float, is_maximizing: bool, last_move: Optional[Tuple[int, int]] = None) -> int: self.nodes_explored += 1 # Truyền last_move vào hàm kiểm tra trạng thái state = GameRules.get_game_state(board, last_move) if state == GameState.COMPUTER_WIN: return COMPUTER_WIN_VALUE elif state == GameState.HUMAN_WIN: return HUMAN_WIN_VALUE elif state == GameState.DRAW: return 0 elif depth == 0: return Evaluator.evaluate(board) legal_moves = board.get_legal_moves() if not legal_moves: return Evaluator.evaluate(board) if is_maximizing: max_eval = -INF for row, col in legal_moves: board.make_move(row, col, Player.COMPUTER) # Kế thừa last_move cho đệ quy eval_score = self.alpha_beta(board, depth - 1, alpha, beta, False, (row, col)) board.undo_move(row, col) max_eval = max(max_eval, eval_score) alpha = max(alpha, eval_score) if beta <= alpha: break return max_eval else: min_eval = INF for row, col in legal_moves: board.make_move(row, col, Player.HUMAN) # Kế thừa last_move cho đệ quy eval_score = self.alpha_beta(board, depth - 1, alpha, beta, True, (row, col)) board.undo_move(row, col) min_eval = min(min_eval, eval_score) beta = min(beta, eval_score) if beta <= alpha: break return min_eval def find_best_move(self, board: Board) -> Tuple[Tuple[int, int], int]: self.reset_stats() legal_moves = board.get_legal_moves() if not legal_moves: return None, 0 best_move = legal_moves[0] best_value = -INF for row, col in legal_moves: board.make_move(row, col, Player.COMPUTER) eval_score = self.alpha_beta(board, self.max_depth - 1, -INF, INF, False, (row, col)) board.undo_move(row, col) if eval_score > best_value: best_value = eval_score best_move = (row, col) return best_move, best_value
class Game:
"""Lớp quản lý trò chơi"""
textdef __init__(self, ai_type='minimax', max_depth=3): """ ai_type: 'minimax' hoặc 'alphabeta' max_depth: độ sâu tìm kiếm """ self.board = Board(BOARD_SIZE) self.ai_type = ai_type self.max_depth = max_depth if ai_type == 'minimax': self.ai = MinimaxAI(max_depth) else: self.ai = AlphaBetaAI(max_depth) self.move_history = [] self.ai_stats = [] def get_human_move(self) -> Optional[Tuple[int, int]]: """Lấy nước đi từ người chơi""" while True: try: move = input(f"Nhập nước đi (row col) hoặc 'q' thoát: ").strip() if move.lower() == 'q': return None parts = move.split() if len(parts) != 2: print("Lỗi: Nhập đúng định dạng 'row col'") continue row, col = int(parts[0]), int(parts[1]) if not self.board.is_valid_move(row, col): print("Lỗi: Ô không hợp lệ hoặc đã có quân") continue return (row, col) except ValueError: print("Lỗi: Nhập số nguyên") def make_computer_move(self) -> Tuple[int, int]: """Máy tính chọn nước đi""" start_time = time.time() best_move, eval_value = self.ai.find_best_move(self.board) elapsed_time = time.time() - start_time # Ghi nhận thống kê stats = { 'move': best_move, 'eval_value': eval_value, 'depth': self.max_depth, 'nodes_explored': self.ai.nodes_explored, 'time': elapsed_time } self.ai_stats.append(stats) print(f"\n--- NƯỚC ĐI CỦA MÁY ---") print(f"Nước đi: ({best_move[0]}, {best_move[1]})") print(f"Giá trị đánh giá: {eval_value}") print(f"Độ sâu tìm kiếm: {self.max_depth}") print(f"Trạng thái xét: {self.ai.nodes_explored}") print(f"Thời gian: {elapsed_time:.4f}s") print() return best_move def play(self): """Vòng lặp chính của trò chơi""" print("\n" + "="*50) print("CHƠI CỜ CARO - AI vs NGƯỜI") print(f"Kích thước bàn: {BOARD_SIZE}x{BOARD_SIZE}") print(f"AI: {self.ai_type.upper()}, Độ sâu: {self.max_depth}") print(f"X: Người, O: Máy, .: Trống") print("="*50) while True: self.board.display() state = GameRules.get_game_state(self.board) if state != GameState.ONGOING: self._print_game_result(state) break # Lượt người print("--- NƯỚC ĐI CỦA NGƯỜI ---") human_move = self.get_human_move() if human_move is None: print("Kết thúc trò chơi.") break self.board.make_move(human_move[0], human_move[1], Player.HUMAN) self.move_history.append(('human', human_move)) # Kiểm tra người thắng state = GameRules.get_game_state(self.board, human_move) if state != GameState.ONGOING: self.board.display() self._print_game_result(state) break # Lượt máy computer_move = self.make_computer_move() self.board.make_move(computer_move[0], computer_move[1], Player.COMPUTER) self.move_history.append(('computer', computer_move)) # Kiểm tra máy thắng state = GameRules.get_game_state(self.board, computer_move) if state != GameState.ONGOING: self.board.display() self._print_game_result(state) break self._print_statistics() def _print_game_result(self, state: GameState): """In kết quả trò chơi""" print("\n" + "="*50) if state == GameState.HUMAN_WIN: print("🎉 NGƯỜI CHƠI THẮNG!") elif state == GameState.COMPUTER_WIN: print("🤖 MÁY TÍNH THẮNG!") elif state == GameState.DRAW: print("🤝 HÒA!") print("="*50 + "\n") def _print_statistics(self): """In thống kê của trò chơi""" print("\n" + "="*50) print("THỐNG KÊ TRẠNG THÁI") print("="*50) print(f"{'Nước':<6} {'Tọa độ':<12} {'Giá trị':<10} {'Trạng thái':<10} {'Thời gian':<10}") print("-"*50) for i, stat in enumerate(self.ai_stats, 1): print(f"{i:<6} {str(stat['move']):<12} {stat['eval_value']:<10} " f"{stat['nodes_explored']:<10} {stat['time']:<10.4f}") print("="*50 + "\n")
class Analysis:
"""Lớp phân tích hiệu quả thuật toán"""
text@staticmethod def compare_algorithms(board: Board, depth=3): """So sánh Minimax và Alpha-Beta trên cùng trạng thái""" print("\n" + "="*70) print("SO SÁNH MINIMAX VÀ ALPHA-BETA") print("="*70) # Chạy Minimax print("\n[1] Chạy Minimax...") minimax_ai = MinimaxAI(depth) start_time = time.time() minimax_move, minimax_value = minimax_ai.find_best_move(board) minimax_time = time.time() - start_time # Chạy Alpha-Beta print("[2] Chạy Alpha-Beta...") alphabeta_ai = AlphaBetaAI(depth) start_time = time.time() alphabeta_move, alphabeta_value = alphabeta_ai.find_best_move(board) alphabeta_time = time.time() - start_time # Hiển thị kết quả print("\nKẾT QUẢ SO SÁNH:") print("-"*70) print(f"{'Tiêu chí':<30} {'Minimax':<20} {'Alpha-Beta':<20}") print("-"*70) print(f"{'Nước đi tốt nhất':<30} {str(minimax_move):<20} {str(alphabeta_move):<20}") print(f"{'Giá trị đánh giá':<30} {minimax_value:<20} {alphabeta_value:<20}") print(f"{'Trạng thái xét':<30} {minimax_ai.nodes_explored:<20} {alphabeta_ai.nodes_explored:<20}") print(f"{'Thời gian (s)':<30} {minimax_time:<20.6f} {alphabeta_time:<20.6f}") reduction = ((minimax_ai.nodes_explored - alphabeta_ai.nodes_explored) / minimax_ai.nodes_explored * 100) if minimax_ai.nodes_explored > 0 else 0 print(f"{'Giảm % trạng thái':<30} {'-':<20} {reduction:<20.2f}%") print("-"*70) same_move = "✓" if minimax_move == alphabeta_move else "✗" print(f"\nCùng chọn nước đi: {same_move}") print("="*70 + "\n") return { 'minimax': { 'move': minimax_move, 'value': minimax_value, 'nodes': minimax_ai.nodes_explored, 'time': minimax_time }, 'alphabeta': { 'move': alphabeta_move, 'value': alphabeta_value, 'nodes': alphabeta_ai.nodes_explored, 'time': alphabeta_time } } @staticmethod def test_cases(): """Tạo các trạng thái kiểm thử""" cases = [] # TC1: Trạng thái đầu ván board1 = Board(BOARD_SIZE) cases.append(("Trạng thái đầu ván", board1)) # TC2: Trạng thái giữa ván board2 = Board(BOARD_SIZE) board2.make_move(4, 4, Player.COMPUTER) board2.make_move(4, 5, Player.HUMAN) board2.make_move(5, 4, Player.COMPUTER) board2.make_move(5, 5, Player.HUMAN) cases.append(("Trạng thái giữa ván", board2.copy())) # TC3: Máy có thể thắng ngay board3 = Board(BOARD_SIZE) board3.make_move(4, 4, Player.COMPUTER) board3.make_move(4, 6, Player.HUMAN) board3.make_move(4, 5, Player.COMPUTER) board3.make_move(5, 5, Player.HUMAN) board3.make_move(5, 4, Player.COMPUTER) board3.make_move(6, 6, Player.HUMAN) # Máy có thể thắng ở (3, 4) cases.append(("Máy có thể thắng ngay", board3.copy())) # TC4: Người sắp thắng, cần chặn board4 = Board(BOARD_SIZE) board4.make_move(4, 4, Player.HUMAN) board4.make_move(5, 5, Player.COMPUTER) board4.make_move(4, 5, Player.HUMAN) board4.make_move(6, 6, Player.COMPUTER) board4.make_move(4, 6, Player.HUMAN) board4.make_move(3, 3, Player.COMPUTER) # Người sắp thắng ở (4, 7) cases.append(("Người sắp thắng, cần chặn", board4.copy())) # TC5: Cả hai có cơ hội tấn công board5 = Board(BOARD_SIZE) board5.make_move(4, 4, Player.COMPUTER) board5.make_move(4, 6, Player.HUMAN) board5.make_move(4, 5, Player.COMPUTER) board5.make_move(4, 7, Player.HUMAN) board5.make_move(5, 5, Player.COMPUTER) board5.make_move(3, 3, Player.HUMAN) cases.append(("Cả hai có cơ hội tấn công", board5.copy())) return cases
def print_menu():
"""In menu chính"""
print("\n" + "="*50)
print("MENU CHÍNH - CỜ CARO")
print("="*50)
print("1. Chơi với Minimax")
print("2. Chơi với Alpha-Beta")
print("3. So sánh thuật toán")
print("4. Kiểm thử nhiều trạng thái")
print("5. Thoát")
print("="*50)
def main():
"""Hàm chính"""
while True:
print_menu()
choice = input("Chọn (1-5): ").strip()
textif choice == '1': depth = int(input("Nhập độ sâu tìm kiếm (2-4): ") or "3") game = Game('minimax', depth) game.play() elif choice == '2': depth = int(input("Nhập độ sâu tìm kiếm (2-4): ") or "3") game = Game('alphabeta', depth) game.play() elif choice == '3': depth = int(input("Nhập độ sâu tìm kiếm (2-4): ") or "3") analysis = Analysis() test_cases = analysis.test_cases() for case_name, board in test_cases: print(f"\nKiểm thử: {case_name}") board.display() analysis.compare_algorithms(board, depth) elif choice == '4': analysis = Analysis() test_cases = analysis.test_cases() print("\n" + "="*70) print("KIỂM THỬ NHIỀU TRẠNG THÁI - PHÂN TÍCH CHI TIẾT") print("="*70) for i, (case_name, board) in enumerate(test_cases, 1): print(f"\n[{i}] {case_name}") board.display() analysis.compare_algorithms(board, 3) elif choice == '5': print("Tạm biệt!") break else: print("Lựa chọn không hợp lệ!")
if name == "main":
main()
Nguyên nhân lớn nhất nằm ở get_game_state(): bạn có truyền last_move, nhưng sau khi kiểm tra nước cuối mà chưa thắng, hàm vẫn quét toàn bộ bàn cờ.
Trong minimax/alpha-beta, hàm này bị gọi ở mọi node. Với vài chục nghìn đến vài trăm nghìn node, việc quét toàn bàn + check 4 hướng liên tục làm mỗi nước đi chậm 30–60 giây.
GameRules.get_game_stateThay toàn bộ hàm get_game_state bằng bản này:
python@staticmethod def get_game_state(board: Board, last_move: Optional[Tuple[int, int]] = None) -> GameState: """ Nếu có last_move: chỉ kiểm tra quân vừa đánh. Trong minimax/alpha-beta, chỉ nước vừa đánh mới có thể tạo thắng. """ if last_move is not None: row, col = last_move player = board.grid[row][col] if player == Player.COMPUTER: if GameRules.check_win(board, row, col, Player.COMPUTER): return GameState.COMPUTER_WIN elif player == Player.HUMAN: if GameRules.check_win(board, row, col, Player.HUMAN): return GameState.HUMAN_WIN if board.move_count == board.size * board.size: return GameState.DRAW return GameState.ONGOING # Chỉ dùng quét toàn bàn khi không biết nước cuối, # ví dụ lúc khởi tạo hoặc kiểm tra ngoài vòng search. for i in range(board.size): for j in range(board.size): player = board.grid[i][j] if player == Player.COMPUTER: if GameRules.check_win(board, i, j, Player.COMPUTER): return GameState.COMPUTER_WIN elif player == Player.HUMAN: if GameRules.check_win(board, i, j, Player.HUMAN): return GameState.HUMAN_WIN if board.move_count == board.size * board.size: return GameState.DRAW return GameState.ONGOING
Đây là sửa quan trọng nhất.
Evaluator.evaluate: đừng gọi get_game_state(board) nữaHiện tại mỗi lá của cây tìm kiếm lại gọi:
pythonstate = GameRules.get_game_state(board)
Dòng này không có last_move, nên lại quét toàn bàn. Trong alpha-beta, terminal state đã được kiểm tra trước đó rồi, nên evaluator không cần gọi lại.
Thay Evaluator.evaluate bằng bản này:
python@staticmethod def evaluate(board: Board) -> int: # Hash phẳng nhanh hơn tuple(tuple(row)) với Enum object board_hash = tuple(cell.value for row in board.grid for cell in row) cached = Evaluator._cache.get(board_hash) if cached is not None: return cached score = 0 directions = [(0, 1), (1, 0), (1, 1), (1, -1)] for i in range(board.size): for j in range(board.size): player = board.grid[i][j] if player == Player.EMPTY: continue for dr, dc in directions: prev_r, prev_c = i - dr, j - dc # Tránh đếm trùng cùng một chuỗi if ( 0 <= prev_r < board.size and 0 <= prev_c < board.size and board.grid[prev_r][prev_c] == player ): continue count = 1 + Evaluator._count_in_direction( board, i, j, player, dr, dc, True ) if count >= WIN_LENGTH: result = COMPUTER_WIN_VALUE if player == Player.COMPUTER else HUMAN_WIN_VALUE Evaluator._cache[board_hash] = result return result elif count == 3: score += COMPUTER_3_VALUE if player == Player.COMPUTER else HUMAN_3_VALUE elif count == 2: score += COMPUTER_2_VALUE if player == Player.COMPUTER else HUMAN_2_VALUE Evaluator._cache[board_hash] = score return score
Nếu chọn menu:
text1. Chơi với Minimax
thì chậm là bình thường. Minimax thường duyệt gần như toàn bộ cây.
Khi chơi thật, nên dùng:
text2. Chơi với Alpha-Beta
và độ sâu nên là 3 hoặc 4.
Alpha-beta chỉ nhanh nếu xét nước tốt trước. Code của bạn đang sort theo gần tâm:
pythonmoves.sort(key=lambda pos: abs(pos[0] - mid) + abs(pos[1] - mid))
Cách này yếu. Cần ưu tiên:
Thay toàn bộ class AlphaBetaAI bằng bản này:
pythonclass SearchTimeout(Exception): pass class AlphaBetaAI: def __init__(self, max_depth=4, time_limit=1.5, max_branch=14): self.max_depth = max_depth self.time_limit = time_limit self.max_branch = max_branch self.nodes_explored = 0 self.start_time = 0 self.completed_depth = 0 def reset_stats(self): self.nodes_explored = 0 self.completed_depth = 0 # Không nên xóa cache mỗi nước đi. # Chỉ xóa nếu quá lớn. if len(Evaluator._cache) > 200_000: Evaluator.clear_cache() def _check_time(self): if self.time_limit is None: return # Không check time ở mọi node để tránh overhead if self.nodes_explored % 2048 == 0: if time.time() - self.start_time >= self.time_limit: raise SearchTimeout() def _line_info_after_move(self, board: Board, row: int, col: int, player: Player, dr: int, dc: int): left = GameRules.count_direction(board, row, col, player, -dr, -dc) right = GameRules.count_direction(board, row, col, player, dr, dc) total = left + right + 1 open_ends = 0 r1 = row - (left + 1) * dr c1 = col - (left + 1) * dc if ( 0 <= r1 < board.size and 0 <= c1 < board.size and board.grid[r1][c1] == Player.EMPTY ): open_ends += 1 r2 = row + (right + 1) * dr c2 = col + (right + 1) * dc if ( 0 <= r2 < board.size and 0 <= c2 < board.size and board.grid[r2][c2] == Player.EMPTY ): open_ends += 1 return total, open_ends def _tactical_score(self, board: Board, row: int, col: int, player: Player) -> int: directions = [(0, 1), (1, 0), (1, 1), (1, -1)] score = 0 for dr, dc in directions: total, open_ends = self._line_info_after_move( board, row, col, player, dr, dc ) # Thắng ngay if total >= WIN_LENGTH: return 10_000_000 # Với WIN_LENGTH = 4: # total == 3 là tạo thế rất nguy hiểm if total == WIN_LENGTH - 1 and open_ends > 0: if open_ends == 2: score += 1_000_000 else: score += 200_000 elif total == WIN_LENGTH - 2 and open_ends > 0: if open_ends == 2: score += 20_000 else: score += 5_000 elif total == 1 and open_ends == 2: score += 20 return score def _find_winning_move(self, board: Board, player: Player, moves): directions = [(0, 1), (1, 0), (1, 1), (1, -1)] for row, col in moves: for dr, dc in directions: total, _ = self._line_info_after_move( board, row, col, player, dr, dc ) if total >= WIN_LENGTH: return (row, col) return None def _ordered_moves(self, board: Board, moves, player: Player): opponent = Player.HUMAN if player == Player.COMPUTER else Player.COMPUTER mid = board.size // 2 def score_move(move): row, col = move attack = self._tactical_score(board, row, col, player) defense = self._tactical_score(board, row, col, opponent) center = 100 - (abs(row - mid) + abs(col - mid)) # Defense hơi cao hơn để ưu tiên chặn nước nguy hiểm return attack * 2 + defense * 3 + center return sorted(moves, key=score_move, reverse=True) def alpha_beta(self, board: Board, depth: int, alpha: float, beta: float, is_maximizing: bool, last_move: Optional[Tuple[int, int]] = None) -> int: self.nodes_explored += 1 self._check_time() state = GameRules.get_game_state(board, last_move) if state == GameState.COMPUTER_WIN: return COMPUTER_WIN_VALUE + depth elif state == GameState.HUMAN_WIN: return HUMAN_WIN_VALUE - depth elif state == GameState.DRAW: return 0 elif depth == 0: return Evaluator.evaluate(board) legal_moves = board.get_legal_moves() if not legal_moves: return Evaluator.evaluate(board) current_player = Player.COMPUTER if is_maximizing else Player.HUMAN # Nếu bên đang đi có nước thắng ngay, trả về luôn winning_move = self._find_winning_move(board, current_player, legal_moves) if winning_move is not None: if is_maximizing: return COMPUTER_WIN_VALUE + depth else: return HUMAN_WIN_VALUE - depth legal_moves = self._ordered_moves(board, legal_moves, current_player) # Giới hạn số nhánh sau khi đã sort chiến thuật if len(legal_moves) > self.max_branch: legal_moves = legal_moves[:self.max_branch] if is_maximizing: max_eval = -INF for row, col in legal_moves: board.make_move(row, col, Player.COMPUTER) try: eval_score = self.alpha_beta( board, depth - 1, alpha, beta, False, (row, col) ) finally: board.undo_move(row, col) max_eval = max(max_eval, eval_score) alpha = max(alpha, eval_score) if beta <= alpha: break return max_eval else: min_eval = INF for row, col in legal_moves: board.make_move(row, col, Player.HUMAN) try: eval_score = self.alpha_beta( board, depth - 1, alpha, beta, True, (row, col) ) finally: board.undo_move(row, col) min_eval = min(min_eval, eval_score) beta = min(beta, eval_score) if beta <= alpha: break return min_eval def find_best_move(self, board: Board) -> Tuple[Tuple[int, int], int]: self.reset_stats() self.start_time = time.time() legal_moves = board.get_legal_moves() if not legal_moves: return None, 0 legal_moves = self._ordered_moves(board, legal_moves, Player.COMPUTER) # 1. Máy thắng ngay thì đánh luôn winning_move = self._find_winning_move(board, Player.COMPUTER, legal_moves) if winning_move is not None: return winning_move, COMPUTER_WIN_VALUE # 2. Người sắp thắng thì chặn ngay blocking_move = self._find_winning_move(board, Player.HUMAN, legal_moves) if blocking_move is not None: return blocking_move, 0 best_move = legal_moves[0] best_value = -INF root_moves = legal_moves[:self.max_branch] # Iterative deepening: # tìm depth 1, rồi 2, rồi 3... # nếu hết thời gian thì dùng kết quả tốt nhất đã hoàn thành for depth in range(1, self.max_depth + 1): current_best_move = best_move current_best_value = -INF try: for row, col in root_moves: board.make_move(row, col, Player.COMPUTER) try: eval_score = self.alpha_beta( board, depth - 1, -INF, INF, False, (row, col) ) finally: board.undo_move(row, col) if eval_score > current_best_value: current_best_value = eval_score current_best_move = (row, col) best_move = current_best_move best_value = current_best_value self.completed_depth = depth # Lần sau xét best_move trước để alpha-beta cắt mạnh hơn root_moves = [best_move] + [m for m in root_moves if m != best_move] except SearchTimeout: break return best_move, best_value
Game.__init__ để dùng giới hạn thời gianTrong class Game, đoạn này:
pythonif ai_type == 'minimax': self.ai = MinimaxAI(max_depth) else: self.ai = AlphaBetaAI(max_depth)
đổi thành:
pythonif ai_type == 'minimax': self.ai = MinimaxAI(max_depth) else: self.ai = AlphaBetaAI( max_depth=max_depth, time_limit=1.5, max_branch=14 )
Nếu muốn máy nghĩ kỹ hơn:
pythontime_limit=2.5 max_branch=16
Nếu muốn phản hồi gần như tức thì:
pythontime_limit=0.7 max_branch=10
Trong make_computer_move, bạn đang in:
pythonprint(f"Độ sâu tìm kiếm: {self.max_depth}")
Nhưng với iterative deepening, AI có thể chỉ hoàn thành depth 2 hoặc 3 trước khi hết thời gian. Sửa thành:
pythonactual_depth = getattr(self.ai, "completed_depth", self.max_depth) print(f"Độ sâu đã hoàn thành: {actual_depth}/{self.max_depth}")
Code của bạn chậm vì:
get_game_state(board, last_move) vẫn quét toàn bộ bàn cờ ở hầu hết node.Evaluator.evaluate() lại gọi get_game_state(board) không có last_move, khiến mỗi leaf quét toàn bàn.Sau khi sửa 3 phần chính: get_game_state, evaluate, và AlphaBetaAI, chương trình sẽ phản hồi nhanh hơn rất nhiều, vì AI sẽ:
time_limit thay vì tìm vô hạn đến hết cây.