Ejemplo n.º 1
0
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)
Ejemplo n.º 2
0
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
Ejemplo n.º 3
0
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))
Ejemplo n.º 4
0
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:
Ejemplo n.º 5
0
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)