Моделирование покера, часть 1

Viktor Love
17 min readOct 9, 2019

--

Меня заинтересовал покер как пример вероятностной игры. Вероятностные игры в целом довольно неплохо отображают ряд вещей из жизни (спойлер: вы умрете в ближайший день с ненулевой вероятностью).

Я решил заняться выработкой стратегии для игры в покер.

Дисклеймер

Автор играет в покер фиговенько. Точно также автор фиговенько моделирует. Не надо воспринимать что-либо здесь написанное как разумные советы.

Зачем?

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

Но мне не интересно учиться играть в покер. Мне интересно научиться моделировать и вырабатывать стратегии игры, а также выработать правильное отношение к вероятностям и рискам. Я питаю надежду, что ряд навыков можно будет перенести на другие игры (в широком смысле понятия “игры”).

Я вполне осознаю, что вряд-ли открою Америку. Большинство моих “открытий” будут баянами и вряд-ли будут работать на проф. игроках.

Теперь, когда вы прослушали исповедь велосипедиста …

Основы моделирования

Что моделируем?

Для изучения я возьму техасский холдем, как один из самых популярных вариантов.

Как моделируем?

Просчитывать вероятности ручками лениво, да и нетривиально. Вместо этого мы будем симулировать игры и подсчитывать количество побед к количеству игр.

Полная симуляция всех вариантов займет дохреналлион времени (в колоде 52 карты), поэтому будем делать симуляцию вероятностным методом: перетасовываем колоду, симулируем, повторяем.

Это называется Метод Монте-Карло, если вам нравятся умные слова.

Сколько раз моделируем

Мне бы не хотелось раннить симуляций слишком много, но я не хотел бы иметь слишком большие погрешности.

Как разумную гарантию возьмем это: с вероятностью в 95% реальный коэффициент “соотношения побед к симуляциям” равен наблюдаемому с относительной погрешностью ±2%: winRate = observedWinRate ± 2%.

Делать буду примерно так: запускаем 32 батчей симуляций по 16 игр в каждом батче. Подсчитываем размер погрешности (margin of error). Переводим размер погрешности в проценты относительно среднего. Если относительная погрешность больше ±2%, то запускаем еще 32 батча. Повторяем, пока не доведем погрешность до необходимой.

Ремарочка: вы не можете достичь этих гарантий на самом деле. Всегда есть риск, что что-то в модели разошлось. Например, любой калькулятор скажет, что у ряда “1, 1, 1” SEM будет равен нулю, хотя следующим членом в ряду может быть не 1. По факту, мы полагаемся на репрезентативность случайных выборок.

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

Почему я выбрал 95%/±2%? Потому что для 99%/±1% симуляции считаются сутками! А нервы они-то не железные, вообще нет.

Относительные и абсолютные погрешности

Допустим, мы определили, что пара тузов выигрывает в 50% играх (т.е. winRate = 0.5). Относительная погрешность 2%, значит абсолютная — 0.5 * 2% = 0.01 (или 1% игр). Тогда мы можем говорить, что пара тузов выигрывает в 50% ± 1% игр.

В то же время, пускай мы определили, что пара двоек выигрывает в 20% случаев (winRate = 0.2). Относительная погрешность 1%, значит абсолютная — 0.2 * 2% = 0.004 (или 0.4% игр). Тогда мы можем говорить, что пара двоек выигрывает в 20% ± 0.4% игр.

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

Имхо, фиксировать относительную погрешность разумнее, потому что это дает возможность сравнивать плохие карты с такими же.

Но для достижения меньшений абсолютной погрешности нужно больше вычислений. Т.е. мы на слабые карты отводим больше вычислительного времени, что может быть слегка глупым.

Вероятности при all-in

В целях упрощения моделирования придется начинать с самих убогих вариантов. Для начала я хочу посмотреть, каковы вероятности выигрыша в all-in игру в зависимости от разных карманных карт.

Покерные правила

Одна “сессия игры” будет состоять из:

  1. Перемешивания колоды
  2. Раздачи карманных карт всем игрокам
  3. Раздачи пяти общих карт на стол
  4. Определения выигравших игроков

Это очень упрощенный ход игры, но это хорошо описывает all-in игры. И пока что этого достаточно.

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

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

Мы вообще не предполагаем, что другие игроки будут сбрасывать слабые карты. Этот момент потом еще сильно выплывет, сломав все моделирование.

Определение победителя

Эту часть можно пропустить, если вы хорошо знакомы с правилами покера.

Каждый игрок должен в конце сессии собрать “руку” из пяти карт. Для этого он может комбинировать любые общие и карманные карты. Игрок не обязан использовать карманные карты в своей руке.

Рука имеет две составляющие: комбинация и кикеры. Что комбинация, что кикеры могут содержать от нуля до пяти карт. Для примера, можно глянуть на следующую инфографику. Светлыми картами показаны комбинации, серыми — кикеры.

(С)пизжено из гугл-картинок

Побеждает рука с более сильной комбинацией. Если комбинации равны, то побеждает рука с более сильными кикерами. При равных комбинациях и кикерах руки считаются равными.

Но есть маленькая особенность. Если комбинаций нет (на таблице — “старшая карта”), то сравниваются только самые высокие кикеры. Если комбинации есть, то сравниваются все кикеры.

Для простоты я буду считать, что флеш-рояль — это “стрит-флеш” (потому что нет разницы при детектировании победы).

Детали моделирования

В рамках нашего моделирования могут случиться такие ситуации:

  • Чистая победа (мы выиграли, все проиграли)
  • Частичная победа (мы выиграли + как минимум один игрок проиграл)
  • Ничья (все выиграли)
  • Проигрыш (мы проиграли, а кто-то выиграл)

Мы будем подсчитывать все ситуации и определять соотношение каждого типа событий к общему количеству симуляций.

Для ускорения моделирования мне пришлось снять статистические гарантии с коэффициента “количество ничьих к количеству игр”, поотому что эти события редки и нестабильны; а для обеспечения стат.гарантий таких событий нужно слишком много симуляций.

Оптимизации

Симуляция с указанными гарантиями отрабатывает реально долго. Поэтому нужно оптимизироваться.

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

Чтобы вы понимали важность оптимизаций, скажу, что на моем компьютере (i7, 2.2GHz/4.1GHz, 12 threads) симуляции отнимают порядка от получаса до трех.

Нужно исключить дублирующие сценарии. Наборы карт A♥K♢ и K♢A♥ абсолютно одинаковы, потому что перестановки не влияют на карманные карты. Эта оптимизация даст x2 ускорения (мы отсекаем примерно половину наборов). Смешно, но сначала я забыл об этой оптимизации.

Поскольку все масти равноценны, то нам нужно запускать перебирать начальные карты только для двух мастей, а не для всех четырех. Так мы по-прежнему будем учитывать эффект от одинаковых/разных мастей, но сократим компьютеру работы. Во сколько? Ну, без этой оптимизации нам нужно сделать перебор для 52 * 51 / 2 = 1326 карт. С этой оптимизацией нам нужно сделать перебор для 26 * 25 / 2 = 325 карт. Деление на двойку возникло из-за предыдущей оптимизации. Выигрыш по скорости — еще x4.

Вообще, в идеале можно еще лучше. Наборы карт A♥K♥ и A♢K♢ тоже равноценны. Как и наборы карт A♥K♢ и A♢K♥. Это даст еще x1.5-x2 (мне лениво считать сколько точно. В духе учебников: “оставим это упражнение внимательному читателю”). Я пока не реализовывал эту оптимизацию вообще. И не собираюсь, ибо подозреваю, что это может исказить общее мат.ожидание из-за неравномерного выкидывания карт.

Итого получаем, что такими нехитрыми штучками мы можем достичь ускорения до x16.

Отдельно могу отметить, что понижение статистических гарантий дает порядочный буст.

Заметки по коду

Пропускайте это. Или вы пришли читать про программирование?

Исходники: https://github.com/vlova/PokerSim. Берем и моделируем все плюс-минус честным образом.

Для того, чтобы различать и сравнивать комбинации создаем перечисление CardCombinationKind, а каждую комбинацию реализуем классом типа FullHouseCombination. В каждом соответствующем классе прописываем правила сравнения комбинации с такой же. В принципе, все сравнения можно свести к “у какой комбинации карты выше”. Увы, стрит является исключением — там туз отыгрывает разные значения карт.

Для детектирования комбинаций заводим для каждой комбинации класс-детектор типа FullHouseCombinationDetector, который получает на вход один аргумент — ISet<Card> и выдает список комбинаций.

Для детектирования лучшей руки делаем классы PokerHand и класс PokerHandDetector, которому будем подавать на вход общие + карманные карты одного игрока. Код нетривиален и его нельзя свести к тривиальному, поэтому обмазываемся параметризованными юнит-тестами (на детектирование лучшей руки у одного игрока, и на сравнение рук игроков) для хоть каких-то гарантий.

Логику генерирования колоды, раздачи карт и определения победителей прячем в класс PokerSession. Перемешивание колоды реализуем с помощью алгоритма “Фишера-Йетса”. В качестве рандома используем RNGCryptoServiceProvider (потому что стандартный рандом в C# — говно).

Логику по достижению заданной точности симуляции прячем в класс BenchmarkRunner. Логику по проведению симуляции и доп. dto-классы прячем в AllInSimulation.

В CSV-файлики сериализуем с помощью библиотеки CSVHelper.

Контринтуитивность вероятности выигрыша

Эту часть можно пропустить, если вам не интересно, что автору кажется контринтуитивным, а что нет.

По факту моделирования я обнаружил, что сумма “вероятность победы” уже для нескольких карт превышает 100%. ААА! Пфф, я долго искал ошибку, а потом осознал, что ошибки нет.

Возьмем примитивную карточную игру. В колоде 4 карты: туз, король, королева, валет. Алиса тянет вслепую карту из колоды, после неё тянет карту Боб. Побеждает тот, у кого карта больше.

У игроков не может быть одинаковых карт после раздачи, поэтому для каждой карты Алисы нужно подсчитать только победы против остальных трех карт, а не четырех.

Итак, в этой игре мы видим, что сумма WinRate равна 200%, значит это не косяк вероятностного моделирования. Ну и ок.

Зато мы можем рассчитать мат.ожидание выигрыша. Допустим Алиса и Боб ставят по 1$. А когда кто-то выигрывает, он забирает 2$. Тогда мы можем рассчитать мат.ожидание:

  Ace: 100%  = -1$ + 2$*1.00 = +1.00$
King: 66.6% = -1$ + 2$*0.66 = +0.33$
Queen: 33.3% = -1$ + 2$*0.33 = -0.33$
Jack: 0% = -1$ + 2$*0.00 = -1.00$
------
Total: = ±0.00$

Как видим, мат. ожидание выгрыша по всем картам в этой игре равно нулю. Что и стоило ожидать: при стратегии “ставить всегда” ни у одного игрока нет преимущества, а доп.источник дохода отсутствует.

В обычном покере суммарное мат. ожидание тоже должно быть равно нулю. При симуляциях оно может плюс-минус отклоняться в сторону, но не очень сильно. Иначе у нас разошлась модель и это лишний повод сказать себе “дурак”. В принципе, я этим способом обнаружил минимум одну ошибку.

Интепретация: мат.ожидание

Окей, мы посчитали вероятности. И что теперь с ними делать?

Для начала мы можем сравнивать карты через вероятности. Больше вероятность — лучше раздача. Меньше вероятность — хуже раздача. Пара тузов — круто, а семерка + двойка — дно. Спасибо кэп.

Другая альтернатива — рассчитать мат.ожидание ставки. Вот у нас есть рассчет вероятностей для пяти игроков. Мы собираемся сделать all-in и ожидаем, что в среднем amountOfPlayers (но не менее 1-го) подтвердит нашу ставку. Мы ожидаем, что игроки не будут сбрасывать слабые руки (это почти глупое предположение, но не совсем), а будут сбрасывать, только если не склонны к риску all-in.

Для того, чтобы подсчитать мат.ожидание нужно: умножить вероятность выигрыша на количество игроков и на ставку, а после отнять изначальную ставку.

E[handIncome] = -bid
+ amountOfPlayers * bid * winSingleRate

Чтобы прям совсем шик, вам нужно учесть, что у winSingleRate есть погрешность в ±1% и она будет менять мат.ожидание.

Для примера, пара тузов при пяти игроках дает вероятность выигрыша в 55.5%. Если все игроки (т.е. четыре игрока) подтвердят ставку, то при ставке в 1$ мы получим мат.ожидание -1$ + 5 * 1$ * 0.555 = +1.775$. Если ставку подтвердит два игрока, то мы получим -1$ + 3 * 1$ * 0.555 = +0.665$.

На всякий случай: amountOfPlayers — это общее количество игроков, включая нас. Подтвердить нашу же ставку мы не можем, поэтому количество подтверждающих равно amountOfPlayers - 1.

Если мы будем использовать такую стратегию достаточно часто, то мы можем гарантировать, что наш счет в долгосроке будет увеличиваться на количество ставок умноженное среднее мат.ожидание, но за вычетом блайндов. Мы можем записать такую формулу для счета:

account = account
+ bidsCount * averageMathExpectation
+ (gamesCount / averageAmountOfPlayers) * averageSmallBlind
+ (gamesCount / averageAmountOfPlayers) * averageBigBlind
± random()

Если мы решили отыгрывать все карты, то bidsCount = gamesCount. Если мы решили ставить только при хороших картах, то bidsCount так, чтобы отобразить тот факт, что хороших карт меньше, чем всех. Т.е. bidsCount = gamesCount * amountOfGoodPairs / amountOfAllPairs.

Такую стратегию можно применять в онлайн-покере, потому что там легкий переход между столами. Главное — не обнулить счет. Для этого нужно, чтобы игрок не вкладывал в одну игру ощутимую часть своего счета. В оффлайне эта стратегия почти бесполезна.

Короче, мат.ожидание рулит и бибикает. Если мат.ожидание меньше нуля — в all-in уходить не стоит.

Интерпретация: не ALL-IN

Давайте предположим, что игра в покер происходит следующим упрощенным образом:

  1. Сначала всем игрокам раздаются карманные карты.
  2. Игроки торгуются.
  3. Раздаются все пять общих карт.
  4. Игроки торгуются.
  5. Определение победителя

Давайте предположим, что игра в основном идет следующим образом: до раздачи общих карт мы поставили 20$, после раздачи общих карт мы поставили 0$ (ничего). Мы можем рассчитать мат.ожидание согласно формулам выше.

Теперь возьмем аналогичную игру, но до раздачи общих карт ставится 10$, а после раздачи общих карт ставится еще 10$. Если мы исключаем события “другие игроки фолдят, потому что увидели фиговые карты”, то мат. ожидание остается таким же.

Если мы рациональны, то во втором туре торговли мы будем фолдить при фиговых картах (и не фолдить при хороших). Фолд плохих карт уменьшает потери в мат.ожидании (т.е. мат.ожидание растет).

Т.е. даже при добавлении доп.раунда торговли мы всего-лишь изменяем базовое мат.ожидание, а потому это все-равно полезный инструмент.

Но кроме ставок 20$+0$ и 10$+10$ есть еще и другие варианты. Чем ближе мы к 20$+0$ (all-in в начале), тем ближе мат.ожидания к рассчетным. Чем ближе мы к 0$+20$ (all-in в конце), тем сильнее мы смещаем начальное мат.ожидание к нулю — понижая преимущество сильных карт и повышая преимущество слабых карт.

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

Неправильные интерпретации

Во-первых, нужно четко понимать, что мат.ожидание подсчитано только для тех случаев, когда игроки не сбрасывают карты вообще. Если игроки сбрасывают некоторые плохие карты, то у хороших карт будет падать мат.ожидание выигрыша, а у других плохих карт — будет расти мат.ожидание выигрыша.

К счастью, многие люди или блефуют, или просто тупые, поэтому мат.ожидание все еще рулит и бибикает, но профита дает не очень много для префлопа.

Во-вторых, если рассчеты были для пяти игроков — то это значит, что нельзя переносить мат.ожидание на четыре/три/два игрока.

Инфографика: вероятности

Если вас заинтересовало обладать какой-то картинкой в более удобном для восприятия виде, то в конце статьи будет секция “Дополнительные материалы” со ссылками на таблицы в GSheets.

Давайте построим веселые картиночки — двухмерную таблицу вероятностей.

Построение табличек:

  1. Выгрузка результатов симуляции — CSV файла в Google Sheets. Ибо мне лень рисовать.
  2. Формирование двух двумерных табличек — для разномастных карт и одномастных карт
  3. Из табличек исключаются дублирующие друг друга варианты (остается пол-таблички выше главной диагонали)
  4. В качестве вероятности победы берется wonRate (включает в себя ситуации и полной победы, и частичной победы, и ничьей)
  5. К табличке применяется монотонный Color Scale с Min = 0.1 (10%), Max = 0.9 (90%). Фиксированные Min/Max нужны, чтобы можно было визуально сравнивать как меняется вероятность победы

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

Префлоп победы при 2-х игроках (1 оппонент)
Префлоп победы при 5-ти игроках (4 оппонента)

Инфографика: мат.ожидания

Вот мат.ожидания куда интереснее и красивее.

Построение табличек:

  1. Мат.ожидание вычисляется не на основе winRate/winSingleRate, а на основе честного симулирования для более точного учета ситуаций “банк делит несколько игроков”. В этом есть один сомнительный момент. Требования к точности для мат.ожидания не фиксировались, но фиксировались требования к точности для winRate/winSingleRate.
  2. Это мат.ожидание выигрыша, а не счета. Если в табличке записано, что набор карт дает 0.32$, то это значит, что при ставке в 1$ вы получаете 1.32$ обратно (заработав 0.32$).
  3. Выгрузка результатов симуляции — CSV файла в Google Sheets. Ибо мне лень рисовать.
  4. Формирование двух двумерных табличек — для разномастных карт и одномастных карт
  5. Из табличек исключаются дублирующие друг друга варианты (остается пол-таблички выше главной диагонали)
  6. К табличке применяется двухцветный Color Scale с Min = -0.5$, Mid = 0.0$, Max = 2$ (90%). Фиксированные Min/Mid/Max нужны, чтобы можно было визуально сравнивать как меняется мат.ожидание при разном количестве игроков.
Мат. ожидание на префлопе при 2-х игроках (1 оппонент)
Мат. ожидание на префлопе при 3-х игроках (2 оппонента)
Мат. ожидание на префлопе при 4-х игроках (3 оппонента)
Мат. ожидание на префлопе при 5-ти игроках (4 оппонента)

Для большей наглядности, давайте расположим рядом мат.ожидание для 2-х игроков (слева) и 5-ти игроков (справа):

Мат.ожидание победы на префлопе для 2-х игроков (слева) и 5-ти (справа)

Получается, что чем больше игроков, тем более критично для победы в долгосроке выбирать только хорошие карты.

Сила карт

Помимо всего прочего, можно придумывать разные глупые правила для определения силы карт.

Например, можно заметить, что со скрипом выполняется правило “сила карт меняется в зависимости от побочной диагонали”. Вот как это выглядит:

Определение силы карт по побочной диагонале очень прикольно тем, что сумма карт всегда одинакова на одной побочной диагонале.

Например, 9♢+8♢ = 17, а также A♢ + 3♢ = 14 + 3 = 17. Здесь A = 14, потому что это 4-я карта после карты 10.

Получается, что нам нужно запомнить два числа: трешхолд для одномастных карт, и трешхолд для разномастных карт.

Ну и конечно, нужно не забыть, что пары всегда круты (кроме двоек и троек).

Получается, что мы можем применять следующее правило:

  • Если это пара (не двоек и не троек), то это хорошие карты
  • Если это одномастные карты и их сумма больше 13, то это хорошие карты
  • Если это разномастные карты и их сумма больше 18, то это хорошие карты.

Почему так меняется мат.ожидание?

Эту часть можно пропустить, если вам не интересно, почему при разном количестве игроков так сильно отличается мат.ожидание.

Довольно просто понять, что происходит с мат.ожиданием хороших карт. Когда игроков больше, то ваш выигрыш больше, а затраты остались те же самые. Т.е. мат. ожидание обязано расти.

Чутка труднее понять, что происходит с плохими картами. Почему они ухудшаются? Когда противник один, то шанс, что у него выпадут карты лучше, чем у вас равен примерно 1/2 (для случайных карт). Но когда противников несколько, то у каждого есть такой же шанс. И вероятность того, что хотя-бы у одного из противников карты будут лучше чем у вас уже много выше, чем 1/2. Т.е. плохие карты теряют победы и соответственно, теряют мат.ожидание.

Если вам выпали именно плохие карты (из нижней четверти), то шанс у оппонента уже 3/4. Чем выше шанс оппонента, чем больше количество оппонентов, тем сильнее мы скатываемся в “парадокс дней рождения”.

В принципе, то же самое происходит со всеми картами. И со средними тоже. Но там хотя-бы уменьшение количества побед компенсируется тем, что в случае победы вырастает выигрыш. А для плохих этой компенсации уже не хватает.

ALL-IN: а если оппоненты поумнее?

Допустим, мы играем с оппонентом, который пользуется стратегией “фолдить плохие карты или ставит all-in на хорошие карты”.

Стратегия оппонента

Для точности сформулируем стратегию:

  • Если карманные карты одинаковые, то идти в all-in
  • Если карты одномастные и сумма больше 13, то идти в all-in
  • Если карты разномастные и сумма больше 18, то идти в all-in
  • Иначе фолдить

Изменения в коде

Для поддержки этой симуляции пришлось завести интерфейс IPlayingStrategy для всех игровых стратегий, а также базовый класс PlayerAction для описания всех будущих экшенов. Из-за лени обработку действий игроков сделал через if/else, но так низзя.

Механизм торговоли обеспечивается в самом примитивном и убогом виде, потому что больше пока что не нужно.

Механизм распределения выигрыша раньше был внутри симуляции, а теперь ушел в PokerSession. На этот механизм пришлось писать юнит-тесты, потому что обнаружил ошибку распределения денег.

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

Заметки по мат.ожиданию

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

Так вот, это не так. Точнее, это так только до тех пор, пока игроки используют одинаковые/схожие стратегии.

Когда оппонент использует хорошую стратегию, а мы глупую, то наше мат.ожидание по всем нашим картам стремится в минуса. Такова плата за глупость.

Замечание по матожиданию в инфографике

У меня в инфографике вырезана почти половина таблицы, но оставлена главная диагональ для одинаковых карт. Это искажает мат.ожидание — с инфографики становится невозможным понять, каково оно реальное.

Если же в инфографику вернуть все удаленные ячейки, то мат.ожидание вернется к нулю.

Инфографика: мат.ожидания

Для двух игроков:

Глупый-глупый (слева) против глупый-умный (справа) при 2-х игроках (1 оппонент)

Для пяти игроков:

Глупый-глупый (слева) против глупый-умный (справа) при 5-х игроках (4 оппонента)

Интерпретация: хорошие карты

Когда наш оппонент умный, то у каждого хорошего набора карт уменьшается мат.ожидание (А♥А♢ с 0.70$ падает до 0.29$). Что вполне логично.

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

  • пара тузов сократила мат.ожидание до 40% от базового;
  • пара девяток сократила до 27% от базового;
  • пара шестерок сократила до 15% от базового.

Интерпретация: остальные карты

Но вот что странно. У каждого ужасного набора карт увеличивается мат.ожидание (3♢2♢ c -0.24$ растет до -0.13$). Эдакая регрессия к среднему. Я не знаю, как это объяснить.

А у каждого из средненьких или слегка плохеньких наборов карт мат.ожидание уменьшается. Это тоже логично. Если уменьшается общее мат.ожидание, то что-то должно уменьшаться, а хороших наборов для этого недостаточно.

Интерпретация: 2 против 5

Если вы внимательно посмотрите на то как воздействует умная стратегия на мат.ожидание отдельных наборов карт, то сможете заметить следующее.

Для двух игроков. Играя против глупых А♥А♢ зарабатывало 0.70$, а играя против умных — 0.29$ (осталось 41% от спекуляций на глупых). Играя против глупых 2♢3♥ теряло 0.32$, а играя против умных — 0.17$ (осталось 53% от спекуляций на глупых).

Для пяти игроков. Играя против глупых A♥А♢ зарабатывало 1.83$, а играя против умных — 0.96$ (осталось 52% от спекуляций на глупых). Играя против глупых 2♢3♥ теряло 0.45$, а играя против умных — 0.37$ (осталось 82% от спекуляций на глупых).

И так примерно по всем картам. Пять игроков оказались устойчивее к применению всеми стратегии сброса плохих карт. Здесь еще есть места для спекуляций.

Как конкретно извлечь из этого выгоду, мне еще не очень понятно.

Глупый-глупый против умный-умный

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

Глупый-глупый (слева) против умный-умный (справа) при 2-х игроках (1 оппонент)

Интерпретация кейса “умный-умный”

Если окинуть взглядом мат.ожидания стратегий “глупый-глупый” и “умный-умный”, то можно заметить, что разброс мат.ожиданий уменьшается.

По сути, это значит, что против типа “умной” стратегии можно противопоставить очень мало выиграшных сценариев.

В пределе у умных (но одинаково умных) игроков мат.ожидание по каждому набору карт будет равно нулю. Ненулевое мат.ожидание отдельного набора карт показывает, что можно получить прибыль изменением стратегии.

Есть концепция “Равновесие Нэша”, которая говорит о том, что в конечных играх всегда есть равновесные наборы стратегий, которые нельзя сломать. Вот равновесие Нэша как раз об этом — мат.ожидание отдельных наборов карт должно превратиться в ноль.

Мысли вслух: интересно, как именно будет выглядеть равновесие Нэша для карманных карт?

Опять про блайнды

Из-за блайндов вы всегда будете терять деньги. Поэтому нельзя бездумно фолдить “плохие карты” и считать, что вы увеличили свое мат.ожидание. Предлагаемая “умная” стратегия срезает половину карт. Это очень много.

Доп. материалы для ознакомления

Все данные (значения для каждых карт, двумерные таблички вероятностей и мат.ожидания) вы можете посмотреть в Google Sheets:

Исходники: https://github.com/vlova/PokerSim

Как можно применять на практике?

  1. Рейзить на первом туре, если у тебя хорошие карты.
  2. Сбрасывать самые дерьмовые карты.
  3. Не коллить высокие ставки на префлопе при плохих картах.
  4. Побольше внимания уделять силе стартовых карт при большом количестве игроков и поменьше при малом количестве игроков.

Спасибо, кэп

… to be continued

--

--

Viktor Love
Viktor Love

Written by Viktor Love

Software Engineer from Ukraine. TypeScript, React, C#, Angular.

Responses (1)