Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions src/main/java/org/example/base/domain/components/Player.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -35,6 +34,10 @@ public Player(String name, Integer chips, Integer position) {
@Setter
private List<Card> pocketCards;

@Getter
@Setter
public boolean isActive = true;

public Hand getBestHand(List<Card> communityCards) {
ArrayList<Card> combinedCards = new ArrayList<>();
combinedCards.addAll(communityCards);
Expand All @@ -44,15 +47,15 @@ public Hand getBestHand(List<Card> 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() {
Expand Down
48 changes: 41 additions & 7 deletions src/main/java/org/example/base/domain/components/Round.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@
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;

import static org.example.base.domain.utils.GeneralUtils.HOLDEM_POCKET_SIZE;
import static org.example.base.domain.utils.GeneralUtils.getLabelFromCards;

@RequiredArgsConstructor
public class Round {
Expand All @@ -26,6 +28,9 @@ public class Round {
@Getter
private Stage stage = Stage.PREFLOP;

@Getter
private Integer pot = 0;

@Getter
private List<Card> flop;

Expand Down Expand Up @@ -85,21 +90,16 @@ private void progressStage() {
}
}

@SuppressWarnings("unused")
public List<Card> getCommunityCards() {
ArrayList<Card> 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() {
Expand Down Expand Up @@ -131,4 +131,38 @@ private void checkStage(Stage stage) {
public String printRoundLog() {
return String.join("", roundLog);
}

public void incrementPot(Integer amount) {
pot += amount;
}

public List<Player> getActivePlayers() {
return players.stream().filter(Player::isActive).toList();
}

public List<Player> getWinningPlayers() {
return RoundUtils.getWinningPlayers(getActivePlayers(), getCommunityCards());
}

public void distributePot() {
List<Player> winningPlayers = getWinningPlayers();
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, winningPlayerSummary);

roundLog.add(potReport);
winningPlayers.forEach(player -> player.winPot(potShare));
}

private String formulateWinningPlayerSummary(List<Player> winningPlayers) {
List<String> 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);
}
}
19 changes: 12 additions & 7 deletions src/main/java/org/example/base/domain/enums/HandType.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,26 @@

@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;

@Getter
final Integer rank;

@Override
public final String toString() {
return name;
}

}
8 changes: 8 additions & 0 deletions src/main/java/org/example/base/domain/enums/PlayerAction.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.example.base.domain.enums;

public enum PlayerAction {
CHECK,
BET,
RAISE,
FOLD
}
34 changes: 34 additions & 0 deletions src/main/java/org/example/base/domain/utils/RoundUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
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.enums.Card;

import static org.example.base.domain.utils.GeneralUtils.getLabelFromCards;

import java.util.List;

public class RoundUtils {

private RoundUtils() {
throw new IllegalStateException("RoundUtils is a static class");
}

public static List<Hand> getPlayerHands(List<Player> players, List<Card> communityCards) {
return players.stream().map(player -> player.getBestHand(communityCards)).toList();
}

public static List<Player> getWinningPlayers(List<Player> players, List<Card> communityCards) {
List<Hand> activeHands = RoundUtils.getPlayerHands(players, communityCards);
List<Hand> winningHands = CompareHandsUtils.getWinningHands(activeHands);
List<String> 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();
}
}
48 changes: 46 additions & 2 deletions src/test/java/org/example/base/domain/components/RoundTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
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;

import static org.assertj.core.api.Assertions.assertThat;
Expand All @@ -25,8 +28,10 @@ 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);
player1.setPosition(0);
player2.setPosition(1);
round.setPlayers(List.of(player1, player2));
round.dealPocketCards();
}
Expand Down Expand Up @@ -96,4 +101,43 @@ 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);
}

@Test
void getActivePlayers() {
Player player3 = new Player("Yellow", 0);
player3.setActive(false);
assertThat(round.getActivePlayers())
.hasSize(2)
.allMatch(Player::isActive)
.containsOnly(player1, player2);
}

@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);
round.dealFlop();
round.dealTurn();
round.dealRiver();
round.distributePot();

ArrayList<String> roundLog = round.getRoundLog();
String potReport = roundLog.get(roundLog.size() - 1);
String regex = "Final pot size: 120 \\| Winners?: (\\w+ - [\\w ]+ - (?>[AKQJTZ2-9][SCHD],?){5}( // )?)+";
assertThat(potReport).matches(regex);

int resultingPlayerChips = player1.getChips() + player2.getChips();
assertThat(resultingPlayerChips).isEqualTo(initialPlayerChips + 120);
}


}
70 changes: 70 additions & 0 deletions src/test/java/org/example/base/domain/utils/RoundUtilsTest.java
Original file line number Diff line number Diff line change
@@ -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<Card> 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<Hand> 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());
}
}