class Dealer(Player): def __init__(self, name='Bob the dealer', money=1000000, delay=1, verbose=True): super().__init__(name, money) self._playingPlayers = [] self._playersWithInsurance = [] self._shoe = Shoe() self.delay = delay self.isVerbose = verbose def sit(self, table): """Override the player sit method. so that the dealer sits on the right side of the table.""" self._table = table table.add_dealer(self) def bet_or_leave(self): """ At the start of each round the player can either bet by entering an amount to bet, sit this hand out by entering 0 for a bet, or leave the table by entering -1. """ # # The dealer will not be in the list of players, so this method will not be # called on the dealer. # pass def wants_insurance(self): """ Returns True if the player should buy insurance else return False. This procedure is called by the dealer after all players have bet and receives their cards and after the dealer has received his cards. It is only called if the dealer is showing an ace (the dealer might have blackjack). """ # # The dealer will not be in the list of players, so this method will not be # called on the dealer. # pass def play(self, hand, dealerShowing): """ Returns the player's action for this hand. The dealer calls this method repeatedly for each of the player's hands until all hands are completed. Valid return values are listed below. Note that two values are returned: choice: one of the plays listeded below additionalBet: the amount to "double down" by additionalBet is discarded by the caller in all other cases. allPlays = {'s': '[S]tand', 'h': '[H]it', 'd': '[D]ouble down', 'p': 's[P]lit', 'u': 's[U]rrender'} return choice, additionalBet """ if hand.value() < 17: choice = 'h' #hit else: choice = 's' #stand additionalBet = None return choice, additionalBet def switch_shoe(self, shoe): """ When we run a simulation instead of a game, we want to make sure that all of dealers are using the same shoe and switching shoes at the same time. :param shoe: a preshuffled shoe, ready to be delt from. :type shoe: Shoe :return: """ self._shoe = shoe for player in self._table.players: player.reset_count() def take_bets(self): sleep(self.delay) if self.isVerbose: print('\n---betting---') self._playingPlayers = [] leavingPlayers = [] for player in self._table.players: # # = -1: player is leaving the table # = 0: players is sitting this hand out # > 0: player is betting this amount # if self.isVerbose: print(f'\n{player}') betAmount = player.bet_or_leave() name = player.name.capitalize() minimumBet = 1 if betAmount == -1 or player.money < 1: # leaving table leavingPlayers.append(player) if self.isVerbose: print( f"{name} is leaving the table with ${player.money:0.2f}." ) elif betAmount == 0: if self.isVerbose: print(f"{name} is sitting this hand out.") elif betAmount > 0 and player.money >= betAmount: if betAmount < minimumBet: betAmount = minimumBet if betAmount > player.money: betAmount = player.money self._playingPlayers.append(player) player.rake_out(betAmount) self.rake_in(betAmount) player.add_hand(Hand(betAmount)) player.handsPlayed += 1 player.totalWagers += betAmount if self.isVerbose: print(f"{name} is betting ${betAmount:0.2f}.") else: raise ValueError( f"{player} doesn't have enough money to bet ${betAmount:0.2f}." ) for player in leavingPlayers: self._table.leave_table(player) def show_card_to_players(self, card): """ Make sure that players who might be counting cards get to see every card that is delt, not just the ones in their hands. """ for player in self._playingPlayers: player.count(card) def deal(self): """ Deal an initial 2 cards to each player and to the dealer. """ # # Shuffle the cards if we need to. # if self._shoe.should_shuffle(): self._shoe.shuffle() for player in self._table.players: player.reset_count() self._table.shuffling_shoe() # # Deal cards to each player. # cardsToDeal = 2 for _ in range(cardsToDeal): for player in self._playingPlayers: card = self._shoe.draw().flip() player.hands[0].hit(card) self.show_card_to_players(card) # # Deal yourself some cards. # bet = 0 self.add_hand(Hand(bet)) card = self._shoe.draw().flip() self.hands[0].hit(card) self.show_card_to_players(card) self.hands[0].hit(self._shoe.draw().flip()) def offer_insurance(self): if self.isVerbose: print('\n') self._playersWithInsurance = [] if self.hands[0][1].name == 'ace': for player in self._playingPlayers: if player.wants_insurance(): self._playersWithInsurance.append(player) player.insurance = player.hands[0].bet / 2 player.timesInsurance += 1 def play_hands(self): """ Loop through the players and let them choose what they want to do. Then process that decision. """ playerOptions = { 's': self.player_stand, 'h': self.player_hit, 'p': self.player_split, 'd': self.player_double_down, 'u': self.player_surrender } if self.isVerbose: print('\n---players are playing---') dealerShowing = self.hands[0][1] for player in self._playingPlayers: for hand in player.hands: if hand.isBlackJack: if self.isVerbose: print(f"{player.name} has Blackjack! {hand}.") while hand.can_hit(): sleep(self.delay) playerDecision, additionalBet = player.play( hand, dealerShowing) if playerDecision.lower() in playerOptions: which_option = playerOptions[playerDecision.lower()] which_option(player, hand, additionalBet) else: if self.isVerbose: print( f"I'm sorry, I don't know what '{playerDecision}' means." ) def player_stand(self, player, hand, *args): hand.stand() if self.isVerbose: print(f"{player.name} stands with {hand}.") def player_hit(self, player, hand, *args): card = self._shoe.draw().flip() self.show_card_to_players(card) hand.hit(card) if self.isVerbose: print(f"{player.name} hit and received a {card} {hand}.") player.timesHit += 1 def player_split(self, player, hand, *args): if hand.can_split() and player.money >= hand.bet: self.rake_in(player.rake_out(hand.bet)) newHand = Hand(hand.bet) newHand.hit(hand.split()) card = self._shoe.draw().flip() self.show_card_to_players(card) newHand.hit(card) player.add_hand(newHand) card = self._shoe.draw().flip() self.show_card_to_players(card) hand.hit(card) if self.isVerbose: print( f"{player.name} split and now has: \n {hand}\n {newHand}" ) player.timesSplit += 1 player.handsPlayed += 1 player.totalWagers += hand.bet else: if self.isVerbose: print("Sorry, you can't split this hand (standing instead).") self.player_stand(player, hand) def player_double_down(self, player, hand, additionalBet): if hand.can_double() and is_number( additionalBet) and player.money >= additionalBet: card = self._shoe.draw().flip() self.show_card_to_players(card) hand.double_down(card, additionalBet) self.rake_in(player.rake_out(additionalBet)) if self.isVerbose: print( f"{player.name} doubled down and received a {card} {hand}." ) player.timesDoubled += 1 player.totalWagers += additionalBet else: if self.isVerbose: print("Sorry, you can't double this hand (hitting instead).") self.player_hit(player, hand) def player_surrender(self, player, hand, *args): print('Sorry, surrender is not implemented (pick again).') player.timesSurrendered += 1 #TODO Make sure dealer shows hole card to players somewhere for counting. def play_own_hand(self): if self.isVerbose: print('\n---dealer is playing---') playerOptions = {'s': self.player_stand, 'h': self.player_hit} hand = self.hands[0] dealerShowing = hand[0] while hand.can_hit(): playerDecision, additionalBet = self.play(hand, dealerShowing) which_option = playerOptions[playerDecision] which_option(self, hand, additionalBet) def payout_hands(self): if self.isVerbose: print('\n---results---') dealerHand = self.hands[0] for player in self._playingPlayers: sleep(self.delay * 2) for hand in player.hands: if hand.isBusted: winnings = 0 text = 'lost' player.timesBusted += 1 player.timesLost += 1 player.lastHand = 'lost' elif hand.isBlackJack and not dealerHand.isBlackJack: winnings = hand.bet * 2.5 text = 'won (Blackjack!)' player.timesWon += 1 player.timesBlackjack += 1 player.lastHand = 'won' elif hand.isBlackJack and dealerHand.isBlackJack: winnings = hand.bet text = 'pushed (blackjack) and lost' player.timesPushed += 1 player.timesBlackjack += 1 player.lastHand = 'pushed' elif not hand.isBlackJack and dealerHand.isBlackJack: winnings = 0 text = 'lost' player.timesLost += 1 player.lastHand = 'lost' elif hand.value() == dealerHand.value(): winnings = hand.bet text = 'pushed and lost' player.timesPushed += 1 player.lastHand = 'pushed' elif dealerHand.isBusted: winnings = hand.bet * 2 text = 'won (dealer busted)' player.timesWon += 1 player.lastHand = 'won' elif hand.value() > dealerHand.value(): winnings = hand.bet * 2 text = 'won' player.timesWon += 1 player.lastHand = 'won' elif hand.value() < dealerHand.value(): winnings = 0 text = 'lost' player.timesLost += 1 player.lastHand = 'lost' player.rake_in(winnings) self.rake_out(winnings) winnings = abs(winnings - hand.bet) if self.isVerbose: print(f"{player.name} {text} ${winnings:0.2f} on {hand}.") # # Payout any insurance bets. # for player in self._playersWithInsurance: if dealerHand.isBlackJack: winnings = player.insurance * 2 player.rake_in(winnings) self.rake_out(winnings) if self.isVerbose: print( f"{player.name} won ${player.insurance:0.2f} on the insurance bet." ) else: if self.isVerbose: print( f"{player.name} lost ${player.insurance:0.2f} on the insurance bet." ) # # Clear the table and get ready for the next round. # for player in self._playingPlayers: player.discard_hands() player.insurance = 0 if player._chips > player.maxMoney: player.maxMoney = player._chips self.discard_hands() if self.isVerbose: print('---results complete---')
class BlackjackGame: HAND_KEY = "hand" BUST_KEY = "bust" DONE_KEY = "done" NUMBER_KEY = "number" BET_KEY = "bet" SURRENDER_KEY = "surrender" def __init__(self, log, bankroll, rules_section="Blackjack"): # Logger self.log = log # Required objects self.rules = BlackjackRules(log, rules_section) self.shoe = Shoe(log, self.rules.get_decks()) self.event_listener = EventListener(log) # Variables self.bankroll = bankroll self.original_bet = 0 self.need_to_shuffle = False self.split_hand_number = 1 self.insurance = 0 # Dealer hand related variables self.dealer_hand = [] self.dealer_bust = False # Player hand related variables self.player_hand = self.new_player_hand() self.split_hands = [] def get_rules(self): return self.rules def get_bankroll(self): return self.bankroll def get_dealer_hand(self): return self.dealer_hand def get_player_hand(self): return self.player_hand[self.HAND_KEY] def get_player_hand_number(self): return self.player_hand[self.NUMBER_KEY] def set_event_listener(self, event_listener): self.event_listener = event_listener def can_double_down(self, hand, bet): can = False if len(hand) == 2 and self.calc_total_bets() + bet <= self.bankroll: if self.rules.can_double_down_on_all() == True: can = True else: total = self.calc_highest_total(hand) if total >= 9 and total <= 11: can = True return can def can_split(self, hand, bet): can = False if len(hand) == 2 and hand[0][0] == hand[1][ 0] and self.calc_total_bets() + bet <= self.bankroll: can = True return can def can_surrender(self): can = False if self.rules.is_surrender_allowed() and len( self.player_hand[self.HAND_KEY]) == 2 and len( self.split_hands) == 0: can = True return can def can_buy_insurance(self): can = False if self.rules.is_insurance_allowed() and self.calc_rank( self.dealer_hand[1] ) == 1 and self.calc_total_bets() < self.bankroll: can = True return can def calc_rank(self, card): rank = 0 char = card[0] if char.isdigit(): rank = int(char) elif char == 'A': rank = 1 elif char == 'T' or char == 'J' or char == 'Q' or char == 'K': rank = 10 return rank def calc_highest_total(self, hand): total = 0 aces = 0 for card in hand: rank = self.calc_rank(card) #self.log.finest("card: " + card + ", rank: " + str(rank)) if rank == 1: aces = aces + 1 total = total + rank while total <= 11 and aces > 0: total = total + 10 aces = aces - 1 return total def calc_lowest_total(self, hand): total = 0 for card in hand: rank = self.calc_rank(card) total = total + rank return total def calc_total_bets(self): total = self.player_hand[self.BET_KEY] for hand in self.split_hands: total = total + hand[self.BET_KEY] return total def is_blackjack(self, hand): blackjack = False if self.calc_highest_total(hand) == 21 and len(hand) == 2: blackjack = True return blackjack def has_splits(self): if len(self.split_hands) > 0: return True return False def new_player_hand(self): new_hand = { self.HAND_KEY: [], self.BUST_KEY: False, self.DONE_KEY: False, self.BET_KEY: self.original_bet, self.NUMBER_KEY: self.split_hand_number, self.SURRENDER_KEY: False } self.split_hand_number = self.split_hand_number + 1 return new_hand def double_bet(self): self.player_hand[self.BET_KEY] = self.player_hand[self.BET_KEY] * 2 self.player_hand[self.DONE_KEY] = True def surrender_hand(self): self.player_hand[self.SURRENDER_KEY] = True self.player_hand[self.DONE_KEY] = True def buy_insurance(self, insurance): if insurance >= 0 and insurance <= self.original_bet / 2 and self.calc_total_bets( ) + insurance <= self.bankroll: self.insurance = insurance return True return False def make_dealer_hole_card_visible(self): self.event_listener.event("card", self.dealer_hand[0]) def deal_card(self, visible=True): card = self.shoe.deal() if card == "": self.need_to_shuffle = True card = self.shoe.deal() if card == "": self.log.warning("Shoe empty! Shuffling when not supposed to!") self.shoe.shuffle() self.event_listener.event("shuffle", "") card = self.shoe.deal() if visible == True: self.event_listener.event("card", card) return card def deal_card_to_dealer(self): self.dealer_hand.append(self.deal_card()) if self.calc_highest_total(self.dealer_hand) > 21: self.dealer_bust = True def deal_card_to_player(self): self.player_hand[self.HAND_KEY].append(self.deal_card()) if self.calc_highest_total(self.player_hand[self.HAND_KEY]) > 21: self.player_hand[self.BUST_KEY] = True def deal_hand(self, bet): # Save original bet self.original_bet = bet # Shuffle if need be if self.need_to_shuffle: self.need_to_shuffle = False self.shoe.shuffle() self.event_listener.event("shuffle", "") # Setup hands self.dealer_hand = [] self.dealer_bust = False self.split_hands = [] self.split_hand_number = 1 self.player_hand = self.new_player_hand() self.insurance = 0 # Deal hands self.player_hand[self.HAND_KEY].append(self.deal_card()) self.dealer_hand.append(self.deal_card(False)) self.player_hand[self.HAND_KEY].append(self.deal_card()) self.dealer_hand.append(self.deal_card()) def split_hand(self): self.log.finer("Before split: " + str(self.player_hand[self.HAND_KEY])) new_hand = self.new_player_hand() card = self.player_hand[self.HAND_KEY].pop() new_hand[self.HAND_KEY].append(card) self.player_hand[self.HAND_KEY].append(self.deal_card()) new_hand[self.HAND_KEY].append(self.deal_card()) self.split_hands.append(new_hand) self.log.finer("After split: " + str(self.player_hand[self.HAND_KEY]) + str(new_hand[self.HAND_KEY])) def next_hand(self): next = False self.player_hand[self.DONE_KEY] = True for i in range(len(self.split_hands)): #print(self.split_hands[i]) if self.split_hands[i][self.DONE_KEY] == False: next = True # Append current hand at the end of the split list self.split_hands.append(self.player_hand) # Pop the next not done hand and make it the current hand self.player_hand = self.split_hands.pop(i) break return next def is_player_hand_over(self): done = self.player_hand[self.DONE_KEY] if done == False: if self.player_hand[self.BUST_KEY] == True: done = True elif self.is_blackjack(self.player_hand[self.HAND_KEY]) == True: done = True self.player_hand[self.DONE_KEY] = done return done def is_dealer_hand_over(self): if self.is_blackjack(self.dealer_hand) == True: return True dealer_total = self.calc_highest_total(self.dealer_hand) if dealer_total > 17: return True if dealer_total == 17 and self.rules.does_dealer_hits_on_soft_17( ) == False: return True return False def finish_hand(self): result = [] # Handle insurance bet dealer_blackjack = self.is_blackjack(self.dealer_hand) if self.insurance > 0: if dealer_blackjack: self.bankroll = self.bankroll + self.insurance * 2 else: self.bankroll = self.bankroll - self.insurance # Go through hands while True: if self.player_hand[self.SURRENDER_KEY] == True: # Surrender, bet should be halved player_won = SURRENDER_RESULT self.bankroll = self.bankroll - self.player_hand[ self.BET_KEY] / 2 else: player_won = PUSH_RESULT dealer_total = self.calc_highest_total(self.dealer_hand) player_total = self.calc_highest_total( self.player_hand[self.HAND_KEY]) # First, test for blackjacks player_blackjack = self.is_blackjack( self.player_hand[self.HAND_KEY]) if player_blackjack == True: # Player blackjack! If dealer has blackjack too, push if dealer_blackjack == False: # No dealer black jack, pay out for blackjack self.bankroll = self.bankroll + self.player_hand[ self.BET_KEY] * self.rules.get_blackjack_payout() player_won = BLACKJACK_RESULT else: # Next, test for dealer blackjack if dealer_blackjack == True: player_won = LOSS_RESULT # Now, test for busts elif self.player_hand[self.BUST_KEY] == True: player_won = LOSS_RESULT elif self.dealer_bust == True: player_won = WIN_RESULT else: # Now, compare hands if dealer_total == player_total: if self.rules.does_push_goes_to_dealer() == True: player_won = LOSS_RESULT elif dealer_total > player_total: player_won = LOSS_RESULT else: player_won = WIN_RESULT # Payout if player_won == WIN_RESULT: self.bankroll = self.bankroll + self.player_hand[ self.BET_KEY] elif player_won == LOSS_RESULT: self.bankroll = self.bankroll - self.player_hand[ self.BET_KEY] result.append(player_won) if len(self.split_hands) > 0: # Pop the next hand and make it the current hand self.player_hand = self.split_hands.pop(0) else: break return result
class Game(object): class Rules(): def __init__(self, shoe_size=4, min_bet=1, max_bet=10): self.shoe_size = shoe_size self.min_bet = min_bet self.max_bet = max_bet self.bet_multiplier = BET_MULTIPLIER def __str__(self): return "RULES\tMin bet: {}, Max bet: {}, Shoe size: {}, Bet multiplier: {}".format( self.min_bet, self.max_bet, self.shoe_size, self.bet_multiplier) class PlayerState(): def __init__(self, p): self.player = p self.bet = 0 self.hand = [] self.bust = False self.done = False self.watch = False def copy(self): return copy.deepcopy(self) def __str__(self): if isinstance(self.player, Dealer): return "{}".format(self.hand) return "{} ({}€)".format(self.hand, self.bet) def __repr__(self): return "{}".format(self.player.name) def hide_card(self): h = self.copy() h.hand = h.hand[1:] return h def want_to_play(self, rules): return self.player.want_to_play(rules) def take_bet(self, state, rules): bet = 0 while (bet <> self.bet and self.bet != 0) or not ( rules.min_bet <= bet <= rules.max_bet ): #bets can't be 0 and double down means double down bet = self.player.bet(state[0].hide_card(), state[1:]) self.bet += bet def __init__(self, players, shoe_size=4, debug=False, verbose=False, min_bet=1, max_bet=10, shoe=None): if verbose: # print(chr(27) + "[2J") print("-" * 80) self.verbose = verbose self.debug = debug self.rules = self.Rules(shoe_size=shoe_size, min_bet=min_bet, max_bet=max_bet) self.shoe = Shoe(shoe_size) if shoe != None: self.shoe = shoe self.shoe.shuffle() self.state = [self.PlayerState(Dealer()) ] + [self.PlayerState(p) for p in players] self.done = False def str_players_hands(self): o = "" for p in self.state[1:]: o += "{:^45}".format(p) return o def str_players_names(self): o = "" for p in self.state[1:]: o += "{:^35}".format(p.player) return o def __str__(self): return (\ "{:^30}\n"\ "╔"+"═══════════════════════════════"*(len(self.state)-1)+"╗\n"\ "{:^45}\n"\ " \n"\ " \n"\ " \n"\ " \n"\ " \n"\ "{}\n"\ "╚"+"═══════════════════════════════"*(len(self.state)-1)+"╝\n"\ "{}\n"\ ).format(self.state[0].player.name, self.state[0].hand if self.done else (["**"]+self.state[0].hide_card().hand if len(self.state[0].hand) else []), self.str_players_hands(), self.str_players_names()) def deal(self, num): return self.shoe.deal_cards(1) def take_bets(self): if self.debug: print(self) for p in self.state[1:]: if p.want_to_play(self.rules): p.take_bet(self.state, self.rules) else: p.watch = True def loop(self): #deal initial cards self.state[0].hand += self.shoe.deal_cards(2) for p in self.state[1:]: if not p.watch: p.hand += self.shoe.deal_cards(2) turn = 0 if card.blackjack( self.state[0].hand ): #if the dealer has blackjack there is no point in playing... self.done = True return [p for p in self.state[1:] if card.blackjack(p.hand)] #lets play while not self.done: turn += 1 hits = 0 for p in self.state[::-1]: if p.watch or p.bust or p.done or card.value( p.hand ) == 21: #skip players watching, bust players, players who have double down and players who already have blackjack! continue if self.debug: print("TURN {}: {}".format(turn, p.player.name)) print(self) action = "" while action not in ["h", "s", "d", "u"]: if isinstance(p.player, Dealer): action = p.player.play(self.state[0], self.state[1:]) else: action = p.player.play(self.state[0].hide_card(), self.state[1:]) if action == "d" and turn != 1: print( "YOU CAN'T DOUBLE DOWN!!! double down is only available on the 1st turn" ) action = "" if action == "u": p.watch = True p.player.payback( -p.bet // 2) #this means the player lost half his bet continue if action == "d": p.take_bet(self.state, self.rules) p.done = True if action in ["h", "d"]: p.hand += self.deal(1) hits += 1 if card.value(p.hand) >= 21: if card.value(p.hand) > 21: p.bust = True else: p.done = True #already has blackjack if isinstance(p.player, Dealer): self.done = True #game is over we already have a blackjack if hits == 0: self.done = True self.done = True return [ p for p in self.state if not isinstance(p.player, Dealer) and #Dealer is not really a winner not card.blackjack(self.state[0].hand) and #If dealer gets blackjack no one wins not p.bust and #bust players can't win :) ( card.value(p.hand) >= card.value(self.state[0].hand) or self.state[0].bust ) #winners have more points then the dealer or the dealer has gone bust ] def show_table(self): for p in self.state[1:]: p.player.show(self.state) def payback(self, winners): for p in self.state[1:]: if p.watch: #skip watchers continue if p in winners and card.value(self.state[0].hand) == card.value( p.hand): p.player.payback(0) #bet is returned elif p in winners: p.player.payback(-p.bet + p.bet * BET_MULTIPLIER) else: p.player.payback(-p.bet) #this means the player lost def run(self): self.take_bets() winners = self.loop() self.show_table() self.payback(winners) if self.verbose: print(self) print("🏆 Winners: " + str(winners))
#!/usr/bin/python from game import Game from shoe import Shoe from strategy import Strategy shoe = Shoe(Shoe.DECK) shoe.shuffle(); game = Game(Strategy(), { 'debug': True }) #game.debug() #print(game.status) print(game.play(shoe))
class Blackjack(object): """ An environment focused on playing blackjack. Can be played by a human or agent. Agents should override `Actor` to provide custom functionality based on the current state. An advantage of using this environment is that it allows for many actors to play at once and accumulate different experiences. """ def __init__(self, agents, print_desc=False): # A typical shoe has 6 decks and reshuffles when roughly 5/6 decks are used. self.shoe = Shoe(6, 0.8) self.dealer = DealerActor() self.agents = [] self.player_count = len(agents) + 1 self.print_desc = print_desc # Add the agents. self.agents = agents def deal(self): """Deals the cards to the various players and agents.""" if self.shoe.depth() > self.shoe.reshuffle_ratio: self.shoe.shuffle() for i in range(2 * self.player_count): card = self.shoe.draw() # Deal the card to the dealer last. This makes indexing easier, as well as # follows Vegas rules. if i % self.player_count == self.player_count - 1: self.dealer.add_card(card) else: self.agents[i % self.player_count].add_card(card) if self.print_desc: print('Dealer: ' + str(self.dealer)) for idx, agent in enumerate(self.agents): print('Player ' + str(idx + 1) + ': ' + str(agent)) print('-------------------------') def play(self): """ Iterates through all of the agents and then the dealer to play their hand. """ if self.dealer.first_card().is_ace(): count = self.shoe.hilo_count() - self.dealer.hidden_card().hilo_count() state = { DEALER_STATE_KEY: self.dealer.first_card(), COUNT_STATE_KEY: count, INSURANCE_STATE_KEY: True } for idx, agent in enumerate(self.agents): a = agent.process_state(state) if a == Action.INSURANCE: agent.took_insurance = True if self.dealer.is_blackjack(): self.dealer.show_hand = True if self.print_desc: print('Dealer: ' + str(self.dealer)) print('Dealer has blackjack.') return for idx, agent in enumerate(self.agents): self.play_agent(agent, idx) self.dealer.show_hand = True if self.print_desc: print('Dealer: ' + str(self.dealer)) # Assess the dealer actions. self.play_dealer(self.dealer) if self.print_desc: print('[FINAL] Dealer: ' + str(self.dealer)) print('-------------------------') def assess_rewards(self): """Finalizes the hand and either takes or hands out winnings.""" for idx, agent in enumerate(self.agents): # Other rewards are the number of cards that were drawn. additional_cards = agent.drawn_cards() running_reward = 0 # Iterate through all of the hands of the agent and assess how they # performed. for hand_idx, hand in enumerate(agent.hands): # Get the new reward from the hand and update the running reward. # Doubling down doubles the player's bet for that hand. bet = (2 if hand.did_double else 1) * agent.bet new_reward = self.compare_hands(self.dealer.hands[0], agent.hands[hand_idx], bet) if agent.took_insurance: if self.dealer.is_blackjack(): if not agent.is_blackjack(hand_idx): new_reward /= 2 else: new_reward = agent.bet else: new_reward -= agent.bet / 2 running_reward += new_reward if self.print_desc: won = new_reward > 0 draw = new_reward == 0 self._print_reward(idx, hand_idx, won, draw, new_reward) if self.print_desc: print('\n\n') # Process the rewards at the end of each agent's calculation. agent.process_reward(running_reward, agent.drawn_cards()) self.dealer.process_reward(0, 0) def compare_hands(self, dealer, player, bet): player_count = player.count() dealer_count = dealer.count() dealer_bust = dealer_count > BLACKJACK_VALUE player_bust = player_count > BLACKJACK_VALUE # Both dealer and player blackjack is a push. if dealer.is_blackjack() and player.is_blackjack(): return 0 loss = player_bust or (player_count < dealer_count and not dealer_bust) win = ((dealer_bust or player_count > dealer_count or player.is_blackjack()) and not player_bust) if win: return (1.5 if player.is_blackjack() else 1) * bet elif loss: return -1 * bet else: return 0 def play_agent(self, agent, idx): hand_idx = 0 while hand_idx < len(agent.hands): self.play_hand(agent, idx, hand_idx) hand_idx += 1 if self.print_desc: if len(agent.hands) == 1: print('[FINAL] Player ' + str(idx + 1) + ': ' + str(agent)) else: print('[FINAL] Player ' + str(idx + 1) + ':\n' + str(agent)) def play_hand(self, agent, idx, hand_idx): """ Plays an individual hand for an agent. agent Agent: - The agent that is currently playing. idx Int: - The index of the agent that is playing. hand_idx Int: - The index of the hand that is currently being played. """ # Splitting aces only allows for one card to be drawn for each hand. # Therefore, don't allow any actions on the second split hand. hand = agent.hands[hand_idx] if hand.is_split: if self.print_desc: print('Player ' + str(idx + 1) + ' (' + str(hand_idx) + '): ' + str(agent.hands[hand_idx])) if hand.cards[0].is_ace(): return d = False while not d: # The count should be adjusted such that the dealer's second card count # isn't revealed. count = self.shoe.hilo_count() - self.dealer.hidden_card().hilo_count() state = { DEALER_STATE_KEY: self.dealer.first_card(), COUNT_STATE_KEY: count, DOUBLE_DOWN_STATE_KEY: hand.can_double_down(), SPLIT_STATE_KEY: hand.can_split(), HAND_INDEX_STATE_KEY: hand_idx } # Process the current state. a = agent.process_state(state) # Add the card if the user hit. if a == Action.HIT or a == Action.DOUBLE_DOWN: agent.add_card(self.shoe.draw(), hand_idx) if a == Action.DOUBLE_DOWN: hand.did_double = True split_finished = False if a == Action.SPLIT: # Split the hands into two hands. split_finished = hand.cards[0].is_ace() agent.split_hand(hand_idx) agent.add_card(self.shoe.draw(), hand_idx) agent.add_card(self.shoe.draw(), hand_idx + 1) # Print the description after the user takes their action. if self.print_desc and a != Action.STAND: print('Player ' + str(idx + 1) + ' (' + str(hand_idx) + '): ' + str(agent.hands[hand_idx])) # The agent is finished after they stand, or double down. A controversial, # but relatively accepted rule is only receiving one card for splitting # aces. d = (a == Action.STAND or a == Action.DOUBLE_DOWN or hand.count() > BLACKJACK_VALUE or split_finished) def play_dealer(self, dealer): """Plays the dealer's hand.""" d = False while not d: a = dealer.process_state({}) if a == Action.HIT: dealer.add_card(self.shoe.draw()) if self.print_desc: print('Dealer: ' + str(dealer)) # Dealer is done when they stand or bust. d = a == Action.STAND or dealer.count() >= BLACKJACK_VALUE def _print_reward(self, agent_idx, hand_idx, won, draw, reward): if not self.print_desc: return player_desc = 'Player ' + str(agent_idx + 1) + ' (' + str(hand_idx) + ')' if won: print(player_desc + ' won. +' + str(reward)) elif draw: print(player_desc + ' pushed.') else: print(player_desc + ' lost. ' + str(reward))
class Dealer(Player): def __init__(self, name, money): super().__init__(name, money) self.shoe = Shoe() self.players = [] # Holds bets before hands have been dealt # We could make this a dictionary with the player's name as the key, # but that assumes the player names are unique. Better solution would # probably be to set bets on the Player object self.playerBets = [] #region Dealer-specific methods def deal_in(self, player): """Add a player to the dealer's table""" self.players.append(player) def has_players(self): """Returns true if the dealer is dealing to at least 1 player""" return len(self.players) > 0 def take_bets(self): """Ask all players how much they want to bet. Removes them from the table if they bet -1""" for player in self.players: bet = player.bet_or_leave() # Add each player's bet to a list of bets self.playerBets.append(bet) # Filter out players if they bet -1 self.players = [p for i, p in enumerate(self.players) if self.playerBets[i] != -1] # Remove the -1 bets from the list of bets self.playerBets = [b for b in self.playerBets if b != -1] def deal(self): """Deal out hands to the players. Assumes we've taken bets""" # Shuffle the shoe if it needs it if (self.shoe.should_shuffle()): self.shoe.shuffle() # Deal out cards to the players for index, player in enumerate(self.players): bet = self.playerBets[index] # Don't deal a hand if the player bet 0 if bet > 0: player.rake_out(bet) hand = Hand(bet) for _ in range(2): hand.hit(self.shoe.draw().flip()) player.add_hand(hand) # Clear out playerBets, all bets are on hands now self.playerBets = [] # Deal out cards to the dealer dealerHand = Hand(0) for _ in range(2): dealerHand.hit(self.shoe.draw().flip()) # Give the dealer the hand self.add_hand(dealerHand) def resolve_hands(self): dealerHand = self.hands[0] dealerShowing = dealerHand[-1] for player in self.players: for hand in player.hands: while hand.can_hit(): choice, bet = player.play(hand, dealerShowing) if (choice == 's'): hand.stand() if (choice == 'h'): hand.hit(self.shoe.draw().flip()) if (choice == 'd'): player.rake_out(bet) hand.double_down(self.shoe.draw().flip(), bet) if (choice == 'p'): player.rake_out(hand.bet) splitHand = Hand(hand.bet) card = hand.split() splitHand.hit(card) splitHand.hit(self.shoe.draw().flip()) player.add_hand(splitHand) hand.hit(self.shoe.draw().flip()) print(f"{player.name} finishes with {hand}") # Now that the players have played, the dealer needs to play print("Let's see what the dealer has...") print(f"{self.name} flips their card, showing {dealerHand}") while dealerHand.can_hit(): self.play(dealerHand, dealerShowing) print(f"{self.name} stands with {dealerHand}") def payout(self): print("Time to pay out!") dealerHand = self.hands[0] for player in self.players: for hand in player.hands: # If the player busts, they lose if hand.isBusted: print(f"{player.name} busts, losing ${hand.bet:,.2f}.") self.rake_in(hand.bet) # If the player's hand is worth less than dealer, they lose elif hand < dealerHand: print(f"{player.name} lost to the dealer, losing ${hand.bet:,.2f}.") self.rake_in(hand.bet) # If the hands are equal, the player gets their bet back elif hand == dealerHand: print(f"{player.name} pushed, returning their bet of ${hand.bet:,.2f}.") player.rake_in(hand.bet) # if player has blackjack and dealer doesn't (checked in == case), they win elif hand.isBlackJack: # Blackjack pays 3:2 print(f"Blackjack! {player.name} wins ${hand.bet * 1.5:,.2f}.") player.rake_in(hand.bet + hand.bet * 1.5) # If the player's hand is worth more than dealer, they win elif hand > dealerHand: # Winning pays 1:1 print(f"{player.name} beat the dealer, winning ${hand.bet:,.2f}.") player.rake_in(hand.bet + hand.bet) else: raise NotImplementedError(f'Unchecked condition in payout. Player hand: {hand} Dealer: {dealerHand}.') player._hands = [] self._hands = [] # Remove players that run out of money for player in self.players: if player.money <= 0: print(f"{player.name} is out of cash. Thanks for playing!") self.players = [p for p in self.players if p.money > 0] #endregion def play(self, hand, dealerShowing): # Dealer hits on soft 17 if (hand.hard_value() >= 17): hand.stand() else: hand.hit(self.shoe.draw().flip())