-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSuitOrder.php
More file actions
107 lines (93 loc) · 3.21 KB
/
Copy pathSuitOrder.php
File metadata and controls
107 lines (93 loc) · 3.21 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
<?php
declare(strict_types=1);
namespace Likewinter\CardDeck;
use Likewinter\CardDeck\Card\Suit;
/**
* Game-specific suit ordering and trump rules for trick-taking games.
*
* Encapsulates the full question 'does card A beat card B in this trick?'
* given a trump configuration, the suit that was led, and the rank
* ordering. Games construct a SuitOrder with their trump rules and
* rank ordering, then use beats() to resolve tricks.
*/
final readonly class SuitOrder
{
/**
* @param Suit|null $trumpSuit The trump suit, or null for no trump.
* @param RankOrder $rankOrder Rank comparison rules used within suits.
*
* @throws \InvalidArgumentException If $trumpSuit is the Joker suit.
*/
public function __construct(
public ?Suit $trumpSuit,
private RankOrder $rankOrder,
) {
if ($trumpSuit === Suit::Joker) {
throw new \InvalidArgumentException('Joker cannot be trump');
}
}
/**
* No trump suit — only lead suit and rank decide tricks.
*/
public static function noTrump(RankOrder $rankOrder): self
{
return new self(null, $rankOrder);
}
/**
* Trump configuration with the given suit as trump.
*/
public static function suit(Suit $suit, RankOrder $rankOrder): self
{
return new self($suit, $rankOrder);
}
/**
* Whether the card belongs to the trump suit. Always false in no-trump.
*/
public function isTrump(Card $card): bool
{
return $this->trumpSuit !== null && $card->suit === $this->trumpSuit;
}
/**
* Does $a beat $b in a trick where $leadSuit was the first card's suit?
*
* Rules (standard trick-taking):
* 1. Trump beats any non-trump.
* 2. Higher trump beats lower trump.
* 3. Within the same suit, higher rank beats lower rank.
* 4. A card not following the lead suit cannot win unless it's trump.
* 5. If neither card follows lead and neither is trump, neither beats
* the other — returns false (the lead card remains winning by
* default; the caller resolves ties by keeping the current winner).
*/
public function beats(Card $a, Card $b, ?Suit $leadSuit): bool
{
$aTrump = $this->isTrump($a);
$bTrump = $this->isTrump($b);
// Trump beats non-trump
if ($aTrump && !$bTrump) {
return true;
}
if (!$aTrump && $bTrump) {
return false;
}
// Both trump: higher rank wins
if ($aTrump) {
return $this->rankOrder->isHigher($a->rank, $b->rank);
}
// Neither trump: must follow lead suit to win
$aFollows = $leadSuit !== null && $a->suit === $leadSuit;
$bFollows = $leadSuit !== null && $b->suit === $leadSuit;
if ($aFollows && !$bFollows) {
return true;
}
if (!$aFollows && $bFollows) {
return false;
}
// Both follow lead (or neither does): higher rank wins if same suit
if ($a->suit === $b->suit) {
return $this->rankOrder->isHigher($a->rank, $b->rank);
}
// Different non-trump, non-lead suits: a cannot beat b
return false;
}
}