Записки Вредного программиста

enjoy, motherfuckers ;)

Покерный класс на PHP

Приветствую тебя, разработчик или случайно зашедший на огонек в этот уютный технический бложек. Сегодня речь пойдет о несколько математической задаче, хоть и немного там математики, да и она понятна школьнику 5го класса. Сегодня мы научимся распознавать комбинации в Техасском холдеме, именно в такую разновидность покера я играю (играл). Т.к. это очень простой класс на PHP, он не будет иметь определять старшинство одинаковых по названию комбинаций, но разнящихся по номиналу, к примеру стрит до 10 и стрит до 8 будут трактоваться этим классом, как просто стрит. В Техасском холдеме всего 10 комбинаций и мы с вами пройдемся от самой старшей из них – флеш рояль до самой младшей – старшая карта.

Флеш рояль

Высшая кобинация в покере – это стрит (стрейт) флеш до туза.

1
2
3
4
protected function _isRoyalFlush()
{
  return ($this->_isFlush() && $this->_isStraight() && array_search(14, $this->_ranks) && array_search('13', $this->_ranks));
}

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

Каре или Quad

Тут проще некуда, нужно наличие четырех одинаковых карт.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
protected function _isQuad()
{

  $test = $this->_ranks;

  $uniqueElementsCount = array();

  foreach($this->_ranks as $key => $card) {
      $test = $this->_ranks;
      unset($test[$key]);
      $uniqueElementsCount[] = count(array_unique($test));
  }


  return 1 === min($uniqueElementsCount);
}
Стрит флеш

Тот же самый флешрояль, только необязательно, чтобы стрит был до туза (10-валет-дама-кароль-туз).

1
2
3
4
protected function _isStraightFlush()
{
  return ($this->_isFlush() && $this->_isStraight());
}
Фул хаус

Это когда три карты одного достоинства и две другого. Не самая удачная реализация :)

1
2
3
4
5
6
7
8
9
10
11
protected function _isFullHouse()
{
  $ranks = $this->_ranks;
  sort($ranks);

  if ((($ranks[0] == $ranks[1] && $ranks[1] == $ranks[2]) && ($ranks[3] == $ranks[4])) // 1=2=3 and 4=5
          || ($ranks[0] == $ranks[1]) && ($ranks[2] == $ranks[3] and $ranks[3] == $ranks[4])) // 1=2 and 3=4=5
      return true;

  return false;
    }
Флеш

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

1
2
3
4
5
6
7
8
9
protected function _isFlush()
{
  $suits = $this->_suits;
  sort($suits);
  if ($suits[0] === $suits[4])
      return true;

  return false;
}
Стрит

Вне зависимости от масти, достоинство карт должно быть по старшенству (5 подряд). Из-за того, что туз может в зависимости от ситуации быть и самой старшей, и самой младшей картой, пришлось сделать две проверки.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
protected function _isStraight()
{

  $ranks = $this->_ranks;
  sort($ranks);

  // if Ace is low card in straight
  if ($key = array_search(14, $ranks)) {
      $tempRanks = $ranks;
      unset($tempRanks[$key]);
      if (array(2, 3, 4, 5) == $tempRanks) {
          return true;
      }
      unset($tempRanks);
  }

  // if Ace is high card - default algorithm
  $min = $ranks[0];
  foreach($ranks as $key => $value) {
      $ranks[$key] -= $min;
      if ($key != $ranks[$key])
          return false;
  }
  return true;
}
Сет, трипс или тройка

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

1
2
3
4
5
6
7
8
9
10
11
protected function _isThreeOfKind()
{
  $ranks = $this->_ranks;
  sort($ranks);

  if (($ranks[0] == $ranks[1] and $ranks[1] == $ranks[2])
          || ($ranks[1] == $ranks[2] and $ranks[2] == $ranks[3])
          || ($ranks[2] == $ranks[3] and $ranks[3] == $ranks[4]))
      return true;
  return false;
}
Две пары

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
protected function _isTwoPairs()
{
  $ranks = $this->_ranks;

  foreach($ranks as $key => $rank) {
      $testRanks = $ranks;
      unset($testRanks[$key]);
      sort($testRanks);
      if (($testRanks[0] == $testRanks[1])
              and ($testRanks[2] == $testRanks[3])
              and ($testRanks[0] != $testRanks[2])
              // exclude full house
              and ($testRanks[0] != $ranks[$key])
              and ($testRanks[2] != $ranks[$key])
      )
          return true;
  }
  return false;
}
Пара

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

1
2
3
4
protected function _isPair()
{
  return 4 === count(array_unique($this->_ranks));
}
Старшая карта

Последний абзаци больше для красоты, потому что если ни одна комбинация не подошла, значит комбинация – старшая карта.

1
2
3
4
protected function _isHighCard()
{
  return true;
}
Исходный код класса для опеределения покерных комбинаций на PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
<?php
/**
 * @author vredniy.ru
 *
**/
class Poker
{

    protected $_cards = array();
    //
    protected $_ranks = array();
    protected $_suits = array();

    public function __construct(array $cards)
    {
        $this->_cards = $cards;

        $rank = null;

        foreach($cards as $card) {
            switch (strtolower($card['rank'])) {
                case 't':
                    $rank = 10;
                    break;
                case 'j':
                    $rank = '11';
                    break;
                case 'q':
                    $rank = '12';
                    break;
                case 'k':
                    $rank = '13';
                    break;
                case 'a':
                    $rank = '14';
                    break;
                    ;
                default:
                    $rank = $card['rank'];
                    break;
            }
            $this->_ranks[] = $rank;
            $this->_suits[] = $card['suit'];
        }
    }

    public function checkCombination()
    {
        // is Royal Flush
        echo $this->_isRoyalFlush() ? 'royal flush' : 'not royal flush';
        echo '<br>';

        // is Quad (four of kind)
        echo $this->_isQuad() ? 'quad' : 'not quad';
        echo '<br>';

        // is StraightFlush
        echo $this->_isStraightFlush() ? 'straight flush' : 'not straight flush';
        echo '<br>';

        // is Full House
        echo $this->_isFullHouse() ? 'full house' : 'not full house';
        echo '<br>';

        // is Flush
        echo $this->_isFlush() ? 'flush' : 'not flush';
        echo '<br>';

        // is Straight
        echo $this->_isStraight() ? 'straight' : 'not straigt';
        echo '<br>';

        // is Three of Kind
        echo $this->_isThreeOfKind() ? 'three of kind' : 'not three of kind';
        echo '<br>';

        // is Two Pairs
        echo $this->_isTwoPairs() ? 'two pairs' : 'not two pairs';
        echo '<br>';

        // is one Pair
        echo $this->_isPair() ? 'pair' : 'not pair';
        echo '<br>';

        // is High Card
        echo $this->_isHighCard() ? 'high card' : 'not high card';
        echo '<br>';
    }

    /**
     * chech that combination is flush and straight and contains ace and king (exclude straight flush "ace-5")
     *
     * @return bool
     */
    protected function _isRoyalFlush()
    {
        return ($this->_isFlush() && $this->_isStraight() && array_search(14, $this->_ranks) && array_search('13', $this->_ranks));
    }

    /**
     * check combination is quad (four of kind)
     *
     * @return bool
     */
    protected function _isQuad()
    {

        $test = $this->_ranks;

        $uniqueElementsCount = array();

        foreach($this->_ranks as $key => $card) {
            $test = $this->_ranks;
            unset($test[$key]);
            $uniqueElementsCount[] = count(array_unique($test));
        }


        return 1 === min($uniqueElementsCount);
    }

    /**
     * is a straight and a flush?
     *
     * @return bool
     */
    protected function _isStraightFlush()
    {
        return ($this->_isFlush() && $this->_isStraight());
    }

    /**
     * is full house?
     * (1=2=3 and 4=5) or (1=2 and 3=4=5)
     *
     * @return bool
     */
    protected function _isFullHouse()
    {
        $ranks = $this->_ranks;
        sort($ranks);

        if ((($ranks[0] == $ranks[1] && $ranks[1] == $ranks[2]) && ($ranks[3] == $ranks[4])) // 1=2=3 and 4=5
                || ($ranks[0] == $ranks[1]) && ($ranks[2] == $ranks[3] and $ranks[3] == $ranks[4])) // 1=2 and 3=4=5
            return true;

        return false;
    }

    /**
     * is flush?
     *
     * @return bool
     */
    protected function _isFlush()
    {
        $suits = $this->_suits;
        sort($suits);
        if ($suits[0] === $suits[4])
            return true;

        return false;
    }

    /**
     * check straight. 2 attempt, 'cause ace may be high card, or low.
     *
     * @return bool
     */
    protected function _isStraight()
    {

        $ranks = $this->_ranks;
        sort($ranks);

        // if Ace is low card in straight
        if ($key = array_search(14, $ranks)) {
            $tempRanks = $ranks;
            unset($tempRanks[$key]);
            if (array(2, 3, 4, 5) == $tempRanks) {
                return true;
            }
            unset($tempRanks);
        }

        // if Ace is high card - default algorithm
        $min = $ranks[0];
        foreach($ranks as $key => $value) {
            $ranks[$key] -= $min;
            if ($key != $ranks[$key])
                return false;
        }
        return true;
    }

    /**
     * is Three of kind
     *
     * @return bool
     */
    protected function _isThreeOfKind()
    {
        $ranks = $this->_ranks;
        sort($ranks);

        if (($ranks[0] == $ranks[1] and $ranks[1] == $ranks[2])
                || ($ranks[1] == $ranks[2] and $ranks[2] == $ranks[3])
                || ($ranks[2] == $ranks[3] and $ranks[3] == $ranks[4]))
            return true;
        return false;
    }

    /**
     * is two pairs
     *
     * @return bool
     */
    protected function _isTwoPairs()
    {
        $ranks = $this->_ranks;

        foreach($ranks as $key => $rank) {
            $testRanks = $ranks;
            unset($testRanks[$key]);
            sort($testRanks);
            if (($testRanks[0] == $testRanks[1])
                    and ($testRanks[2] == $testRanks[3])
                    and ($testRanks[0] != $testRanks[2])
                    // exclude full house
                    and ($testRanks[0] != $ranks[$key])
                    and ($testRanks[2] != $ranks[$key])
            )
                return true;
        }
        return false;
    }

    /**
     * is single Pair
     *
     * @return bool
     */
    protected function _isPair()
    {
        return 4 === count(array_unique($this->_ranks));
    }

    /**
     * is High Card
     *
     * @return bool true
     */
    protected function _isHighCard()
    {
        return true;
    }

}
Небольшой пример использования
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php

require_once 'Poker.php';

$cards = array(
    array('suit' => 's', 'rank' => '3'),
    array('suit' => 's', 'rank' => '7'),
    array('suit' => 's', 'rank' => 'a'),
    array('suit' => 's', 'rank' => 't'),
    array('suit' => 's', 'rank' => 't')
);

$poker = new Poker($cards);

$poker->checkCombination();

Комбинация из 5 карт для данного класса задается массивом из 5 элементов, которые в свою очередь явлеются ассоциативными массивами (suit – это масть, rank – достоинство карты).

Эпилог

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

Комментарии