Да, алгоритм MCTS (Monte Carlo Tree Search / Поиск по дереву Монте-Карло) идеально подходит для Реверси, так как игра обладает огромным числом вариантов ходов. Этот алгоритм не требует сложной математической оценки позиции — он буквально симулирует тысячи случайных партий до конца из текущего положения и выбирает лучший путь.
Ниже представлен полный, рабочий код Реверси с ИИ на базе MCTS, полностью интегрированный в графическое окно tkinter (из прошлого ответа).
ИИ думает прямо во время вашего хода — вы можете настроить количество симуляций (переменная simulations), чтобы сделать его умнее или быстрее.
import tkinter as tk
from tkinter import messagebox
import copy
import random
import math
class ReversiBoard:
"""Класс логики игры, отделенный от интерфейса для нужд MCTS"""
def __init__(self, size=8):
self.size = size
self.board = [[0] * size for _ in range(size)]
self.board[3][3], self.board[4][4] = 2, 2
self.board[3][4], self.board[4][3] = 1, 1
self.current_player = 1
def get_flipped_disks(self, row, col, player):
if self.board[row][col] != 0:
return []
opponent = 2 if player == 1 else 1
directions = [(-1,-1), (-1,0), (-1,1), (0,-1), (0,1), (1,-1), (1,0), (1,1)]
flipped = []
for dr, dc in directions:
r, c = row + dr, col + dc
temp = []
while 0 <= r < self.size and 0 <= c < self.size and self.board[r][c] == opponent:
temp.append((r, c))
r += dr
c += dc
if 0 <= r < self.size and 0 <= c < self.size and self.board[r][c] == player:
flipped.extend(temp)
return flipped
def get_valid_moves(self, player):
moves = []
for r in range(self.size):
for c in range(self.size):
if self.get_flipped_disks(r, c, player):
moves.append((r, c))
return moves
def make_move(self, row, col):
flipped = self.get_flipped_disks(row, col, self.current_player)
if not flipped:
return False
self.board[row][col] = self.current_player
for r, c in flipped:
self.board[r][c] = self.current_player
# Смена игрока с проверкой доступных ходов
next_player = 2 if self.current_player == 1 else 1
if self.get_valid_moves(next_player):
self.current_player = next_player
elif not self.get_valid_moves(self.current_player):
self.current_player = 0 # Игра окончена
return True
def is_game_over(self):
return self.current_player == 0
def get_winner(self):
b = sum(row.count(1) for row in self.board)
w = sum(row.count(2) for row in self.board)
if b > w: return 1
if w > b: return 2
return 0 # Ничья
# ==================== РЕАЛИЗАЦИЯ MCTS ====================
class MCTSNode:
def __init__(self, state: ReversiBoard, parent=None, move=None):
self.state = state
self.parent = parent
self.move = move
self.children = []
self.visits = 0
self.wins = 0
self.untried_moves = state.get_valid_moves(state.current_player)
def ucb1(self, exploration_constant=1.41):
if self.visits == 0:
return float('inf')
return (self.wins / self.visits) + exploration_constant * math.sqrt(math.log(self.parent.visits) / self.visits)
def select_child(self):
return max(self.children, key=lambda c: c.ucb1())
def expand(self):
move = self.untried_moves.pop()
next_state = copy.deepcopy(self.state)
next_state.make_move(*move)
child_node = MCTSNode(next_state, parent=self, move=move)
self.children.append(child_node)
return child_node
def update(self, result):
self.visits += 1
# Результат оценивается относительно игрока, который СДЕЛАЛ ход, приведший в этот узел
if self.parent:
player_who_moved = self.parent.state.current_player
if result == player_who_moved:
self.wins += 1
elif result == 0:
self.wins += 0.5 # Ничья
def mcts_search(root_state, simulations=300):
"""Главный цикл MCTS: Выбор -> Расширение -> Симуляция -> Обратное распространение"""
root = MCTSNode(copy.deepcopy(root_state))
for _ in range(simulations):
node = root
# 1. Selection (Выбор)
while not node.untried_moves and node.children:
node = node.select_child()
# 2. Expansion (Расширение)
if node.untried_moves and not node.state.is_game_over():
node = node.expand()
# 3. Simulation (Симуляция / Rollout)
sim_state = copy.deepcopy(node.state)
while not sim_state.is_game_over():
possible_moves = sim_state.get_valid_moves(sim_state.current_player)
if possible_moves:
sim_state.make_move(*random.choice(possible_moves))
else:
break
# 4. Backpropagation (Обратное распространение)
game_result = sim_state.get_winner()
while node is not None:
node.update(game_result)
node = node.parent
# Выбираем ход с наибольшим количеством посещений (самый надежный)
best_child = max(root.children, key=lambda c: c.visits)
return best_child.move
# ==================== ГРАФИЧЕСКИЙ ИНТЕРФЕЙС ====================
class ReversiGUI:
def __init__(self, root):
self.root = root
self.root.title("Реверси с ИИ (MCTS)")
self.game = ReversiBoard()
self.cell_size = 60
self.ai_player = 2 # 1 - Черные (Игрок), 2 - Белые (MCTS Компьютер)
self.canvas = tk.Canvas(root, width=8*self.cell_size, height=8*self.cell_size, bg="#006633")
self.canvas.pack()
self.canvas.bind("<Button-1>", self.handle_click)
self.draw_board()
def draw_board(self):
self.canvas.delete("all")
# Сетка
for i in range(9):
coord = i * self.cell_size
self.canvas.create_line(coord, 0, coord, 8*self.cell_size, fill="#004d26")
self.canvas.create_line(0, coord, 8*self.cell_size, coord, fill="#004d26")
# Фишки
for r in range(8):
for c in range(8):
val = self.game.board[r][c]
if val != 0:
color = "black" if val == 1 else "white"
x0, y0 = c * self.cell_size + 5, r * self.cell_size + 5
x1, y1 = (c + 1) * self.cell_size - 5, (r + 1) * self.cell_size - 5
self.canvas.create_oval(x0, y0, x1, y1, fill=color, outline="gray")
def handle_click(self, event):
if self.game.current_player != 1 or self.game.is_game_over():
return # Ход компьютера или игра завершена
col = event.x // self.cell_size
row = event.y // self.cell_size
if self.game.make_move(row, col):
self.draw_board()
if self.game.is_game_over():
self.check_winner()
elif self.game.current_player == self.ai_player:
self.root.after(100, self.make_ai_move)
def make_ai_move(self):
if self.game.is_game_over():
return
# Запуск MCTS поиска (300 симуляций для баланса скорости и ума)
best_move = mcts_search(self.game, simulations=300)
if best_move:
self.game.make_move(*best_move)
self.draw_board()
if self.game.is_game_over():
self.check_winner()
elif self.game.current_player == 1:
return # Ход возвращается игроку
else:
# Если у игрока нет ходов, ИИ ходит снова
self.root.after(100, self.make_ai_move)
def check_winner(self):
b = sum(row.count(1) for row in self.game.board)
w = sum(row.count(2) for row in self.game.board)
if b > w:
res = f"Вы победили! ({b}:{w})"
elif w > b:
res = f"Победил ИИ! ({w}:{b})"
else:
res = f"Ничья! ({b}:{w})"
messagebox.showinfo("Игра окончена", res)
if __name__ == "__main__":
root = tk.Tk()
gui = ReversiGUI(root)
root.mainloop()
Как устроен этот MCTS:
- Selection (Выбор): Используя формулу UCB1, алгоритм спускается по уже известному дереву ходов, балансируя между исследованием новых путей (Exploration) и выбором заведомо выигрышных (Exploitation). [1]
- Expansion (Расширение): Дойдя до края изученной зоны, алгоритм создает новый узел для еще не опробованного хода.
- Simulation (Симуляция): Из нового узла игра "доигрывается" до самого конца случайными ходами за обе стороны.
- Backpropagation (Обратная связь): Результат этой случайной игры (победа/проигрыш) поднимается вверх по дереву, обновляя статистику каждого узла. [1]
Хотите ли вы увеличить сложность (например, добавить веса для угловых клеток в фазу симуляции) или нужно помочь с оптимизацией скорости кода?
Комментариев нет:
Отправить комментарий