za.co.knowles.pokewhat.domain.Game Maven / Gradle / Ivy
package za.co.knowles.pokewhat.domain;
import za.co.knowles.pokewhat.HandComparator;
import za.co.knowles.pokewhat.HandGeneration;
import za.co.knowles.pokewhat.domain.lookup.EBetResult;
import za.co.knowles.pokewhat.domain.lookup.EGameState;
import za.co.knowles.pokewhat.domain.lookup.EHand;
import java.util.*;
import java.util.stream.Collectors;
public class Game {
private static final List STAGES = Arrays.asList(3, 1, 1);
private static final int MIN_PLAYERS = 2;
private static final int IN_HAND_CARDS = 2;
private final List players = new ArrayList<>();
private final Map playerHands = new HashMap<>();
private final List roundBets = new ArrayList<>();
private final List folded = new ArrayList<>();
private Hand openCards;
private Deck deck;
private final double blind;
// start dealer index at -1 so the first dealer is the first joined player
int dealerIndex = -1;
public Game(double blind) {
this.blind = blind;
deck = new Deck();
}
public Game(double blind, Deck deck) {
this.blind = blind;
this.deck = deck;
}
private EGameState gameState = EGameState.WAITING_FOR_PLAYERS;
public void addPlayer(Player player) {
if (!gameState.preGame()) {
return;
}
players.add(player);
if (players.size() < MIN_PLAYERS) {
gameState = EGameState.WAITING_FOR_PLAYERS;
} else {
gameState = EGameState.NOT_STARTED;
}
}
public void dealCards() {
if (!playerHands.isEmpty()) {
return;
}
for (Player player : players) {
Hand hand = new Hand();
for (int i = 0; i < IN_HAND_CARDS; i++) {
hand.add(deck.takeCard());
}
playerHands.put(player, hand);
}
}
public GameState start() {
if (gameState == EGameState.WAITING_FOR_PLAYERS) {
return buildGameState();
}
if (gameState != EGameState.NOT_STARTED) {
return buildGameState();
}
openCards = new Hand();
for (int number : STAGES) {
for (int i = 0; i < number; i++) {
openCards.add(deck.takeCard());
}
}
dealerIndex = incrementPlayerIndex(dealerIndex);
advanceGameState();
newBettingRound();
return buildGameState();
}
public Map getBets() {
RoundBets roundBets = currentBets();
if (roundBets == null) {
return Collections.emptyMap();
}
return roundBets.getBets();
}
public void setupBlinds() {
if (currentBets() != null) {
return;
}
int bigIndex = incrementPlayerIndex(dealerIndex);
int smallIndex = incrementPlayerIndex(bigIndex);
roundBets.add(new RoundBets(blind, players.get(bigIndex), players.get(smallIndex), players));
}
private void advanceGameState() {
if (gameState == EGameState.DONE) {
return;
}
gameState = gameState.advance();
if (gameState != null) {
gameState.event(this);
}
}
private void newBettingRound() {
RoundBets roundBets = currentBets();
if (roundBets == null) {
setupBlinds();
} else {
if (roundBets.isRoundDone()) {
List activeAfterRound = roundBets.getActiveAfterRound();
if (activeAfterRound.isEmpty() || activeAfterRound.size() == 1) {
while (gameState != EGameState.DONE) {
advanceGameState();
}
roundBets.next(null);
return;
}
Player nextPlayer = getFirstActivePlayer(activeAfterRound);
roundBets.next(nextPlayer);
activeAfterRound = roundBets.getActiveAfterRound();
if (roundBets.isAllIn() && activeAfterRound.size() != 1) {
this.roundBets.add(new RoundBets(activeAfterRound, nextPlayer));
}
}
}
}
private Player getFirstActivePlayer(List activeAfterRound) {
Player nextPlayer = null;
if (activeAfterRound.size() != 1) {
int nextIndex = dealerIndex + 1;
while (nextPlayer == null) {
nextPlayer = players.get(nextIndex % players.size());
if (!activeAfterRound.contains(nextPlayer)) {
nextPlayer = null;
nextIndex++;
}
}
}
return nextPlayer;
}
private int incrementPlayerIndex(int currentIndex) {
return (currentIndex + 1) % players.size();
}
private GameState buildGameState() {
return new GameState(gameState, currentCards());
}
private List currentCards() {
List allCards = openCards.getCards();
List currentCards = new ArrayList<>();
int cardIndex = 0;
EGameState state = EGameState.FIRST_STATE;
do {
state = state.advance();
if (state != null) {
for (int j = 0; j < state.getCardsToReveal(); j++) {
currentCards.add(allCards.get(cardIndex));
cardIndex++;
}
}
} while (state != null && state != gameState);
return currentCards;
}
public EBetResult bet(Player player, double betAmount) {
RoundBets roundBets = currentBets();
if (roundBets == null) {
return EBetResult.OUT_OF_TURN;
}
if (currentRoundDone()) {
return EBetResult.OUT_OF_TURN;
}
if (!getCurrentPlayer().equals(player)) {
return EBetResult.OUT_OF_TURN;
}
EBetResult eBetResult = roundBets.placeBet(player, betAmount);
if (eBetResult == EBetResult.FOLDED) {
folded.add(player);
}
return eBetResult;
}
public void fold(Player player) {
RoundBets roundBets = currentBets();
if (roundBets == null) {
return;
}
roundBets.fold(player);
while (currentRoundDone() && gameState != EGameState.DONE) {
nextRound();
}
}
public boolean currentRoundDone() {
RoundBets roundBets = currentBets();
if (roundBets == null) {
return false;
}
return roundBets.isRoundDone();
}
private RoundBets currentBets() {
if (roundBets.isEmpty()) {
return null;
}
return roundBets.get(roundBets.size() - 1);
}
public double getCurrentBet() {
RoundBets roundBets = currentBets();
if (roundBets == null) {
return 0.0;
}
return roundBets.getCurrentBet();
}
public EGameState getState() {
return gameState;
}
public Player getDealer() {
return players.get(dealerIndex);
}
public Player getCurrentPlayer() {
RoundBets roundBets = currentBets();
if (roundBets == null) {
return null;
}
return roundBets.getCurrentPlayer();
}
public int getPlayerCount() {
return players.size();
}
public GameState nextRound() {
if (gameState == EGameState.WAITING_FOR_PLAYERS || gameState == EGameState.NOT_STARTED) {
return buildGameState();
}
if (gameState == EGameState.DONE) {
return buildGameState();
}
RoundBets roundBets = currentBets();
if (roundBets == null || !roundBets.isRoundDone()) {
return buildGameState();
}
newBettingRound();
advanceGameState();
return buildGameState();
}
public Hand handFor(Player playerOne) {
return playerHands.get(playerOne);
}
public List getResult() {
if (gameState != EGameState.DONE) {
return null;
}
List results = new ArrayList<>();
for (RoundBets roundBet : roundBets) {
List normalized = roundBet.normalize();
for (RoundBets bets : normalized) {
results.add(getGameResultForPlayers(bets));
}
}
return results;
}
private GameResult getGameResultForPlayers(RoundBets roundBets) {
List inGamePlayers = roundBets.getInGamePlayers();
inGamePlayers.removeAll(folded);
if (inGamePlayers.size() == 1) {
return new GameResult(Collections.singletonMap(inGamePlayers.get(0), new HandResult(null, EHand.FOLD)), roundBets.getDescription(), roundBets.getTotalBetPool());
}
Map bestHands = new HashMap<>();
for (Player player : inGamePlayers) {
Hand best = HandGeneration.getBest(openCards.getCards(), playerHands.get(player).getCards());
bestHands.put(best, player);
}
List compare = HandComparator.compare(bestHands.keySet());
List topRankedHands = compare.stream().filter(c -> c.getHandResult() == compare.get(0).getHandResult()).collect(Collectors.toList());
if (topRankedHands.size() > 1) {
topRankedHands = topRankedHands.stream().filter(HandResult::isTieBreakWinner).collect(Collectors.toList());
}
Map winningPlayers = new HashMap<>();
for (HandResult topRankedHand : topRankedHands) {
winningPlayers.put(bestHands.get(topRankedHand.getHand()), topRankedHand);
}
return new GameResult(winningPlayers, roundBets.getDescription(), roundBets.getTotalBetPool());
}
public double getCurrentPool() {
RoundBets roundBets = currentBets();
if (roundBets == null) {
return 0;
}
return roundBets.getTotalBetPool();
}
public GameState getGameState() {
return buildGameState();
}
public double getBlind() {
return blind;
}
}