diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/.DS_Store differ diff --git a/README.md b/README.md index e5d7b00..055341a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,13 @@ # Blackjack * [Part 1](README_1.md) -* [Part 2](README_2.md) \ No newline at end of file +The game layout uses 5 classes: Card, Deck, Player, Hand, and Game State +The Game State class will collect the deck(s) of cards and deal them as well +as implement the turn flow of the game and win/loss conditions. This is subject +to change as I code it out, possibly breaking up Game State into a couple more +classes for Shoe, Deal, Bet. +* [Part 2](README_2.md) +The layout now uses 6 classes and an interface (play_blackjack.py). +The game_state file sets up a new shoe and hands as well as the deal method. +The interface contains functions for intro display, hand display, the dealer's +logic, player's moves, and the evaluation and results for a round. diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/card.py b/card.py new file mode 100644 index 0000000..3fb2550 --- /dev/null +++ b/card.py @@ -0,0 +1,33 @@ +suits = ['♡', '♢', '♧', '♤'] +ranks = ['2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K', 'A'] + + +class Card: + """Playing card. + + Responsibilities: + + *Card has a rank, suit and value derived from the rank. + *Two cards with same rank and suit should be equal to eachother + + Collaborators: + + *Collected in a Deck + *Collected in a Hand for the players and a Hand for the dealer. + """ + def __init__(self, rank, suit): + self.rank = rank + self.suit = suit + if self.rank.isdigit(): + self.value = int(self.rank) + elif self.rank is 'A': + self.value = 1 + else: + self.value = 10 + + def __eq__(self, other): + """Test equality for 2 cards.""" + return self.suit == other.suit and self.rank == other.rank + + def __str__(self): + return'{}{}'.format(self.rank, self.suit) diff --git a/deck.py b/deck.py new file mode 100644 index 0000000..245f3ce --- /dev/null +++ b/deck.py @@ -0,0 +1,30 @@ +import random +from card import Card, ranks, suits + + +class Deck: + """A shuffled deck of playing cards. + + Responsibilities: + *Constructs a shuffled deck containing 1 of each card. + *Allows cards to be drawn + *Should be able to report it's current size + collaborators: + +Collects 1 of each card from the Card class. + """ + def __init__(self): + self.cards = [] + for rank in ranks: + for suit in suits: + card = Card(rank, suit) + self.cards.append(card) + random.shuffle(self.cards) + + def __len__(self): + return len(self.cards) + + def __str__(self): + return " ".join([card.__str__() for card in self.cards]) + + def __eq__(self, other): + return self.__cards__ == other.__cards__ diff --git a/game_state.py b/game_state.py new file mode 100644 index 0000000..0687806 --- /dev/null +++ b/game_state.py @@ -0,0 +1,39 @@ +import random +from hand import Hand +from shoe import Shoe + + +class Game(): + """Contains the initial state of a game, as well as methods for actions and + results within the game. + + Responsiblities: + *Initiates new hands for the player and dealer + *Contains the hit method for drawing cards into those hands + + Collaborators: + +Collects hands from Hand + +Collects the Shoe + """ + def __init__(self): + self.player_hand = Hand() + self.dealer_hand = Hand() + self.shoe = Shoe() + + def __str__(self): + return ' '.join([cards.__str__() for cards in self.player.hand]) + + def deal(self): + """Method for the initial card dealing to the player and dealer.""" + for _ in range(2): + self.player_hand.cards.append(self.shoe.draw()) + for _ in range(2): + self.dealer_hand.cards.append(self.shoe.draw()) + + def hit(self): + """Allows the player draw another card (hit).""" + self.player_hand.cards.append(self.shoe.draw()) + + def dealer_hit(self): + """Allows the dealer draw another card (hit).""" + self.dealer_hand.cards.append(self.shoe.draw()) diff --git a/hand.py b/hand.py new file mode 100644 index 0000000..33ded63 --- /dev/null +++ b/hand.py @@ -0,0 +1,32 @@ +from card import Card, ranks, suits + + +class Hand: + """Contains the value for a given hand and the Ace exception rule. + + *Determines the value of a given hand from the rank of each card + *Implements the ace swap if a card is an ace and the hand value < 12. + + Collaborators: + +Collects Cards. + """ + + def __init__(self): + self.cards = [] + self.value = 0 + + def __str__(self): + return ' '.join([str(card) for card in self.cards]) + + def get_value(self): + """Determines the value of the current hand.""" + self.value = 0 + for card in self.cards: + self.value += card.value + for card in self.cards: + if card.rank == 'A' and self.value < 12: + self.value += 10 + if len(self.cards) == 2 and self.value == 21: + return 'BLACKJACK' + else: + return self.value diff --git a/play_blackjack.py b/play_blackjack.py new file mode 100644 index 0000000..23a03d9 --- /dev/null +++ b/play_blackjack.py @@ -0,0 +1,160 @@ +from card import Card, ranks, suits +from deck import Deck +from player import Player +from hand import Hand +from game_state import Game +import sys + + +def intro(): + print("="*80) + print('\n____ __ __ __ __' + '\n/ __ )/ /___ ______/ /__ / /___ ______/ /__' + '\n/ __ / / __ `/ ___/ //_/ __ / / __ `/ ___/ //_/' + '\n/ /_/ / / /_/ / /__/ ,< / /_/ / /_/ / /__/ ,<' + '\n/_____/_/\__,_/\___/_/|_| \____/\__,_/\___/_/|_|' + ) + print("\n\n") + print("="*80) + + +def get_shoe_size(): + """Allows the player to choose 1 or 6 decks for the shoe.""" + while True: + try: + shoe_size = int(input("\nPlay with 1 or 6 decks in the shoe?\n>:")) + if shoe_size == 1 or shoe_size == 6: + break + else: + print("\nLooking for a 1 or a 6...try again.") + continue + except ValueError: + print("\nLooking for a 1 or a 6...try again.") + continue + return shoe_size + + +def display(): + """Prints out the current hands as well as the player's bet/bank.""" + print('*' * 80) + print('Dealer Hand:{} and Hole:??'.format([str(card) + for card + in game.dealer_hand.cards[1:]])) + print('*' * 80) + print('Your Hand:{}'.format([str(card) + for card + in game.player_hand.cards])) + print("Bank: {}, Bet: {}".format(player.bank, 10)) + print('*' * 80) + + +def player_move(): + move = input("(H)it or (S)tand? \n>:").lower() + if move == 'h': + game.hit() + return + elif move == 's': + return 'STAND' + else: + print("I'm sorry what was that?") + return player_move() + + +def dealer_move(): + """Contains the dealers simple logic.""" + dealer_value = game.dealer_hand.get_value() + if len(game.dealer_hand.cards) == 2 and dealer_value == 17: + print("\nDealer hits...") + game.dealer_hit() + return 'HIT' + elif game.dealer_hand.value < 17: + print("\nDealer hits...") + game.dealer_hit() + return 'HIT' + else: + return 'STAND' + + +def game_flow(): + """Contains the turn scheme of the game. Including hand evaluations.""" + print("\nSetting up a new game...") + game.deal() + print("\nDealing the cards...") + print("\n\nLet's begin!!!") + while True: + display() + pmove = player_move() + dmove = dealer_move() + player_value = game.player_hand.get_value() + if player_value > 21: + return + elif pmove == 'STAND' and dmove == 'STAND': + return + + +def results(): + """Prints the final hand values and determines the winner of the game.""" + player_value = game.player_hand.get_value() + dealer_value = game.dealer_hand.get_value() + print("Your hand: {}, Value: {}".format([str(card) + for card + in game.player_hand.cards], + player_value)) + print("Dealer's hand: {}, Value: {}".format([str(card) + for card + in game.dealer_hand.cards], + dealer_value)) + if player_value == 'BLACKJACK' and dealer_value != 'BLACKJACK': + print("\nWhoa you got BLACKJACK!!") + player.win_blackjack(bet) + return + elif player_value > 21: + print("Oh no! You BUSTED!!") + return 'BUST' + elif player_value == 21 and dealer_value == 'BLACKJACK': + print("\nOh no! the house got BLACKJACK!!") + return + elif dealer_value > 21: + print("\nThe house busts! You win!") + player.win_no_blackjack(bet) + return + elif player_value > dealer_value: + print("\nYou beat the house!!") + player.win_no_blackjack(bet) + return + elif player_value < dealer_value: + print("\nOh no! The house wins!") + return + elif player_value == dealer_value: + print("\nAhh it's a push...") + player.push(bet) + else: + return + + +def get_bet(): + """Allows the player to choose their bet.""" + while True: + try: + bet = int(input("\nHow much will you bet on this hand?\n>:")) + break + except ValueError: + print("\nThat was not a valid bet. Try again...") + return bet + + +if __name__ == "__main__": + intro() + player = Player() + shoe_size = get_shoe_size() + while player.bank > 0: + game = Game() + game.shoe.set(shoe_size) + print("\nYou have ${}".format(player.bank)) + bet = get_bet() + player.bet(bet) + print("\nTaking your bet...") + game_flow() + results() + print("\n\nGet outta here!! You're broke!!") + sys.exit() diff --git a/player.py b/player.py new file mode 100644 index 0000000..89039ca --- /dev/null +++ b/player.py @@ -0,0 +1,36 @@ +from hand import Hand + + +class Player: + """Contains the Players bank. + + responsibilties: + *Contains the method for decrementing the player's bet. + *Contains the methods for incrementing the players bank based + on the win/push condition (blackjack or not). + + collaborators: + *Collected in the game state + """ + + def __init__(self): + self.bank = 100 + + def __str__(self): + self.hand = ' '.join([cards.__str__() for cards in self.hand]) + + def bet(self, bet): + """Decrements the bet from the player's bank.""" + self.bank -= bet + + def win_no_blackjack(self, bet): + """Increments non-blackjack winnings to the player's bank.""" + self.bank += bet * 2 + + def win_blackjack(self, bet): + """Increments blackjack winnings to the player's bank.""" + self.bank += bet * 2.5 + + def push(self, bet): + """Return's the player's bet when the result is a push.""" + self.bank += bet diff --git a/shoe.py b/shoe.py new file mode 100644 index 0000000..e865d2f --- /dev/null +++ b/shoe.py @@ -0,0 +1,29 @@ +from deck import Deck + + +class Shoe(): + """Gathers the deck(s) into a shoe. + + *Contains the draw() method to draw from a concatenated list of decks. + + Collaborators: + +Collects decks into a shoe. + """ + + def __init__(self): + self.total = Deck().cards + self.drawn_cards = [] + + def __str__(self): + return " ".join([cards.__str__() for cards in self.total]) + + def set(self, n): + """Method to determine the number of decks (n) in the shoe.""" + self.total *= n + return self.total + + def draw(self): + """Take a new card from the shoe and return it.""" + new_card = self.total.pop() + self.drawn_cards.append(new_card) + return new_card diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_blackjack_card.py b/tests/test_blackjack_card.py new file mode 100644 index 0000000..7e2d735 --- /dev/null +++ b/tests/test_blackjack_card.py @@ -0,0 +1,25 @@ +from card import Card + +def test_card_is_generated(): + assert Card(rank='Q', suit='♡').__str__() == 'Q♡' + + +def test_identical_cards_are_equal(): + queen1 = Card('Q', 'Hearts') + queen2 = Card('Q', 'Hearts') + assert queen1 == queen2 + + +def test_card_value(): + testcard = Card('2', '♡') + assert testcard.value == 2 + + +def test_facecard_value(): + testcard = Card('Q', '♡') + assert testcard.value == 10 + + +def test_ace_value(): + testcard = Card('A', '♡') + assert testcard.value == 1 diff --git a/tests/test_blackjack_deck.py b/tests/test_blackjack_deck.py new file mode 100644 index 0000000..8a7f18d --- /dev/null +++ b/tests/test_blackjack_deck.py @@ -0,0 +1,10 @@ +from card import Card +from deck import Deck + + +def test_deck_generate(): + assert Deck() + + +def test_deck_length(): + assert len(Deck()) == 52 diff --git a/tests/test_blackjack_game_state.py b/tests/test_blackjack_game_state.py new file mode 100644 index 0000000..bb9ba0f --- /dev/null +++ b/tests/test_blackjack_game_state.py @@ -0,0 +1,22 @@ +from game_state import Game + + +def test_initial_hands(): + game = Game() + game.deal() + assert len(game.player_hand.cards) == 2 + assert len(game.dealer_hand.cards) == 2 + + +def test_player_hit(): + game = Game() + game.deal() + game.hit() + assert len(game.player_hand.cards) == 3 + + +def test_dealer_hit(): + game = Game() + game.deal() + game.dealer_hit() + assert len(game.dealer_hand.cards) == 3 diff --git a/tests/test_blackjack_hand.py b/tests/test_blackjack_hand.py new file mode 100644 index 0000000..2be6137 --- /dev/null +++ b/tests/test_blackjack_hand.py @@ -0,0 +1,51 @@ +from card import Card +from deck import Deck +from hand import Hand + + +def test_hand_has_card(): + dummyhand = Hand() + dummyhand.cards = [Card('2', '♡')] + assert Card('2', '♡') in dummyhand.cards + + +def test_hand_value_1_card(): + dummyhand = Hand() + dummyhand.cards = [Card('2', '♡')] + assert dummyhand.get_value() == 2 + + +def test_hand_value_2_cards(): + dummyhand = Hand() + dummyhand.cards = [Card('2', '♡'), Card('4', '♡')] + assert dummyhand.get_value() == 6 + + +def test_hand_value_2_face_cards(): + dummyhand = Hand() + dummyhand.cards = [Card('K', '♡'), Card('Q', '♡')] + assert dummyhand.get_value() == 20 + + +def test_hand_busted(): + dummyhand = Hand() + dummyhand.cards = [Card('K', '♡'), Card('K', '♡'), Card('K', '♡')] + assert dummyhand.get_value() == 30 + + +def test_hand_value_win(): + dummyhand = Hand() + dummyhand.cards = [Card('K', '♡'), Card('4', '♡'), Card('7', '♡')] + assert dummyhand.get_value() == 21 + + +def test_hand_value_blackjack(): + dummyhand = Hand() + dummyhand.cards = [Card('A', '♡'), Card('Q', '♡')] + assert dummyhand.get_value() == 'BLACKJACK' + + +def test_more_than_one_ace(): + dummyhand = Hand() + dummyhand.cards = [Card('A', '♡'), Card('A', '♡')] + assert dummyhand.get_value() == 12 diff --git a/tests/test_blackjack_player.py b/tests/test_blackjack_player.py new file mode 100644 index 0000000..38c7a6c --- /dev/null +++ b/tests/test_blackjack_player.py @@ -0,0 +1,28 @@ +from player import Player +from shoe import Shoe + +def test_player_has_starting_bank(): + assert Player().bank == 100 + + +def test_bet_loss_to_bank(): + player = Player() + bet = 50 + player.bet(bet) + assert player.bank == 50 + + +def test_win_no_blackjack(): + player = Player() + bet = 100 + player.bet(bet) + player.win_no_blackjack(bet) + assert player.bank == 200 + + +def test_win_with_blackjack(): + player = Player() + bet = 100 + player.bet(bet) + player.win_blackjack(bet) + assert player.bank == 250 diff --git a/tests/test_blackjack_shoe.py b/tests/test_blackjack_shoe.py new file mode 100644 index 0000000..b6ce0f4 --- /dev/null +++ b/tests/test_blackjack_shoe.py @@ -0,0 +1,17 @@ +from shoe import Shoe + +def test_shoe_contains_decks(): + my_shoe = Shoe() + my_shoe.total = my_shoe.set(1) + assert len(my_shoe.total) == 52 + +def test_another_shoe_length(): + my_shoe = Shoe() + my_shoe.total = my_shoe.set(3) + assert len(my_shoe.total) == 156 + +def test_draw_from_total_shoe(): + my_shoe = Shoe() + my_shoe.total = my_shoe.set(3) + my_shoe.draw() + assert len(my_shoe.total) == 155