
behaviors.play.BlackjackPlay.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of cards_2.13 Show documentation
Show all versions of cards_2.13 Show documentation
Scala library for card-playing functionality, including games Blackjack and Thirty-One
The newest version!
package cards.behaviors.play
import cards.behaviors.Commons
import cards.behaviors.evaluation.BlackjackHandEvaluation
import cards.classes.state.{ BlackjackGameState, BlackjackPlayerState }
import cards.classes.{ Card, Rank, Suit, Deck }
import cards.classes.Rank._
import cards.classes.Suit._
import cards.classes.hand.Hand
import cards.classes.actions.{ Action, BlackjackAction }
import cards.classes.actions.BlackjackAction._
import cards.classes.options.blackjack.BlackjackOptions
import cards.classes.options.blackjack.DealerHitLimit._
trait BlackjackPlay {
type EVAL <: BlackjackHandEvaluation
type COMMONS <: Commons
val commons: COMMONS
val evaluation: EVAL
import commons._
import evaluation._
private def pairMatchingRank(cards: Seq[Card]): Boolean = cards.length == 2 && countRank(cards).values.toSeq.contains(2)
private def pairOfAces(cards: Seq[Card]): Boolean = pairMatchingRank(cards) && cards.head.rank == Ace
def canDoubleDown(game: BlackjackGameState): Boolean = {
val hand = game.currentHand()
val player = game.currentPlayer()
game.players.length > 0 && // at least 1 player
game.currentPlayerIndex.isDefined && // current player exists
hand.hand.length == 2 && // current hand is length 2
hand.bets.keySet.contains(player.id) && // current player has bet on his hand
player.bank > 2 * hand.bets(player.id) // current player has enough in bank to cover the bet again
}
// Players can only split on first play (only 2 cards) when both cards have same value
// Tens, Jacks, Queens, and Kings are all considered the same value, meaning that player can split on Ten and Queen, etc...
// Dealers cannot split but this function doesn't check for this
// o - game options, specifically resplitLimit and resplitOnSplitAces
// splitCount - number of times player has split in this turn, applicable when options.resplitLimit is specified as non-Unlimitted
// splitAcesCount - number of times player has split aces in this turn, applicable when options.resplitOnSplitAces is false
def canSplit(cards: Seq[Card], o: BlackjackOptions = BlackjackOptions(), splitCount: Int = 0, splitAcesCount: Int = 0): Boolean = {
(splitCount, o.splitLimit, o.resplitOnSplitAces, pairOfAces(cards), splitAcesCount) match {
case (count, Some(limit), _, _, _) if (count >= limit) => false
case (_, _, false, true, n) if (n > 0) => false // cannot split aces more than once per turn, unless variation on options allows it
case (_, _, _, _, _) => {
val tens: Seq[Rank] = Seq(Ten, Jack, Queen, King) // split is by value and not by rank: Ten and Jack can split even though they differ in rank
cards.length == 2 &&
(tens.contains(cards.head.rank) && tens.contains(cards.tail.head.rank) || pairMatchingRank(cards))
}
}
}
def getPlayerBet(playerState: BlackjackPlayerState, playerId: String): Option[(Seq[Card], Int)] = {
(for {
x <- playerState.handsAndBets
if (x.bets.keys.toSeq.contains(playerId))
} yield (x.hand, x.bets.filter(_._1 == playerId).values.head)
).headOption
}
def getPlayerBets(game: BlackjackGameState, playerId: String): Seq[(Seq[Card], Int)] = {
for {
player <- game.players
bet <- getPlayerBet(player, playerId)
} yield bet
}
def hasPlayerBet(game: BlackjackGameState, playerId: String): Boolean = getPlayerBets(game, playerId) != Nil
def handCompleted(hand: Hand): Boolean = hand.outcome.isDefined
def handsCompleted(player: BlackjackPlayerState) = player.handsAndBets.count(h => handCompleted(h)) == player.hands.length
def isFirstRound(hand: Hand): Boolean = hand.outcome.isEmpty && hand.hand.length == 2
def betsHaveBeenPlaced(game: BlackjackGameState): Boolean = {
(for {
player <- game.players
} yield hasPlayerBet(game, player.id)).count(p => p) == game.players.length
}
def isTimeToPlay(game: BlackjackGameState): Boolean = {
if (game.players == Nil)
throw new IllegalArgumentException("cannot determine whether isTimeToPlay when there are no players")
val hands: Seq[Seq[Card]] = game.players.flatMap(p => p.hands)
hands.count(cs => cs.length >= 2) == hands.length &&
betsHaveBeenPlaced(game) &&
(game.players.flatMap(_.handsAndBets.map(h => h.outcome)).count(w => w.isDefined) != game.players.length)
}
def isTimeToDeal(game: BlackjackGameState): Boolean = {
// time to deal if either dealer doesn't have all cards or 1 or more players doesn't have all cards
game.players.length > 0 &&
(game.dealerHand.hand.length < 2 ||
game.players.count(p => p.hands == Nil || p.hands.count(_.length < 2) > 0) > 0 ) // then it's time to deal
}
def deal(game: BlackjackGameState): BlackjackGameState = {
if (!isTimeToDeal(game)) {
throw new IllegalArgumentException("cannot deal cards because it's not currently time to deal")
}
var deck = game.deck
val (updatedPlayersAndHistories): Seq[(BlackjackPlayerState, Option[Action[BlackjackAction]])] = (for {
player <- game.players
hand <- player.handsAndBets
} yield {
val (updatedCards, updatedDeck): (Seq[Card], Deck) = deck.deal(2 - hand.hand.length)
deck = updatedDeck
val history = if (hand.hand.length < 2)
// Some(Action(player.id, IsDealt, updatedCards, 0, hand.hand, Seq(hand.hand ++ updatedCards) ))
Some(Action(player.id, IsDealt, updatedCards, None ))
else
None
(player.updateHand(hand.hand, updatedCards), history) // only updates hand when updatedCards isn't empty
})
val (newlyDealtDealerCards, updatedDeck): (Seq[Card], Deck) = game.dealerHand.hand.length match {
case n if (n < 2) => deck.deal(2 - n)
case _ => (Nil, deck)
}
val updatedDealerHand: Seq[Card] = game.dealerHand.hand ++ newlyDealtDealerCards
val originalDealerWithFaceDown: Seq[Card] = game.dealerHand.hand.length match {
case 0 => Nil
case 1 => Seq(Card.FaceDownCard)
case _ => Seq(Card.FaceDownCard) ++ game.dealerHand.hand.tail
}
val newDealerWithFaceDown: Seq[Card] = Seq(Card.FaceDownCard) ++ updatedDealerHand.tail
val dealerHistory: Seq[Action[BlackjackAction]] = newlyDealtDealerCards.length match {
case 0 => Nil
case _ => {
Seq(Action("Dealer", IsDealt, newDealerWithFaceDown, None))
}
}
val history: Seq[Action[BlackjackAction]] = updatedPlayersAndHistories.filter(_._2.isDefined).map(_._2.get) ++ dealerHistory
val updatedPlayers: Seq[BlackjackPlayerState] = updatedPlayersAndHistories.map(_._1)
assert(updatedDeck != game.deck)
game.copy(players = updatedPlayers, deck = updatedDeck, dealerHand = game.dealerHand.copy(hand = updatedDealerHand), history = game.history ++ history)
}
def isTimeForDealerToPlay(game: BlackjackGameState): Boolean = {
if (game.players == Nil)
throw new IllegalArgumentException("cannot determine whether isTimeForDealerToPlay when there are no players")
game.currentPlayerIndex.isEmpty && // only time for dealer to play when no current player is still playing his or her hand
isTimeToPlay(game) && // play time
!isTimeToDeal(game) // && // not time to deal
}
def dealerPlay(game: BlackjackGameState): BlackjackGameState = {
if (!isTimeForDealerToPlay(game)) {
throw new IllegalArgumentException("dealer cannot play hand because it's not currently time for dealer to play")
}
val action: BlackjackAction =
(evaluation.eval(game.dealerHand.hand), game.options.dealerHitLimit, game.dealerHand.hand.map(_.rank).contains(Ace)) match {
case (17, H17, true) => Hit // dealer hits on soft 17
case (17, S17, true) => Stand // dealer stands on soft 17
case (n, _, _) if (n > 17) => Stand
case (_, _, _) => Hit
}
val (newDealerCards, newHistory, newDeck): (Seq[Card], Seq[Action[BlackjackAction]], Deck) = action match {
case Stand => (game.dealerHand.hand, Seq(Action("Dealer", Stand, Nil, None, Nil, Seq(Seq(Card.FaceDownCard) ++ game.dealerHand.hand.tail))), game.deck)
case Hit => {
// Hit deals 1 card
val (dealt, nextDeck): (Seq[Card], Deck) = game.deck.deal()
(game.dealerHand.hand ++ dealt, Seq(Action("Dealer", Hit, dealt, None, Nil, Seq(Seq(Card.FaceDownCard) ++ (game.dealerHand.hand ++ dealt).tail))), nextDeck)
}
case a => throw new IllegalArgumentException(s"Unexpected dealer action [$a]; dealer can only ever Hit or Stand")
}
val nextState: BlackjackGameState = game.copy(
deck = newDeck,
history = game.history ++ newHistory,
dealerHand = game.dealerHand.copy(hand = newDealerCards))
// if dealer's 21 or busted or is Standing, then game is over and bets should be settled
val gameOverHistory: Seq[Action[BlackjackAction]] = (eval(newDealerCards), action, nextState.history.reverse.head.action) match {
case (n, _, _) if (n > 21) => Seq(Action("Dealer", Bust, newDealerCards))
case (_, Stand, _) => Seq(Action("Dealer", ShowCards, newDealerCards))
case (_, _, ShowCards) => Seq(Action("Dealer", ShowCards, newDealerCards))
case (_, _, _) => Nil // game not yet over
}
val gameOver: Boolean = gameOverHistory != Nil
gameOver match {
case false => nextState
case true => {
evaluation.outcomes(nextState.copy(history = nextState.history ++ gameOverHistory)) // game over: evaluate each hand against dealer's to prepare to settleBets
}
}
}
// Basic Strategy in Blackjack
def nextAction(game: BlackjackGameState): BlackjackAction = {
if (game.players == Nil) {
throw new IllegalArgumentException("Cannot play current hand because there are no players")
}
if (game.currentPlayerIndex.isEmpty) {
throw new IllegalArgumentException("Cannot play current hand because current player is not specified")
}
if (game.currentHandIndex.isEmpty) {
throw new IllegalArgumentException("Cannot play current hand because current hand is not specified")
}
if (game.currentCards().length < 2) {
throw new IllegalArgumentException(s"Cannot play current hand because current hand length [${game.currentCards().length}] is less than length 2")
}
if (game.dealerHand.hand.length != 2) {
throw new IllegalArgumentException(s"Cannot play current hand because dealer's hand length [${game.dealerHand.hand.length}] is not length 2")
}
if (!isTimeToPlay(game)) {
throw new IllegalArgumentException(s"Cannot play current hand because it's not currently time to play hand")
}
// TODO: update this method to also inspect current player's last bet as well as their bank to determine whether they have funds to DoubleDown
val canDouble: Boolean = canDoubleDown(game) //game.currentCards().length == 2
// we only care about this current player's actions
val previousActions: Seq[Action[BlackjackAction]] = game.history.filter(_.playerId == game.currentPlayer().id)
// current player's split count from history
val splitCount: Int = previousActions.count(a => a.action == Split)
// current player's aces split count from history
val acesSplitCount: Int = previousActions.count(a => pairOfAces(a.beforeCards) && a.action == Split)
val eligibleToSplit: Boolean = canSplit(game.currentCards(), game.options, splitCount, acesSplitCount)
// player's turn: based on player's cards and dealers face up card, decide which action to take
// looking at cards in reverse order so aces are at head, and changed to list in order to pattern match on head :: tail
val highestRank: Rank = game.currentCards().sorted.reverse.head.rank
val totalScore: Long = eval(game.currentCards())
val tailScore: Long = eval(game.currentCards().tail)
// val dealerFaceUpRank: Rank = game.dealerHand.hand.head.rank
val dealerFaceUpRank: Rank = game.dealerHand.hand.tail.head.rank
val surrenderOffered: Boolean = game.options.allowSurrender
val tens: Seq[Rank] = Seq(Ten, Jack, Queen, King) // ranks which have value of 10
// this logic is Basic Strategy in Blackjack
(highestRank, tailScore, totalScore, dealerFaceUpRank, canDouble, eligibleToSplit, surrenderOffered) match {
// **** case 1: pairs
// eligible to split on aces, do it regardless of dealer's showing card
case (Ace, _, _, _, _, true, _) => Split
// always Stand on 20, regardless whether Split is available
case (_, 20, _, _, _, _, _) => Stand
// case (rank, _, _, _, _, true, _) if (tens.contains(rank)) => Stand
// stand on 18 when dealer shows 7, 10, or Ace
case (Nine, _, _, dealer, _, true, _) if (Seq(Seven, Ten, Ace).contains(dealer)) => Stand
// else split on 18 when dealer shows anything else
case (Nine, _, _, _, _, true, _) => Split
// surrender on 16 when it's an available option and dealer shows Ace
case (Eight, _, _, Ace, _, true, true) => Surrender
// dealer shows Ace but surrender is not allowed, so Split eights
case (Eight, _, _, Ace, _, true, false) => Split
// if dealer shows 8 or higher then Hit on 14
case (Seven, _, _, dealer, _, true, _) if ((Seq(Eight, Nine, Ace) ++ tens).contains(dealer)) => Hit
// else dealer shows 7 or lower, so Split on 2 Sevens
case (Seven, _, _, _, _, true, _) => Split
// if dealer shows 7 or higher then Hit on 12
case (Six, _, _, dealer, _, true, _) if ((Seq(Seven, Eight, Nine, Ace) ++ tens).contains(dealer)) => Hit
// else dealer shows 6 or lower, so Split on 2 Sixes
case (Six, _, _, _, _, true, _) => Split
// if dealer shows 10 or higher then Hit on 10
case (Five, _, _, dealer, _, true, _) if ((Seq(Ace) ++ tens).contains(dealer)) => Hit
// dealer shows 9 or lower and double down is offered so double down on 10
case (Five, _, _, _, true, true, _) => DoubleDown
// dealer shows 9 or lower but doubling down isn't available, so Hit on 10
case (Five, _, _, _, true, false, _) => Hit
// dealer shows 5 or 6 so Split on 2 Fours
case (Four, _, _, dealer, _, true, _) if (Seq(Five, Six).contains(dealer)) => Split
// else dealer does not show 5 or 6 so Hit on 8
case (Four, _, _, _, _, true, _) => Hit
// dealer shows 8 or higher so hit on 6
case (Three, _, _, dealer, _, true, _) if ((Seq(Eight, Nine, Ace) ++ tens).contains(dealer)) => Hit
// else dealer shows 7 or lower so Split 2 Threes
case (Three, _, _, _, _, true, _) => Split
// dealer shows 8 or higher so hit on 4
case (Two, _, _, dealer, _, true, _) if ((Seq(Eight, Nine, Ace) ++ tens).contains(dealer)) => Hit
// else dealer shows 7 or lower so Split 2 Twos
case (Two, _, _, _, _, true, _) => Split
// **** case 2: player has a soft total
// soft total of 20 should Stand
case (Ace, 9, _, _, _, _, _) => Stand
// if hand is 19 and dealer shows 6, double down (if it's an available option)
case (Ace, 8, _, Six, true, _, _) => DoubleDown
// else Stand on 19 when double down is unavailable, regardless what dealer shows
case (Ace, 8, _, _, _, _, _) => Stand
// Hit on 18 when dealer shows Nine or higher
case (Ace, 7, _, dealer, _, _, _) if ((Seq(Nine, Ace) ++ tens).contains(dealer)) => Hit
// Stand on 18 when dealer shows Seven or Eight
case (Ace, 7, _, dealer, _, _, _) if (Seq(Seven, Eight).contains(dealer)) => Stand
// Double-down if it's available on 18 when dealer shows Six or lower
case (Ace, 7, _, _, true, _, _) => DoubleDown
// else Double-down is not an option, so Stand on 18 when dealer shows Six or lower
case (Ace, 7, _, _, false, _, _) => Stand
// Double-down if it's available on a 17 when dealer shows 3, 4, 5, or 6
case (Ace, 6, _, dealer, true, _, _) if (Seq(Three, Four, Five, Six).contains(dealer)) => DoubleDown
// else Double-down is unavailable, so Hit on 17
case (Ace, 6, _, _, false, _, _) => Hit
// Double-down if it's available on a 15 or 16 when dealer shows 4, 5, or 6
case (Ace, tail, _, dealer, true, _, _) if (Seq(Four, Five, Six).contains(dealer) && (tail == 5 || tail == 4)) => DoubleDown
// else Double-down is unavailable, so Hit on 16
case (Ace, tail, _, _, false, _, _) if (tail == 5 || tail == 4) => Hit
// Double-down if it's available on either 13 or 14 when dealer shows 5 or 6
case (Ace, tail, _, dealer, true, _, _) if (Seq(Five, Six).contains(dealer) && (tail == 3 || tail == 2)) => DoubleDown
// else Double-down is unavailable, so Hit on either 13 or 14
case (Ace, tail, _, _, false, _, _) if (tail == 3 || tail == 2) => Hit
// **** case 3: hard-totals (excluding pairs)
// Stand on 18-21
case (_, _, hand, _, _, _, _) if (Seq(18, 19, 20, 21).contains(hand)) => Stand
// if dealer shows Ace then Surrender if available on 17
case (_, _, 17, Ace, _, _, true) => Surrender
// else Surrender is not offered, so Stand on 17 when dealer shows Ace
case (_, _, 17, Ace, _, _, false) => Stand
// dealer doesn't show Ace, so Stand on 17
case (_, _, 17, _, _, _, _) => Stand
// if dealer shows 9 or higher, Surrender on 16 if option is available
case (_, _, 16, dealer, _, _, true) if ((Seq(Nine, Ace) ++ tens).contains(dealer)) => Surrender
// else Surrender is not offered when dealer shows 9 or higher, so Hit on 16
case (_, _, 16, dealer, _, _, false) if ((Seq(Nine, Ace) ++ tens).contains(dealer)) => Hit
// if dealer shows 7 or 8 then Hit on 16
case (_, _, 16, dealer, _, _, _) if (Seq(Seven, Eight).contains(dealer)) => Hit
// else dealer shows 6 or lower so Stand 16
case (_, _, 16, _, _, _, _) => Stand
// if dealer shows 10 or higher, Surrender on 15 if option is available
case (_, _, 15, dealer, _, _, true) if ((Seq(Ace) ++ tens).contains(dealer)) => Surrender
// else Surrender is not offered when dealer shows 10 or higher, so Hit on 15
case (_, _, 15, dealer, _, _, false) if ((Seq(Ace) ++ tens).contains(dealer)) => Hit
// if dealer shows 7, 8, or 9 then Hit on 15
case (_, _, 15, dealer, _, _, _) if (Seq(Seven, Eight, Nine).contains(dealer)) => Hit
// else dealer shows 6 or lower so Stand 15
case (_, _, 15, _, _, _, _) => Stand
// if dealer shows 7 or higher and hand is either 13 or 14 then Hit
case (_, _, hand, dealer, _, _, _) if (Seq(13, 14).contains(hand) && ((Seq(Seven, Eight, Nine, Ace) ++ tens).contains(dealer))) => Hit
// else dealer shows 6 or less so Stand on either 13 or 14
case (_, _, hand, _, _, _, _) if (Seq(14, 15).contains(hand)) => Stand
// if dealer shows 4, 5, or 6 then Stand on 12
case (_, _, 12, dealer, _, _, _) if (Seq(Four, Five, Six).contains(dealer)) => Stand
// else dealer Hit on 12 when dealer shows anything other than 4, 5, or 6
case (_, _, 12, _, _, _, _) => Hit
// Double-down if available on 11
case (_, _, 11, _, true, _, _) => DoubleDown
// Hit on 11 if Double-down is not an option
case (_, _, 11, _, false, _, _) => Hit
// Hit on 10 if dealer shows 10 or 11
case (_, _, 10, dealer, _, _, _) if ((Seq(Ace) ++ tens).contains(dealer)) => Hit
// else dealer shows 9 or less, if the option is available then player should Double-down on 10
case (_, _, 10, _, true, _, _) => DoubleDown
// the Double-down option is unavailable so Hit on 10 when dealer is 9 or less
case (_, _, 10, _, false, _, _) => Hit
// if Double-down option is available and dealer has 3, 4, 5 or 6 then Double-down on 9
case (_, _, 9, dealer, true, _, _) if (Seq(Three, Four, Five, Six).contains(dealer)) => DoubleDown
// else if Double-down option is unavailable then hit on 9
case (_, _, 9, _, _, _, _) => Hit
// else hit on 8 or lower
case (_, _, _, _, _, _, _) => Hit
}
}
// Current player plays current hand
// Note that this does not increment game's player index nor the current player's hand index (not this function's responsibility)
def playHand(game: BlackjackGameState): BlackjackGameState = {
if (game.players == Nil) {
throw new IllegalArgumentException("Cannot play current hand because there are no players")
}
if (game.currentPlayerIndex.isEmpty) {
throw new IllegalArgumentException("Cannot play current hand because current player is not specified")
}
if (game.currentHandIndex.isEmpty) {
throw new IllegalArgumentException("Cannot play current hand because current hand is not specified")
}
if (game.currentCards().length < 2) {
throw new IllegalArgumentException(s"Cannot play current hand because current hand length [${game.currentCards().length}] is less than length 2")
}
if (game.dealerHand.hand.length != 2) {
throw new IllegalArgumentException(s"Cannot play current hand because dealer's hand length [${game.dealerHand.hand.length}] is not length 2")
}
if (!isTimeToPlay(game)) {
throw new IllegalArgumentException(s"Cannot play current hand because it's not currently time to play hand")
}
// next action based on Basic Strategy in Blackjack
val action: BlackjackAction = nextAction(game)
// perform the action
val (outcomeHands, updatedDeck, newHistory): (Seq[Hand], Deck, Seq[Action[BlackjackAction]]) =
performPlayAction(game.currentPlayer().id, action, game.currentHand(), game.deck)
val updatedPlayers: Seq[BlackjackPlayerState] = game.players.map{ p =>
if (p.id == game.currentPlayer().id) {
p.copy(handsAndBets = outcomeHands)
} else {
p
}
}
val updatedState: BlackjackGameState = game.copy(deck = updatedDeck, history = game.history ++ newHistory, players = updatedPlayers)
if (outcomeHands.length == 1) {
// only increment to the next hand if player did NOT split into an additional hand
updatedState.toNextHand(action, evaluation.eval(outcomeHands.head.hand) > 21)
} else {
// player has split into 2 hands, don't proceed yet to the next hand, player still needs to play this current first split hand
updatedState
}
}
private def bustedBlackjackOrWinHandOutcome(
playerId: String,
hand: Hand,
history: Seq[Action[BlackjackAction]] = Nil,
actionCards: Seq[Card] = Nil,
actionBet: Option[Int]): Hand = {
if (eval(hand.hand) == 21 && hand.hand.length == 2) {
hand.copy(outcome = Some(Blackjack)) // blackjack
} else if (eval(hand.hand) > 21) { // busted
hand.copy(outcome = Some(Bust))
} else if (eval(hand.hand) == 21) { // 3 or more cards evaulated as 21
hand.copy(outcome = Some(Win))
} else {
hand
}
}
// returns updated cards (seq of hands to account for Splits), updated deck, and new history
def performPlayAction(
playerId: String,
action: BlackjackAction,
hand: Hand,
deck: Deck): (Seq[Hand], Deck, Seq[Action[BlackjackAction]]) = {
action match {
case Stand => {
// val (updatedHand, updatedHistory) = addBustedOrBlackjackToHistory(playerId, hand, Nil, Nil, None)
val updatedHand = bustedBlackjackOrWinHandOutcome(playerId, hand, Nil, Nil, None)
val updatedHistory = updatedHand.outcome match {
case None => Seq(Action(playerId, Stand, Nil, None, Nil, Seq(hand.hand)))
case Some(as) => Nil
}
(Seq(updatedHand), deck, updatedHistory)
}
case a if (a == Hit || a == DoubleDown) => {
// Hit and DoubleDown both deal 1 card
val (dealt, nextDeck): (Seq[Card], Deck) = deck.deal()
// bets is potentially modified (if player doubled-down)
val (additionalBet, nextBets): (Int, Map[String, Int]) = a match {
case DoubleDown => {
( // additional bet is same that player already had bet (doubling it)
hand.bets(playerId),
// bets adjusted to double player's bet
(for ((player, bet) <- hand.bets) yield {
if (player == playerId)
player -> bet * 2
else
player -> bet
}).toMap)
}
case _ => (0, hand.bets)
}
val newHand: Hand = hand.copy(hand = hand.hand ++ dealt, bets = nextBets)
val newHistory: Seq[Action[BlackjackAction]] = Seq(Action(playerId, a, dealt, Some(additionalBet), Nil, Seq(newHand.hand)))
val updatedHand = bustedBlackjackOrWinHandOutcome(playerId, newHand, newHistory, dealt, Some(additionalBet))
(Seq(updatedHand), nextDeck, newHistory)
}
case Split => {
// deal 2 cards, 1 for each split hand
val (dealt, nextDeck): (Seq[Card], Deck) = deck.deal(2)
val hand1: Hand = Hand(Seq(hand.hand.head, dealt.head), hand.bets)
val hand2: Hand = Hand(Seq(hand.hand.tail.head, dealt.tail.head), hand.bets)
// original bet amount is added as as new bet to the new hand
val additionalBet: Int = hand.bets(playerId)
(Seq(hand1, hand2), nextDeck, Seq(Action(playerId, Split, dealt, Some(additionalBet), Nil, Seq(hand1, hand2).map(_.hand))))
}
case Surrender => {
val (surrenderAmount, nextBets): (Int, Map[String, Int]) = {
(
hand.bets(playerId) / 2, // surrender bet is half original bet
// bets adjusted to halve player's bet
(for ((player, bet) <- hand.bets) yield {
if (player == playerId)
player -> bet / 2 // halved
else
player -> bet
}).toMap
)
}
// afterwards bet is adjusted so player only pays half
(Seq(hand.copy(bets = nextBets)), deck, Seq(Action(playerId, Surrender, Nil, Some(surrenderAmount), hand.hand, Seq(hand.hand))))
}
case a => throw new IllegalArgumentException(s"Unexpected BlackjackAction [$a], at this phase the only expected actions are [Hit, Stand, DoubleDown, Split, Surrender]")
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy