package com.flexpoker.table.command.aggregate.pot; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.UUID; import java.util.stream.Collectors; import com.flexpoker.table.command.aggregate.HandEvaluation; import com.flexpoker.table.command.events.PotAmountIncreasedEvent; import com.flexpoker.table.command.events.PotClosedEvent; import com.flexpoker.table.command.events.PotCreatedEvent; import com.flexpoker.table.command.framework.TableEvent; public class PotHandler { private final Set<Pot> pots; private final UUID gameId; private final UUID tableId; private final UUID handId; private final List<HandEvaluation> handEvaluationList; public PotHandler(UUID gameId, UUID tableId, UUID handId, List<HandEvaluation> handEvaluationList) { this.gameId = gameId; this.tableId = tableId; this.handId = handId; this.handEvaluationList = handEvaluationList; pots = new HashSet<>(); } public void removePlayerFromAllPots(UUID playerId) { pots.forEach(x -> x.removePlayer(playerId)); } public void addToPot(UUID potId, int amountToAdd) { pots.stream() .filter(x -> x.getId().equals(potId)) .findAny().get() .addChips(amountToAdd); } public void closePot(UUID potId) { pots.stream() .filter(x -> x.getId().equals(potId)) .findAny().get() .closePot(); } public void addNewPot(UUID potId, Set<UUID> playersInvolved) { Set<HandEvaluation> handEvaluationsOfPlayersInPot = handEvaluationList .stream().filter(x -> playersInvolved.contains(x.getPlayerId())) .collect(Collectors.toSet()); if (handEvaluationsOfPlayersInPot.isEmpty()) { throw new IllegalArgumentException( "trying to add a new pot with players that are not part of the hand"); } pots.add(new Pot(potId, handEvaluationsOfPlayersInPot)); } public Set<UUID> fetchPlayersRequriedToShowCards(Set<UUID> playersStillInHand) { Set<UUID> playersToShowCards = new HashSet<>(); pots.forEach(pot -> { playersStillInHand.forEach(playerInHand -> { if (pot.forcePlayerToShowCards(playerInHand)) { playersToShowCards.add(playerInHand); } }); }); return playersToShowCards; } public Map<UUID, Integer> fetchChipsWon(Set<UUID> playersStillInHand) { Map<UUID, Integer> playersToChipsWonMap = new HashMap<>(); pots.forEach(pot -> { playersStillInHand.forEach(playerInHand -> { int numberOfChipsWonForPlayer = pot.getChipsWon(playerInHand); int existingChipsWon = playersToChipsWonMap.getOrDefault(playerInHand, 0); int newTotalOfChipsWon = numberOfChipsWonForPlayer + existingChipsWon; playersToChipsWonMap.put(playerInHand, newTotalOfChipsWon); }); }); return playersToChipsWonMap; } /** * The general approach to calculating pots is as follows: * * 1. Discover all of the distinct numbers of chips in front of each player. * For example, if everyone has 30 chips in front, 30 would be the only * number in the distinct set. If two players had 10 and one person had 20, * then 10 and 20 would be in the set. * * 2. Loop through each chip count, starting with the smallest, and shave * off the number of chips from each stack in front of each player, and * place them into an open pot. * * 3. If an open pot does not exist, create a new one. * * 4. If it's determined that a player is all-in, then the pot for that * player's all-in should be closed. Multiple closed pots can exist, but * only one open pot should ever exist at any given time. * * @param aggregateVersion * @param playersStillInHand * @param chipsInFrontMap */ public List<TableEvent> calculatePots(int aggregateVersion, Map<UUID, Integer> chipsInFrontMap, Map<UUID, Integer> chipsInBackMap, Set<UUID> playersStillInHand) { List<TableEvent> newPotEvents = new ArrayList<>(); List<Integer> distinctChipsInFrontAmounts = chipsInFrontMap.values().stream() .filter(x -> x.intValue() != 0) .distinct() .sorted() .collect(Collectors.toList()); int totalOfPreviousChipLevelIncreases = 0; for (int chipsPerLevel : distinctChipsInFrontAmounts) { Optional<Pot> openPotOptional = pots.stream() .filter(x -> x.isOpen()).findAny(); UUID openPotId = openPotOptional.isPresent() ? openPotOptional.get().getId() : UUID.randomUUID(); Set<UUID> playersAtThisChipLevel = chipsInFrontMap.entrySet().stream() .filter(x -> x.getValue() >= chipsPerLevel) .map(x -> x.getKey()) .collect(Collectors.toSet()); if (!openPotOptional.isPresent()) { PotCreatedEvent potCreatedEvent = new PotCreatedEvent(tableId, aggregateVersion, gameId, handId, openPotId, playersAtThisChipLevel); newPotEvents.add(potCreatedEvent); addNewPot(potCreatedEvent.getPotId(), potCreatedEvent.getPlayersInvolved()); } // subtract the total of the previous levels from the current level // before multiplying by the number of player, which will have the // same effect as actually reducing that amount from each player int increaseInChips = (chipsPerLevel - totalOfPreviousChipLevelIncreases) * playersAtThisChipLevel.size(); totalOfPreviousChipLevelIncreases += chipsPerLevel; PotAmountIncreasedEvent potAmountIncreasedEvent = new PotAmountIncreasedEvent( tableId, aggregateVersion + newPotEvents.size(), gameId, handId, openPotId, increaseInChips); newPotEvents.add(potAmountIncreasedEvent); addToPot(potAmountIncreasedEvent.getPotId(), potAmountIncreasedEvent.getAmountIncreased()); // if a player bet, but no longer has any chips, then they are all // in and the pot should be closed if (playersAtThisChipLevel.stream() .filter(Objects::nonNull) .filter(player -> chipsInFrontMap.get(player) >= 1) .filter(player -> chipsInBackMap.get(player) == 0) .count() > 0) { PotClosedEvent potClosedEvent = new PotClosedEvent(tableId, aggregateVersion + newPotEvents.size(), gameId, handId, openPotId); newPotEvents.add(potClosedEvent); closePot(potClosedEvent.getPotId()); }; } return newPotEvents; } }