# теория · шаг 4 из 7
ИИ соперника: минимакс, отсечения и таблица позиций
Коротко
Компьютерный соперник выбирает ход, перебирая дерево игры: свой ход, ответ, свой ход - минимакс из раздела 16. На простой игре «камни» (берут 1-3, взявший последний выиграл) полный перебор 16 камней - 23 249 узлов. Альфа-бета отсечение даёт ту же оценку за 5601: ветку бросают, как только ясно, что соперник её не допустит. Таблица позиций - запоминание по позиции, ключом которой в настоящей игре служит хеш Зобриста, - сводит всё к 46 узлам: позиций всего 17, а путей к ним тысячи. Порядок ходов решает, сколько отсечёт альфа-бета: при «лучшем первым» 242 узла на 18 камнях, при неудачном - 11 204.
Если ты знаешь, что дают альфа-бета отсечения и таблица позиций и почему порядок ходов важен, листай до «Теперь сам».
Как думаешь, сколько ходов смотреть
На острове 16 камней. Ходящий берёт 1, 2 или 3, выигрывает тот, кто возьмёт последний. Компьютер хочет сыграть безошибочно - перебрать все варианты до конца партии. Сколько позиций ему придётся посмотреть?
Ответь себе до чтения. Партия - до 16 ходов, на каждом до трёх вариантов: дерево игры растёт почти как 3ⁿ,
и полный перебор на 16 камнях - десятки тысяч узлов, на 30 - около 118 миллионов. Но разных ПОЗИЦИЙ всего
17: «осталось n камней». Чей ход, в ключ можно не класть: правила для обоих игроков одинаковы, а оценка
дана с точки зрения ходящего. Все узлы дерева - это 17 позиций, пришедших разными путями. Если оценку
позиции запомнить, остальные пути к ней ничего не стоят.
На что это похоже
Шахматист, который считает варианты. Он не досчитывает до конца ход, который после ответа соперника явно хуже уже найденного: «этот ход я не сделаю, потому что у него есть ответ хуже моего плана» - и переходит к следующему. А позицию, до которой дошёл двумя порядками ходов, он узнаёт и не пересчитывает.
Механизм: отрицательный максимум, отсечение, таблица
Оценку удобно давать с точки зрения ходящего: +1 - выигрывает, −1 - проигрывает. Тогда минимакс
записывается одной функцией: оценка позиции - максимум по ходам от минус оценки позиции после хода,
потому что после хода ходит соперник. Это форма из раздела 16, без отдельных «макс» и «мин».
Альфа-бета передаёт вглубь окно [alfa, beta]: alfa - то, что ходящий себе уже гарантировал, beta -
то, больше чего соперник ему не позволит выше по дереву. Как только найден ход с оценкой ≥ beta, остальные
ходы этой позиции можно не смотреть: соперник в эту позицию не пойдёт. Ответ не меняется - меняется работа,
и тем сильнее, чем раньше попался лучший ход.
Таблица позиций - запоминание из раздела 15 для игр: оценку позиции кладут в словарь и при повторном приходе берут готовую. В игре «камни» ключ - число камней. В «Архипелаге» ключ - вся карта, и тут пригождается хеш Зобриста из прошлого урока: он обновляется ходом и стоит несколько операций.
Правило
ИИ соперника перебирает дерево игры минимаксом. Альфа-бета отсечение не меняет ответа и сокращает перебор, сильнее всего - при хорошем порядке ходов. Таблица позиций схлопывает пути, пришедшие в одну позицию, - и на играх, где позиций мало, а путей много, превращает экспоненту в число позиций.
Разбор: три способа на 8, 13 и 16 камнях
Полный минимакс, альфа-бета и минимакс с таблицей позиций. Оценки обязаны совпасть, узлы - нет.
# Игра «камни»: на острове n камней, игроки по очереди берут 1, 2 или 3.
# Кто взял последний камень - выиграл. Оценка позиции для ходящего:
# 1 - выигрыш, -1 - проигрыш.
uzlov = [0]
def minimaks(n):
# полный перебор дерева игры (раздел 16)
uzlov[0] = uzlov[0] + 1
if n == 0:
return -1 # камней нет: предыдущий взял последний
luchshee = -1
for k in [1, 2, 3]:
if k <= n:
luchshee = max(luchshee, -minimaks(n - k))
return luchshee
def alfa_beta(n, alfa, beta):
# отсечение: как только нашли ход не хуже beta, дальше не смотрим
uzlov[0] = uzlov[0] + 1
if n == 0:
return -1
for k in [1, 2, 3]:
if k <= n:
alfa = max(alfa, -alfa_beta(n - k, -beta, -alfa))
if alfa >= beta:
return alfa
return alfa
def s_tablicej(n, tablica):
# таблица позиций: одна и та же позиция приходит разными путями
uzlov[0] = uzlov[0] + 1
if n in tablica:
return tablica[n]
if n == 0:
return -1
luchshee = -1
for k in [1, 2, 3]:
if k <= n:
luchshee = max(luchshee, -s_tablicej(n - k, tablica))
tablica[n] = luchshee
return luchshee
for n in [8, 13, 16]:
uzlov[0] = 0
a = minimaks(n)
u1 = uzlov[0]
uzlov[0] = 0
b = alfa_beta(n, -1, 1)
u2 = uzlov[0]
uzlov[0] = 0
c = s_tablicej(n, {})
print(n, "камней: оценка", a, b, c)
print(" узлов: перебор", u1, "| отсечения", u2, "| таблица",
uzlov[0])
8 камней: оценка -1 -1 -1
узлов: перебор 177 | отсечения 113 | таблица 22
13 камней: оценка 1 1 1
узлов: перебор 3736 | отсечения 800 | таблица 37
16 камней: оценка -1 -1 -1
узлов: перебор 23249 | отсечения 5601 | таблица 46
Оценки совпали во всех трёх способах: 8 и 16 камней - проигрыш ходящего, 13 - выигрыш. Это и есть теория
игры: позиция проигрышна, когда камней кратно 4 - что ни возьми, соперник дополнит до 4. Перебор растёт
примерно вшестеро на каждые три камня. Отсечения режут работу от полутора раз на 8 камнях до четырёх-пяти на
13-16, но растут так же быстро - экспоненту они не отменяют. Таблица прибавляет три узла за камень: позиций
n + 1, и из каждой три хода. Граница правила: таблица схлопывает дерево, только если позиций мало. В
«Архипелаге» позиций астрономически много, и таблица держит лишь часть - поэтому её дополняют ограничением
глубины и оценкой позиции «на глаз», когда до конца партии не досчитать.
Разбор: порядок ходов решает
Та же альфа-бета на 17 и 18 камнях с тремя порядками ходов: «1-2-3», «3-2-1» и «лучший первым» - взять столько, чтобы осталось кратное 4.
uzlov = [0]
def hody(n, poryadok):
# порядок, в котором пробуем ходы: 1-2-3, 3-2-1 или «сначала лучший»
if poryadok == "лучший первым" and n % 4 != 0:
out = [n % 4] # оставить сопернику кратное 4
for k in [1, 2, 3]:
if k != n % 4:
out.append(k)
return out
if poryadok == "3-2-1":
return [3, 2, 1]
return [1, 2, 3]
def alfa_beta(n, alfa, beta, poryadok):
uzlov[0] = uzlov[0] + 1
if n == 0:
return -1
for k in hody(n, poryadok):
if k <= n:
alfa = max(alfa, -alfa_beta(n - k, -beta, -alfa, poryadok))
if alfa >= beta:
return alfa
return alfa
for n in [17, 18]:
for poryadok in ["1-2-3", "3-2-1", "лучший первым"]:
uzlov[0] = 0
o = alfa_beta(n, -1, 1, poryadok)
print(n, "камней,", poryadok + ":", "оценка", o, "| узлов",
uzlov[0])
17 камней, 1-2-3: оценка 1 | узлов 5602
17 камней, 3-2-1: оценка 1 | узлов 758
17 камней, лучший первым: оценка 1 | узлов 242
18 камней, 1-2-3: оценка 1 | узлов 11204
18 камней, 3-2-1: оценка 1 | узлов 602
18 камней, лучший первым: оценка 1 | узлов 242
Оценка везде та же, работа различается в 46 раз. Отсечение срабатывает, когда хороший ход найден рано: с ним окно сужается, и остальные ходы обрезаются почти сразу. При «1-2-3» выигрышный ход часто идёт последним, и до него альфа-бета честно перебирает проигрышные. Отсюда главный инженерный приём игровых программ: сначала пробовать ходы, которые были лучшими в прошлый раз или выглядят сильными. Граница правила: «лучший первым» здесь известен из теории; в настоящей игре его не знают, и порядок берут из таблицы позиций и прошлых итераций - угадывают, а не знают наверняка.
Что ломается без этого
ИИ, который думает минуту. Полный перебор без отсечений и таблицы на глубине 8 - и ход компьютера длится дольше хода человека.
Отсечения при плохом порядке. Альфа-бету включили, а выигрыша почти нет: ходы перебираются в порядке номеров, и лучший всегда последний.
Таблица без «чей ход». Позиция с ходом белых и та же с ходом чёрных делят ключ - и ИИ берёт оценку для чужой очереди.
Зачем это в работе
Шахматные движки, планировщики, где у среды есть «противник» (худший случай нагрузки), проверка протоколов перебором состояний, решатели головоломок - везде дерево вариантов, отсечения и таблица уже виденных состояний. А «пробуй сначала то, что сработало прошлый раз» - общий принцип ускорения перебора.
Теперь сам
- В игре можно брать 1, 2, 3 или 4 камня. Какие позиции проигрышны для ходящего?
- Сколько узлов посмотрит минимакс с таблицей на 30 камнях, если из каждой позиции три хода?
- Почему альфа-бета не может дать ДРУГУЮ оценку, чем полный минимакс?
Ответ. 1. Кратные 5: что ни возьми из 1-4, соперник дополнит до 5.
-
88: первый вызов плюс по три хода из каждой позиции от 30 до 3, плюс два хода из позиции 2 и один из позиции 1 -
1 + 3 · 28 + 2 + 1 = 88. Каждая позиция раскрывается один раз, остальные заходы берут готовую оценку. -
Она отбрасывает только ходы, которые соперник гарантированно не допустит, - их оценка не может стать ответом. Ответ меняется, только если окно задано неверно.
Главное
ИИ соперника - перебор дерева игры, и его цену решают три вещи: отсечения, порядок ходов и таблица позиций. Ответ от них не меняется, меняется число узлов - на порядки.
Комментарии
Пока нет комментариев. Будь первым!
Оставить комментарий