Веб-сервисы играют в покер

в 13:12, , рубрики: game development, game server, Gamedev, покер, Спортивное программирование, метки: , , ,

imageЗдравствуй.

Меня очень привлекает спортивное программирование, а ещё я люблю покер. Поэтому я решил убить сразу двух зайцев, запустив турнир покерных ботов.

В отличие от других подобных мероприятий, программа участника может быть реализована в виде веб-сервиса, с которым «крупье» будет общаться через HTTP.

Изначальная идея проекта принадлежит моему другу (он не с Хабра). Первый раз она прозвучала примерно так:

Я считаю, что нет ничего дурного в том, чтобы боты играли в покер-румах. Хотя, может быть, честнее было бы организовать специальный покерный клуб, где роботы играли бы только друг с другом. Вот я бы с удовольствием написал такого бота. А ты?

Не помню, что я ответил. Мне интереснее было именно сделать такой сервис. Challenge accepted ;)

Движок

На забавной картинке чуть выше обозначены основные компоненты системы и их связи друг с другом. Отдельного комментария требует часть, связанная с покерным движком. Подходящий готовый движок мне найти не удалось, поэтому я написал небольшой (~1000 строк) python-пакет, который реализует логику покерного крупье и общения с ботами. К пакету прилагается простой unix-style интерфейс для генерации покерных раздач и их непосредственного проведения.

Движок ничего «не знает» про логику турнира, базу данных пользователей и т.п. Он выступает исключительно в роли «крупье» и возвращает результат своей работы в виде XML, содержащего конечное состояние данной раздачи.

Такая реализация даёт возможность использовать один и тот же движок как внутри покер-рума, так и на компьютере пользователя во время разработки/тестирования бота (то, что я называю SDK). Это гарантирует, что бот будет находится в одних и тех же условиях на тест-лабе пользователя и во время реальной турнирной игры, а также сокращает дублирование кода.

Исходный код движка опубликован на Гитхаб (лицензия MIT). Для использования в виде SDK под Windows собирается (с помощью py2exe) exe-шник с включенным python-интерпретатором. Под Linux и Mac SDK поставляется в исходных текстах и использует интерпретатор, установленный (по умолчанию) в операционной системе.

В текущей реализации движок поддерживает только одну версию покера — «Texas hold'em» с фиксированными ставками. Судя по википедии, это одна из самых популярных покерных игр в мире.

Я не стал делать SDK в виде веб-сервиса, чтобы пользователь мог запускать систему полностью локально. Таким образом можно написать сотни регрессионных тестов на бота и прогонять их очень быстро, хоть после каждого изменения. Причём для реализации такого сценария не нужно патчить исходники или писать собственный интерфейс к движку — всё работает из коробки «by design».

Сами тесты пишутся легко и приятно благодаря stateless API.

API

Если у меня есть выбор, я почти всегда делаю stateless API. В данном случае этот выбор был не столь очевиден, т.к. необходимо на каждом ходу передавать боту всю историю раздачи (это минимально-необходимая информация для принятия решения).

В текущий момент эта информация (я называю её «состоянием» раздачи) передаётся боту в формате XML с помощью HTTP POST запроса. В худшем случае этот XML может весить до 4КБ. Много это или мало — судить вам, но это та цена, которую я готов заплатить ради упрощения процесса разработки бота и снижения порога на вход для новых участников.

Разработка является действительно довольно несложной — участнику нужно просто написать программу, которая парсит некоторый XML, анализирует описанную в нём ситуацию и принимает определённое решение (пасовать, поддержать, повысить, пойти ва-банк и т.д.).

Схему XML-документа пришлось разработать самостоятельно.

Пример

<?xml version="1.0"?>
<game>
  <table button="2">
    <player sit="0" name="vbo"    stack="80" in_stack="100"/>
    <player sit="2" name="lenny"  stack="80" in_stack="100"/>
    <player sit="5" name="psycho" stack="90" in_stack="100"/>
  </table>
  <posts>
    <post amount="10" player="psycho" type="small_blind"/>
    <post amount="20" player="vbo"    type="big_blind"/>
  </posts>
  <betting>
    <round name="preflop">
      <action amount="20" player="lenny"  type="call"/>
      <action amount="10" player="psycho" type="fold"/>
      <action amount="20" player="vbo"    type="check"/>
    </round>
    <round name="flop">
      <action amount="0" player="vbo"   type="check"/>
      <action amount="0" player="lenny" type="check"/>
    </round>
    <round name="turn"/>
    <round name="river"/>
  </betting>
  <community>
    <card rank="Q" suit="H"/>
    <card rank="A" suit="H"/>
    <card rank="7" suit="S"/>
    <card rank="T" suit="S"/>
  </community>
</game>

Данная схема мне кажется довольно простой для понимания человеком (в том числе за счёт лёгкой избыточности, например, //player/@stack) и удобной для анализа роботом. В дополнение я планирую в ближайшее время сделать JSON-версию схемы — кому как нравится.

Для внутреннего представления раздачи движок использует расширенную версию схемы, где присутствует колода, для каждого бота указан его транспорт (URL и т.д.), карманные карты и т.д. Конечное состояние раздачи также включает в себя ноду «showdown», которая даёт понять, какой игрок выиграл и какие у него были карты (если ему пришлось вскрываться). В текущей реализации, конечное состояние раздачи видит только автор бота, когда просматривает статистику игр на сайте покер-рума.

Вся остальная информация передаётся (в случае HTTP-транспорта) в отдельных полях POST-запроса. Это сделано для того, чтобы можно было написать простого бота вообще без парсинга XML. К тому-же, такая реализация делает более удобным написание тестов — тестовый бот может вообще не парсить XML и действовать, например, рандомно или следуя простейшему алгоритму (в духе, «check/call any» или «bet/raise good cards»).

Для этого «крупье» также передаёт боту список действий, которые вообще возможно совершить в данной ситуации. Если бот совершает действие, которого нет в этом списке — его ответ рассматривается, как fold, а текст ответа (обычно, это stack trace исключения) записывается в логи на стороне покер-рума. На данный момент, я сам читаю эти логи и пишу участнику email, если бот ломается. Разумеется, в самое ближайшее время данный процесс будет автоматизирован ;)

Турнир

В состязаниях по спортивному программированию меня всегда очень расстраивает следующее:

  • Они не вовремя начинаются.
    Например, в последние выходные перед запуском большого проекта на работе.
  • Они заканчиваются в самый интересный момент.
    Например, когда я придумал гениальную идею, которая сделает мою программу ну совсем потрясающей.

Поэтому, я с самого начала решил делать основное состязание «бесконечным». Представьте себе реальный он-лайн покерный клуб для людей. Туда можно прийти в любой момент, закинуть деньжат, сесть за стол и отлично провести время. Можно совершенствовать свои навыки и садится за столы с более высокими ставками, а можно просто развлекаться игрой «на фантики» в свободное время.

Разумеется, сразу делать нечто подобное, только для роботов, я не стал — было бы очень обидно, например, после шести месяцев разработки осознать, что у сервиса слишком высокий порог на вход и я играю там в гордом одиночестве.

Поэтому для начала я запустил нечто вроде sandbox'а — турнира покерных ботов, к которому можно присоединиться в любой момент и получить представление об уровне своей программы на фоне других участников.

В начале каждого дня покер-рум составляет рейтинг участников турнира, на основе суммы «денег», которые им удалось выиграть (или пришлось проиграть) за предыдущие сутки. После этого баланс каждого пользователя устанавливается в ноль и соревнование начинается с начала. В рамках соревнования покер-рум старается обеспечить, чтобы все боты постоянно играли, причём, по возможности, с различными соперниками. В текущей реализации для этого используются следующие правила:

  • Если бот свободен, он сразу садится за первый попавшийся стол. При этом приоритет отдаётся столам, за которыми бот давно (или никогда) не сидел и столам с наименьшим количеством игроков.
  • Бот встаёт из-за стола, как только проиграется или сыграет за ним более N раздач. Последнее правило применяется, чтобы равные соперники не играли друг с другом до бесконечности.
  • Покер-рум поддерживает «оптимальное» число открытых столов — чтобы всем хватало и ещё оставались места, куда можно пересесть после факапа. Такое число линейно зависит от кол-ва активных игроков.

Алгоритмы, реализующие эти правила (на схеме это «Tournament worker») работают параллельно, асинхронно и совершенно независимо. Таким образом обеспечивается равномерность нагрузки на машину и постоянство кол-ва раздач на единицу времени. Также такая архитектура делает систему более масштабируемой.

Worker'ы, как и сайт, реализованы на PHP (у меня здесь больше опыта, чем в python'е) и работают с PostgreSQL базой данных. Для обеспечения параллельной обработки очереди столов используется pg_try_advisory_lock при выборке кандидатов. Не ручаюсь, что этот способ самый верный, т.ч. буду рад обсудить данный вопрос в комментариях.

Планы

Я пока не вижу смысла выкладывать исходники покер-рума в открытый доступ — код получился не слишком сложным и слишком привязанным к проекту. Вместо этого я сосредоточусь на улучшении «движка» (например, добавлю поддержку безлимитного покера), реализации API для «обучения» ботов (т.е. для получения результатов прошедших игр), а так же на разработке нового типа игры, в котором участники через отдельное API смогут сами управлять рассадкой за столы и выходить из-за стола по мере необходимости. В общем, смогут вести себя почти, как люди.

Также, я планирую сократить порог на вход для начинающих (или просто далёких от веб) программистов — дать возможность разрабатывать бота прямо в браузере на языке вроде CoffeeScript и тестировать его через специальный веб-интерфейс. Было бы просто замечательно сделать также некоторый визуальный редактор алгоритма. Конечно, он не обязательно должен давать абсолютные возможности, но это позволит пользоваться сервисом даже не-программистам, что очень полезно. На данный момент у меня нет идей, как сделать такой редактор, т.ч. если у вас они есть, пожалуйста, отпишитесь в комментариях.

Проект делается «just for fun», т.ч. сроки реализации всех этих «корованов» публиковать бесполезно. Судя по git log текущую реализацию я начал делать в начале февраля. Из этого можно сделать выводы о моей скорости.

Если читателю интересно поиграть — бета версия покер-рума работает здесь. А на youtube можно посмотреть короткое видео о том, как сделать простейшего бота на PHP, протестировать его и зарегистрировать в турнире.

Автор: vbo


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


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