Phát Triển Game Tic-Tac-Toe (Cờ Caro 3x3) - Hướng dẫn AI cơ bản
· 35 phút để đọc
Chào mừng các bạn đến với bài hướng dẫn thứ hai trong series Game Development với Python! Hôm nay chúng ta sẽ xây dựng một game Tic-Tac-Toe hoàn chỉnh với AI opponent thông minh.
Tic-Tac-Toe (hay còn gọi là Cờ caro 3x3) là game kinh điển mà ai cũng biết, nhưng việc lập trình nó lại mang đến những thách thức thú vị về game theory, AI algorithms, và 2D array manipulation. Chúng ta sẽ không chỉ tạo ra một game đơn giản, mà còn implement Minimax algorithm để tạo ra AI không thể bị đánh bại!
Game Demo và Giới Thiệu
Trước khi bắt đầu code, hãy xem game chúng ta sẽ xây dựng:
🎯 TIC-TAC-TOE - CỜ CARO 3X3 🎯
📋 CHỌN CHE ĐỘ CHƠI:
1. 👥 Người vs Người
2. 🤖 Người vs AI (Dễ)
3. 🧠 Người vs AI (Khó - Minimax)
4. 🎮 AI vs AI (Demo)
Lựa chọn: 3
🎮 NGƯỜI vs AI (MINIMAX) 🎮
Bạn là X, AI là O
1 2 3
A | |
-----------
B | |
-----------
C | |
Lượt của bạn (vd: A1, B2): B2
1 2 3
A | |
-----------
B | X |
-----------
C | |
AI đang suy nghĩ... 🤔
AI chọn: A1
1 2 3
A O | |
-----------
B | X |
-----------
C | |
Lượt của bạn (vd: A1, B2): A3
1 2 3
A O | | X
-----------
B | X |
-----------
C | |
AI đang suy nghĩ... 🤔
AI chọn: C2
1 2 3
A O | | X
-----------
B | X |
-----------
C | O |
🎉 AI THẮNG! 🎉
Sequence thắng: A1-B2-C3 (Đường chéo)
Bạn Sẽ Học Được Gì?
Qua bài hướng dẫn này, bạn sẽ nắm vững:
- ✅ 2D Array manipulation - Làm việc với ma trận 2 chiều
- ✅ Game Board representation - Biểu diễn bàn cờ trong code
- ✅ Win condition detection - Thuật toán phát hiện thắng/thua
- ✅ Minimax Algorithm - AI decision making cơ bản
- ✅ Game Tree traversal - Duyệt cây game states
- ✅ Alpha-Beta Pruning - Tối ưu hóa AI performance
- ✅ Strategy Patterns - Design patterns cho game AI
- ✅ Player vs AI interaction - Human-computer gameplay
Yêu Cầu Trước Khi Bắt Đầu
- Python cơ bản: Lists, functions, classes, loops
- Logic thinking: Hiểu về game rules và strategy
- Python 3.6+ đã cài đặt
- Kiến thức về recursion (sẽ giải thích chi tiết)
- Tinh thần thách thức với AI programming! 🤖
Bước 1: Phân Tích Game - Hiểu Rõ Tic-Tac-Toe
Game Rules - Luật Chơi Cơ Bản
- Bàn cờ: Lưới 3x3 có 9 ô vuông
- Người chơi: 2 người, một dùng X, một dùng O
- Lượt chơi: Luân phiên đánh X và O vào ô trống
- Thắng: Tạo thành 1 hàng 3 ký hiệu giống nhau (ngang/dọc/chéo)
- Hòa: Hết ô trống mà không ai thắng
Game States - Các Trạng Thái Game
Game Flow - Luồng Chơi Chi Tiết
Board Representation - Biểu Diễn Bàn Cờ
Chúng ta sẽ sử dụng 2D list để biểu diễn bàn cờ:
# Board dạng 2D array
board = [
[' ', ' ', ' '], # Hàng A (index 0)
[' ', ' ', ' '], # Hàng B (index 1)
[' ', ' ', ' '] # Hàng C (index 2)
]
# Mapping tọa độ người dùng -> array index
# A1 -> board[0][0], A2 -> board[0][1], A3 -> board[0][2]
# B1 -> board[1][0], B2 -> board[1][1], B3 -> board[1][2]
# C1 -> board[2][0], C2 -> board[2][1], C3 -> board[2][2]
Win Conditions - Điều Kiện Thắng
Bước 2: Thiết Kế Kiến Trúc - Architecture Design
Class Diagram - Sơ Đồ Classes
Module Structure - Cấu Trúc Files
tic_tac_toe/
├── main.py # Entry point
├── config.py # Game configuration
├── game_manager.py # Main game controller
├── board_manager.py # Board operations
├── win_checker.py # Win condition logic
├── ai_player.py # AI algorithms
├── ui_manager.py # User interface
├── game_stats.py # Statistics tracking
├── utils.py # Helper utilities
└── tests/
├── test_board.py # Board tests
├── test_ai.py # AI tests
└── test_win_checker.py # Win logic tests
Bước 3: Implementation - Xây Dựng Game Từng Bước
3.1. Tạo Configuration (config.py)
# config.py
"""
Cấu hình cho Tic-Tac-Toe Game
"""
# Game symbols
PLAYER_X = 'X'
PLAYER_O = 'O'
EMPTY_CELL = ' '
# Game modes
GAME_MODES = {
1: {
'name': 'Người vs Người',
'description': 'Hai người chơi với nhau',
'type': 'pvp'
},
2: {
'name': 'Người vs AI (Dễ)',
'description': 'AI chơi random, phù hợp cho mới bắt đầu',
'type': 'pv_ai_easy'
},
3: {
'name': 'Người vs AI (Khó)',
'description': 'AI sử dụng Minimax, rất khó thắng',
'type': 'pv_ai_hard'
},
4: {
'name': 'AI vs AI Demo',
'description': 'Xem AI chơi với nhau',
'type': 'ai_vs_ai'
}
}
# AI difficulty settings
AI_SETTINGS = {
'easy': {
'algorithm': 'random',
'thinking_time': 1.0,
'mistake_probability': 0.3 # 30% tỷ lệ đánh sai
},
'hard': {
'algorithm': 'minimax',
'thinking_time': 2.0,
'max_depth': 9, # Depth tối đa cho minimax
'use_alpha_beta': True
}
}
# Board display settings
BOARD_DISPLAY = {
'show_coordinates': True,
'use_colors': True,
'animation_speed': 0.5
}
# Input validation
VALID_ROWS = ['A', 'B', 'C']
VALID_COLS = ['1', '2', '3']
# Game messages
MESSAGES = {
'welcome': '🎯 TIC-TAC-TOE - CỜ CARO 3X3 🎯',
'choose_mode': '📋 CHỌN CHẾ ĐỘ CHƠI:',
'player_turn': 'Lượt của {player}',
'ai_thinking': 'AI đang suy nghĩ... 🤔',
'invalid_move': '❌ Nước đi không hợp lệ!',
'game_draw': '🤝 HÒA! Không ai thắng.',
'player_wins': '🎉 {player} THẮNG! 🎉',
'ai_wins': '🤖 AI THẮNG! 🤖',
'play_again': '🔄 Bạn có muốn chơi lại không?'
}
# Statistics file
STATS_FILE = 'data/tic_tac_toe_stats.json'
3.2. Xây Dựng Board Manager (board_manager.py)
# board_manager.py
"""
Quản lý bàn cờ Tic-Tac-Toe
"""
from config import EMPTY_CELL, PLAYER_X, PLAYER_O
import copy
class BoardManager:
"""Lớp quản lý bàn cờ"""
def __init__(self):
self.board = self.create_empty_board()
def create_empty_board(self):
"""
Tạo bàn cờ trống 3x3
Returns:
List[List[str]]: Bàn cờ rỗng
"""
return [[EMPTY_CELL for _ in range(3)] for _ in range(3)]
def display_board(self):
"""Hiển thị bàn cờ với tọa độ"""
print("\n 1 2 3")
for i, row in enumerate(self.board):
row_label = chr(ord('A') + i) # A, B, C
print(f"{row_label} {row[0]} | {row[1]} | {row[2]} ")
if i < 2: # Không vẽ gạch dưới hàng cuối
print(" -----------")
print() # Dòng trống cho dễ nhìn
def is_valid_move(self, row, col):
"""
Kiểm tra nước đi có hợp lệ không
Args:
row (int): Hàng (0-2)
col (int): Cột (0-2)
Returns:
bool: True nếu hợp lệ
"""
# Kiểm tra trong bounds
if not (0 <= row <= 2 and 0 <= col <= 2):
return False
# Kiểm tra ô trống
return self.board[row][col] == EMPTY_CELL
def make_move(self, row, col, player):
"""
Thực hiện nước đi
Args:
row (int): Hàng
col (int): Cột
player (str): Người chơi ('X' hoặc 'O')
Returns:
bool: True nếu đánh được
"""
if self.is_valid_move(row, col):
self.board[row][col] = player
return True
return False
def undo_move(self, row, col):
"""
Hoàn tác nước đi (dùng cho AI minimax)
Args:
row (int): Hàng
col (int): Cột
"""
self.board[row][col] = EMPTY_CELL
def get_available_moves(self):
"""
Lấy danh sách các nước đi có thể
Returns:
List[Tuple[int, int]]: Danh sách (row, col) có thể đánh
"""
moves = []
for row in range(3):
for col in range(3):
if self.board[row][col] == EMPTY_CELL:
moves.append((row, col))
return moves
def is_board_full(self):
"""
Kiểm tra bàn cờ đã đầy chưa
Returns:
bool: True nếu đầy
"""
return len(self.get_available_moves()) == 0
def get_board_copy(self):
"""
Tạo bản copy của board (dùng cho AI)
Returns:
List[List[str]]: Copy của board
"""
return copy.deepcopy(self.board)
def reset_board(self):
"""Reset bàn cờ về trạng thái ban đầu"""
self.board = self.create_empty_board()
def get_board_state_string(self):
"""
Chuyển board thành string để hash (cho memoization)
Returns:
str: Board state dạng string
"""
return ''.join([''.join(row) for row in self.board])
def count_symbols(self):
"""
Đếm số lượng X và O trên board
Returns:
dict: {'X': count, 'O': count, 'empty': count}
"""
counts = {'X': 0, 'O': 0, 'empty': 0}
for row in self.board:
for cell in row:
if cell == PLAYER_X:
counts['X'] += 1
elif cell == PLAYER_O:
counts['O'] += 1
else:
counts['empty'] += 1
return counts
3.3. Win Checker - Phát Hiện Thắng/Thua (win_checker.py)
# win_checker.py
"""
Kiểm tra điều kiện thắng trong Tic-Tac-Toe
"""
from config import EMPTY_CELL
class WinChecker:
"""Lớp kiểm tra điều kiện thắng"""
def __init__(self):
self.winning_line = None # Lưu đường thắng để highlight
def check_winner(self, board):
"""
Kiểm tra xem có ai thắng không
Args:
board (List[List[str]]): Bàn cờ hiện tại
Returns:
str: 'X', 'O', hoặc None nếu chưa có ai thắng
"""
# Kiểm tra các hàng ngang
winner = self.check_rows(board)
if winner:
return winner
# Kiểm tra các cột dọc
winner = self.check_columns(board)
if winner:
return winner
# Kiểm tra các đường chéo
winner = self.check_diagonals(board)
if winner:
return winner
return None
def check_rows(self, board):
"""
Kiểm tra thắng theo hàng ngang
Args:
board (List[List[str]]): Bàn cờ
Returns:
str: Người thắng hoặc None
"""
for i, row in enumerate(board):
if self._is_winning_line(row):
# Lưu thông tin đường thắng
self.winning_line = {
'type': 'row',
'index': i,
'positions': [(i, 0), (i, 1), (i, 2)]
}
return row[0] # Trả về người thắng
return None
def check_columns(self, board):
"""
Kiểm tra thắng theo cột dọc
Args:
board (List[List[str]]): Bàn cờ
Returns:
str: Người thắng hoặc None
"""
for col in range(3):
column = [board[row][col] for row in range(3)]
if self._is_winning_line(column):
# Lưu thông tin đường thắng
self.winning_line = {
'type': 'column',
'index': col,
'positions': [(0, col), (1, col), (2, col)]
}
return column[0]
return None
def check_diagonals(self, board):
"""
Kiểm tra thắng theo đường chéo
Args:
board (List[List[str]]): Bàn cờ
Returns:
str: Người thắng hoặc None
"""
# Đường chéo chính (top-left to bottom-right)
main_diagonal = [board[i][i] for i in range(3)]
if self._is_winning_line(main_diagonal):
self.winning_line = {
'type': 'main_diagonal',
'positions': [(0, 0), (1, 1), (2, 2)]
}
return main_diagonal[0]
# Đường chéo phụ (top-right to bottom-left)
anti_diagonal = [board[i][2-i] for i in range(3)]
if self._is_winning_line(anti_diagonal):
self.winning_line = {
'type': 'anti_diagonal',
'positions': [(0, 2), (1, 1), (2, 0)]
}
return anti_diagonal[0]
return None
def _is_winning_line(self, line):
"""
Kiểm tra 3 ký hiệu có giống nhau và không rỗng
Args:
line (List[str]): Dãy 3 ký hiệu
Returns:
bool: True nếu là đường thắng
"""
return (line[0] == line[1] == line[2] and
line[0] != EMPTY_CELL)
def get_winning_line(self):
"""
Lấy thông tin đường thắng cuối cùng
Returns:
dict: Thông tin đường thắng hoặc None
"""
return self.winning_line
def is_game_over(self, board):
"""
Kiểm tra game đã kết thúc chưa (thắng hoặc hòa)
Args:
board (List[List[str]]): Bàn cờ
Returns:
tuple: (is_over, winner, is_draw)
"""
winner = self.check_winner(board)
if winner:
return True, winner, False
# Kiểm tra hòa (board đầy mà không ai thắng)
is_full = all(
board[row][col] != EMPTY_CELL
for row in range(3)
for col in range(3)
)
if is_full:
return True, None, True
return False, None, False
def evaluate_position(self, board, player):
"""
Đánh giá vị thế trên bàn cờ cho AI
Args:
board (List[List[str]]): Bàn cờ
player (str): Người chơi đánh giá
Returns:
int: Điểm đánh giá (-100 to +100)
"""
winner = self.check_winner(board)
if winner == player:
return 100 # Thắng
elif winner and winner != player:
return -100 # Thua
else:
# Đánh giá dựa trên tiềm năng thắng
return self._evaluate_potential(board, player)
def _evaluate_potential(self, board, player):
"""
Đánh giá tiềm năng thắng dựa trên số đường có thể tạo thành 3
Args:
board (List[List[str]]): Bàn cờ
player (str): Người chơi
Returns:
int: Điểm tiềm năng
"""
opponent = 'O' if player == 'X' else 'X'
score = 0
# Tất cả các đường có thể thắng
lines = [
# Hàng ngang
[(0,0), (0,1), (0,2)],
[(1,0), (1,1), (1,2)],
[(2,0), (2,1), (2,2)],
# Cột dọc
[(0,0), (1,0), (2,0)],
[(0,1), (1,1), (2,1)],
[(0,2), (1,2), (2,2)],
# Đường chéo
[(0,0), (1,1), (2,2)],
[(0,2), (1,1), (2,0)]
]
for line in lines:
line_score = self._evaluate_line(board, line, player, opponent)
score += line_score
return score
def _evaluate_line(self, board, line, player, opponent):
"""
Đánh giá một đường cụ thể
Args:
board: Bàn cờ
line: Danh sách 3 vị trí
player: Người chơi hiện tại
opponent: Đối thủ
Returns:
int: Điểm cho đường này
"""
player_count = 0
opponent_count = 0
empty_count = 0
for row, col in line:
cell = board[row][col]
if cell == player:
player_count += 1
elif cell == opponent:
opponent_count += 1
else:
empty_count += 1
# Nếu đối thủ đã chiếm 1 ô trong đường này, không thể thắng
if opponent_count > 0 and player_count > 0:
return 0
# Tính điểm dựa trên số ô đã chiếm
if player_count == 2 and empty_count == 1:
return 50 # Sắp thắng
elif player_count == 1 and empty_count == 2:
return 10 # Có tiềm năng
elif opponent_count == 2 and empty_count == 1:
return -50 # Đối thủ sắp thắng, phải block
elif opponent_count == 1 and empty_count == 2:
return -10 # Đối thủ có tiềm năng
return 0
3.4. AI Player với Minimax Algorithm (ai_player.py)
Đây là phần thú vị nhất! Chúng ta sẽ implement Minimax algorithm - thuật toán AI cổ điển cho game turn-based.
# ai_player.py
"""
AI Player sử dụng Minimax algorithm cho Tic-Tac-Toe
"""
import random
import time
from config import AI_SETTINGS, EMPTY_CELL
from win_checker import WinChecker
class AIPlayer:
"""Lớp AI Player với các mức độ khó khác nhau"""
def __init__(self, symbol, difficulty='hard'):
"""
Khởi tạo AI Player
Args:
symbol (str): Ký hiệu của AI ('X' hoặc 'O')
difficulty (str): Mức độ khó ('easy' hoặc 'hard')
"""
self.symbol = symbol
self.opponent_symbol = 'O' if symbol == 'X' else 'X'
self.difficulty = difficulty
self.settings = AI_SETTINGS[difficulty]
self.win_checker = WinChecker()
# Statistics cho debugging
self.nodes_evaluated = 0
self.max_depth_reached = 0
def get_best_move(self, board):
"""
Lấy nước đi tốt nhất cho AI
Args:
board (List[List[str]]): Bàn cờ hiện tại
Returns:
Tuple[int, int]: (row, col) tốt nhất
"""
# Reset statistics
self.nodes_evaluated = 0
self.max_depth_reached = 0
# Simulate thinking time
time.sleep(self.settings['thinking_time'])
if self.difficulty == 'easy':
return self._get_easy_move(board)
else:
return self._get_minimax_move(board)
def _get_easy_move(self, board):
"""
AI dễ: Chơi random với một số logic cơ bản
Args:
board (List[List[str]]): Bàn cờ
Returns:
Tuple[int, int]: Nước đi
"""
available_moves = self._get_available_moves(board)
# 70% tỷ lệ chơi thông minh, 30% random
if random.random() > self.settings['mistake_probability']:
# Thử chơi thông minh: block opponent hoặc win
smart_move = self._get_smart_move(board)
if smart_move:
return smart_move
# Fallback: chọn random
return random.choice(available_moves)
def _get_smart_move(self, board):
"""
Logic thông minh cơ bản: thắng trước, block sau
Args:
board (List[List[str]]): Bàn cờ
Returns:
Tuple[int, int] hoặc None: Nước đi thông minh
"""
available_moves = self._get_available_moves(board)
# 1. Kiểm tra xem có thể thắng ngay không
for row, col in available_moves:
# Thử đánh và kiểm tra
board[row][col] = self.symbol
if self.win_checker.check_winner(board) == self.symbol:
board[row][col] = EMPTY_CELL # Undo
return (row, col)
board[row][col] = EMPTY_CELL # Undo
# 2. Kiểm tra xem có cần block đối thủ không
for row, col in available_moves:
# Thử để đối thủ đánh và kiểm tra
board[row][col] = self.opponent_symbol
if self.win_checker.check_winner(board) == self.opponent_symbol:
board[row][col] = EMPTY_CELL # Undo
return (row, col) # Block tại đây
board[row][col] = EMPTY_CELL # Undo
# 3. Ưu tiên trung tâm
if board[1][1] == EMPTY_CELL:
return (1, 1)
# 4. Ưu tiên góc
corners = [(0,0), (0,2), (2,0), (2,2)]
available_corners = [(r,c) for r,c in corners if (r,c) in available_moves]
if available_corners:
return random.choice(available_corners)
return None
def _get_minimax_move(self, board):
"""
AI khó: Sử dụng Minimax algorithm
Args:
board (List[List[str]]): Bàn cờ
Returns:
Tuple[int, int]: Nước đi tối ưu
"""
best_score = float('-inf')
best_move = None
alpha = float('-inf')
beta = float('inf')
available_moves = self._get_available_moves(board)
# Nếu là nước đi đầu tiên, chọn random để game thú vị hơn
if len(available_moves) == 9:
return random.choice([(0,0), (0,2), (1,1), (2,0), (2,2)])
for row, col in available_moves:
# Thử nước đi này
board[row][col] = self.symbol
# Tính điểm bằng minimax
if self.settings.get('use_alpha_beta', False):
score = self._minimax_alpha_beta(
board, 0, False, alpha, beta
)
else:
score = self._minimax(board, 0, False)
# Undo nước đi
board[row][col] = EMPTY_CELL
# Cập nhật best move
if score > best_score:
best_score = score
best_move = (row, col)
alpha = max(alpha, score)
print(f"AI evaluated {self.nodes_evaluated} nodes, max depth: {self.max_depth_reached}")
return best_move
def _minimax(self, board, depth, is_maximizing):
"""
Minimax algorithm cơ bản
Args:
board (List[List[str]]): Bàn cờ
depth (int): Độ sâu hiện tại
is_maximizing (bool): True nếu đang maximize
Returns:
int: Điểm đánh giá
"""
self.nodes_evaluated += 1
self.max_depth_reached = max(self.max_depth_reached, depth)
# Kiểm tra terminal state
is_over, winner, is_draw = self.win_checker.is_game_over(board)
if is_over:
if winner == self.symbol:
return 100 - depth # Thắng càng nhanh càng tốt
elif winner == self.opponent_symbol:
return -100 + depth # Thua càng muộn càng tốt
else:
return 0 # Hòa
# Giới hạn depth để tránh lag
if depth >= self.settings.get('max_depth', 9):
return self.win_checker.evaluate_position(board, self.symbol)
available_moves = self._get_available_moves(board)
if is_maximizing:
# AI turn - maximize score
max_score = float('-inf')
for row, col in available_moves:
board[row][col] = self.symbol
score = self._minimax(board, depth + 1, False)
board[row][col] = EMPTY_CELL
max_score = max(max_score, score)
return max_score
else:
# Opponent turn - minimize score
min_score = float('inf')
for row, col in available_moves:
board[row][col] = self.opponent_symbol
score = self._minimax(board, depth + 1, True)
board[row][col] = EMPTY_CELL
min_score = min(min_score, score)
return min_score
def _minimax_alpha_beta(self, board, depth, is_maximizing, alpha, beta):
"""
Minimax với Alpha-Beta Pruning để tối ưu performance
Args:
board (List[List[str]]): Bàn cờ
depth (int): Độ sâu
is_maximizing (bool): Maximize hay minimize
alpha (float): Alpha value
beta (float): Beta value
Returns:
int: Điểm đánh giá
"""
self.nodes_evaluated += 1
self.max_depth_reached = max(self.max_depth_reached, depth)
# Terminal state check
is_over, winner, is_draw = self.win_checker.is_game_over(board)
if is_over:
if winner == self.symbol:
return 100 - depth
elif winner == self.opponent_symbol:
return -100 + depth
else:
return 0
if depth >= self.settings.get('max_depth', 9):
return self.win_checker.evaluate_position(board, self.symbol)
available_moves = self._get_available_moves(board)
if is_maximizing:
max_score = float('-inf')
for row, col in available_moves:
board[row][col] = self.symbol
score = self._minimax_alpha_beta(board, depth + 1, False, alpha, beta)
board[row][col] = EMPTY_CELL
max_score = max(max_score, score)
alpha = max(alpha, score)
# Alpha-Beta Pruning
if beta <= alpha:
break # Prune remaining branches
return max_score
else:
min_score = float('inf')
for row, col in available_moves:
board[row][col] = self.opponent_symbol
score = self._minimax_alpha_beta(board, depth + 1, True, alpha, beta)
board[row][col] = EMPTY_CELL
min_score = min(min_score, score)
beta = min(beta, score)
# Alpha-Beta Pruning
if beta <= alpha:
break # Prune remaining branches
return min_score
def _get_available_moves(self, board):
"""
Lấy các nước đi khả dụng
Args:
board (List[List[str]]): Bàn cờ
Returns:
List[Tuple[int, int]]: Danh sách nước đi
"""
moves = []
for row in range(3):
for col in range(3):
if board[row][col] == EMPTY_CELL:
moves.append((row, col))
return moves
def get_move_explanation(self, board, move):
"""
Giải thích tại sao AI chọn nước đi này (cho educational purpose)
Args:
board (List[List[str]]): Bàn cờ
move (Tuple[int, int]): Nước đi được chọn
Returns:
str: Giải thích
"""
row, col = move
# Check if it's a winning move
board[row][col] = self.symbol
if self.win_checker.check_winner(board) == self.symbol:
board[row][col] = EMPTY_CELL
return "Nước đi thắng ngay! 🎯"
board[row][col] = EMPTY_CELL
# Check if it's blocking opponent's win
board[row][col] = self.opponent_symbol
if self.win_checker.check_winner(board) == self.opponent_symbol:
board[row][col] = EMPTY_CELL
return "Block đối thủ sắp thắng! 🛡️"
board[row][col] = EMPTY_CELL
# Strategic positions
if (row, col) == (1, 1):
return "Chiếm trung tâm - vị trí chiến lược! ⭐"
if (row, col) in [(0,0), (0,2), (2,0), (2,2)]:
return "Chiếm góc để tạo nhiều đường thắng! 📐"
return "Nước đi tối ưu theo Minimax! 🤖"
3.5. UI Manager (ui_manager.py)
# ui_manager.py
"""
Quản lý giao diện người dùng cho Tic-Tac-Toe
"""
import time
import os
from config import GAME_MODES, MESSAGES, VALID_ROWS, VALID_COLS
class UIManager:
"""Lớp quản lý giao diện người dùng"""
def __init__(self):
self.animation_chars = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '