В этой статье я расскажу про мой способ расчета приоритета комбинаций в техасском холдеме (покере с 2 карманными картами и 5 общими). Условно разделю статью на 6 частей: Создание массива карт, Расчет Стрита, Расчет Флеша и Стрит Флеша, Расчет Парных комбинаций, Окончательный расчет, Вывод на экран. Код пишу в процедурном стиле.
Цель: получить на выходе программы удобочитаемый результат, доступный для дебаггинга и отладки еще на стадии написания кода. Для достижения поставленной цели делаю разницу между комбинациями в 1e+2 (100) раз.
Примерный вид того, что должно получиться в итоге, где 2 последние карты считаю карманными, а первые 5 общими:
Создание массива карт
В колоде 4 масти и 13 достоинств, поэтому массив из 7 чисел буду формировать в диапазоне [2 — 14, 102 — 114, 202 — 214, 302 — 314], а далее оперировать остатками от деления на 100 (%100) этих чисел. Можно, конечно, формировать массив из чисел в диапазоне [2 — 14, 22 — 34, 42 — 54, 62 — 74], а затем сравнивать остатками от деления на 20 (%20). Но удобочитаемость первого варианта по-моему на лицо, поэтому остановлюсь на нем.
function cardsCreation()
{
$arrayCards = [];
for ($i = 0; $i < 7; $i++) {
$card = mt_rand(2, 14); // создаю случайное число в диапозоне от 2 до 14
$multiplier = mt_rand(0, 3); //формирую случайным образом множитель
if (1 == $multiplier) { //если множитель равен 1, добавляю к числу 100
$card = $card + 100;
} else if (2 == $multiplier) { //если множитель равен 2, добавляю к числу 200
$card = $card + 200;
} else if (3 == $multiplier) { //если множитель равен 3, добавляю к числу 300
$card = $card + 300;
}
if (!in_array($card, $arrayCards)) { //проверяю, есть ли в массиве такое число, если нет добавляю его в массив
$arrayCards = array_merge($arrayCards, [$card]);
} else { // если число есть в массиве, откатываю цикл, чтобы сформировать новое число
$i--;
}
}
return $arrayCards;
}
Определение и расчет комбинации Стрит
Стрит — это комбинация, где достоинства карт идут по порядку. Поэтому нет смысла учитывать при расчете все карты, составляющие данную комбинацию, достаточно лишь первой по старшинству карты.
Существует несколько вариантов определения и расчета комбинации Стрит. Думаю, большинство из них сводится к задаче отсортировать остатки от деления членов исходного массива в порядке убывания, а затем каким-либо образом выделить элементы, которые составляю комбинацию (в случае, когда более 5 карт составляют стрит, нужно выбрать наибольший).
Суть моего метода определения стрита сводится к тому, что после сортировки я буду проверять сначала первые пять карт на стрит, затем вторые и третьи. Также не забываю что туз, двойка, тройка, четверка и пятерка тоже составляю стрит. Поэтому, если в наборе есть туз (значение 14), к массиву добавляю восьмой элемент — 1. И следовательно в этом случае нужно проверить четвертую пятерку карт.
function straight(array $arrayCards) {
$newArrayCards = [];
$ace = false;
foreach ($arrayCards as $arrayCard) {
$newArrayCards = array_merge($newArrayCards, [$arrayCard % 100]); //создаю массив с остатками от деления на 100
if(14 == $arrayCard % 100) { //проверка на туз для комбинации А, 2, 3, 4, 5
$ace = true;
}
}
if($ace == true) {
$newArrayCards = array_merge($newArrayCards, [1]); //если в массиве присутствует туз, добавляю к массиву 1
}
rsort($newArrayCards); //сортирую массив с числами в порядке убывания
$count = 0; //счетчик, к которому добавляется 1 в случае, если разница между текущим элементом цикла и предыдущим = 1
$length = 4; //число, показывает до какого элемента массива проверять
$result = 0; //окончательный результат
$begin = 0; //число показывает, с какого элемента массива проверять
for($i = 1; $i <= $length; $i++) {
if (-1 == ($newArrayCards[$i] - $newArrayCards[$i - 1])) {
$count++;
}
if($length == $i) {
if(4 == $count) { //$count == 4, когда стрит
$result = $newArrayCards[$begin] * 1e+8;
}
else if($length < 6 or (6 == $length and $ace == true)) {//если стрита нет, идем еще на один круг
$length++; //увеличиваем число, до которого будем проверять
$begin++; //увеличиваем число, с которого будем проверять
$i = $begin; //при попадании в for к $i автоматически добавится 1 ($i++ в for)
$count = 0; //обнуляю счетчик
}
}
}
return $result;
}
Определение и расчет комбинаций Флеш и Стрит Флеш
Флеш — комбинация из карт одной масти. При расчете нужно учитывать все карты, составляющие комбинацию.
При расчете стрит флеша, как и при расчете стрита, можно учитывать только старшую карту комбинации.
Чтобы определить флеш, пробегусь циклом по массиву. В цикле создам 4 вспомогательных массива (по количеству мастей). В перый массив положу все карты первой масти, во второй — второй и т.д. Если какой-то масти нет, соответствующий массив будет пустой. Если количество элементов массива больше либо равно 5, значит данная масть образовала флеш.
Флеш более приоритетен, чем стрит, поэтому именно в функции флеша логично проверить не образовала ли комбинация стрит флеш. Также, думаю, понятно, что неудобно писать отдельную функцию, которая проверяет и стрит, и флеш. Лучше написать функцию, которая проверяет массив на флеш, и в ней же вызвать функцию, которая проверяет флеш на стрит.
Если флеш не образует стрит флеш, отсортирую остатки от деления на 100 (%100) исходного массива. Далее умножу первые пять членов полученного массива на 1е+10, 1е+8, 1е+6, 1е+4, 1е+2 соответственно.
Функцию по определению стрит флеша построю на основе функции по определению стрита, с той лишь разницей, что нужно учесть, что в функцию может прийти массив с количеством элементов меньше 7 (я ведь туда отправляю только те карты, которые образуют флеш).
Функция для определения Флеша:
function pokerFlush(array $arrayCards) {
$suit1 = []; //первая масть
$suit2 = []; //вторая масть
$suit3 = []; //третья масть
$suit4 = []; //четвертая масть
foreach ($arrayCards as $arrayCard) { //создаю 4 массива, содержащих разные масти исходного массива
if($arrayCard >= 2 and $arrayCard <= 14) {
$suit1 = array_merge($suit1, [$arrayCard]);
} else if($arrayCard >= 102 and $arrayCard <= 114) {
$suit2 = array_merge($suit2, [$arrayCard]);
} else if($arrayCard >= 202 and $arrayCard <= 214) {
$suit3 = array_merge($suit3, [$arrayCard]);
} else {
$suit4 = array_merge($suit4, [$arrayCard]);
}}
if(count($suit1) >= 5) { //если количество карт первой масти больше или равно 5
$result = straightFlush($suit1); //проверяю не образует ли данная комбинация стрит флеш
if(0 == $result) {//если стрит флеша нет
foreach ($suit1 as $key1 => $s1) {//выбираю остатки от деления на 100
$suit1[$key1] = $s1 % 100;
}
rsort($suit1); //сортирую массив по убыванию
$result = $suit1[0] * 1e+10 + $suit1[1] * 1e+8 + $suit1[2] * 1e+6 + $suit1[3] * 1e+4 + $suit1[4] * 1e+2;
}
} else if (count($suit2) >= 5) { //если количество карт второй масти больше или равно 5
$result = straightFlush($suit2);
if(0 == $result) {
foreach ($suit2 as $key2 => $s2) {
$suit2[$key2] = $s2 % 100;
}
rsort($suit2);
$result = $suit2[0] * 1e+10 + $suit2[1] * 1e+8 + $suit2[2] * 1e+6 + $suit2[3] * 1e+4 + $suit2[4] * 1e+2;
}
} else if (count($suit3) >= 5) { //если количество карт третьей масти больше или равно 5
$result = straightFlush($suit3);
if(0 == $result) {
foreach ($suit3 as $key3 => $s3) {
$suit3[$key3] = $s3 % 100;
}
rsort($suit3);
$result = $suit3[0] * 1e+10 + $suit3[1] * 1e+8 + $suit3[2] * 1e+6 + $suit3[3] * 1e+4 + $suit3[4] * 1e+2;
}
} else if (count($suit4) >= 5) { //если количество карт четвертой масти больше или равно 5
$result = straightFlush($suit4);
if(0 == $result) {
foreach ($suit4 as $key4 => $s4) {
$suit4[$key4] = $s4 % 100;
}
rsort($suit4);
$result = $suit4[0] * 1e+10 + $suit4[1] * 1e+8 + $suit4[2] * 1e+6 + $suit4[3] * 1e+4 + $suit4[4] * 1e+2;
}
} else {
$result = 0;
}
return $result;
}
Функция для определения Стрит Флеша (аналогична Стриту, только нужно учесть, что количество элементов массива может быть не только 7, но еще и 5, и 6, и что возвращаемый результат этой функции должен быть больше):
function straightFlush(array $arrayCards) {
$newArrayCards = [];
$ace = false;
foreach ($arrayCards as $arrayCard) {
$newArrayCards = array_merge($newArrayCards, [$arrayCard % 100]);
if (14 == $arrayCard % 100) {
$ace = true;
}
}
if ($ace == true) {
$newArrayCards = array_merge($newArrayCards, [1]);
}
rsort($newArrayCards);
$count = 0;
$length = 4;
$result = 0;
$begin = 0;
for ($i = 1; $i <= $length; $i++) {
if (-1 == ($newArrayCards[$i] - $newArrayCards[$i - 1])) {
$count++;
}
if ($length == $i) {
if (4 == $count) {
$result = $newArrayCards[$begin] * 1e+16;
} else if ((7 == count($arrayCards) and ($length < 6 or (6 == $length and $ace == true))) or
(6 == count($arrayCards) and ($length < 5 or (5 == $length and $ace == true))) or //если число элементов исходного массива = 6
(5 == count($arrayCards) and (5 == $length and $ace == true))) { //если число элементов исходного массива = 5
$length++;
$begin++;
$i = $begin;
$count = 0;
}
}
}
return $result;
}
Определение и расчет парных комбинаций
Существует пять парных комбинаций: каре (4 карты одного достоинства), фулл хаус (3 карты одного достоинства и 2 карты другого достоинства), тройка (3 карты одного достоинства), две пары (2 карты одного достоинства и 2 карты другого достоинства), пара (2 карты одного достоинства).
Важно при расчете помнить, что в комбинации фулл хаус главным является сочетание из трех карт, а из двух второстепенным, т.е., например, комбинацию из 3-х двоек и 2-х десяток будем считать, как 3*1e+12 + 10*1e+10, а не по достоинству карт.
А также при расчете парных комбинаций нужно учитывать проблемы:
1. То, что более 5 карт образуют комбинацию.
2. То, что у нас может возникнуть ситуация, при которой комбинаций более одной.
Для подсчета совпадения карт буду использовать три счетчика, которые будут считать парные карты в колоде. Там, где счетчик равен 1 — участвуют 2 карты в комбинации, где счетчик 2 — 3 карты, где 3 — 4 карты.
Рассмотрим все возможные варианты сочетания счетчиков:
100 — пара
110 — 2 пары
111 — 3 пары (нужно преобразовать к виду 110 (две пары))
200 — тройка
210 — фулл хаус
120 — фулл хаус (нужно преобразовать к виду 210 (фулл хаус))
211 (121, 112) — фулл хаус + пара (нужно преобразовать к виду 210 (фулл хаус))
220 — 2 тройки или фулл хаус + 1 карта (нужно преобразовать к виду 210 (фулл хаус))
300 — каре
310 (130) — каре + пара (нужно преобразовать к виду 300 (каре))
320 (230) — каре + тройка (нужно преобразовать к виду 300 (каре))
Далее произведу окончательный расчет парных комбинаций.
function couple(array $arrayCards) {
$newArrayCards = [];
foreach ($arrayCards as $arrayCard) {
$newArrayCards = array_merge($newArrayCards, [$arrayCard % 100]); //создаю массив с остатками от деления на 100
}
rsort($newArrayCards); //сортирую массив с числами в порядке убывания
$count1 = 0; //счетчик для первой пары
$count2 = 0; //счетчик для второй пары
$count3 = 0; //счетчик для третьей пары
$match1 = 0; //сюда положу значение первой пары
$match2 = 0; //сюда положу значение второй пары
$match3 = 0; //сюда положу значение третьей пары
for($i = 1; $i < count($newArrayCards); $i++) {
if ($newArrayCards[$i] == $match1 or $match1 == 0) { //первое парное сочетание
if ($newArrayCards[$i] == $newArrayCards[$i - 1]) {
$match1 = $newArrayCards[$i];
$count1++;
}
} else if ($newArrayCards[$i] == $match2 or $match2 == 0) { //второе парное сочетание
if ($newArrayCards[$i] == $newArrayCards[$i - 1]) {
$match2 = $newArrayCards[$i];
$count2++;
}
} else if ($newArrayCards[$i] == $match3 or $match3 == 0) { //третье парное сочетание
if ($newArrayCards[$i] == $newArrayCards[$i - 1]) {
$match3 = $newArrayCards[$i];
$count3++;
}
}
}
//здесь я преобразую 111 к 110 (2 пары) и 211 к 210 (фулл хаус)
if(($count1 == 1 or $count1 == 2) and $count2 == 1 and $count3 == 1) {
$count3 = 0;
}
//здесь я преобразую 121 сначала к 211 для простоты вычислений, а затем к 210 (фулл хаус)
else if($count2 == 2 and $count1 == 1 and $count3 == 1) {
$support = $match2;
$match2 = $match1;
$match1 = $support;
$count1 = 2;
$count2 = 1;
$count3 = 0;
}
//здесь я преобразую 112 сначала к 211 для простоты вычислений, а затем к 210 (фулл хаус)
else if($count3 == 2 and $count1 == 1 and $count2 == 1) {
$support = $match3;
$match2 = $match1;
$match1 = $support;
$count1 = 2;
$count3 = 0;
}
//здесь я преобразую 220 к 210 (фулл хаус)
else if($count1 == 2 and $count2 == 2 and $count3 == 0) {
$count2 = 1;
}
//здесь я преобразую 120 к 210 (фулл хаус)
else if ($count1 == 1 and $count2 == 2 and $count3 == 0) {
$support = $match1;
$match1 = $match2;
$match2 = $support;
$count1 = 2;
$count2 = 1;
}
//320 к 300 и 310 к 300
else if($count1 == 3 and ($count2 == 2 or $count2 == 1)) {
$count2 = 0;
}
//230 к 320 и затем к 300 и 130 к 310 и затем к 300
else if($count2 == 3 and($count1 == 2 or $count1 == 1)) {
$support = $match2;
$match2 = $match1;
$match1 = $support;
$count1 = 3;
$count2 = 0;
}
//каре
if ($count1 == 3) {
$count1 = 1e+14;
}
//фулл хаус
else if ($count1 == 2 and $count2 == 1) {
$count1 = 1e+12;
$count2 = 1e+10;
}
//тройка
else if ($count1 == 2 and $count2 == 0) {
$count1 = 1e+6;
}
//2 пары
else if ($count1 == 1 and $count2 == 1) {
$count1 = 1e+4;
$count2 = 1e+2;
}
//пара
else if ($count1 == 1 and $count2 == 0) {
$count1 = 1e+2;
}
$result = $match1 * $count1 + $match2 * $count2;// $match1 и $match2 будут равны 0, если совпадений не было
return $result;
}
Окончательный расчет
Теперь у нас имеются функции, которые проверяют массив карт на стрит, флеш, стрит флеш и парные комбинации. Вызову их вместе и посчитаю окончательный результат, не забываю также и про расчет старшей карты.
Расчет старшей карты:
1. Определю наибольшую и наименьшие по приоритетности карманные карты. Для этого сравню их остатками от деления на 100 (%100).
2. К наибольшей по приоритетности карте добавлю наименьшую по приоритетности деленную на 100.
function priority(array $arrayCards) {
//здесь я определяю старшую карту
if($arrayCards[5] % 100 > $arrayCards[6] % 100) { //условились, что две последние карты массива - карманные
$highCard1 = $arrayCards[5] % 100;
$highCard2 = $arrayCards[6] % 100;
} else {
$highCard1 = $arrayCards[6] % 100;
$highCard2 = $arrayCards[5] % 100;
}
$flush = pokerFlush($arrayCards); //вызываю функцию для расчета флеша
$straight = straight($arrayCards); //вызываю функцию для расчета стрита
$couple = couple($arrayCards); //вызываю функцию для расчета пар
//далее определяю результат согласно приоритету комбинаций
if($flush >= 1e+16) {
$result = $flush; //стрит флеш
} else if($couple >= 1e+14 and $couple < 1e+16) {
$result = $couple; //каре
} else if($couple >= 1e+12 and $couple < 1e+14) {
$result = $couple; //фулл хаус
} else if($flush >= 1e+10) {
$result = $flush; //флеш
} else if($straight >= 1e+8 and $straight < 1e+10) {
$result = $straight; //стрит
} else if($couple >= 1e+6 and $couple < 1e+8) {
$result = $couple; //тройка
} else if($couple >= 1e+4 and $couple < 1e+6) {
$result = $couple; //две пары
} else if($couple >= 1e+2 and $couple < 1e+4) {
$result = $couple; //пара
} else {
$result = $highCard1 + $highCard2 * 1e-2; //старшая карта
}
return $result;
}
О выводе на экран
Выводила я результат в браузер, используя вот такой спрайт (брала картинку с сайта: fondhristianin.ru/?p=2941), как background-image для div.
Ширину и высоту для div задавала равными размеру одной карты, т.е. если размер изображения 572px*328px (как в данном случае), а в ширину количество карт равно 13, в высоту — 5, ширину и высоту задавала 44px*65.6px. Далее меняла background-position с учетом ранее сформированного массива карта.
Расчет background-position для любого из divов по оси х:
100/12 * ($arrayCards[$i] % 100 - 2)
где $arrayCards — ранее сформированный массив.
$i — порядковый номер карты.
Пояснения к расчету:
В ряду 13 карт, начало 1-ой карты — 0%, начало 13-ой карты — 100%, поэтому разница позиций — 100% / 12 (а не 13). Приводим числа карт из вида [2-14, 102-114, 202-214, 302-314] к [0-12]. Для этого возьмет остаток от числа карты и отнимем от остатка 2. Умножим полученное число на разницу позиций.
Расчет background-position для любого из divов по оси y:
100/4 * floor($arrayCards[$i] / 100)
Пояснение к расчету:
В ряду 5 карт, начало 1-ой карты — 0%, начало 5-ой карты — 100%, поэтому разница позиций — 100% / 4 (а не 5). Приводим числа карт из вида [2-14, 102-114, 202-214, 302-314] к [0-3]. Для этого разделим число карты на 100 и округлим в меньшую сторону (можно использовать и простую операцию округления, она сработает аналогичным образом). Умножим полученное число на разницу позиций.
Автор: heartsease