Алгоритм и тактика поиска слов в игре Балда

в 0:07, , рубрики: Без рубрики

Однажды на Хабре нашел статью об алгоритме поиска слов в игре балда: habrahabr.ru/post/207734/ Я сам являюсь автором решателя «Робот Балда 2», который за многие годы приобрел популярность у многих онлайн игроков в игре Балда. И я хотел бы то же поделиться своим опытом и рассказать об одном уникальном алгоритме в игре балда, который еще ни кем не применялся.

Про ту статью в целом. По такому же алгоритму и у меня ищутся слова, через префиксные деревья (но более оптимизировано). А в «турбо-режиме» включается другое префиксное дерево, которое содержит символ «пустышки». Что позволяет избавиться от перебора 32-х букв в каждой итерации на пустых клетках. Это увеличивает размер префиксного словаря до ~150мб на словаре примерно из 70K слов. В чистом виде это давало ускорение в 4 раза (если не изменяет память), но у меня помимо поиска слов время тратится еще и на анализ, поэтому общее ускорение всего в 1.5 раза.

Многие могут спросить, зачем еще быстрей. Если даже самые простые и медленные алгоритмы ищут слова за миллисекунды. Эти вопросы могут возникать только у тех программистов, кто не пробовал играть против «чемпионов». Их программы умеют думать на несколько ходов вперед. Значит, программы должны не просто искать слова, но и применять тактику. А самая универсальная для всех логических игр тактика — минимакс. Что это такое: хорошо описано здесь или здесь. А применение минимакса означает, что нужно делать очень много виртуальных ходов. Ну например, если каждому игроку в среднем достается 100 слов в каждом ходу, то что бы просмотреть все варианты развития игры на 4 хода вперед потребуется 100^(4-1)=1.000.000 поисков. Если ваша программа умеет искать все слова за 1мс, то она проверит игру на 4 хода вперед за 16 минут! А на раздумья над ходом дается как правило 2 минуты. Теперь вы понимаете, зачем нужен очень быстрый поиск. Моя программа умеет за несколько секунд анализировать игру на 8-10 ходов вперед.

Самое большое ускорение дает отсеивание слов. Если сказать проще — ветками дерева перебора становятся только самые длинные слова(Это примерно. В реальности, у меня отбор слов чуточку сложней). Как показала практика, крайне редко бывает, что в начале и середине партии более короткие слова в будущем отыгрывают свою разницу в очках с более длинным словом, да еще и приносят больше очков. У соперника слишком много вариантов «отыграться», и вероятность поймать его в ловушку слишком низкая. А значит, нет смысла терять время на короткие слова. А если уже конец партии, и остается не много ходов, программа расширяет список анализируемых слов. У меня уже за 4-6 ходов до конца игры участвуют в переборе абсолютно все слова. И к концу партии как раз и уместно смотреть короткие слова. Не редко бывает, что под конец партии имея слова из 4-5 букв, выгодней походить из 2-3-х.

Второе существенное ускорение дает альфа бета отсечения.

До этого я упоминал стандартные алгоритмы, которые вы всегда сможете найти и ознакомиться. А теперь напишу свое «изобретение». Оно касается оптимизации поиска слов в процессе построения дерева виртуальных ходов. Вот смотрите. Как обычно мы строим дерево перебора в балде:
1. Находим все слова.
2. Перебираем все слова, проставляя поочередно каждое слово на игровое поле.
3. Переходим рекурсивно на пункт 1. или возвращаемся с рекурсии, если слишком глубоко залезли.

А теперь напишу, как у меня:
1. Находим все слова.
2. Перебираем все слова, проставляя поочередно каждое слово на игровое поле.
3. Находим все слова, но уже по другому принципу! Копируем в результат поиска все слова, которые уже были найдены на предыдущем уровне. Исключаем из этого списка те слова, которые теперь невозможно составить, а именно, вычеркиваем все слова, чья вставляемая буква находилась в той же самой клетке, куда была поставлена буква последнего сыгранного слова (ведь эта клетка теперь занята, а значит больше не составишь слова, которые использовали ту же самую пустую клетку). А теперь добавляем в список новые слова. Для этого ищем не все слова на игровом поле. А только те, которые проходят через занятую клетку последним словом. Ведь что изменилось с предыдущего хода — на игровом поле появилась новая буква. А значит, если и появились новые слова, то они все должны проходить через новую букву. Если не проходят — то эти слова уже ранее найдены, и есть у нас в списке.
Что в итоге: Вместо того, что бы пускать рекурсивный поиск слов от каждой клетки игрового поля, мы ее пускаем всего в одну клетку! и не важно, какого размера игровое поле. Пусть даже игровое поле содержит миллион клеток. Мы будем «доискивать» новые слова всегда только в одной клетке!
3. Переходим рекурсивно на пункт 2. или возвращаемся с рекурсии, если слишком глубоко залезли.

Ну вот. Замечу лишь, технически пункт 3 реализован более оптимизировано. И у меня на самом деле список слов не копируется каждый раз, и слова не удаляются в чистом виде. А осуществляются все это за счет переключения указателя на страницы в многомерном массиве, чья размерность определяется как «адрес клетки», «глубина хода», и собственно ID-шники слов, которые были найдены в конкретной клетке, на конкретной глубине. Таким образом, когда мы откатываемся в дереве на уровень назад, нам не нужно восстанавливать список найденных слов в этом узле, мы просто уменьшаем указатель на «страницу». А когда наоборот, углубляемся — указатель увеличивается, и в новую страницу заносятся новые слова. Таким образом. Где нибудь на 10 ходу чтоб узнать все слова в этом ходу, нам нужно сложить все слова из 10-ти «страниц». Все они в сумме и представляют собой список слов, как если бы искали обычным способом.

Если честно, я могу ошибаться в технических подробностях структуры хранения слов в многомерном массиве, давно делал, подзабыл, и там реально запутанная схема, даже глянув на исходники не сразу поймешь что к чему. Но цель была одна: избежать копирования больших данных и других «долгих» действий. Вполне возможно, если делать простым методом, и непосредственно копировать ID-шники слов, перебирать и убирать лишние, дополнять новыми — возможно такой подход будет не менее быстрым. Я не проверял. А сразу делал структуру хранения слов в многомерном массиве.

Как ни странно, но мой алгоритм «доискивания» слов не дал ошеломляющего ускорения. Но зато он дал стабильность времени поиска. Обычный метод поиска слов вначале игры достаточно быстрый, когда мало «играемых» пустых клеток, и мало поставленных букв. А вот потом, по мере развития игры обычный метод существенно замедляется. А мой если и замедляется, то в гораздо меньшей степени (ведь как ранее говорилось, сколько бы букв не было на игровом поле — на каждый виртуальный ход будут искаться слова только в 1-ой клетке, а не во всех.). Поэтому, на маленьких игровых полях метод «доискивания» не сильно опережает обычный метод, а вот на больших(более 5x5) ему нет альтернативы.

В заключение хочу сказать, что анализ минимаксом сильно зависит от точности словаря. Поэтому важен не размер словаря, а его соответствие словарю игрового портала.

Автор: Pro5

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js