From 14b1e48483b7322bbf418829515444442f08e546 Mon Sep 17 00:00:00 2001 From: Acunil Date: Sat, 4 Feb 2023 11:47:08 +0000 Subject: [PATCH 1/5] adds pot --- .../example/base/domain/components/Round.java | 12 ++++++++++++ .../example/base/domain/utils/RoundUtils.java | 19 +++++++++++++++++++ .../base/domain/components/RoundTest.java | 9 +++++++++ 3 files changed, 40 insertions(+) create mode 100644 src/main/java/org/example/base/domain/utils/RoundUtils.java diff --git a/src/main/java/org/example/base/domain/components/Round.java b/src/main/java/org/example/base/domain/components/Round.java index fe0e6c3..0394ea5 100644 --- a/src/main/java/org/example/base/domain/components/Round.java +++ b/src/main/java/org/example/base/domain/components/Round.java @@ -7,6 +7,7 @@ import org.example.base.domain.enums.Stage; import org.example.base.domain.utils.DealUtils; import org.example.base.domain.utils.GeneralUtils; +import org.example.base.domain.utils.RoundUtils; import java.util.ArrayList; import java.util.List; @@ -26,6 +27,9 @@ public class Round { @Getter private Stage stage = Stage.PREFLOP; + @Getter + private Integer pot = 0; + @Getter private List flop; @@ -131,4 +135,12 @@ private void checkStage(Stage stage) { public String printRoundLog() { return String.join("", roundLog); } + + public void incrementPot(Integer amount) { + pot += amount; + } + + public List getWinningPlayers() { + return RoundUtils.getWinningPlayers(this); + } } diff --git a/src/main/java/org/example/base/domain/utils/RoundUtils.java b/src/main/java/org/example/base/domain/utils/RoundUtils.java new file mode 100644 index 0000000..e71d1d5 --- /dev/null +++ b/src/main/java/org/example/base/domain/utils/RoundUtils.java @@ -0,0 +1,19 @@ +package org.example.base.domain.utils; + +import org.example.base.domain.components.Player; +import org.example.base.domain.components.Round; + +import java.util.ArrayList; +import java.util.List; + +public class RoundUtils { + + private RoundUtils() { + throw new IllegalStateException("RoundUtils is a static class"); + } + + public static List getWinningPlayers(Round round) { + ArrayList winningPlayers = new ArrayList<>(); + return winningPlayers; + } +} diff --git a/src/test/java/org/example/base/domain/components/RoundTest.java b/src/test/java/org/example/base/domain/components/RoundTest.java index 1f0eb86..c61b8b0 100644 --- a/src/test/java/org/example/base/domain/components/RoundTest.java +++ b/src/test/java/org/example/base/domain/components/RoundTest.java @@ -96,4 +96,13 @@ void stageIsProgressed() { assertThat(round.getStage()).isEqualTo(Stage.RIVER); } + @Test + void incrementPot() { + assertThat(round.getPot()).isZero(); + round.incrementPot(5); + assertThat(round.getPot()).isEqualTo(5); + round.incrementPot(11); + assertThat(round.getPot()).isEqualTo(16); + } + } \ No newline at end of file From 3eff453bcf60106cb6b55fb4246234cb29eadd0f Mon Sep 17 00:00:00 2001 From: Acunil Date: Sat, 4 Feb 2023 14:45:04 +0000 Subject: [PATCH 2/5] adds getActivePlayers getWinningPlayers getPlayerHands RoundUtilsTest PlayerAction --- .../base/domain/components/Player.java | 13 ++-- .../example/base/domain/components/Round.java | 21 +++++- .../base/domain/enums/PlayerAction.java | 8 +++ .../example/base/domain/utils/RoundUtils.java | 25 +++++-- .../base/domain/components/RoundTest.java | 15 +++- .../base/domain/utils/RoundUtilsTest.java | 70 +++++++++++++++++++ 6 files changed, 138 insertions(+), 14 deletions(-) create mode 100644 src/main/java/org/example/base/domain/enums/PlayerAction.java create mode 100644 src/test/java/org/example/base/domain/utils/RoundUtilsTest.java diff --git a/src/main/java/org/example/base/domain/components/Player.java b/src/main/java/org/example/base/domain/components/Player.java index 9d5aebd..3ec8e8b 100644 --- a/src/main/java/org/example/base/domain/components/Player.java +++ b/src/main/java/org/example/base/domain/components/Player.java @@ -13,10 +13,9 @@ @RequiredArgsConstructor public class Player { - public Player(String name, Integer chips, Integer position) { + public Player(String name, Integer chips) { this.name = name; this.chips = chips; - this.position = position; } @Getter @@ -35,6 +34,10 @@ public Player(String name, Integer chips, Integer position) { @Setter private List pocketCards; + @Getter + @Setter + public boolean isActive = true; + public Hand getBestHand(List communityCards) { ArrayList combinedCards = new ArrayList<>(); combinedCards.addAll(communityCards); @@ -44,15 +47,15 @@ public Hand getBestHand(List communityCards) { public Integer placeBet(Integer bet) { if (bet > chips) { - return null; + throw new IllegalArgumentException( + String.format("Player %s can not bet %s. Player chips: %s", name, bet, chips)); } chips -= bet; return chips; } - public Integer winPot(Integer pot) { + public void winPot(Integer pot) { chips += pot; - return chips; } public String logStatus() { diff --git a/src/main/java/org/example/base/domain/components/Round.java b/src/main/java/org/example/base/domain/components/Round.java index 0394ea5..bc2065f 100644 --- a/src/main/java/org/example/base/domain/components/Round.java +++ b/src/main/java/org/example/base/domain/components/Round.java @@ -11,6 +11,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; import static org.example.base.domain.utils.GeneralUtils.HOLDEM_POCKET_SIZE; @@ -89,7 +90,6 @@ private void progressStage() { } } - @SuppressWarnings("unused") public List getCommunityCards() { ArrayList communityCards = new ArrayList<>(flop); if (stage == Stage.FLOP) { @@ -140,7 +140,24 @@ public void incrementPot(Integer amount) { pot += amount; } + public List getActivePlayers() { + return players.stream().filter(Player::isActive).toList(); + } + public List getWinningPlayers() { - return RoundUtils.getWinningPlayers(this); + return RoundUtils.getWinningPlayers(getActivePlayers(), getCommunityCards()); + } + + @SuppressWarnings("unused") + private void distributePot() { + List winningPlayers = getWinningPlayers(); + String winningPlayersNames = winningPlayers.stream().map(Player::getName).collect(Collectors.joining(", ")); + Integer numWinners = winningPlayers.size(); + Integer potShare = pot / numWinners; + String splitPot = numWinners == 1 ? "s" : ""; + String potReport = String.format("Final pot size: %s | Winner%s: %s", pot, splitPot, winningPlayersNames); + + roundLog.add(potReport); + winningPlayers.forEach(player -> player.winPot(potShare)); } } diff --git a/src/main/java/org/example/base/domain/enums/PlayerAction.java b/src/main/java/org/example/base/domain/enums/PlayerAction.java new file mode 100644 index 0000000..f56c076 --- /dev/null +++ b/src/main/java/org/example/base/domain/enums/PlayerAction.java @@ -0,0 +1,8 @@ +package org.example.base.domain.enums; + +public enum PlayerAction { + CHECK, + BET, + RAISE, + FOLD +} diff --git a/src/main/java/org/example/base/domain/utils/RoundUtils.java b/src/main/java/org/example/base/domain/utils/RoundUtils.java index e71d1d5..5a9d560 100644 --- a/src/main/java/org/example/base/domain/utils/RoundUtils.java +++ b/src/main/java/org/example/base/domain/utils/RoundUtils.java @@ -1,9 +1,11 @@ package org.example.base.domain.utils; +import org.example.base.domain.components.Hand; import org.example.base.domain.components.Player; -import org.example.base.domain.components.Round; +import org.example.base.domain.enums.Card; + +import static org.example.base.domain.utils.GeneralUtils.getLabelFromCards; -import java.util.ArrayList; import java.util.List; public class RoundUtils { @@ -12,8 +14,21 @@ private RoundUtils() { throw new IllegalStateException("RoundUtils is a static class"); } - public static List getWinningPlayers(Round round) { - ArrayList winningPlayers = new ArrayList<>(); - return winningPlayers; + public static List getPlayerHands(List players, List communityCards) { + return players.stream().map(player -> player.getBestHand(communityCards)).toList(); + } + + public static List getWinningPlayers(List players, List communityCards) { + List activeHands = RoundUtils.getPlayerHands(players, communityCards); + List winningHands = CompareHandsUtils.getWinningHands(activeHands); + List winningHandsCardsLabels = winningHands.stream() + .map(hand -> getLabelFromCards(hand.getCards())) + .toList(); + return players.stream() + .filter(player -> { + String labelFromPlayerCards = getLabelFromCards(player.getBestHand(communityCards).getCards()); + return winningHandsCardsLabels.contains(labelFromPlayerCards); + }) + .toList(); } } diff --git a/src/test/java/org/example/base/domain/components/RoundTest.java b/src/test/java/org/example/base/domain/components/RoundTest.java index c61b8b0..e65afd0 100644 --- a/src/test/java/org/example/base/domain/components/RoundTest.java +++ b/src/test/java/org/example/base/domain/components/RoundTest.java @@ -25,8 +25,8 @@ class RoundTest { @BeforeEach void setUp() { round = new Round(); - player1 = new Player("Red", 100, 0); - player2 = new Player("Blue", 150, 1); + player1 = new Player("Red", 100); + player2 = new Player("Blue", 150); round.setPlayers(List.of(player1, player2)); round.dealPocketCards(); } @@ -105,4 +105,15 @@ void incrementPot() { assertThat(round.getPot()).isEqualTo(16); } + @Test + void getActivePlayers() { + Player player3 = new Player("Yellow", 0); + player3.setActive(false); + assertThat(round.getActivePlayers()) + .hasSize(2) + .allMatch(Player::isActive) + .containsOnly(player1, player2); + } + + } \ No newline at end of file diff --git a/src/test/java/org/example/base/domain/utils/RoundUtilsTest.java b/src/test/java/org/example/base/domain/utils/RoundUtilsTest.java new file mode 100644 index 0000000..5fcc5cf --- /dev/null +++ b/src/test/java/org/example/base/domain/utils/RoundUtilsTest.java @@ -0,0 +1,70 @@ +package org.example.base.domain.utils; + +import lombok.val; +import org.example.base.domain.components.Hand; +import org.example.base.domain.components.Player; +import org.example.base.domain.enums.Card; +import org.example.base.domain.enums.HandType; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.example.base.domain.utils.GeneralUtils.getCardsFromLabel; + +import java.util.List; + + +class RoundUtilsTest { + + Player player1; + Player player2; + List communityCards; + + @BeforeEach + void setUp() { + player1 = new Player("Player1", 100); + player2 = new Player("Player2", 150); + communityCards = getCardsFromLabel("KC,TS,4H,9C,4C"); + player1.setPocketCards(getCardsFromLabel("4S,TD")); + player2.setPocketCards(getCardsFromLabel("AC,6C")); + } + + @Test + void getPlayerHands() { + Hand p1ExpectedHand = new Hand(HandType.FULL_HOUSE, getCardsFromLabel("4S,4C,4H,TS,TD")); + Hand p2ExpectedHand = new Hand(HandType.FLUSH, getCardsFromLabel("AC,KC,9C,6C,4C")); + + List result = RoundUtils.getPlayerHands(List.of(player1, player2), communityCards); + + assertThat(result).hasSize(2); + + val p1Hand = result.get(0); + assertThat(p1Hand.getHandType()).isEqualTo(p1ExpectedHand.getHandType()); + assertThat(p1Hand.getCards()).containsAll(p1ExpectedHand.getCards()); + + val p2Hand = result.get(1); + assertThat(p2Hand.getHandType()).isEqualTo(p2ExpectedHand.getHandType()); + assertThat(p2Hand.getCards()).containsAll(p2ExpectedHand.getCards()); + } + + @Test + void getWinningPlayers_returnsSingleWinner() { + val result = RoundUtils.getWinningPlayers(List.of(player1, player2), communityCards); + assertThat(result).hasSize(1); + Player winningPlayer = result.get(0); + assertThat(winningPlayer.getName()).isEqualTo(player1.getName()); + } + + @Test + void getWinningPlayers_returnsMultipleWinners() { + player1.setPocketCards(getCardsFromLabel("KS,2D")); + player2.setPocketCards(getCardsFromLabel("KH,3D")); + + val result = RoundUtils.getWinningPlayers(List.of(player1, player2), communityCards); + assertThat(result).hasSize(2); + Player winningPlayer1 = result.get(0); + assertThat(winningPlayer1.getName()).isEqualTo(player1.getName()); + Player winningPlayer2 = result.get(1); + assertThat(winningPlayer2.getName()).isEqualTo(player2.getName()); + } +} \ No newline at end of file From 99cb0efb6ec431f107274bd1098b81e27cbc4e66 Mon Sep 17 00:00:00 2001 From: Acunil Date: Sat, 4 Feb 2023 15:09:15 +0000 Subject: [PATCH 3/5] adds distributePot --- .../example/base/domain/components/Round.java | 19 ++++++++----------- .../base/domain/components/RoundTest.java | 16 ++++++++++++++++ 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/example/base/domain/components/Round.java b/src/main/java/org/example/base/domain/components/Round.java index bc2065f..c1ef3a4 100644 --- a/src/main/java/org/example/base/domain/components/Round.java +++ b/src/main/java/org/example/base/domain/components/Round.java @@ -92,18 +92,14 @@ private void progressStage() { public List getCommunityCards() { ArrayList communityCards = new ArrayList<>(flop); - if (stage == Stage.FLOP) { - return communityCards; - } if (stage == Stage.TURN) { communityCards.add(turn); - return communityCards; } if (stage == Stage.RIVER) { + communityCards.add(turn); communityCards.add(river); - return communityCards; } - return List.of(); + return communityCards; } private void logCardsDealt() { @@ -148,14 +144,15 @@ public List getWinningPlayers() { return RoundUtils.getWinningPlayers(getActivePlayers(), getCommunityCards()); } - @SuppressWarnings("unused") - private void distributePot() { + public void distributePot() { List winningPlayers = getWinningPlayers(); - String winningPlayersNames = winningPlayers.stream().map(Player::getName).collect(Collectors.joining(", ")); + String winningPlayersNames = winningPlayers.stream() + .map(Player::getName) + .collect(Collectors.joining(", ")); Integer numWinners = winningPlayers.size(); Integer potShare = pot / numWinners; - String splitPot = numWinners == 1 ? "s" : ""; - String potReport = String.format("Final pot size: %s | Winner%s: %s", pot, splitPot, winningPlayersNames); + String plural = numWinners == 1 ? "" : "s"; + String potReport = String.format("Final pot size: %s | Winner%s: %s", pot, plural, winningPlayersNames); roundLog.add(potReport); winningPlayers.forEach(player -> player.winPot(potShare)); diff --git a/src/test/java/org/example/base/domain/components/RoundTest.java b/src/test/java/org/example/base/domain/components/RoundTest.java index e65afd0..94960ec 100644 --- a/src/test/java/org/example/base/domain/components/RoundTest.java +++ b/src/test/java/org/example/base/domain/components/RoundTest.java @@ -6,6 +6,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import java.util.ArrayList; import java.util.List; import static org.assertj.core.api.Assertions.assertThat; @@ -27,6 +28,8 @@ void setUp() { round = new Round(); player1 = new Player("Red", 100); player2 = new Player("Blue", 150); + player1.setPosition(0); + player2.setPosition(1); round.setPlayers(List.of(player1, player2)); round.dealPocketCards(); } @@ -115,5 +118,18 @@ void getActivePlayers() { .containsOnly(player1, player2); } + @Test + void distributePot() { + round.incrementPot(120); + round.dealFlop(); + round.dealTurn(); + round.dealRiver(); + round.distributePot(); + ArrayList roundLog = round.getRoundLog(); + String potReport = roundLog.get(roundLog.size() - 1); + String regex = "Final pot size: 120 \\| Winners?: \\w+(, \\w+)*"; + assertThat(potReport).matches(regex); + } + } \ No newline at end of file From 082504bd173fcccb87cb544e562649802da26164 Mon Sep 17 00:00:00 2001 From: Acunil Date: Sat, 4 Feb 2023 19:44:14 +0000 Subject: [PATCH 4/5] adds test for player chips after distributePot --- .../java/org/example/base/domain/components/RoundTest.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/test/java/org/example/base/domain/components/RoundTest.java b/src/test/java/org/example/base/domain/components/RoundTest.java index 94960ec..2c4d053 100644 --- a/src/test/java/org/example/base/domain/components/RoundTest.java +++ b/src/test/java/org/example/base/domain/components/RoundTest.java @@ -120,15 +120,20 @@ void getActivePlayers() { @Test void distributePot() { + int initialPlayerChips = player1.getChips() + player2.getChips(); round.incrementPot(120); round.dealFlop(); round.dealTurn(); round.dealRiver(); round.distributePot(); + ArrayList roundLog = round.getRoundLog(); String potReport = roundLog.get(roundLog.size() - 1); String regex = "Final pot size: 120 \\| Winners?: \\w+(, \\w+)*"; assertThat(potReport).matches(regex); + + int resultingPlayerChips = player1.getChips() + player2.getChips(); + assertThat(resultingPlayerChips).isEqualTo(initialPlayerChips + 120); } From e4454bb6bd229e2acad47838a0562df43d2e1a3c Mon Sep 17 00:00:00 2001 From: Acunil Date: Sat, 4 Feb 2023 20:37:35 +0000 Subject: [PATCH 5/5] adjusts winning player summary updates test regex to match --- .../example/base/domain/components/Round.java | 18 +++++++++++++----- .../example/base/domain/enums/HandType.java | 19 ++++++++++++------- .../base/domain/components/RoundTest.java | 7 +++++-- 3 files changed, 30 insertions(+), 14 deletions(-) diff --git a/src/main/java/org/example/base/domain/components/Round.java b/src/main/java/org/example/base/domain/components/Round.java index c1ef3a4..9d00da4 100644 --- a/src/main/java/org/example/base/domain/components/Round.java +++ b/src/main/java/org/example/base/domain/components/Round.java @@ -11,9 +11,9 @@ import java.util.ArrayList; import java.util.List; -import java.util.stream.Collectors; import static org.example.base.domain.utils.GeneralUtils.HOLDEM_POCKET_SIZE; +import static org.example.base.domain.utils.GeneralUtils.getLabelFromCards; @RequiredArgsConstructor public class Round { @@ -146,15 +146,23 @@ public List getWinningPlayers() { public void distributePot() { List winningPlayers = getWinningPlayers(); - String winningPlayersNames = winningPlayers.stream() - .map(Player::getName) - .collect(Collectors.joining(", ")); + String winningPlayerSummary = formulateWinningPlayerSummary(winningPlayers); Integer numWinners = winningPlayers.size(); Integer potShare = pot / numWinners; String plural = numWinners == 1 ? "" : "s"; - String potReport = String.format("Final pot size: %s | Winner%s: %s", pot, plural, winningPlayersNames); + String potReport = String.format("Final pot size: %s | Winner%s: %s", pot, plural, winningPlayerSummary); roundLog.add(potReport); winningPlayers.forEach(player -> player.winPot(potShare)); } + + private String formulateWinningPlayerSummary(List winningPlayers) { + List winningPlayersSummaries = new ArrayList<>(); + winningPlayers.forEach(player -> { + Hand hand = player.getBestHand(getCommunityCards()); + String description = String.format("%s - %s - %s", player.getName(), hand.getHandType(), getLabelFromCards(hand.getCards())); + winningPlayersSummaries.add(description); + }); + return String.join(" // ", winningPlayersSummaries); + } } diff --git a/src/main/java/org/example/base/domain/enums/HandType.java b/src/main/java/org/example/base/domain/enums/HandType.java index ec138f8..030ce05 100644 --- a/src/main/java/org/example/base/domain/enums/HandType.java +++ b/src/main/java/org/example/base/domain/enums/HandType.java @@ -5,16 +5,16 @@ @RequiredArgsConstructor public enum HandType { - ROYAL_FLUSH("Royal flush", 10), - STRAIGHT_FLUSH("Straight flush", 9), - FOUR_KIND("Four of a kind", 8), - FULL_HOUSE("Full house", 7), + ROYAL_FLUSH("Royal Flush", 10), + STRAIGHT_FLUSH("Straight Flush", 9), + FOUR_KIND("Four of a Kind", 8), + FULL_HOUSE("Full House", 7), FLUSH("Flush", 6), STRAIGHT("Straight", 5), - THREE_KIND("Three of a kind", 4), - TWO_PAIR("Two pair", 3), + THREE_KIND("Three of a Kind", 4), + TWO_PAIR("Two Pair", 3), PAIR("Pair", 2), - HIGH_CARD("High card", 1); + HIGH_CARD("High Card", 1); @Getter final String name; @@ -22,4 +22,9 @@ public enum HandType { @Getter final Integer rank; + @Override + public final String toString() { + return name; + } + } diff --git a/src/test/java/org/example/base/domain/components/RoundTest.java b/src/test/java/org/example/base/domain/components/RoundTest.java index 2c4d053..4b32eb8 100644 --- a/src/test/java/org/example/base/domain/components/RoundTest.java +++ b/src/test/java/org/example/base/domain/components/RoundTest.java @@ -5,6 +5,8 @@ import org.example.base.domain.utils.GeneralUtils; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import java.util.ArrayList; import java.util.List; @@ -118,7 +120,8 @@ void getActivePlayers() { .containsOnly(player1, player2); } - @Test + @ParameterizedTest + @ValueSource(ints = {1,2,3,4,5,6,7,8,9,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}) void distributePot() { int initialPlayerChips = player1.getChips() + player2.getChips(); round.incrementPot(120); @@ -129,7 +132,7 @@ void distributePot() { ArrayList roundLog = round.getRoundLog(); String potReport = roundLog.get(roundLog.size() - 1); - String regex = "Final pot size: 120 \\| Winners?: \\w+(, \\w+)*"; + String regex = "Final pot size: 120 \\| Winners?: (\\w+ - [\\w ]+ - (?>[AKQJTZ2-9][SCHD],?){5}( // )?)+"; assertThat(potReport).matches(regex); int resultingPlayerChips = player1.getChips() + player2.getChips();