2015-08-09

О покере

В Омске случился Ленивый Покер. И я там был. И мы даже победили.


Многие знают, что покер — это карточная игра. Это — правда. Мне правила покера объяснили за пятнадцать минут в поезде, за неделю до этого турнира. Они проще дурака :)
Тебе дают две карты. Ты делаешь ставки, такие же или выше, чем сделали до тебя. Или же выходишь из игры. Потом выкладываются еще карты. И еще раз торги и ставки. Потом еще по одной карте и еще ставки, два раза. Ну а потом все ставки забирает либо единственный оставшийся игрок, либо тот, у кого будет самая крутая пятикарточная комбинация из двух карт на руке, выданных в самом начале, и карт, выложенных на стол по ходу игры.
В покере игра идет на деньги. Ну или на фишки, которые символизируют собой деньги. В домашнем кругу обычно играют именно на фишки, а победитель просто получает символические сто рублей. Аналогично в Лин Покере игра идет условными попугаями, а победитель розыгрыша получает пять очков. За второе место дают три очка. Ну а победитель турнира — тот, кто наберет больше всех очков.
Лин Покер — это соревнование программистов. Программисты собираются в команды. Команды пишут бота. Бот играет с ботами других команд. Все происходит весьма быстро и увлекательно.
Бота можно писать почти на любом языке программирования, на котором можно запустить HTTP сервер, принять POST запрос, распарсить JSON и вернуть целое число. Собственно, состояние игры: твои карты, ставки игроков до тебя, параметры — постятся боту в JSONе, а он отвечает целым числом. Мы писали на Node.js, почему-то.
Код бота выкладывается на GitHub, откуда автоматически разворачивается на Heroku. Делает это платформа Лин Покера. Делает забавно, не учитывая веток. Любой пуш приводит к редеплою бота. Сделано это специально, чтобы разработчики не стеснялись фигачить код в продакшен и не прятали изменения в ветках.
В продакшен!
Так же платформа Лин Покера шлет запросы вашему боту. Есть два типа запросов: запрос ставки, который, собственно, и делает игру вашего бота, и отправка всего состояния игры по окончанию розыгрыша. Второй вызов можно использовать для машинного обучения, но фиг его знает, когда дело дойдет до этого самого обучения.
Все боты стартуют одновременно и с одинаковыми начальными условиями. Поэтому мы первым делом запушили версию, которая всегда делает фиксированную ставку в пятьдесят. И на этой «стратегии» наш бот уверенно побеждал первые полчаса. Ведь остальные боты работали по-дефолту, скидывали карты.
Потом мы разобрались с форматом JSONов в запросах и запилили простейшую стратегию на повышение. Бот смотрел уже сделанные ставки и отвечал повышением на два блайнда. Всегда. Без анализа карт вообще.
И с этой стратегией мы побеждали еще две итерации. Итерация в Лин Покере — это сеанс кодинга на сорок минут, за которым следует небольшая ретроспектива. Ретроспектива — момент для блефа. Можно и нужно похвастаться другим командам, что вы прикручиваете супер-пупер движок машинного обучения, который всех порвет на следующей итерации, как только вы отловите все баги. Хотя на самом деле у вас стоит return 50;.
На самом деле...
Есть и еще один грязный хак. Код всех ботов турнира лежит на GitHub. Команды в платформе Лин Покера регистрируются под гитхабовым аккаунтом. И код ботов лежит в Гитхабе лидера команды. Его довольно легко можно найти. И подсмотреть код соперников. Правда, это сильно грязно и не приветствуется. И это никак не помогает. Ибо времени разбираться в чужом коде нет совсем. А код грязный и нечитаемый. И вообще другие команды на других языках пишут.
Времени нет совсем. Команды не дремлют. Боты совершенствуются. С каждой итерацией интервал между розыгрышами уменьшается. Скорость набора или слива очков увеличивается. И если еще пятнадцать минут назад твой бот полз вверх, он может довольно быстро быть свергнут с пьедестала более совершенным ботом соперников.
Вот и наша стратегия игры на повышение начала сдавать. И мы начали пилить что-то посерьезнее. В конце концов все свелось к стратегии Пуш-Фолда. У нас в команде был покерный эксперт.
То, что организаторы говорят, что знать покер не обязательно, — не вполне правда. Знать покер очень даже желательно. Оно конечно, боту все равно. Принял JSON, что-то подумал, вернул int. Но люди-то думают, что играют в покер. И пытаются делать вид, что играют в покер. А значит, наличие покерного эксперта в команде позволит быстрее написать бота, который будет делать вид, что играет в покер. При прочих равных, это приведет к более быстрой победе среди команд, считающих, что они играют в покер. Вполне допускаю, что, ничего не меняя в самой платформе, можно сказать командам, что мы играем в какую-то другую игру, и тогда наличие покерного эксперта уже не будет таким преимуществом.
Так вот. Стратегия. По исходному коду ни черта не понятно. Здесь учитывается ценность тех двух карт, что у нас в руке. Какова вероятность того, что они дадут выигрышную комбинацию с другими случайными картами на столе в конце розыгрыша. Еще учитывается количество игроков, оставшихся в игре. Цель — любой ценой оказаться в числе тех двух счастливчиков, что получают пять или три очка за первое и второе места. Пока игроков много, играем агрессивно, потом, когда остаемся один на один, играем осторожнее. Или наоборот. Еще учитывается размер стека, сколько мы еще можем поставить. Чтобы не рисковать понапрасну.



Создателями Лин Покера предлагается сервис для оценки комбинации карт. Но он работает только для финальных пятикарточных комбинаций. Мы им так и не воспользовались. И так неплохо получилось. Да, мы выиграли совсем-совсем не смотря на карты на столе.
Такую вот супер-дупер сложную и эффективную стратегию не удалось закодить сразу правильно. Были баги в говнокоде оценки комбинации карт. Пришлось покрыть это дело тестами и исправить с десяток багов. Были подкручивания поправочных коэффициентов на лету. И добавление новых коэффициентов. И без генератора случайных чисел тоже не обошлось, он тоже вносит свой вклад в решения нашего бота.
Пока все ковыряли и исправляли, аж полторы итерации, даже начали проигрывать. Но в последний момент вырвались вперед.
Последняя ретроспектива получилась сильно длинее. Помимо хвастовства алгоритмами, каждый участник рассказал, чему он научился, чему он удивился и что он собирается начать делать на работе в понедельник после всего этого. А боты в это время продолжали биться. И наш совсем-совсем уверенно вырвался вперед.



Любопытно, что после последнего коммита, когда код всех ботов перестал изменяться, очки всех ботов стали расти линейно. Получается, что в среднем, на большом числе игр, один бот действительно лучше другого на определенное фиксированное число попугаев. Я полагаю, это от того, что ни один бот не был обучаемым, то есть не использовал данные прошлых игр. Если бы кто-то из них подкручивал стратегию самостоятельно, линейности, пожалуй, не наблюдалось бы.
Были мысли замутить обучение. Даже что-то написали для сбора данных о прошедших играх. Но это совсем не пригодилось. И самые тупые стратегии успешно работали. И никто не знал, как это обучение кодить. И данных для обучения было в начале мало. В общем, ИИ и машинное обучение — это не про Лин Покер. Тут все надо быстро-быстро. Быстро обойти другие команды. И все.
Может ли получившийся бот обыграть человека? Новичка, пожалуй, сможет. Игрока, который хоть пару вечеров посвятил игре, наверное, нет. Хотя, конечно, исход каждого розыгрыша в покере во многом определяется случайностью.
Точно не сможет обыграть профессионального бота. Еще раз, в Лин Покере главное — скорость. Как можно быстрее выдать стратегию, которая будет побеждать ботов других команд. Быстрее, значит тупее. Интересно и захватывает именно это непрямое соревнование. В реальном времени. В рамках данного очного турнира. Ну а ботов-победителей разных турниров, наверное, даже неинтересно стравливать друг с другом. Ну кто за это будет болеть?