WOO logo

На этой странице

Моя методология анализа видеопокера

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

В моей первоначальной программе использовался метод перебора для перебора всех 2 598 960 стартовых комбинаций карт, а затем для разыгрывания всех 32 возможных вариантов сброса, включая перебор всех 1 533 939 карт-заменителей при сбросе всех пяти. Это было примерно в 1998 году. На моем компьютере в то время я определил, что на завершение программы потребуется более года. Сегодня подобная программа заняла бы всего около месяца. Однако, используя два упрощенных способа, можно сократить время с месяца до примерно трех секунд. Ниже описано, как это сделать.

Чтобы сократить время обработки до нескольких дней, можно избежать анализа похожих комбинаций карт на старте. Например, если стартовая комбинация состоит из четырех тузов и короля, то масть короля не имеет значения. Можно сэкономить время, присвоив королю произвольную масть и умножив результат на четыре. Используя ту же логику, количество различных типов стартовых комбинаций можно сократить с 2 598 960 до 134 459. В следующих таблицах показаны способы упорядочивания мастей и соответствующего им веса для каждого класса комбинаций по рангу.

Пять одиночных близнецов

Пройдите циклом по всем комбинациям (13,5) = 1287 возможных способов выбрать 5 различных рангов из 13. Для каждой комбинации рангов установите масти (пронумерованные от 1 до 4) и весовые коэффициенты следующим образом. Например, в первой строке каждому рангу-синглтону присваивается масть 1. Существует четыре возможных масти, поэтому вместо того, чтобы делать это четыре раза, сделайте это один раз и умножьте результат на весовой коэффициент 4.

Пять уникальных рангов

class="data-heading">Вес
Пс. 1 Пс. 2 Пс. 3 Пс. 4 Петь.5
1 1 1 1 1 4
2 1 1 1 1 12
1 2 1 1 1 12
1 1 2 1 1 12
1 1 1 2 1 12
1 1 1 1 2 12
2 2 1 1 1 12
2 1 2 1 1 12
2 1 1 2 1 12
2 1 1 1 2 12
1 2 2 1 1 12
1 2 1 2 1 12
1 2 1 1 2 12
1 1 2 2 1 12
1 1 2 1 2 12
1 1 1 2 2 12
2 3 1 1 1 24
2 1 3 1 1 24
2 1 1 3 1 24
2 1 1 1 3 24
1 2 3 1 1 24
1 2 1 3 1 24
1 2 1 1 3 24
1 1 2 3 1 24
1 1 2 1 3 24
1 1 1 2 3 24
1 1 2 2 3 24
1 2 1 2 3 24
1 2 2 1 3 24
1 1 2 3 2 24
1 2 1 3 2 24
1 2 2 3 1 24
1 1 3 2 2 24
1 2 3 1 2 24
1 2 3 2 1 24
1 3 1 2 2 24
1 3 2 1 2 24
1 3 2 2 1 24
3 1 1 2 2 24
3 1 2 1 2 24
3 1 2 2 1 24
4 4 1 2 3 24
4 1 4 2 3 24
4 2 3 4 1 24
4 1 2 3 4 24
1 4 4 2 3 24
1 4 2 4 3 24
1 4 2 3 4 24
2 3 4 4 1 24
2 3 4 1 4 24
1 2 3 4 4 24

Пара

Проходим циклом по всем 13×combin(12,3)=2860 возможным способам выбора ранга для пары и трех рангов из 12 оставшихся для трех одиночных карт. Для каждой комбинации рангов устанавливаем масти (пронумерованные от 1 до 4) и весовые коэффициенты следующим образом. Например, в первой строке масти пары устанавливаются равными 1 и 2, а масти одиночных карт — 1. Существует combin(4,2)=6 способов выбрать масти пары и 2 способа выбрать масть для одиночных карт, равную одной из мастей пары, что дает весовой коэффициент 6×2=12.

Пара

Пара 1 Пара 2 Пс. 1 Пс. 2 Пс. 3 Масса
1 2 1 1 1 12
1 2 1 1 2 12
1 2 1 2 1 12
1 2 2 1 1 12
1 2 1 1 3 24
1 2 1 3 1 24
1 2 3 1 1 24
1 2 1 3 3 24
1 2 3 1 3 24
1 2 3 3 1 24
1 2 3 3 3 12
1 2 1 2 3 24
1 2 1 3 2 24
1 2 3 1 2 24
1 2 3 4 4 12
1 2 4 3 4 12
1 2 4 4 3 12
1 2 1 3 4 24
1 2 3 1 4 24
1 2 3 4 1 24

Две пары

Проходим по всем комбинациям (13,2) × 11 = 858 возможных способов выбрать два ранга из 13 для двух пар и один ранг из оставшихся 11 для одиночной пары. Для каждой комбинации рангов устанавливаем масти (пронумерованные от 1 до 4) и весовые коэффициенты следующим образом. Например, в первой строке масти первой пары устанавливаются равными 1 и 2, масти второй пары — 3 и 4, а масть одиночной пары — 1. Существует комбинаций (4,2) = 6 способов выбрать масти для первой пары. Вторая пара имеет две другие масти, поэтому есть только один вариант выбора — 1. Одиночная пара может иметь любую из мастей первой пары, поэтому есть две возможности. Таким образом, весовой коэффициент в первой строке равен 6 × 1 × 2 = 12.

Две пары

Пара 1
Карта 1
Пара 1
Карта 2
Пара 2
Карта 1
Пара 2
Карта 2
Пс. 1 Масса
1 2 3 4 1 12
1 2 3 4 3 12
1 2 1 3 1 24
1 2 1 3 2 24
1 2 1 3 3 24
1 2 1 3 4 24
1 2 1 2 1 12
1 2 1 2 3 12

Тройка

Проходим по всем 13×combin(12,2)=858 возможным способам выбрать один ранг из 13 для тройки и 66 способам выбрать два синглтона из остальных 12 рангов. Для каждой комбинации рангов устанавливаем масти (пронумерованные от 1 до 4) и весовые коэффициенты следующим образом. Например, в первом ряду масти тройки равны 1, 2 и 3, а масти двух синглтонов равны двум из трех мастей, представленных в тройке. Существует combin(4,3)=4 способа выбрать 3 из 4 мастей для тройки, 3 способа выбрать масть из этих трех для первого синглтона и 2 способа выбрать масть для второго синглтона. Таким образом, весовой коэффициент для первого ряда равен 4×3×2=24.

Тройка

3 вида
Карта 1
3 вида
Карта 2
3 вида
Карта 3
Пс. 1 Пс. 2 Масса
1 2 3 1 2 24
1 2 3 1 4 12
1 2 3 4 1 12
1 2 3 1 1 12
1 2 3 4 4 4

Аншлаг

Пройдите циклом все 13×12=156 возможных способов выбрать один ранг из 13 для тройки и 12 способов выбрать ранг для пары. Для каждой комбинации рангов установите масти (пронумерованные от 1 до 4) и весовые коэффициенты следующим образом. Например, в первом ряду масти пары установлены равными 1 и 2, а масти тройки равными 1, 2 и 3. Существует комбинаций (4,2)=6 способов выбрать масти для пары. Тройка использует обе масти пары и одну из двух других. Таким образом, весовой коэффициент для первого ряда равен 6×2×2=12.

Аншлаг

Пара
Карта 1
Пара
Карта 2
3 вида
Карта 1
3 вида
Карта 2
3 вида
Карта 3
Масса
1 2 1 2 3 12
1 4 1 2 3 12

Четыре одинаковых

Пройдите циклом все 13×12=156 возможных способов выбрать один ранг из 13 для четверки и 12 способов выбрать ранг для одиночной карты. Для каждой комбинации рангов установите масти (пронумерованные от 1 до 4) и весовые коэффициенты следующим образом. Например, в первом ряду масти четверки равны 1, 2, 3 и 4, а масти одиночной карты равны 1. Есть только один способ выбрать 4 масти из 4 для тройки и 4 способа выбрать масть из 4 для одиночной карты. Таким образом, весовой коэффициент для первого ряда равен 1×4×2=4.

Четыре одинаковых

4 вида
Карта 1
4 вида
Карта 2
4 вида
Карта 3
4 вида
Карта 4
Пс. 1 Масса
1 2 3 4 1 4

Описанный выше шаг сократит время вычислений на 95%, но всё равно займёт несколько часов, если перебирать 1 533 939 возможных комбинаций карт-заменителей. Секрет программы, работающей за три секунды, заключается в том, чтобы не зацикливаться на этапе розыгрыша. Вот как это сделать:

  1. Инициализируйте следующие массивы:
    • Массив 1: размер 2 598 960
    • Массив 2: размер 270 725 на 16
    • Массив 3: размер 22100 на 16
    • Массив 4: размер 1326 на 16
    • Массив 5: размер 52 на 16
    • Массив 6: размер 16
    Число 16 обозначает максимальное количество типов выигрышных комбинаций в дро-игре. Настройте это значение по своему усмотрению. Я никогда не видел видеопокера с более чем 16 элементами в таблице выплат.
  2. Пройдите циклом все 2 598 960 комбинаций из 5 карт (из 52). На этом этапе НЕ используйте сокращенный вариант с 134 459 комбинациями карт. Для каждой комбинации карт в раздаче выполните следующие действия:
    • Оцените его по покерной ценности.
    • Поместите счет в массив array0. Поместите первую раздачу в элемент 0 массива и увеличивайте значение на 1 для каждой последующей раздачи.
    • Для каждого из 5 способов выбрать 4 из 5 карт в раздаче, преобразуйте четыре карты в индекс от 0 до 270 724 (я объясню, как это сделать позже), и увеличьте элемент [индекс][счет руки] массива array1 на 1.
    • Для каждого из 10 способов выбрать 3 из 5 карт в раздаче, преобразуйте три карты в индекс от 0 до 22099 и увеличьте элемент [индекс][счет руки] массива array2 на 1.
    • Для каждого из 10 способов выбрать 2 из 5 карт в раздаче, преобразуйте две карты в индекс от 0 до 1325 и увеличьте элемент [индекс][счет руки] массива array3 на 1.
    • Для каждого из 5 способов выбрать 1 из 5 карт при раздаче, преобразуйте карту в индекс от 0 до 51 и увеличьте элемент [индекс][счет руки] массива array4 на 1.
    • Увеличьте элемент [hand score] массива array5 на 1.
    На этом этапе у вас будут массивы, показывающие возможные результаты наличия любого набора карт при раздаче, без учета штрафных карт (карт, которые вы сбросили). Массив 0 будет содержать результат сброса нуля карт, массив 1 — сброса одной карты и так далее.
  3. Далее, пройдите циклом по 134 459 классам комбинаций карт, описанным выше.
  4. Чтобы определить ценность всех пяти карт, переведите значения пяти карт в индекс и найдите значение покерной карты в массиве lin array0.
  5. Чтобы определить ценность любых четырех карт, переведите эти четыре карты в порядковый номер и найдите возможные исходы раздачи в соответствующем элементе массива array1. Однако это будет включать в себя получение карты, которую вы сбросили при раздаче. Поэтому вам следует вычесть единицу из элемента массива, связанного с ценностью покера, при которой у вас есть все пять карт. Например, если у вас есть J♣, Q♣, K♣, A♣ и вы сбрасываете 2♥, то будет 1 способ получить роял-карту, 8 способов получить флеш, 3 способа получить стрит, 12 способов получить пару валетов или лучше и 23 способа получить проигрышную руку. Однако массив array1 покажет, что есть 24 способа получить проигрышную руку, включая получение 2♥ при раздаче. Поэтому вам нужно вычесть результат раздачи всех карт из возможных исходов 5 способов раздачи 4 карт.
  6. Убедитесь, что вы понимаете логику предыдущего шага, потому что на этом шаге мы переходим на следующий уровень. Для 10 способов держать любые 3 карты вам нужно будет найти возможные исходы в массиве array2. Затем вычтите возможные исходы из массива array1 для 3 карт, которые вы держите, и для каждой из карт, которые вы сбрасываете. Например, для значения, полученного при оставлении 2♣ 2♥ 2♠ и сбросе 4♥ и J♠, начните со значений в array2 для 2♣ 2♥ 2&пики, а затем вычтите значения для 2♣ 2♥ 2♠ 4♥ и 2♣ 2♥ 2♠ J♠. Однако это приведет к двойному вычитанию при оставлении всех пяти карт. Поэтому вам нужно добавить обратно то, что вы получите, держа все 5 карт.
  7. По той же логике, чтобы хранить любые 2 карты, начните со значений в массиве array3, вычтите соответствующие наборы карт из массива array2, добавьте обратно соответствующие наборы карт из массива array1 и вычтите обратно элемент, хранящий все карты, из массива array0.
  8. Для хранения 1 карты начните с соответствующих значений из массива 4, вычтите соответствующие значения из массива 3, добавьте соответствующие значения из массива 2, вычтите соответствующие значения из массива 1 и добавьте соответствующее значение из массива 0.
  9. Чтобы отбросить все значения, начните с массива array5, затем вычтите соответствующие значения из array4, добавьте соответствующие значения из array3, вычтите соответствующие значения из array2, добавьте соответствующие значения из array1 и вычтите соответствующее значение из array0.
  10. Теперь у вас должно быть количество комбинаций всех возможных исходов для всех 32 способов разыграть эту руку. Определите ожидаемое значение каждой из них. Для хода, приведшего к наибольшему ожидаемому значению, добавьте в массив всех возможных исходов игры возможные исходы этого хода. Не забудьте умножить на соответствующий вес раздачи.
  11. После перебора всех 134 459 вариантов стартовых рук вы должны получить количество способов получить каждую из них в качестве дро-комбинации. Используйте этот массив, чтобы определить общую отдачу от игры.

Ниже представлены четыре подпрограммы для перевода от 2 до 5 карт (пронумерованных от 0 до 51) и возврата значения индекса.

int HandIndex2(int c1, int c2){ int r; r=combin_array[52][2]-combin_array[52-c1][2]; r+=combin_array[51-c1][1]-combin_array[52-c2][1]; return r;}int HandIndex3(int c1, int c2, int c3){ int r; r=combin_array[52][3]-combin_array[52-c1][3]; r+=combin_array[51-c1][2]-combin_array[52-c2][2]; r+=combin_array[51-c2][1]-combin_array[52-c3][1]; return r;}int HandIndex4(int c1, int c2, int c3, int c4){ int r; r=combin_array[52][4]-combin_array[52-c1][4]; r+=combin_array[51-c1][3]-combin_array[52-c2][3]; r+=combin_array[51-c2][2]-combin_array[52-c3][2]; r+=combin_array[51-c3][1]-combin_array[52-c4][1]; return r;}int HandIndex5(int CardIndex[]){ int r; r=combin_array[52][5]-combin_array[52-CardIndex[0]][5]; r+=combin_array[51-CardIndex[0]][4]-combin_array[52-CardIndex[1]][4]; r+=combin_array[51-CardIndex[1]][3]-combin_array[52-CardIndex[2]][3]; r+=combin_array[51-CardIndex[2]][2]-combin_array[52-CardIndex[3]][2]; r+=combin_array[51-CardIndex[3]][1]-combin_array[52-CardIndex[4]][1]; return r;}

Ссылки

На этом сайте объясняется, как автор ускорил работу своего анализатора видеопокера с года до семи секунд.

На сайте VP Genius есть отличная страница, посвященная программированию видеопокера.