-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathRankOrder.php
More file actions
234 lines (211 loc) · 6.79 KB
/
Copy pathRankOrder.php
File metadata and controls
234 lines (211 loc) · 6.79 KB
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
<?php
declare(strict_types=1);
namespace Likewinter\CardDeck;
use Likewinter\CardDeck\Card\Rank;
/**
* Game-specific rank ordering and values.
*
* A Rank by itself has no ordering — it is just an identity. Different games
* order ranks differently (poker: 2 < 3 < ... < A; blackjack: J/Q/K all
* equal 10; belote: J > 9 > A > 10 > K > Q > 8 > 7). This class encapsulates
* a game's rank ordering so generic entities stay game-agnostic.
*
* The "value" of a rank is an integer used for comparison; it has no
* intrinsic meaning beyond ordering within a given RankOrder.
*
* Construct via the static factories or fromRanks():
* RankOrder::poker()
* RankOrder::blackjack()
* RankOrder::fromRanks(Rank::Nine, Rank::Jack, Rank::Queen, Rank::King, Rank::Ten, Rank::Ace)
*/
final readonly class RankOrder
{
/**
* @param array<string, int> $values Map of rank name => comparison value.
* @param array<string, Rank> $nextMap Map of rank name => next-higher Rank.
* @param array<string, Rank> $prevMap Map of rank name => next-lower Rank.
*/
private function __construct(
private array $values,
private Rank $highest,
private array $nextMap,
private array $prevMap,
) {}
/**
* Build a RankOrder from an ordered list of ranks (lowest first).
*
* Each rank gets a value equal to its position (1-based). Duplicate
* values are not supported — for games where multiple ranks share a
* value (e.g. blackjack face cards), use the dedicated factory.
*/
public static function fromRanks(Rank ...$ordered): self
{
if ($ordered === []) {
throw new \InvalidArgumentException('RankOrder requires at least one rank');
}
$values = [];
$i = 0;
foreach ($ordered as $rank) {
$values[$rank->name] = ++$i;
}
return self::build($values, $ordered[count($ordered) - 1]);
}
/**
* Standard poker ordering: 2 < 3 < ... < K < A, with Ace highest.
* Values match rank face values (Two=2, ..., Ace=14) because
* PokerHand's wheel detection depends on them.
*/
public static function poker(): self
{
return self::build(values: [
Rank::Two->name => 2,
Rank::Three->name => 3,
Rank::Four->name => 4,
Rank::Five->name => 5,
Rank::Six->name => 6,
Rank::Seven->name => 7,
Rank::Eight->name => 8,
Rank::Nine->name => 9,
Rank::Ten->name => 10,
Rank::Jack->name => 11,
Rank::Queen->name => 12,
Rank::King->name => 13,
Rank::Ace->name => 14,
], highest: Rank::Ace);
}
/**
* Poker ordering where Ace is treated as 1 (for the wheel straight
* A-2-3-4-5). Used by PokerHand for straight detection.
*/
public static function pokerLowAce(): self
{
return self::build(values: [
Rank::Ace->name => 1,
Rank::Two->name => 2,
Rank::Three->name => 3,
Rank::Four->name => 4,
Rank::Five->name => 5,
Rank::Six->name => 6,
Rank::Seven->name => 7,
Rank::Eight->name => 8,
Rank::Nine->name => 9,
Rank::Ten->name => 10,
Rank::Jack->name => 11,
Rank::Queen->name => 12,
Rank::King->name => 13,
], highest: Rank::King);
}
/**
* Blackjack hard values: 2-10 at face, J/Q/K = 10, Ace = 11.
* Soft/hand-total logic is the game's responsibility.
*
* Note: J/Q/K share the value 10, so this cannot use fromRanks()
* (which assigns unique positional values).
*/
public static function blackjack(): self
{
return self::build(values: [
Rank::Two->name => 2,
Rank::Three->name => 3,
Rank::Four->name => 4,
Rank::Five->name => 5,
Rank::Six->name => 6,
Rank::Seven->name => 7,
Rank::Eight->name => 8,
Rank::Nine->name => 9,
Rank::Ten->name => 10,
Rank::Jack->name => 10,
Rank::Queen->name => 10,
Rank::King->name => 10,
Rank::Ace->name => 11,
], highest: Rank::Ace);
}
/**
* The comparison value of a rank in this ordering.
*
* @throws \InvalidArgumentException If the rank is not part of this ordering.
*/
public function value(Rank $rank): int
{
return (
$this->values[$rank->name] ?? throw new \InvalidArgumentException(
"Rank {$rank->name} is not in this RankOrder",
)
);
}
/**
* Returns -1, 0, or 1 if $a is lower, equal, or higher than $b.
*
* @throws \InvalidArgumentException If either rank is not in this ordering.
*/
public function compare(Rank $a, Rank $b): int
{
return $this->value($a) <=> $this->value($b);
}
/**
* Whether $a ranks above $b in this ordering.
*
* @throws \InvalidArgumentException If either rank is not in this ordering.
*/
public function isHigher(Rank $a, Rank $b): bool
{
return $this->value($a) > $this->value($b);
}
/**
* Whether $rank is the highest rank of this ordering.
*/
public function isHighest(Rank $rank): bool
{
return $rank === $this->highest;
}
/**
* Returns the next-higher rank in this ordering, or null if $rank is
* the highest.
*/
public function next(Rank $rank): ?Rank
{
$this->value($rank);
return $this->nextMap[$rank->name] ?? null;
}
/**
* Returns the next-lower rank in this ordering, or null if $rank is
* the lowest.
*/
public function previous(Rank $rank): ?Rank
{
$this->value($rank);
return $this->prevMap[$rank->name] ?? null;
}
/**
* @param array<string, int> $values
*/
private static function build(array $values, Rank $highest): self
{
$rankByName = [];
foreach (Rank::cases() as $case) {
$rankByName[$case->name] = $case;
}
$byValue = [];
foreach ($values as $name => $value) {
$byValue[$value][] = $name;
}
ksort($byValue);
$flat = [];
foreach ($byValue as $names) {
foreach ($names as $name) {
$flat[] = $name;
}
}
$nextMap = [];
$prevMap = [];
for ($i = 0, $n = count($flat); $i < $n; $i++) {
if ($i < ($n - 1)) {
$nextMap[$flat[$i]] = $rankByName[$flat[$i + 1]];
}
if ($i > 0) {
$prevMap[$flat[$i]] = $rankByName[$flat[$i - 1]];
}
}
return new self($values, $highest, $nextMap, $prevMap);
}
}