From afb802bdb0ce7cf472222350e3ec1105af625976 Mon Sep 17 00:00:00 2001 From: florentdevk Date: Tue, 23 Jun 2026 20:10:10 +0200 Subject: [PATCH 01/12] feat: Hand detects pair --- src/Domain/Hand.php | 6 ++++++ tests/Domain/HandTest.php | 9 +++++++++ 2 files changed, 15 insertions(+) diff --git a/src/Domain/Hand.php b/src/Domain/Hand.php index 2177526..20e537a 100644 --- a/src/Domain/Hand.php +++ b/src/Domain/Hand.php @@ -57,4 +57,10 @@ public function isSoft(): bool return $aces > 0 && $total <= 21; } + + public function isPair(): bool + { + return count($this->cards) === 2 + && $this->cards[0]->rank() === $this->cards[1]->rank(); + } } diff --git a/tests/Domain/HandTest.php b/tests/Domain/HandTest.php index 146f465..dee878d 100644 --- a/tests/Domain/HandTest.php +++ b/tests/Domain/HandTest.php @@ -70,4 +70,13 @@ public function testHandWithAceCountedAs11IsSoft(): void $this->assertTrue($hand->isSoft()); } + + public function testHandWithTwoIdenticalRanksIsPair(): void + { + $hand = new Hand(); + $hand->addCard(new Card('8', 'Hearts')); + $hand->addCard(new Card('8', 'Spades')); + + $this->assertTrue($hand->isPair()); + } } From babb4d21d1bdbb74d82bf745fb59382d9730480e Mon Sep 17 00:00:00 2001 From: florentdevk Date: Tue, 23 Jun 2026 20:10:47 +0200 Subject: [PATCH 02/12] test: hand with different ranks is not a pair --- tests/Domain/HandTest.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/Domain/HandTest.php b/tests/Domain/HandTest.php index dee878d..579a18d 100644 --- a/tests/Domain/HandTest.php +++ b/tests/Domain/HandTest.php @@ -79,4 +79,13 @@ public function testHandWithTwoIdenticalRanksIsPair(): void $this->assertTrue($hand->isPair()); } + + public function testHandWithDifferentRanksIsNotPair(): void + { + $hand = new Hand(); + $hand->addCard(new Card('8', 'Hearts')); + $hand->addCard(new Card('9', 'Spades')); + + $this->assertFalse($hand->isPair()); + } } From 6bfb750c4d8f77bf3b9f6e8f4f732c2271ec0d2c Mon Sep 17 00:00:00 2001 From: florentdevk Date: Tue, 23 Jun 2026 20:13:52 +0200 Subject: [PATCH 03/12] feat: Hand returns pair rank --- src/Domain/Hand.php | 5 +++++ tests/Domain/HandTest.php | 9 +++++++++ 2 files changed, 14 insertions(+) diff --git a/src/Domain/Hand.php b/src/Domain/Hand.php index 20e537a..05b0fc9 100644 --- a/src/Domain/Hand.php +++ b/src/Domain/Hand.php @@ -63,4 +63,9 @@ public function isPair(): bool return count($this->cards) === 2 && $this->cards[0]->rank() === $this->cards[1]->rank(); } + + public function pairRank(): string + { + return $this->cards[0]->rank(); + } } diff --git a/tests/Domain/HandTest.php b/tests/Domain/HandTest.php index 579a18d..4b0df49 100644 --- a/tests/Domain/HandTest.php +++ b/tests/Domain/HandTest.php @@ -88,4 +88,13 @@ public function testHandWithDifferentRanksIsNotPair(): void $this->assertFalse($hand->isPair()); } + + public function testPairRankReturnsCardRank(): void + { + $hand = new Hand(); + $hand->addCard(new Card('8', 'Hearts')); + $hand->addCard(new Card('8', 'Spades')); + + $this->assertSame('8', $hand->pairRank()); + } } From 0486eb32cf5e2e3392033e8af81be57233ac66f0 Mon Sep 17 00:00:00 2001 From: florentdevk Date: Tue, 23 Jun 2026 20:17:05 +0200 Subject: [PATCH 04/12] feat: BasicStrategy splits Aces and 8s always --- src/Domain/BasicStrategy.php | 8 ++++++++ tests/Domain/BasicStrategyTest.php | 13 +++++++++++++ 2 files changed, 21 insertions(+) diff --git a/src/Domain/BasicStrategy.php b/src/Domain/BasicStrategy.php index 7d0f2e9..bfa87f6 100644 --- a/src/Domain/BasicStrategy.php +++ b/src/Domain/BasicStrategy.php @@ -9,6 +9,14 @@ public function decide(Hand $hand, Card $dealerCard): Decision $value = $hand->value(); $dealerValue = $dealerCard->numericValue(); + if ($hand->isPair()) { + $rank = $hand->pairRank(); + + if ($rank === 'Ace' || $rank === '8') { + return Decision::Split; + } + } + if ($hand->isSoft() && $value === 18) { if ($dealerValue >= 3 && $dealerValue <= 6) { return Decision::Double; diff --git a/tests/Domain/BasicStrategyTest.php b/tests/Domain/BasicStrategyTest.php index eeaa732..ac059f9 100644 --- a/tests/Domain/BasicStrategyTest.php +++ b/tests/Domain/BasicStrategyTest.php @@ -386,4 +386,17 @@ public function testHard16AgainstDealer7ShouldHit(): void $this->assertSame(Decision::Hit, $strategy->decide($hand, $dealerCard)); } + + public function testPairOf8sShouldAlwaysSplit(): void + { + $strategy = new BasicStrategy(); + + $hand = new Hand(); + $hand->addCard(new Card('8', 'Hearts')); + $hand->addCard(new Card('8', 'Spades')); + + $dealerCard = new Card('6', 'Diamonds'); + + $this->assertSame(Decision::Split, $strategy->decide($hand, $dealerCard)); + } } \ No newline at end of file From 72520e905d4a0053020eb88163e2ca22c3d24b58 Mon Sep 17 00:00:00 2001 From: florentdevk Date: Tue, 23 Jun 2026 20:23:09 +0200 Subject: [PATCH 05/12] feat: BasicStrategy splits 9s against dealer 2-9 except 7 --- src/Domain/BasicStrategy.php | 4 ++++ tests/Domain/BasicStrategyTest.php | 13 +++++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/Domain/BasicStrategy.php b/src/Domain/BasicStrategy.php index bfa87f6..2f7c747 100644 --- a/src/Domain/BasicStrategy.php +++ b/src/Domain/BasicStrategy.php @@ -15,6 +15,10 @@ public function decide(Hand $hand, Card $dealerCard): Decision if ($rank === 'Ace' || $rank === '8') { return Decision::Split; } + + if ($rank === '9' && !in_array($dealerValue, [7, 10, 11], true)) { + return Decision::Split; + } } if ($hand->isSoft() && $value === 18) { diff --git a/tests/Domain/BasicStrategyTest.php b/tests/Domain/BasicStrategyTest.php index ac059f9..f63b9f3 100644 --- a/tests/Domain/BasicStrategyTest.php +++ b/tests/Domain/BasicStrategyTest.php @@ -399,4 +399,17 @@ public function testPairOf8sShouldAlwaysSplit(): void $this->assertSame(Decision::Split, $strategy->decide($hand, $dealerCard)); } + + public function testPairOf9sAgainstDealer6ShouldSplit(): void + { + $strategy = new BasicStrategy(); + + $hand = new Hand(); + $hand->addCard(new Card('9', 'Hearts')); + $hand->addCard(new Card('9', 'Spades')); + + $dealerCard = new Card('6', 'Diamonds'); + + $this->assertSame(Decision::Split, $strategy->decide($hand, $dealerCard)); + } } \ No newline at end of file From d0278900943885dd26eb62272dead4ccb7d3e267 Mon Sep 17 00:00:00 2001 From: florentdevk Date: Tue, 23 Jun 2026 20:24:00 +0200 Subject: [PATCH 06/12] test: pair of 9s against dealer 7 should stand --- tests/Domain/BasicStrategyTest.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/Domain/BasicStrategyTest.php b/tests/Domain/BasicStrategyTest.php index f63b9f3..acf972a 100644 --- a/tests/Domain/BasicStrategyTest.php +++ b/tests/Domain/BasicStrategyTest.php @@ -412,4 +412,17 @@ public function testPairOf9sAgainstDealer6ShouldSplit(): void $this->assertSame(Decision::Split, $strategy->decide($hand, $dealerCard)); } + + public function testPairOf9sAgainstDealer7ShouldStand(): void + { + $strategy = new BasicStrategy(); + + $hand = new Hand(); + $hand->addCard(new Card('9', 'Hearts')); + $hand->addCard(new Card('9', 'Spades')); + + $dealerCard = new Card('7', 'Diamonds'); + + $this->assertSame(Decision::Stand, $strategy->decide($hand, $dealerCard)); + } } \ No newline at end of file From 3f374d03e1d3b58bb2680bee703386e52ccee985 Mon Sep 17 00:00:00 2001 From: florentdevk Date: Tue, 23 Jun 2026 20:27:42 +0200 Subject: [PATCH 07/12] feat: BasicStrategy splits 7s against dealer 2-7 --- src/Domain/BasicStrategy.php | 4 ++++ tests/Domain/BasicStrategyTest.php | 13 +++++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/Domain/BasicStrategy.php b/src/Domain/BasicStrategy.php index 2f7c747..acd658b 100644 --- a/src/Domain/BasicStrategy.php +++ b/src/Domain/BasicStrategy.php @@ -19,6 +19,10 @@ public function decide(Hand $hand, Card $dealerCard): Decision if ($rank === '9' && !in_array($dealerValue, [7, 10, 11], true)) { return Decision::Split; } + + if ($rank === '7' && $dealerValue >= 2 && $dealerValue <= 7) { + return Decision::Split; + } } if ($hand->isSoft() && $value === 18) { diff --git a/tests/Domain/BasicStrategyTest.php b/tests/Domain/BasicStrategyTest.php index acf972a..c1638a0 100644 --- a/tests/Domain/BasicStrategyTest.php +++ b/tests/Domain/BasicStrategyTest.php @@ -425,4 +425,17 @@ public function testPairOf9sAgainstDealer7ShouldStand(): void $this->assertSame(Decision::Stand, $strategy->decide($hand, $dealerCard)); } + + public function testPairOf7sAgainstDealer5ShouldSplit(): void + { + $strategy = new BasicStrategy(); + + $hand = new Hand(); + $hand->addCard(new Card('7', 'Hearts')); + $hand->addCard(new Card('7', 'Spades')); + + $dealerCard = new Card('5', 'Diamonds'); + + $this->assertSame(Decision::Split, $strategy->decide($hand, $dealerCard)); + } } \ No newline at end of file From b52285a7982e05a98b97716606624445acef94f7 Mon Sep 17 00:00:00 2001 From: florentdevk Date: Tue, 23 Jun 2026 20:29:39 +0200 Subject: [PATCH 08/12] feat: BasicStrategy splits 6s against dealer 2-6 --- src/Domain/BasicStrategy.php | 4 ++++ tests/Domain/BasicStrategyTest.php | 13 +++++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/Domain/BasicStrategy.php b/src/Domain/BasicStrategy.php index acd658b..be050a6 100644 --- a/src/Domain/BasicStrategy.php +++ b/src/Domain/BasicStrategy.php @@ -23,6 +23,10 @@ public function decide(Hand $hand, Card $dealerCard): Decision if ($rank === '7' && $dealerValue >= 2 && $dealerValue <= 7) { return Decision::Split; } + + if ($rank === '6' && $dealerValue >= 2 && $dealerValue <= 6) { + return Decision::Split; + } } if ($hand->isSoft() && $value === 18) { diff --git a/tests/Domain/BasicStrategyTest.php b/tests/Domain/BasicStrategyTest.php index c1638a0..213893d 100644 --- a/tests/Domain/BasicStrategyTest.php +++ b/tests/Domain/BasicStrategyTest.php @@ -438,4 +438,17 @@ public function testPairOf7sAgainstDealer5ShouldSplit(): void $this->assertSame(Decision::Split, $strategy->decide($hand, $dealerCard)); } + + public function testPairOf6sAgainstDealer4ShouldSplit(): void + { + $strategy = new BasicStrategy(); + + $hand = new Hand(); + $hand->addCard(new Card('6', 'Hearts')); + $hand->addCard(new Card('6', 'Spades')); + + $dealerCard = new Card('4', 'Diamonds'); + + $this->assertSame(Decision::Split, $strategy->decide($hand, $dealerCard)); + } } \ No newline at end of file From f9428f052bdf418de397a100540271d8082548b4 Mon Sep 17 00:00:00 2001 From: florentdevk Date: Tue, 23 Jun 2026 20:33:02 +0200 Subject: [PATCH 09/12] feat: BasicStrategy splits 4s against dealer 5-6 --- src/Domain/BasicStrategy.php | 4 ++++ tests/Domain/BasicStrategyTest.php | 17 +++++++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/Domain/BasicStrategy.php b/src/Domain/BasicStrategy.php index be050a6..d22f7c7 100644 --- a/src/Domain/BasicStrategy.php +++ b/src/Domain/BasicStrategy.php @@ -27,6 +27,10 @@ public function decide(Hand $hand, Card $dealerCard): Decision if ($rank === '6' && $dealerValue >= 2 && $dealerValue <= 6) { return Decision::Split; } + + if ($rank === '4' && $dealerValue >= 5 && $dealerValue <= 6) { + return Decision::Split; + } } if ($hand->isSoft() && $value === 18) { diff --git a/tests/Domain/BasicStrategyTest.php b/tests/Domain/BasicStrategyTest.php index 213893d..d902239 100644 --- a/tests/Domain/BasicStrategyTest.php +++ b/tests/Domain/BasicStrategyTest.php @@ -340,8 +340,8 @@ public function testHard8ShouldAlwaysHit(): void $strategy = new BasicStrategy(); $hand = new Hand(); - $hand->addCard(new Card('4', 'Hearts')); - $hand->addCard(new Card('4', 'Spades')); + $hand->addCard(new Card('5', 'Hearts')); + $hand->addCard(new Card('3', 'Spades')); $dealerCard = new Card('6', 'Diamonds'); @@ -451,4 +451,17 @@ public function testPairOf6sAgainstDealer4ShouldSplit(): void $this->assertSame(Decision::Split, $strategy->decide($hand, $dealerCard)); } + + public function testPairOf4sAgainstDealer5ShouldSplit(): void + { + $strategy = new BasicStrategy(); + + $hand = new Hand(); + $hand->addCard(new Card('4', 'Hearts')); + $hand->addCard(new Card('4', 'Spades')); + + $dealerCard = new Card('5', 'Diamonds'); + + $this->assertSame(Decision::Split, $strategy->decide($hand, $dealerCard)); + } } \ No newline at end of file From af09e327085abf9abec3686b1081972492e16f87 Mon Sep 17 00:00:00 2001 From: florentdevk Date: Tue, 23 Jun 2026 20:34:24 +0200 Subject: [PATCH 10/12] feat: BasicStrategy splits 3s against dealer 2-7 --- src/Domain/BasicStrategy.php | 4 ++++ tests/Domain/BasicStrategyTest.php | 13 +++++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/Domain/BasicStrategy.php b/src/Domain/BasicStrategy.php index d22f7c7..6cc1bfd 100644 --- a/src/Domain/BasicStrategy.php +++ b/src/Domain/BasicStrategy.php @@ -31,6 +31,10 @@ public function decide(Hand $hand, Card $dealerCard): Decision if ($rank === '4' && $dealerValue >= 5 && $dealerValue <= 6) { return Decision::Split; } + + if ($rank === '3' && $dealerValue >= 2 && $dealerValue <= 7) { + return Decision::Split; + } } if ($hand->isSoft() && $value === 18) { diff --git a/tests/Domain/BasicStrategyTest.php b/tests/Domain/BasicStrategyTest.php index d902239..dd0182b 100644 --- a/tests/Domain/BasicStrategyTest.php +++ b/tests/Domain/BasicStrategyTest.php @@ -464,4 +464,17 @@ public function testPairOf4sAgainstDealer5ShouldSplit(): void $this->assertSame(Decision::Split, $strategy->decide($hand, $dealerCard)); } + + public function testPairOf3sAgainstDealer4ShouldSplit(): void + { + $strategy = new BasicStrategy(); + + $hand = new Hand(); + $hand->addCard(new Card('3', 'Hearts')); + $hand->addCard(new Card('3', 'Spades')); + + $dealerCard = new Card('4', 'Diamonds'); + + $this->assertSame(Decision::Split, $strategy->decide($hand, $dealerCard)); + } } \ No newline at end of file From faba9b0fa95c82d90e6f27c3fdf6ed562a49b410 Mon Sep 17 00:00:00 2001 From: florentdevk Date: Tue, 23 Jun 2026 20:35:37 +0200 Subject: [PATCH 11/12] feat: BasicStrategy splits 2s against dealer 2-7 --- src/Domain/BasicStrategy.php | 4 ++++ tests/Domain/BasicStrategyTest.php | 13 +++++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/Domain/BasicStrategy.php b/src/Domain/BasicStrategy.php index 6cc1bfd..49335d3 100644 --- a/src/Domain/BasicStrategy.php +++ b/src/Domain/BasicStrategy.php @@ -35,6 +35,10 @@ public function decide(Hand $hand, Card $dealerCard): Decision if ($rank === '3' && $dealerValue >= 2 && $dealerValue <= 7) { return Decision::Split; } + + if ($rank === '2' && $dealerValue >= 2 && $dealerValue <= 7) { + return Decision::Split; + } } if ($hand->isSoft() && $value === 18) { diff --git a/tests/Domain/BasicStrategyTest.php b/tests/Domain/BasicStrategyTest.php index dd0182b..de7908c 100644 --- a/tests/Domain/BasicStrategyTest.php +++ b/tests/Domain/BasicStrategyTest.php @@ -477,4 +477,17 @@ public function testPairOf3sAgainstDealer4ShouldSplit(): void $this->assertSame(Decision::Split, $strategy->decide($hand, $dealerCard)); } + + public function testPairOf2sAgainstDealer4ShouldSplit(): void + { + $strategy = new BasicStrategy(); + + $hand = new Hand(); + $hand->addCard(new Card('2', 'Hearts')); + $hand->addCard(new Card('2', 'Spades')); + + $dealerCard = new Card('4', 'Diamonds'); + + $this->assertSame(Decision::Split, $strategy->decide($hand, $dealerCard)); + } } \ No newline at end of file From f0d38b9c5742d6c4df2c34b98794b4b8508a06eb Mon Sep 17 00:00:00 2001 From: florentdevk Date: Tue, 23 Jun 2026 20:36:27 +0200 Subject: [PATCH 12/12] test: pair of 5s and 10s should not split --- tests/Domain/BasicStrategyTest.php | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/Domain/BasicStrategyTest.php b/tests/Domain/BasicStrategyTest.php index de7908c..c2802da 100644 --- a/tests/Domain/BasicStrategyTest.php +++ b/tests/Domain/BasicStrategyTest.php @@ -490,4 +490,30 @@ public function testPairOf2sAgainstDealer4ShouldSplit(): void $this->assertSame(Decision::Split, $strategy->decide($hand, $dealerCard)); } + + public function testPairOf5sShouldNotSplit(): void + { + $strategy = new BasicStrategy(); + + $hand = new Hand(); + $hand->addCard(new Card('5', 'Hearts')); + $hand->addCard(new Card('5', 'Spades')); + + $dealerCard = new Card('6', 'Diamonds'); + + $this->assertSame(Decision::Double, $strategy->decide($hand, $dealerCard)); + } + + public function testPairOf10sShouldNotSplit(): void + { + $strategy = new BasicStrategy(); + + $hand = new Hand(); + $hand->addCard(new Card('10', 'Hearts')); + $hand->addCard(new Card('10', 'Spades')); + + $dealerCard = new Card('6', 'Diamonds'); + + $this->assertSame(Decision::Stand, $strategy->decide($hand, $dealerCard)); + } } \ No newline at end of file