package com.flexpoker.table.command.aggregate; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.UUID; import com.flexpoker.exception.FlexPokerException; import com.flexpoker.model.PlayerAction; import com.flexpoker.model.card.FlopCards; import com.flexpoker.model.card.PocketCards; import com.flexpoker.model.card.RiverCard; import com.flexpoker.model.card.TurnCard; import com.flexpoker.table.command.aggregate.pot.PotHandler; import com.flexpoker.table.command.events.ActionOnChangedEvent; import com.flexpoker.table.command.events.AutoMoveHandForwardEvent; import com.flexpoker.table.command.events.FlopCardsDealtEvent; import com.flexpoker.table.command.events.HandCompletedEvent; import com.flexpoker.table.command.events.HandDealtEvent; import com.flexpoker.table.command.events.LastToActChangedEvent; import com.flexpoker.table.command.events.PlayerCalledEvent; import com.flexpoker.table.command.events.PlayerCheckedEvent; import com.flexpoker.table.command.events.PlayerFoldedEvent; import com.flexpoker.table.command.events.PlayerForceCheckedEvent; import com.flexpoker.table.command.events.PlayerForceFoldedEvent; import com.flexpoker.table.command.events.PlayerRaisedEvent; 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.events.RiverCardDealtEvent; import com.flexpoker.table.command.events.RoundCompletedEvent; import com.flexpoker.table.command.events.TurnCardDealtEvent; import com.flexpoker.table.command.events.WinnersDeterminedEvent; import com.flexpoker.table.command.framework.TableEvent; public class Hand { private final UUID gameId; private final UUID tableId; private final UUID entityId; private final Map<Integer, UUID> seatMap; private final FlopCards flopCards; private final TurnCard turnCard; private final RiverCard riverCard; private final int buttonOnPosition; private final int smallBlindPosition; private final int bigBlindPosition; private int actionOnPosition; private final Map<UUID, PocketCards> playerToPocketCardsMap; private final Map<UUID, Set<PlayerAction>> possibleSeatActionsMap; private UUID originatingBettorPlayerId; private UUID lastToActPlayerId; private HandDealerState handDealerState; private final List<HandEvaluation> handEvaluationList; private final Set<UUID> playersStillInHand; private final Map<UUID, Integer> chipsInBackMap; private final Map<UUID, Integer> chipsInFrontMap; private final Map<UUID, Integer> callAmountsMap; private final Map<UUID, Integer> raiseToAmountsMap; private final int smallBlind; private final int bigBlind; private final Set<UUID> playersToShowCards; private boolean flopDealt; private boolean turnDealt; private boolean riverDealt; private final PotHandler potHandler; public Hand(UUID gameId, UUID tableId, UUID entityId, Map<Integer, UUID> seatMap, FlopCards flopCards, TurnCard turnCard, RiverCard riverCard, int buttonOnPosition, int smallBlindPosition, int bigBlindPosition, UUID lastToActPlayerId, Map<UUID, PocketCards> playerToPocketCardsMap, Map<UUID, Set<PlayerAction>> possibleSeatActionsMap, Set<UUID> playersStillInHand, List<HandEvaluation> handEvaluationList, HandDealerState handDealerState, Map<UUID, Integer> chipsInBack, Map<UUID, Integer> chipsInFrontMap, Map<UUID, Integer> callAmountsMap, Map<UUID, Integer> raiseToAmountsMap, int smallBlind, int bigBlind) { this.gameId = gameId; this.tableId = tableId; this.entityId = entityId; this.seatMap = seatMap; this.flopCards = flopCards; this.turnCard = turnCard; this.riverCard = riverCard; this.buttonOnPosition = buttonOnPosition; this.smallBlindPosition = smallBlindPosition; this.bigBlindPosition = bigBlindPosition; this.lastToActPlayerId = lastToActPlayerId; this.playerToPocketCardsMap = playerToPocketCardsMap; this.handEvaluationList = handEvaluationList; this.possibleSeatActionsMap = possibleSeatActionsMap; this.playersStillInHand = playersStillInHand; this.handDealerState = handDealerState; this.chipsInBackMap = chipsInBack; this.chipsInFrontMap = chipsInFrontMap; this.callAmountsMap = callAmountsMap; this.raiseToAmountsMap = raiseToAmountsMap; this.smallBlind = smallBlind; this.bigBlind = bigBlind; this.playersToShowCards = new HashSet<>(); this.potHandler = new PotHandler(gameId, tableId, entityId, handEvaluationList); } public List<TableEvent> dealHand(int aggregateVersion, int actionOnPosition) { List<TableEvent> eventsCreated = new ArrayList<>(); seatMap.keySet().stream().filter(x -> seatMap.get(x) != null) .forEach(seatPosition -> { handleStartOfHandPlayerValues(seatPosition.intValue()); }); lastToActPlayerId = seatMap.get(Integer.valueOf(bigBlindPosition)); handDealerState = HandDealerState.POCKET_CARDS_DEALT; HandDealtEvent handDealtEvent = new HandDealtEvent(tableId, aggregateVersion, gameId, entityId, flopCards, turnCard, riverCard, buttonOnPosition, smallBlindPosition, bigBlindPosition, lastToActPlayerId, seatMap, playerToPocketCardsMap, possibleSeatActionsMap, playersStillInHand, handEvaluationList, handDealerState, chipsInBackMap, chipsInFrontMap, callAmountsMap, raiseToAmountsMap, smallBlind, bigBlind); eventsCreated.add(handDealtEvent); // creat an initial empty pot for the table eventsCreated.add(new PotCreatedEvent(tableId, aggregateVersion + 1, gameId, entityId, UUID.randomUUID(), playersStillInHand)); UUID actionOnPlayerId = seatMap.get(Integer.valueOf(actionOnPosition)); ActionOnChangedEvent actionOnChangedEvent = new ActionOnChangedEvent(tableId, aggregateVersion + 2, gameId, entityId, actionOnPlayerId); eventsCreated.add(actionOnChangedEvent); return eventsCreated; } private void handleStartOfHandPlayerValues(int seatPosition) { UUID playerId = seatMap.get(Integer.valueOf(seatPosition)); int chipsInFront = 0; int callAmount = bigBlind; int raiseToAmount = bigBlind * 2; if (seatPosition == bigBlindPosition) { chipsInFront = bigBlind; callAmount = 0; } else if (seatPosition == smallBlindPosition) { chipsInFront = smallBlind; callAmount = smallBlind; } if (chipsInFront > chipsInBackMap.get(playerId).intValue()) { chipsInFrontMap.put(playerId, chipsInBackMap.get(playerId)); } else { chipsInFrontMap.put(playerId, Integer.valueOf(chipsInFront)); } subtractFromChipsInBack(playerId, chipsInFrontMap.get(playerId).intValue()); if (callAmount > chipsInBackMap.get(playerId).intValue()) { callAmountsMap.put(playerId, chipsInBackMap.get(playerId)); } else { callAmountsMap.put(playerId, Integer.valueOf(callAmount)); } int totalChips = chipsInBackMap.get(playerId).intValue() + chipsInFrontMap.get(playerId).intValue(); if (raiseToAmount > totalChips) { raiseToAmountsMap.put(playerId, Integer.valueOf(totalChips)); } else { raiseToAmountsMap.put(playerId, Integer.valueOf(raiseToAmount)); } if (raiseToAmountsMap.get(playerId).intValue() > 0) { Set<PlayerAction> playerActions = possibleSeatActionsMap.get(playerId); playerActions.add(PlayerAction.RAISE); } if (callAmountsMap.get(playerId).intValue() > 0) { Set<PlayerAction> playerActions = possibleSeatActionsMap.get(playerId); playerActions.add(PlayerAction.CALL); playerActions.add(PlayerAction.FOLD); } else { Set<PlayerAction> playerActions = possibleSeatActionsMap.get(playerId); playerActions.add(PlayerAction.CHECK); } } public TableEvent check(UUID playerId, boolean forced, int aggregateVersion) { checkActionOnPlayer(playerId); checkPerformAction(playerId, PlayerAction.CHECK); return forced ? new PlayerForceCheckedEvent(tableId, aggregateVersion, gameId, entityId, playerId) : new PlayerCheckedEvent(tableId, aggregateVersion, gameId, entityId, playerId); } public PlayerCalledEvent call(UUID playerId, int aggregateVersion) { checkActionOnPlayer(playerId); checkPerformAction(playerId, PlayerAction.CALL); PlayerCalledEvent playerCalledEvent = new PlayerCalledEvent(tableId, aggregateVersion, gameId, entityId, playerId); return playerCalledEvent; } public TableEvent fold(UUID playerId, boolean forced, int aggregateVersion) { checkActionOnPlayer(playerId); checkPerformAction(playerId, PlayerAction.FOLD); return forced ? new PlayerForceFoldedEvent(tableId, aggregateVersion, gameId, entityId, playerId) : new PlayerFoldedEvent(tableId, aggregateVersion, gameId, entityId, playerId); } public PlayerRaisedEvent raise(UUID playerId, int aggregateVersion, int raiseToAmount) { checkActionOnPlayer(playerId); checkPerformAction(playerId, PlayerAction.RAISE); checkRaiseAmountValue(playerId, raiseToAmount); PlayerRaisedEvent playerRaisedEvent = new PlayerRaisedEvent(tableId, aggregateVersion, gameId, entityId, playerId, raiseToAmount); return playerRaisedEvent; } TableEvent expireActionOn(UUID playerId, int aggregateVersion) { if (callAmountsMap.get(playerId).intValue() == 0) { return check(playerId, true, aggregateVersion); } return fold(playerId, true, aggregateVersion); } public List<TableEvent> changeActionOn(int aggregateVersion) { if (chipsInBackMap.values().stream().allMatch(x -> x == 0)) { return Collections.emptyList(); } List<TableEvent> eventsCreated = new ArrayList<>(); // do not change action on if the hand is over. starting a new hand // should adjust that if (handDealerState == HandDealerState.COMPLETE) { return eventsCreated; } UUID actionOnPlayerId = seatMap.get(Integer.valueOf(actionOnPosition)); // the player just bet, so a new last to act needs to be determined if (actionOnPlayerId.equals(originatingBettorPlayerId)) { UUID nextPlayerToAct = findNextToAct(); UUID lastPlayerToAct = seatMap.get(Integer.valueOf(determineLastToAct())); ActionOnChangedEvent actionOnChangedEvent = new ActionOnChangedEvent(tableId, aggregateVersion, gameId, entityId, nextPlayerToAct); LastToActChangedEvent lastToActChangedEvent = new LastToActChangedEvent( tableId, aggregateVersion + 1, gameId, entityId, lastPlayerToAct); eventsCreated.add(actionOnChangedEvent); eventsCreated.add(lastToActChangedEvent); } // if it's the last player to act, recalculate for a new round else if (actionOnPlayerId.equals(lastToActPlayerId)) { UUID nextPlayerToAct = findActionOnPlayerIdForNewRound(); UUID newRoundLastPlayerToAct = seatMap.get(Integer .valueOf(determineLastToAct())); ActionOnChangedEvent actionOnChangedEvent = new ActionOnChangedEvent(tableId, aggregateVersion, gameId, entityId, nextPlayerToAct); LastToActChangedEvent lastToActChangedEvent = new LastToActChangedEvent( tableId, aggregateVersion + 1, gameId, entityId, newRoundLastPlayerToAct); eventsCreated.add(actionOnChangedEvent); eventsCreated.add(lastToActChangedEvent); } // just a normal transition mid-round else { UUID nextPlayerToAct = findNextToAct(); ActionOnChangedEvent actionOnChangedEvent = new ActionOnChangedEvent(tableId, aggregateVersion, gameId, entityId, nextPlayerToAct); eventsCreated.add(actionOnChangedEvent); } return eventsCreated; } Optional<TableEvent> dealCommonCardsIfAppropriate(int aggregateVersion) { if (handDealerState == HandDealerState.FLOP_DEALT && !flopDealt) { return Optional.of(new FlopCardsDealtEvent(tableId, aggregateVersion, gameId, entityId)); } if (handDealerState == HandDealerState.TURN_DEALT && !turnDealt) { return Optional.of(new TurnCardDealtEvent(tableId, aggregateVersion, gameId, entityId)); } if (handDealerState == HandDealerState.RIVER_DEALT && !riverDealt) { return Optional.of(new RiverCardDealtEvent(tableId, aggregateVersion, gameId, entityId)); } return Optional.empty(); } List<TableEvent> handlePotAndRoundCompleted(int aggregateVersion) { if (!seatMap.get(Integer.valueOf(actionOnPosition)).equals(lastToActPlayerId) && playersStillInHand.size() > 1) { return Collections.emptyList(); } List<TableEvent> tableEvents = new ArrayList<>(); tableEvents.addAll(potHandler.calculatePots(aggregateVersion, chipsInFrontMap, chipsInBackMap, playersStillInHand)); HandDealerState nextHandDealerState = playersStillInHand.size() == 1 ? HandDealerState.COMPLETE : HandDealerState.values()[handDealerState.ordinal() + 1]; RoundCompletedEvent roundCompletedEvent = new RoundCompletedEvent(tableId, aggregateVersion + tableEvents.size(), gameId, entityId, nextHandDealerState); tableEvents.add(roundCompletedEvent); applyEvent(roundCompletedEvent); return tableEvents; } Optional<TableEvent> finishHandIfAppropriate(int aggregateVersion) { if (handDealerState == HandDealerState.COMPLETE) { return Optional.of(new HandCompletedEvent(tableId, aggregateVersion, gameId, entityId, chipsInBackMap)); } return Optional.empty(); } private void checkRaiseAmountValue(UUID playerId, int raiseToAmount) { int playersTotalChips = chipsInBackMap.get(playerId).intValue() + chipsInFrontMap.get(playerId).intValue(); if (raiseToAmount < raiseToAmountsMap.get(playerId).intValue() || raiseToAmount > playersTotalChips) { throw new IllegalArgumentException("Raise amount must be between the " + "minimum and maximum values."); } } private void checkActionOnPlayer(UUID playerId) { UUID actionOnPlayerId = seatMap.get(Integer.valueOf(actionOnPosition)); if (!playerId.equals(actionOnPlayerId)) { throw new FlexPokerException("action is not on the player attempting action"); } } private void checkPerformAction(UUID playerId, PlayerAction playerAction) { if (!possibleSeatActionsMap.get(playerId).contains(playerAction)) { throw new FlexPokerException("not allowed to " + playerAction); } } void applyEvent(PlayerCheckedEvent event) { UUID playerId = event.getPlayerId(); possibleSeatActionsMap.get(playerId).clear(); callAmountsMap.put(playerId, Integer.valueOf(0)); raiseToAmountsMap.put(playerId, Integer.valueOf(0)); } void applyEvent(PlayerForceCheckedEvent event) { UUID playerId = event.getPlayerId(); possibleSeatActionsMap.get(playerId).clear(); callAmountsMap.put(playerId, Integer.valueOf(0)); raiseToAmountsMap.put(playerId, Integer.valueOf(0)); } void applyEvent(PlayerCalledEvent event) { UUID playerId = event.getPlayerId(); possibleSeatActionsMap.get(playerId).clear(); int newChipsInFront = chipsInFrontMap.get(playerId).intValue() + callAmountsMap.get(playerId).intValue(); chipsInFrontMap.put(playerId, Integer.valueOf(newChipsInFront)); int newChipsInBack = chipsInBackMap.get(playerId).intValue() - callAmountsMap.get(playerId).intValue(); chipsInBackMap.put(playerId, Integer.valueOf(newChipsInBack)); callAmountsMap.put(playerId, Integer.valueOf(0)); raiseToAmountsMap.put(playerId, Integer.valueOf(0)); } void applyEvent(PlayerFoldedEvent event) { UUID playerId = event.getPlayerId(); playersStillInHand.remove(playerId); potHandler.removePlayerFromAllPots(playerId); possibleSeatActionsMap.get(playerId).clear(); callAmountsMap.put(playerId, Integer.valueOf(0)); raiseToAmountsMap.put(playerId, Integer.valueOf(0)); } void applyEvent(PlayerForceFoldedEvent event) { UUID playerId = event.getPlayerId(); playersStillInHand.remove(playerId); potHandler.removePlayerFromAllPots(playerId); possibleSeatActionsMap.get(playerId).clear(); callAmountsMap.put(playerId, Integer.valueOf(0)); raiseToAmountsMap.put(playerId, Integer.valueOf(0)); } void applyEvent(PlayerRaisedEvent event) { UUID playerId = event.getPlayerId(); int raiseToAmount = event.getRaiseToAmount(); originatingBettorPlayerId = playerId; int raiseAboveCall = raiseToAmount - (chipsInFrontMap.get(playerId).intValue() + callAmountsMap .get(playerId).intValue()); int increaseOfChipsInFront = raiseToAmount - chipsInFrontMap.get(playerId).intValue(); playersStillInHand.stream().filter(x -> !x.equals(playerId)).forEach(x -> { adjustPlayersFieldsAfterRaise(raiseToAmount, raiseAboveCall, x); }); possibleSeatActionsMap.get(playerId).clear(); callAmountsMap.put(playerId, Integer.valueOf(0)); raiseToAmountsMap.put(playerId, Integer.valueOf(bigBlind)); chipsInFrontMap.put(playerId, Integer.valueOf(raiseToAmount)); int newChipsInBack = chipsInBackMap.get(playerId).intValue() - increaseOfChipsInFront; chipsInBackMap.put(playerId, Integer.valueOf(newChipsInBack)); } void applyEvent(ActionOnChangedEvent event) { actionOnPosition = seatMap.entrySet().stream() .filter(x -> x.getValue().equals(event.getPlayerId())).findAny().get() .getKey().intValue(); } void applyEvent(LastToActChangedEvent event) { lastToActPlayerId = event.getPlayerId(); } void applyEvent(@SuppressWarnings("unused") FlopCardsDealtEvent event) { flopDealt = true; } void applyEvent(@SuppressWarnings("unused") TurnCardDealtEvent event) { turnDealt = true; } void applyEvent(@SuppressWarnings("unused") RiverCardDealtEvent event) { riverDealt = true; } private UUID findActionOnPlayerIdForNewRound() { int buttonIndex = buttonOnPosition; for (int i = buttonIndex + 1; i < seatMap.size(); i++) { UUID playerAtTable = seatMap.get(Integer.valueOf(i)); if (playerAtTable != null && playersStillInHand.contains(playerAtTable) && chipsInBackMap.get(playerAtTable).intValue() != 0) { return playerAtTable; } } for (int i = 0; i < buttonIndex; i++) { UUID playerAtTable = seatMap.get(Integer.valueOf(i)); if (playerAtTable != null && playersStillInHand.contains(playerAtTable) && chipsInBackMap.get(playerAtTable).intValue() != 0) { return playerAtTable; } } throw new FlexPokerException("couldn't determine new action on after round"); } private void resetChipsInFront() { Set<UUID> playersInMap = chipsInFrontMap.keySet(); playersInMap.forEach(x -> chipsInFrontMap.put(x, Integer.valueOf(0))); } private void resetCallAndRaiseAmountsAfterRound() { playersStillInHand .forEach(playerInHand -> { callAmountsMap.put(playerInHand, Integer.valueOf(0)); if (bigBlind > chipsInBackMap.get(playerInHand) .intValue()) { raiseToAmountsMap.put(playerInHand, chipsInBackMap.get(playerInHand)); } else { raiseToAmountsMap.put(playerInHand, Integer.valueOf(bigBlind)); } }); } private void resetPossibleSeatActionsAfterRound() { playersStillInHand.forEach(playerInHand -> { possibleSeatActionsMap.get(playerInHand).clear(); possibleSeatActionsMap.get(playerInHand).add(PlayerAction.CHECK); possibleSeatActionsMap.get(playerInHand).add(PlayerAction.RAISE); }); } private UUID findNextToAct() { for (int i = actionOnPosition + 1; i < seatMap.size(); i++) { UUID playerAtTable = seatMap.get(Integer.valueOf(i)); if (playerAtTable != null && playersStillInHand.contains(playerAtTable)) { return playerAtTable; } } for (int i = 0; i < actionOnPosition; i++) { UUID playerAtTable = seatMap.get(Integer.valueOf(i)); if (playerAtTable != null && playersStillInHand.contains(playerAtTable)) { return playerAtTable; } } throw new IllegalStateException("unable to find next to act"); } Optional<WinnersDeterminedEvent> determineWinnersIfAppropriate(int aggregateVersion) { if (handDealerState == HandDealerState.COMPLETE) { Set<UUID> playersRequiredToShowCards = potHandler.fetchPlayersRequriedToShowCards(playersStillInHand); Map<UUID, Integer> playersToChipsWonMap = potHandler.fetchChipsWon(playersStillInHand); return Optional.of(new WinnersDeterminedEvent(tableId, aggregateVersion, gameId, entityId, playersRequiredToShowCards, playersToChipsWonMap)); } return Optional.empty(); } private void addToChipsInBack(UUID playerId, int chipsToAdd) { int currentAmount = chipsInBackMap.get(playerId).intValue(); chipsInBackMap.put(playerId, Integer.valueOf(currentAmount + chipsToAdd)); } private void subtractFromChipsInBack(UUID playerId, int chipsToSubtract) { int currentAmount = chipsInBackMap.get(playerId).intValue(); chipsInBackMap.put(playerId, Integer.valueOf(currentAmount - chipsToSubtract)); } private void subtractFromChipsInFront(UUID playerId, int chipsToSubtract) { int currentAmount = chipsInFrontMap.get(playerId).intValue(); chipsInFrontMap.put(playerId, Integer.valueOf(currentAmount - chipsToSubtract)); } private int determineLastToAct() { int seatIndex; if (originatingBettorPlayerId == null) { seatIndex = buttonOnPosition; } else { seatIndex = seatMap.entrySet().stream() .filter(x -> x.getValue().equals(originatingBettorPlayerId)) .findAny().get().getKey().intValue(); if (seatIndex == 0) { seatIndex = seatMap.size() - 1; } else { seatIndex--; } } for (int i = seatIndex; i >= 0; i--) { UUID playerAtTable = seatMap.get(Integer.valueOf(i)); if (playerAtTable != null && playersStillInHand.contains(playerAtTable)) { return i; } } for (int i = seatMap.size() - 1; i > seatIndex; i--) { UUID playerAtTable = seatMap.get(Integer.valueOf(i)); if (playerAtTable != null && playersStillInHand.contains(playerAtTable)) { return i; } } throw new IllegalStateException("unable to determine last to act"); } boolean idMatches(UUID handId) { return entityId.equals(handId); } private void adjustPlayersFieldsAfterRaise(int raiseToAmount, int raiseAboveCall, UUID playerId) { possibleSeatActionsMap.get(playerId).clear(); possibleSeatActionsMap.get(playerId).add(PlayerAction.CALL); possibleSeatActionsMap.get(playerId).add(PlayerAction.FOLD); int totalChips = chipsInBackMap.get(playerId).intValue() + chipsInFrontMap.get(playerId).intValue(); if (totalChips <= raiseToAmount) { callAmountsMap.put(playerId, totalChips - chipsInFrontMap.get(playerId)); raiseToAmountsMap.put(playerId, 0); } else { callAmountsMap.put( playerId, Integer.valueOf(raiseToAmount - chipsInFrontMap.get(playerId).intValue())); possibleSeatActionsMap.get(playerId).add(PlayerAction.RAISE); if (totalChips < raiseToAmount + raiseAboveCall) { raiseToAmountsMap.put(playerId, Integer.valueOf(totalChips)); } else { raiseToAmountsMap.put(playerId, Integer.valueOf(raiseToAmount + raiseAboveCall)); } } } void applyEvent(PotAmountIncreasedEvent event) { potHandler.addToPot(event.getPotId(), event.getAmountIncreased()); playersStillInHand.forEach(x -> subtractFromChipsInFront(x, event.getAmountIncreased())); } void applyEvent(PotClosedEvent event) { potHandler.closePot(event.getPotId()); } void applyEvent(PotCreatedEvent event) { potHandler.addNewPot(event.getPotId(), event.getPlayersInvolved()); } void applyEvent(RoundCompletedEvent event) { handDealerState = event.getNextHandDealerState(); originatingBettorPlayerId = null; resetChipsInFront(); resetCallAndRaiseAmountsAfterRound(); resetPossibleSeatActionsAfterRound(); } void applyEvent(WinnersDeterminedEvent event) { playersToShowCards.addAll(event.getPlayersToShowCards()); event.getPlayersToChipsWonMap().forEach((playerId, chips) -> { addToChipsInBack(playerId, chips.intValue()); }); } Optional<TableEvent> autoMoveHandForward(int aggregateVersion) { return chipsInBackMap.values().stream().allMatch(x -> x == 0) ? Optional.of(new AutoMoveHandForwardEvent(tableId, aggregateVersion, gameId, entityId)) : Optional.empty(); } }