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
|
<?php
/**
* Parser class. Converts hand input string to array of sorted and normalized suits.
**/
class OsikaParser {
private $_hand;
/**
* Constructor for the parser class
* @param $hand (optional) hand to parse
**/
public function __construct($hand = NULL) {
$this->setHand($hand);
}
/**
* Sets the hand string to parse
* @param $hand string of xxxx|xxx|xxx|xxx format
**/
public function setHand($hand) {
$this->_hand = $hand;
}
/**
* Card comparison function
* @param $cardA, $cardB - characters denoting cards
* @return 1 or -1, as usort() expects
**/
private function _sort($cardA, $cardB) {
// aces first
if ($cardA == 'a') {
return -1;
}
// ...than kings...
if ($cardA == 'k') {
return ($cardB == 'a') ? 1 : -1;
}
// ...queens...
if ($cardA == 'q') {
return (in_array($cardB, ['a', 'k'])) ? 1 : -1;
}
// ...jacks...
if ($cardA == 'j') {
return (in_array($cardB, ['a', 'k', 'q'])) ? 1 : -1;
}
// ...tens...
if ($cardA == 't') {
return (in_array($cardB, ['a', 'k', 'q', 'j'])) ? 1 : -1;
}
// ... and nines
if ($cardA == '9') {
return (in_array($cardB, ['a', 'k', 'q', 'j', 't'])) ? 1 : -1;
}
// anything else goes last, as it was
return 1;
}
/**
* Suit sorting function. Uses OsikaParser::_sort as user-defined sort function for the exploded string.
**/
private function _sortSuit(&$suit) {
$temp = str_split($suit);
usort($temp, [$this, '_sort']);
$suit = implode('', $temp);
}
/**
* Where the magic happens.
**/
public function parse() {
if (!$this->_hand) {
throw new OsikaParserException('Brak podanej ręki', OsikaParserException::NO_HAND);
}
// input is case-insensitive
$this->_hand = strtolower($this->_hand);
// allow (and interpret) Polish figures abbrevs. and "10" as Ten
$this->_hand = strtr($this->_hand,
[
'10' => 't',
'w' => 'j',
'd' => 'q']);
// strip whitespace
$this->_hand = preg_replace('/\s/', '', $this->_hand);
$suits = explode('|', $this->_hand);
// check for invalid number of suits suits in the hand
if (count($suits) !== 4) {
throw new OsikaParserException('Ręka nie zawiera 4 kolorów', OsikaParserException::INVALID_SUIT_COUNT);
}
$cardCount = 0;
foreach ($suits as &$suit) {
// check for invalid characters
if (preg_match('/[^akqjtx2-9]/', $suit)) {
throw new OsikaParserException('Kolor '.$suit.' zawiera nieprawidłowe znaki', OsikaParserException::INVALID_CHARS);
}
// check for duplicate cards
foreach (['a', 'k', 'q', 'j', 't', '9'] as $honor) {
if (substr_count($suit, $honor) > 1) {
throw new OsikaParserException('Kolor '.$suit.' zawiera zduplikowany honor (lub 9)', OsikaParserException::DUPLICATE_CHARS);
}
}
$this->_sortSuit($suit);
$cardCount += strlen($suit);
}
unset($suit);
// check for wrong card count
if ($cardCount !== 13) {
throw new OsikaParserException('Ręka nie zawiera 13 kart', OsikaParserException::INVALID_CARD_COUNT);
}
return $suits;
}
}
/**
* Exception class for parser errors
**/
class OsikaParserException extends Exception {
const NO_HAND = 1; // empty (or equivalent) string provided
const INVALID_SUIT_COUNT = 2; // the hand does not contain 4 suit (i.e. 3 "|" chars)
const INVALID_CHARS = 3; // the hand contains characters that make no sense
const DUPLICATE_CHARS = 4; // the hand contains duplicate honors (or 9)
const INVALID_CARD_COUNT = 5; // the hand does not contain 13 cards
/*
Should we check if the hand contains exactly 13 cards?
I don't think the algorithm technically relies on the hand being complete.
But does the evaluation stand for incomplete hands?
E.g. if we played the first 7 tricks and are left with AK AK AK ==,
do all the quick tricks, short honor and grouped honor evaluations compute correctly?
Or even make sense?
*/
};
?>
|