Есть данные полного перебора этой позиции в игре реверси. Как их использовать для поиска лучшего хода?
В Реверси имеется определенная позиция, и все возможные комбинации записаны в словаре с ключами и значениями. Полный перебор не гарантирует идеального хода, так как соперник может изменить свою стратегию, даже если вы выбрали вариант с наибольшими очками. Ходы обозначены индексами: 0 — клетка A8, 1 — клетка B8
(1, 2, 0, 'пропуск', 3, 'пропуск', 6) счет (36, 28)
(1, 2, 0, 'пропуск', 6, 'пропуск', 3) счет (36, 28)
(6, 'пропуск', 1, 2, 0, 'пропуск', 3) счет (36, 28)
(6, 'пропуск', 1, 2, 3, 'пропуск', 0) счет (36, 28)
(1, 3, 0, 'пропуск', 2, 'пропуск', 6) счет (35, 29)
(1, 3, 0, 'пропуск', 6, 'пропуск', 2) счет (35, 29)
(1, 3, 6, 'пропуск', 0, 'пропуск', 2) счет (35, 29)
(6, 'пропуск', 1, 3, 0, 'пропуск', 2) счет (35, 29)
(3, 2, 6, 'пропуск', 1, 'пропуск', 0) счет (34, 30)
(2, 1, 3, 'пропуск', 0, 'пропуск', 6) счет (33, 31)
(2, 1, 3, 'пропуск', 6, 'пропуск', 0) счет (33, 31)
(6, 'пропуск', 2, 0, 3, 'пропуск', 1) счет (33, 31)
(6, 'пропуск', 2, 1, 3, 'пропуск', 0) счет (33, 31)
(6, 'пропуск', 3, 2, 1, 'пропуск', 0) счет (33, 31)
(0, 1, 3, 2, 6, 'пропуск', 'пропуск') счет (32, 32)
(3, 2, 0, 1, 6, 'пропуск', 'пропуск') счет (32, 32)
(0, 1, 2, 3, 6, 'пропуск', 'пропуск') счет (30, 34)
(1, 2, 6, 0, 3, 'пропуск', 'пропуск') счет (30, 34)
(1, 3, 6, 'пропуск', 2, 0, 'пропуск') счет (30, 34)
(2, 1, 0, 3, 6, 'пропуск', 'пропуск') счет (30, 34)
(2, 1, 6, 3, 0, 'пропуск', 'пропуск') счет (30, 34)
(2, 3, 6, 0, 1, 'пропуск', 'пропуск') счет (30, 34)
(2, 3, 6, 1, 0, 'пропуск', 'пропуск') счет (30, 34)
(6, 'пропуск', 1, 3, 2, 0, 'пропуск') счет (30, 34)
(0, 1, 6, 'пропуск', 3, 2, 'пропуск') счет (29, 35)
(6, 'пропуск', 0, 1, 3, 2, 'пропуск') счет (29, 35)
(6, 'пропуск', 3, 2, 0, 1, 'пропуск') счет (29, 35)
(2, 3, 0, 1, 6, 'пропуск', 'пропуск') счет (28, 36)
(0, 1, 6, 'пропуск', 2, 3, 'пропуск') счет (27, 37)
(1, 2, 3, 0, 6, 'пропуск', 'пропуск') счет (27, 37)
(2, 0, 6, 3, 1, 'пропуск', 'пропуск') счет (27, 37)
(3, 2, 6, 'пропуск', 0, 1, 'пропуск') счет (27, 37)
(6, 'пропуск', 0, 1, 2, 3, 'пропуск') счет (27, 37)
(6, 'пропуск', 2, 1, 0, 3, 'пропуск') счет (27, 37)
(1, 3, 2, 0, 6, 'пропуск', 'пропуск') счет (25, 39)
(2, 3, 1, 0, 6, 'пропуск', 'пропуск') счет (25, 39)
(3, 2, 1, 0, 6, 'пропуск', 'пропуск') счет (25, 39)
(6, 'пропуск', 2, 3, 1, 0, 'пропуск') счет (25, 39)
(2, 0, 3, 1, 6, 'пропуск', 'пропуск') счет (24, 40)
(6, 'пропуск', 2, 3, 0, 1, 'пропуск') счет (23, 41)
(2, 0, 1, 3, 6, 'пропуск', 'пропуск') счет (22, 42)
(6, 'пропуск', 2, 0, 1, 3, 'пропуск') счет (22, 42)
В итоге из всех этих вариаций 100% выигрышные вот эти 5 из 42 считал на бумажке
(1, 2, 0, 'пропуск', 3, 'пропуск', 6) счет (36, 28)
(1, 2, 0, 'пропуск', 6, 'пропуск', 3) счет (36, 28)
(1, 3, 0, 'пропуск', 2, 'пропуск', 6) счет (35, 29)
(1, 3, 0, 'пропуск', 6, 'пропуск', 2) счет (35, 29)
(1, 3, 6, 'пропуск', 0, 'пропуск', 2) счет (35, 29)
Ответы (1 шт):
Гм. Минимакс уже не преподают в современных университетах?
Коротко.
Лучшая игра для белых: [1, 3, 0, None, 2, None, 6]
, итоговый счёт (35, 29)
Решение: https://pastebin.com/DickndEz
Длинно.
Нужно построить дерево текущий ход
+ возможные продолжения
и для каждого узла в дереве дать оценку позиции.
Если ход последний, то оценка очевидна - разница между числом белых и чёрных фишек.
Если ход белых, то в качестве оценки берётся минимум оценок дочерних поддеревьев: чёрные постараются выбрать продолжение, максимизирующее число чёрных фишек в итоге.
Если ход чёрных, то белые будут разыгрывать такое продолжение, которое максимизирует белые фишки. То есть оценка равна максимуму оценки продолжения.
Чтобы найти наилучшую игру для белых, нужно выбрать первый ход с максимальной оценкой. Соответственно, черные всякий раз будут выбирать ход с минимальной оценкой.
class Tree:
"""Дерево игры: ход и оценка хода. Дочерние узлы - возможные продолжения игры.
Оценка хода - разница в счёте в наихудшем продолжении после данного хода."""
def __init__(self, move: Move, score: Tuple[int] = None):
self.move = move
self.score = score
self._minimax: int = None
if score:
# Если последний ход в игре, то оценка хода - итоговый счёт
self._minimax = score[0] - score[1]
self.children: List[Self] = []
@property
def minimax(self) -> int:
"""Оценка хода: разница в счёте в наихудшем продолжении после данного хода."""
if self._minimax is not None:
return self._minimax
if not self.children:
raise ValueError("Листовой узел, но итоговый счёт не задан.")
if self.move.side == WHITE:
# Чёрные выберут продолжение с минимальным счётом.
return min(c.minimax for c in self.children)
# Белые выберут продолжение с максимальным счётом
return max(c.minimax for c in self.children)
def add_child(self, child) -> Self:
"""Добавить дочернее дерево продолжения игры"""
self.children.append(child)
return self
def get_by_minimax(self) -> Self:
"""Возвращает дочернее дерево с наилучшим продолжением игры."""
minimax = self.minimax
for child in self.children:
if child.minimax == minimax:
return child
def best_game(self) -> Game:
"""Возвращает лучшую игру, начинающуюся с этого хода."""
if not self.children:
return Game([self.move.position], self.move.side, self.score)
child = self.get_by_minimax()
best_game = child.best_game()
return Game(
[self.move.position] + best_game.moves, self.move.side, best_game.score
)
def print_tree(self, level=0):
"""Печатает дерево игры"""
print("| " * level, self.move, self.minimax)
for child in self.children:
child.print_tree(level + 1)
Служебные классы для представления игры и отдельного хода
WHITE = "W"
BLACK = "B"
class Move:
"""Ход в игре: позиция и сторона"""
def __init__(self, position: int, side: str):
self.position = position
self.side = side
def __hash__(self) -> int:
return hash((self.position, self.side))
def __eq__(self, other) -> bool:
if isinstance(other, Move):
return self.position == other.position and self.side == other.side
return False
def __repr__(self) -> str:
return f"Move({self.position}, {self.side})"
def __str__(self) -> str:
return f"{self.side}:{self.position}"
@property
def opponent(self) -> str:
"""Цвет противника"""
return WHITE if self.side == BLACK else BLACK
class Game:
"""Игра: последовательность ходов, сторона, делающая первый ход, и итоговый счёт"""
def __init__(self, moves: Tuple[int], side: str, score: Tuple[int]):
self.moves = moves
self.side = side
self.score = score
def __len__(self) -> int:
return len(self.moves)
@property
def head(self) -> Move:
"""Первый ход"""
return Move(self.moves[0], self.side)
@property
def tail(self) -> Self:
"""Оставшиеся ходы"""
return Game(self.moves[1:], self.opponent, self.score)
@property
def opponent(self) -> str:
"""Цвет противника"""
return self.head.opponent
Построитель дерева
class TreeBuilder:
"""Построитель дерева игры из списка игр."""
def __init__(self, move, games):
self.games = games
self.tree = Tree(move)
self.games_by_move = defaultdict(list)
self._build_tree()
def _build_tree(self):
for game in self.games:
if len(game) == 1:
self.tree.add_child(Tree(game.head, game.score))
else:
self.games_by_move[game.head].append(game.tail)
for move, tails in self.games_by_move.items():
self.tree.add_child(TreeBuilder(move, tails).tree)
self.tree.children.sort(key=lambda c: c.move.position)
Ну и собственно анализ:
# список игр из задачи
GAMES = [
Game((0, 1, 2, 3, 6), WHITE, (30, 34)),
Game((0, 1, 3, 2, 6), WHITE, (32, 32)),
Game((0, 1, 6, None, 2, 3), WHITE, (27, 37)),
Game((0, 1, 6, None, 3, 2), WHITE, (29, 35)),
Game((1, 2, 0, None, 3, None, 6), WHITE, (36, 28)),
Game((1, 2, 0, None, 6, None, 3), WHITE, (36, 28)),
Game((1, 2, 3, 0, 6), WHITE, (27, 37)),
Game((1, 2, 6, 0, 3), WHITE, (30, 34)),
Game((1, 3, 0, None, 2, None, 6), WHITE, (35, 29)),
Game((1, 3, 0, None, 6, None, 2), WHITE, (35, 29)),
Game((1, 3, 2, 0, 6), WHITE, (25, 39)),
Game((1, 3, 6, None, 0, None, 2), WHITE, (35, 29)),
Game((1, 3, 6, None, 2, 0), WHITE, (30, 34)),
Game((2, 0, 1, 3, 6), WHITE, (22, 42)),
Game((2, 0, 3, 1, 6), WHITE, (24, 40)),
Game((2, 0, 6, 3, 1), WHITE, (27, 37)),
Game((2, 1, 0, 3, 6), WHITE, (30, 34)),
Game((2, 1, 3, None, 0, None, 6), WHITE, (33, 31)),
Game((2, 1, 3, None, 6, None, 0), WHITE, (33, 31)),
Game((2, 1, 6, 3, 0), WHITE, (30, 34)),
Game((2, 3, 0, 1, 6), WHITE, (28, 36)),
Game((2, 3, 1, 0, 6), WHITE, (25, 39)),
Game((2, 3, 6, 0, 1), WHITE, (30, 34)),
Game((2, 3, 6, 1, 0), WHITE, (30, 34)),
Game((3, 2, 0, 1, 6), WHITE, (32, 32)),
Game((3, 2, 1, 0, 6), WHITE, (25, 39)),
Game((3, 2, 6, None, 0, 1), WHITE, (27, 37)),
Game((3, 2, 6, None, 1, None, 0), WHITE, (34, 30)),
Game((6, None, 0, 1, 2, 3), WHITE, (27, 37)),
Game((6, None, 0, 1, 3, 2), WHITE, (29, 35)),
Game((6, None, 1, 2, 0, None, 3), WHITE, (36, 28)),
Game((6, None, 1, 2, 3, None, 0), WHITE, (36, 28)),
Game((6, None, 1, 3, 0, None, 2), WHITE, (35, 29)),
Game((6, None, 1, 3, 2, 0), WHITE, (30, 34)),
Game((6, None, 2, 0, 1, 3), WHITE, (22, 42)),
Game((6, None, 2, 0, 3, None, 1), WHITE, (33, 31)),
Game((6, None, 2, 1, 0, 3), WHITE, (27, 37)),
Game((6, None, 2, 1, 3, None, 0), WHITE, (33, 31)),
Game((6, None, 2, 3, 0, 1), WHITE, (23, 41)),
Game((6, None, 2, 3, 1, 0), WHITE, (25, 39)),
Game((6, None, 3, 2, 0, 1), WHITE, (29, 35)),
Game((6, None, 3, 2, 1, None, 0), WHITE, (33, 31)),
]
# Дерево игры
tree = TreeBuilder(Move(None, BLACK), GAMES).tree
for kid in tree.children:
print(
f"Если белые сделают ход {kid.move.position}, то разница в итоговом счёте будет {kid.minimax}"
)
white_best = max(tree.children, key=lambda c: c.minimax)
game = white_best.best_game()
print("Лучшая игра для белых: ", game.moves, game.score)
for kid in tree.children:
game = kid.best_game()
print(
f"Если белые сделают ход {kid.move.position}, то лучшая игра будет ",
game.moves,
game.score,
)
Результат:
Если белые сделают ход 0, то разница в итоговом счёте будет 0
Если белые сделают ход 1, то разница в итоговом счёте будет 6
Если белые сделают ход 2, то разница в итоговом счёте будет -10
Если белые сделают ход 3, то разница в итоговом счёте будет 4
Если белые сделают ход 6, то разница в итоговом счёте будет 6
Лучшая игра для белых: [1, 3, 0, None, 2, None, 6] (35, 29)
Если белые сделают ход 0, то лучшая игра будет [0, 1, 3, 2, 6] (32, 32)
Если белые сделают ход 1, то лучшая игра будет [1, 3, 0, None, 2, None, 6] (35, 29)
Если белые сделают ход 2, то лучшая игра будет [2, 0, 6, 3, 1] (27, 37)
Если белые сделают ход 3, то лучшая игра будет [3, 2, 6, None, 1, None, 0] (34, 30)
Если белые сделают ход 6, то лучшая игра будет [6, None, 1, 3, 0, None, 2] (35, 29)
Для проверки вот дерево ходов с оценками
B:None 6
| W:0 0
| | B:1 0
| | | W:2 -4
| | | | B:3 -4
| | | | | W:6 -4
| | | W:3 0
| | | | B:2 0
| | | | | W:6 0
| | | W:6 -6
| | | | B:None -6
| | | | | W:2 -10
| | | | | | B:3 -10
| | | | | W:3 -6
| | | | | | B:2 -6
| W:1 6
| | B:2 8
| | | W:0 8
| | | | B:None 8
| | | | | W:3 8
| | | | | | B:None 8
| | | | | | | W:6 8
| | | | | W:6 8
| | | | | | B:None 8
| | | | | | | W:3 8
| | | W:3 -10
| | | | B:0 -10
| | | | | W:6 -10
| | | W:6 -4
| | | | B:0 -4
| | | | | W:3 -4
| | B:3 6
| | | W:0 6
| | | | B:None 6
| | | | | W:2 6
| | | | | | B:None 6
| | | | | | | W:6 6
| | | | | W:6 6
| | | | | | B:None 6
| | | | | | | W:2 6
| | | W:2 -14
| | | | B:0 -14
| | | | | W:6 -14
| | | W:6 6
| | | | B:None 6
| | | | | W:0 6
| | | | | | B:None 6
| | | | | | | W:2 6
| | | | | W:2 -4
| | | | | | B:0 -4
| W:2 -10
| | B:0 -10
| | | W:1 -20
| | | | B:3 -20
| | | | | W:6 -20
| | | W:3 -16
| | | | B:1 -16
| | | | | W:6 -16
| | | W:6 -10
| | | | B:3 -10
| | | | | W:1 -10
| | B:1 2
| | | W:0 -4
| | | | B:3 -4
| | | | | W:6 -4
| | | W:3 2
| | | | B:None 2
| | | | | W:0 2
| | | | | | B:None 2
| | | | | | | W:6 2
| | | | | W:6 2
| | | | | | B:None 2
| | | | | | | W:0 2
| | | W:6 -4
| | | | B:3 -4
| | | | | W:0 -4
| | B:3 -4
| | | W:0 -8
| | | | B:1 -8
| | | | | W:6 -8
| | | W:1 -14
| | | | B:0 -14
| | | | | W:6 -14
| | | W:6 -4
| | | | B:0 -4
| | | | | W:1 -4
| | | | B:1 -4
| | | | | W:0 -4
| W:3 4
| | B:2 4
| | | W:0 0
| | | | B:1 0
| | | | | W:6 0
| | | W:1 -14
| | | | B:0 -14
| | | | | W:6 -14
| | | W:6 4
| | | | B:None 4
| | | | | W:0 -10
| | | | | | B:1 -10
| | | | | W:1 4
| | | | | | B:None 4
| | | | | | | W:0 4
| W:6 6
| | B:None 6
| | | W:0 -6
| | | | B:1 -6
| | | | | W:2 -10
| | | | | | B:3 -10
| | | | | W:3 -6
| | | | | | B:2 -6
| | | W:1 6
| | | | B:2 8
| | | | | W:0 8
| | | | | | B:None 8
| | | | | | | W:3 8
| | | | | W:3 8
| | | | | | B:None 8
| | | | | | | W:0 8
| | | | B:3 6
| | | | | W:0 6
| | | | | | B:None 6
| | | | | | | W:2 6
| | | | | W:2 -4
| | | | | | B:0 -4
| | | W:2 -14
| | | | B:0 2
| | | | | W:1 -20
| | | | | | B:3 -20
| | | | | W:3 2
| | | | | | B:None 2
| | | | | | | W:1 2
| | | | B:1 2
| | | | | W:0 -10
| | | | | | B:3 -10
| | | | | W:3 2
| | | | | | B:None 2
| | | | | | | W:0 2
| | | | B:3 -14
| | | | | W:0 -18
| | | | | | B:1 -18
| | | | | W:1 -14
| | | | | | B:0 -14
| | | W:3 2
| | | | B:2 2
| | | | | W:0 -6
| | | | | | B:1 -6
| | | | | W:1 2
| | | | | | B:None 2
| | | | | | | W:0 2