-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathTrick.php
More file actions
137 lines (118 loc) · 3.73 KB
/
Copy pathTrick.php
File metadata and controls
137 lines (118 loc) · 3.73 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
<?php
declare(strict_types=1);
namespace Likewinter\CardDeck;
use Likewinter\CardDeck\Card\Suit;
/**
* A single trick in a trick-taking game: the cards played by each player
* in one round of play, plus the rules to determine the winner.
*
* A trick starts when the current player leads a card. Each subsequent
* player plays in turn order (enforced internally via PlayerRing). When
* all players have played, winner() returns the index of the player
* whose card won the trick, determined by the SuitOrder.
*
* Trump rules, lead-suit rules, and rank ordering are all carried by
* the SuitOrder supplied at construction.
*/
final class Trick
{
/** @var list<Card> Cards in the order they were played. */
private array $cards = [];
/** @var list<int> Player index for each played card, parallel to $cards. */
private array $players = [];
private ?Suit $leadSuit = null;
private PlayerRing $ring;
/**
* @param SuitOrder $suitOrder Rules deciding which card beats which.
* @param int $numPlayers Number of players; each plays one card per trick.
* @param int $startingPlayer Index of the player who leads the trick.
*
* @throws \InvalidArgumentException If $numPlayers < 2.
*/
public function __construct(
private readonly SuitOrder $suitOrder,
private readonly int $numPlayers,
int $startingPlayer = 0,
) {
if ($numPlayers < 2) {
throw new \InvalidArgumentException('Trick requires at least 2 players');
}
$this->ring = new PlayerRing($numPlayers, $startingPlayer);
}
/**
* Plays a card for the current player and advances the turn ring.
* The first card played sets the lead suit. Callers enforce turn
* order by checking currentPlayer() before playing.
*
* @throws \LogicException If every player has already played.
*/
public function play(Card $card): void
{
if (count($this->cards) >= $this->numPlayers) {
throw new \LogicException('Trick is complete; no more cards can be played');
}
if ($this->leadSuit === null) {
$this->leadSuit = $card->suit;
}
$this->cards[] = $card;
$this->players[] = $this->ring->current();
$this->ring->next();
}
/**
* Returns the index of the player whose turn it is.
*/
public function currentPlayer(): int
{
return $this->ring->current();
}
/**
* The suit that was led. Null if no cards have been played yet.
*/
public function leadSuit(): ?Suit
{
return $this->leadSuit;
}
/**
* Has every player played a card?
*/
public function isComplete(): bool
{
return count($this->cards) === $this->numPlayers;
}
/**
* Returns true if no cards have been played yet.
*/
public function isEmpty(): bool
{
return $this->cards === [];
}
/**
* Returns the index of the player who won the trick.
*
* @throws \LogicException if the trick is not complete.
*/
#[\NoDiscard]
public function winner(): int
{
if (!$this->isComplete()) {
throw new \LogicException('Cannot determine winner of an incomplete trick');
}
$winnerIdx = 0;
for ($i = 1, $n = count($this->cards); $i < $n; $i++) {
if (!$this->suitOrder->beats($this->cards[$i], $this->cards[$winnerIdx], $this->leadSuit)) {
continue;
}
$winnerIdx = $i;
}
return $this->players[$winnerIdx];
}
/**
* Returns the cards played so far, in play order.
*
* @return list<Card>
*/
public function cards(): array
{
return $this->cards;
}
}