Статья Автор: Лебедев Дмитрий Алексеевич

BFS для игровой стратегии

Придумаем игру с достаточно сложными правилами для реализации
Вначале рассмотрим одну кучу
Два игрока,  Алиса и Боб играют в следующую игру . 
Перед игроками лежит куча камней. Игроки ходят по очереди, первый ход делает Алиса.
За один ход игрок может:
  • добавить в кучу 1 или 5 камней (то есть применить операцию сложения)
  • увеличить количество камней в два или три раза (то есть применить операцию умножения)
  • игрок не может повторять операцию хода соперника
    (то есть, если Алиса применила сложение, то Боб должен делать умножение и наоборот) 

Игра завершается, когда количество камней в куче становиться не менее 100.
Если при этом количества камней кратно 4 , то победителем считается игрок, сделавший последний ход, 
а если количество камней некратно 4, то этот игрок считается проигравшим и считается, что его соперник сделал ход.
 

Способ решения bfs - метод "раскраски"
Для решения от множества вершин перейдем к позициям/ребрам.
Каждая позиция - (вершина, команда выхода) - для таких вершин и будем производить раскраску
После завершения раскраски - анализ и разбиение вершин на две доли (выигрышные, проигрышные)
Для выигрышных определим возможные "команды выхода" (для проигрышных - это все) 
Для решения задачи определимся с переменными для описания задачи и хранения результатов.
  • swin - параметр завершения игры (в примене swin = 100)
  • G - множество стартовых вершин (в примере G =set(range(1,swin)
  • H - множество ходов/наборов ходов. (в примере можно считать, что
    H = { 'a':['x+3', 'x + 5'], 'b':['x * 2', 'x * 3']} )
  • E - множество "выходящих ребер" (декартовое произведение G на H}
  • также определим словарь для хранения "выигрышных" и "проигрышных" вершин
    LW = {0 : {'W'} , 1:{L'}} 'W' - это "кружок", в который ходить не надо, "L" - это "прямоугольник", в который надо стремиться 
  • создаем словарь для промежуточных ответов Rez
  • создадим словарь для вершин (к какой доли относятся, возможные выходы)
Для решения потребуются подпрограммы:
  • def fz - будет получать множество значений
  • def piA - определять является ли позицая "хорошей" для игрока A ("кружок")
  • def piB - определять является ли позицая "хорошей" для игрока B ("прямоугольник")
  • def step - поиск "кружков" на нечётных тактах и определение "прямоугольников" на четных


Дописываем остальное. Стараемся все реализовать в самом общем варианте.
Пока реализуем, для получения информации по ребрам
(подпрограмму fz убираем с экрана)


Пока получена раскраска ребер.
Теперь надо решить с раскраской вершин.
Если из вершины идет хотя бы одно "выигрышное ребро", то вся вершина выигрышная
То есть, если вершина есть Rez[i] при нечетных i, то вершина "выигрышная"
Аналогично - вершина проигрышная, если все её ребра в R[i] с четными номерами
Оформим решение в подпрограмму, возвращающую Rez иопределим
словарь Ans с ключом <вершина>  и значениям  = <номер такта появления, вершина, команд выхода, Выигрышная/проигрышная)


"Уберем" получение Ans в подпрограмму solvе и попробуем немного изменить условие задачи
"Нельзя повторять команду соперника" вместо "Нельзя повторять операцию команды соперника" 
Для решения достаточно изменить только словарь команд H


Можно проверить решение.
Так для вершины 3 Ans[3] покажет, что нужно выполнить переход по команде d, то есть в вершину 9
Из вершины 9 соперник может перейти в (а = 10, b = 14, c = 18)
Из 18 переход возможен по b или a (b=23, a = 19) откуда соперник или проиграет (умножением) или сложением перейдет в число кратное 4
Из 14 переход по a в 15 - соперник или проигрывает умножением, или сложением перейдет в число 16, далее умножем
Из 10 переход по b в 15 - соперник или проигрывает умножением или сложением перейдет в число 16, далее умножаем
Печать