class DeckTest(unittest.TestCase): def setUp(self): self.deck = Deck() def test_deck_reshuffle(self): self.assertIsNotNone(self.deck.cards) self.assertListEqual(self.deck.dealt, []) def test_generate_deck(self): new_deck = self.deck._generate() self.assertIsInstance(new_deck, list) self.assertEqual(len(set(new_deck)), 52) self.assertEqual(len(new_deck[0]), 2) def test_shuffle(self): old_deck = list(self.deck.cards) self.deck.shuffle() new_deck = list(self.deck.cards) self.assertEqual(len(old_deck), len(new_deck)) self.assertNotEqual(old_deck, new_deck) self.assertEqual(sum([1 for card in self.deck.cards if card in old_deck]), len(old_deck)) def test_deal(self): for n in [0, 1, 2, 52]: with self.subTest(n=n): old_deck = list(self.deck.cards) if n <=0: with self.assertRaises(AssertionError): self.deck.deal(n_cards=n) elif n > len(old_deck): with self.assertRaises(DeckException): self.deck.deal(n_cards=n) else: dealt = self.deck.deal(n_cards=n) dealt.reverse() self.assertEqual(len(self.deck.cards), len(old_deck) - n) self.assertEqual(len(dealt), n) self.assertListEqual(old_deck[-n:], dealt) self.assertNotIn(dealt[0], self.deck.cards)
def play_blackjack(chips=Account('Chris', 500)): """ Play BlackJack :param chips: object :return: """ # request a name be entered if one isn't present while not len(chips.name): try: named = input('What\'s your name? ') if named.isalpha(): chips.name = named break else: print( f"{named} is not a valid name, please enter a valid name") continue except: print( 'Sorry something went wrong, please try and input your name again' ) continue # Add fund to account if none is present if not chips.has_funds(): chips.add_funds() # Add bet if none has been placed while not chips.has_bet(): chips.make_bet() # Build deck and shuffle deck = Deck() deck.shuffle() deal_count = 1 player = Hand() dealer = Hand() player.cards = [] dealer.cards = [] # Start first phase of the dealing while deal_count <= 4: deal_count += 1 if deal_count % 2 == 0: if deal_count == 4: the_fourth_card = deck.deal() the_fourth_card.hide_card() dealer.add_card(the_fourth_card) else: dealer.add_card(deck.deal()) else: player.add_card(deck.deal()) # Second phase of the dealing while True: # display cards clear() print(chips.name + ': ') print(player) print('Dealers: ') print(dealer) if player.bust(): break if player.next_move(): player.add_card(deck.deal()) continue else: break # change view permissions for card in dealer.cards: card.show_card() # Once player finishes turn continue to deal dealers hand while True: # display cards clear() print(chips.name + ': ') print(player) print('Dealers: ') print(dealer) if dealer.bust(): break if player.bust(): break if dealer.total() > player.total(): break else: dealer.add_card(deck.deal()) continue # display results and finalise transaction if player.bust(): clear() chips.subtract() print( f"{chips.name} you lost this one\n\nYour new Balance {chips.balance()}\n" f"{chips.name}: {player}" f"Dealers: {dealer}" f"\nYour Score {player.total()}\nDealers Score {dealer.total()}") elif player.total() < dealer.total() and not dealer.bust(): clear() chips.subtract() print( f"{chips.name} the dealer beat your score\n\nYour new Balance {chips.balance()}\n" f"{chips.name}: {player}" f"Dealers: {dealer}" f"\nYour Score {player.total()}\nDealers Score {dealer.total()}") else: clear() chips.add() print(f"{chips.name} you won!\n\nYour new Balance {chips.balance()}\n" f"{chips.name}: {player}" f"Dealers: {dealer}" f"\nYour Score {player.total()}\nDealers Score {dealer.total()}") # Do they want to replay while True: try: replay = input('Do you want to play again? ').upper() if replay == 'YES': play_blackjack() break elif replay == 'NO': print( f'{chips.name} thank you for playing and we look forward to seeing you again' ) break else: print( 'We didn\'t understand your request, a YES or NO is desired' ) continue except ValueError: print( 'Invalid Input: We didn\'t recognise your request please try again YES or No' ) continue
class Blackjack: shuffle_counter = 0 max_deals = 50 def __init__(self, players): self.deck = Deck() self.deck.shuffle() self.dealer = Dealer() self.players = players self.player_records = {player: PlayerRecord( player) for player in players} def start_round(self): logger.info('[START]') logger.info('[CARDS] {0}'.format( list(card.value for card in self.deck.cards))) self.deal_starting_hands() self.pay_out_naturals() self.run_main_loop() results = self.generate_round_results() self.reset_records() return results def deal_starting_hands(self): for player in self.players: self.player_records[player].bet = player.get_bet() self.deal_card(player) self.deal_card(self.dealer) for player in self.players + [self.dealer]: self.deal_card(player) def pay_out_naturals(self): dealer = self.dealer players = self.players players_with_naturals = [ player for player in players if player.has_natural()] if dealer.has_natural(): for player in players: if player not in players_with_naturals: self.end_player_turn(player, 'L') else: self.end_player_turn(player, 'D') else: for player in players_with_naturals: self.end_player_turn(player, 'W', True) def run_main_loop(self): active_players = self.get_active_players() dealer = self.dealer while len(active_players) > 0: for player in active_players: if player.will_hit(): self.deal_card(player) if player.hand.get_score() > 21: self.end_player_turn(player, 'L') else: self.stand(player) active_players = self.get_active_players() logger.info('[ROUND] {0} active players left'.format( len(active_players))) while dealer.will_hit(): self.deal_card(dealer) dealer_score = dealer.hand.get_score() for player in self.get_standing_players(): player_score = player.hand.get_score() if dealer_score > 21: result = 'W' else: if player_score == dealer_score: result = 'D' elif player_score > dealer_score: result = 'W' else: result = 'L' self.end_player_turn(player, result) def generate_round_results(self): results = [] for player in self.players: record = self.player_records[player] player_score = player.hand.get_score() dealer_score = self.dealer.hand.get_score() bet = record.bet balance = player.balance result = record.result results.append(Result( player, player_score, dealer_score, bet, balance, result)) return results def reset_records(self): for player in self.players: self.player_records[player].reset() self.dealer.reset() def get_active_players(self): return [player for player in self.players if self.player_records[player].is_player_active()] def get_standing_players(self): return [player for player in self.players if self.player_records[player].is_standing] def set_player_finished(self, player, result): record = self.player_records[player] record.in_game = False record.result = result def end_player_turn(self, player, result, natural=False): """ End turn with provided result and split bets accordingly. Arguments: player -- Currently playing result -- ['W', 'L', 'D'] for win, loss or draw natural -- Indicates if the player has a natural 21 """ logger.info('[RESULT] {0}: {1}'.format(player, result)) bet = self.player_records[player].bet if result == 'W': if natural: bet = bet * 1.5 self.dealer.pay(player, bet) elif result == 'L': player.pay(self.dealer, bet) self.set_player_finished(player, result) def notify_players(self, card, is_dealer): for player in self.players: player.on_card_dealt(card, is_dealer) def notify_players_shuffle(self): for player in self.players: player.on_shuffle() def deal_card(self, player, face_up=True): card = self.deck.deal() logger.info('[HIT] ({0}) {1}'.format(player, card)) player.hand.add_card(card) if face_up: self.notify_players(card, player == self.dealer) if self.shuffle_counter > self.max_deals: self.notify_players_shuffle() self.deck.shuffle() self.shuffle_counter = 0 self.shuffle_counter += 1 def stand(self, player): self.player_records[player].is_standing = True logger.info('[STAND] ({0})'.format(player))
dealer = Dealer() player = Player() while True: print('*** Starting a new game...') playing = True player.hand = [] dealer.hand = [] dealer.show_hand = False deck = Deck() deck.shuffle() player.place_bet() player.get_card(deck.deal()) dealer.get_card(deck.deal()) player.get_card(deck.deal()) dealer.get_card(deck.deal()) print(dealer) print(player) while playing: hs = input('Hit or stand? (h/s) ') if hs == 'h': player.get_card(deck.deal()) print(player) if hand_total(player.hand) < 21:
class Dealer: ''' Class representing the dealer. The dealer manages a single round of the game from initial dealing through dealing additional cards to monitoring hand values and logging the results. Args: `player`: blackjack.Player instance to interact with. ''' def __init__(self, player, **kwargs): self.player = player self.deck = Deck() self.options = ['stand', 'hit'] def deal_starting_hands(self): ''' Reshuffle deck and deal 2 cards for player and one card for house. Returns: self ''' self.deck.reshuffle() self.player_cards = self.deck.deal(n_cards=2) self.house_cards = self.deck.deal(n_cards=1) return self @property def player_value(self): ''' Returns the current value of player cards. Returns: (int): the current value ''' return self.evaluate_cards(self.player_cards) @property def house_value(self): ''' Returns the current value of house cards. Returns: (int): the current value ''' return self.evaluate_cards(self.house_cards) @staticmethod def evaluate_cards(hand): ''' Calculates the value of a hand (list of cards) according to blackjack rules (2-T: face value, J-K: 10, A: either 1 or 11). Args: `hand`: list(str): list of card strings (first character has to be the card rank, eg. 'As', '4c', but 'A', '4' works too) Returns: (int): the current value of the hand. ''' aces = [card for card in hand if card[0] == 'A'] non_aces = [card for card in hand if card[0] != 'A'] # All Aces count as 1 value = len(aces) # Count non-aces for card in non_aces: try: card_value = int(card[0]) except ValueError: card_value = 10 value += card_value # Check if one of the Aces can be counted as 11 if len(aces) > 0 and value <= 11: value += 10 return value @property def reward(self): ''' Returns the current reward (winning) based on the card values ( even mid-round). The winning is calculated with the initial bet (10) inclued, therefore the result is in [-10, 0, 10]. Returns: (int): 0 if player value == house value, and both are valid hands -10 if player lost 10 if player won ''' if self.house_value <= 21 and self.player_value <= 21: if self.player_value < self.house_value: reward = -10 elif self.player_value > self.house_value: reward = 10 else: reward = 0 elif self.player_value > 21: reward = -10 elif self.house_value > 21: reward = 10 return reward @property def game_state(self): ''' Creates ((P1, P2, ...), H) tuple, where P1, P2, ... are the player's cards ranks, while H is the rank of the open card of the house. The tuple represents the current state of the game, which is needed by the learning agent. Note: the suit of the cards is stripped as it is not necessary for computing the hand values and bloats state space for the learner. Returns: (tuple): state of the game, eg. (('A', '4'), 'K') ''' player_cards = [card[0] for card in sorted(self.player_cards)] house_card = [card[0] for card in self.house_cards][0] state = (tuple(player_cards), house_card) return state @property def player_action(self): ''' Returns the player's chosen action, after calling player.action() with current state and the given options for player actions. Logs the given action (phase, player hand, house hand, action). Returns: (str): players chose action, eg. 'hit' or 'stand' ''' action = self.player.action(self.game_state, self.options) return action def hit_hand(self, which_hand): ''' Deals one additional card for the hand defined by `which_hand`. Params: `which_hand`: (str): 'player' or 'house' Returns: None ''' assert which_hand in ['player', 'house'] new_card = self.deck.deal() if which_hand == 'player': self.player_cards.extend(new_card) else: self.house_cards.extend(new_card) def run_game(self): ''' Runs one round of blackjack game with player: 1. Deal starting hands 2. Deal cards to player until 'stand' or bust 3. Deal cards for the house if needed (player stays in game) 4. Evaluate game and give reward to player 5. Log results Returns: None ''' self.deal_starting_hands() if self.player_value < 21: while self.player_action != 'stand': self.hit_hand('player') if self.player_value >= 21: break if self.player_value <= 21: while self.house_value < 17: self.hit_hand('house') self.player.set_reward(self.reward, self.game_state)