tại sao code của tôi mỗi nước đi rất chậm (30s đến...

Creado el: 12 de mayo de 2026

Respondido usando GPT-5.5 Thinking por Chat01

Pregunta

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

============================================================================

PHẦN 1: ĐỊNH NGHĨA HẰNG SỐ VÀ CẤU TRÚC

============================================================================

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 = '.'

Giá trị đánh giá cho hàm eval

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

============================================================================

PHẦN 2: LỚP BÀNG CỜ

============================================================================

class Board:
"""Lớp quản lý bàn cờ"""

text
def __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()

============================================================================

PHẦN 3: KIỂM TRA THẮNG/THUA

============================================================================

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

============================================================================

PHẦN 4: HÀM ĐÁNH GIÁ TRẠNG THÁI

============================================================================

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

============================================================================

PHẦN 5: THUẬT TOÁN MINIMAX

============================================================================

class MinimaxAI:
"""Lớp cài đặt thuật toán Minimax"""

text
def __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

============================================================================

PHẦN 6: THUẬT TOÁN ALPHA-BETA PRUNING

============================================================================

class AlphaBetaAI:
def init(self, max_depth=3):
self.max_depth = max_depth
self.nodes_explored = 0

text
def 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

============================================================================

PHẦN 7: TRẠNG THÁI TRÒ CHƠI

============================================================================

class Game:
"""Lớp quản lý trò chơi"""

text
def __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")

============================================================================

PHẦN 8: PHÂN TÍCH VÀ KIỂM THỬ

============================================================================

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

============================================================================

PHẦN 9: CHƯƠNG TRÌNH CHÍNH

============================================================================

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()

text
if 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()

Pensamiento

Improving move ordering and evaluation

I'm refining the move ordering strategy by focusing on the center and prioritizing attack and defense. The idea is to prioritize defending against urgent threats by scoring blocks higher. I’m also adding a specific check for winning moves before branching in the Alpha-Beta pruning to improve efficiency and response time.

Thought for 1m 54s

Respuesta

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.

1. Sửa ngay GameRules.get_game_state

Thay 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.


2. Sửa Evaluator.evaluate: đừng gọi get_game_state(board) nữa

Hiện tại mỗi lá của cây tìm kiếm lại gọi:

python
state = 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

3. Dùng Alpha-Beta, đừng dùng Minimax thường khi chơi

Nếu chọn menu:

text
1. 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:

text
2. Chơi với Alpha-Beta

và độ sâu nên là 3 hoặc 4.


4. Alpha-Beta hiện tại vẫn thiếu move ordering tốt

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:

python
moves.sort(key=lambda pos: abs(pos[0] - mid) + abs(pos[1] - mid))

Cách này yếu. Cần ưu tiên:

  1. Nước máy thắng ngay.
  2. Nước chặn người thắng ngay.
  3. Nước tạo chuỗi 3.
  4. Nước chặn chuỗi 3.
  5. Nước gần tâm.

Thay toàn bộ class AlphaBetaAI bằng bản này:

python
class 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

5. Sửa Game.__init__ để dùng giới hạn thời gian

Trong class Game, đoạn này:

python
if ai_type == 'minimax': self.ai = MinimaxAI(max_depth) else: self.ai = AlphaBetaAI(max_depth)

đổi thành:

python
if 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:

python
time_limit=2.5 max_branch=16

Nếu muốn phản hồi gần như tức thì:

python
time_limit=0.7 max_branch=10

6. Sửa phần in độ sâu thực tế

Trong make_computer_move, bạn đang in:

python
print(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:

python
actual_depth = getattr(self.ai, "completed_depth", self.max_depth) print(f"Độ sâu đã hoàn thành: {actual_depth}/{self.max_depth}")

7. Tóm tắt nguyên nhân chậm

Code của bạn chậm vì:

  1. get_game_state(board, last_move) vẫn quét toàn bộ bàn cờ ở hầu hết node.
  2. 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.
  3. Move ordering chỉ ưu tiên gần tâm, không ưu tiên thắng/chặn/thế nguy hiểm.
  4. Nếu dùng Minimax thường thì số node tăng theo cấp số nhân.
  5. Không có giới hạn thời gian, nên AI cố tìm hết cây ở độ sâu đã chọ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ẽ:

  • không quét toàn bàn ở mọi node,
  • ưu tiên nước thắng/chặn trước,
  • cắt alpha-beta hiệu quả hơn,
  • dừng theo time_limit thay vì tìm vô hạn đến hết cây.

Compartir este Q&A