5
\$\begingroup\$

I have set up card, deck, hand, player, dealer, and game classes. The game class is there to keep a lot of the logic out of the interface (main.py). Let me know any thoughts on that.

I haven't ran pyflakes or a PEP8 checker on it yet, so keep that in mind!

main.py

from game import Game
from card import Card
from deck import Deck
from hand import Hand
from player import Player
from dealer import Dealer


name = input("Please enter your name: ")

dealer = Dealer()
player = Player(name)
deck = Deck()
game = Game(dealer, player, deck)
game_loop = True

while game_loop:

    game.new_turn()

    print("You have {} dollars".format(game.player.stack))
    print("How much would you like to bet?")
    game.place_bet(input("> "))

    game.dealer.show_hand()
    game.player.show_hand()

    hit = game.hit_or_stand()

    while hit and not game.player.hand.bust():
        game.player.hit(game.deck)
        game.player.show_hand()
        if game.player.hand.best_hand < 21:
            hit = game.hit_or_stand() #player can't bust

    if player.hand.best_hand <= 21 and dealer.hand.best_hand != 21:
        game.dealer.play_out_hand(game.deck)
        game.dealer.reveal_hand()
        game.player.show_hand()

        if game.dealer.hand.best_hand > 21:
            print("Dealer busts!, you win!")
            game.player.stack += game.pot * 2
        else:
            game.check_for_winner(game.dealer, game.player)
    else:
        if dealer.hand.best_hand == 21:
            print("Dealer has blackjack! You lose!")
        else:
            print("Your hand is over 21, you lose!!")

    if player.stack < 1:
        print("You are out of money! Game over!")
        break

    game_loop = game.play_again()

game.py

from dealer import Dealer
from player import Player
from hand import Hand
from card import Card
from deck import Deck, Clubs, Diamonds, Hearts, Spades


class Game:
    """Information about the game.

    Responsibilities:

    * Check for winner
    * Handles betting and bankroll
    * Resets the game and hands

    Collaborators

    * Checks the player and dealer's hand amounts and compares them
    """

    def __init__(self, dealer, player, deck):
        self.dealer = dealer
        self.player = player
        self.deck = deck
        self.pot = 0

    def play_again(self):
        choice = input('Press "p" to play again or "q" to quit: ').lower()
        return choice == 'p'


    def hit_or_stand(self):
        choice = input('Press "h" to Hit, or "s" to stand: ').lower()
        return choice == 'h'

    def check_for_winner(self, dealer, player):
        if self.player.hand.best_hand < self.dealer.hand.best_hand:
            return("You Lose")
        elif self.dealer.hand.best_hand < self.player.hand.best_hand:
            print("You Win!")
            self.player.stack += self.pot * 2
        elif self.dealer.hand.best_hand == self.player.hand.best_hand:
            print("Push!")
            self.player.stack += self.pot

    def new_turn(self):
        self.dealer.hand.reset_hand()
        self.player.hand.reset_hand()
        self.deck = Deck()
        self.deck.shuffle()
        self.dealer.hand.new_hand(self.deck)
        self.player.hand.new_hand(self.deck)
        self.pot = 0


    def place_bet(self, amount):
        try:
            int(amount)
            if int(amount) <= self.player.stack:
                self.player.stack -= int(amount)
                self.pot += int(amount)
            else:
                self.place_bet(input("Not enough funds! You have {} dollars. "
                                     "Place a bet: ".format(self.player.stack)))
        except ValueError:
            self.place_bet(input("Place a bet: "))

player.py

class Player:
    """The player of the blackjack game.

    Responsibilities:

    * Holds a hand
    * Can bet
    * Can hit
    * Can display hand

    Collaborators:

    *Uses a hand of cards
    """
    def __init__(self, name, stack = 100, hand = None):
        if hand is None:
            hand = Hand([])
        self.name = name
        self.hand = hand
        self.cards = hand.hand
        self.stack = stack


    def hit(self, deck):
        self.hand.draw(deck)


    def bet(self, amount):
        chips = self.stack
        new_chips = chips - amount
        self.stack = new_chips
        return self.stack

    def show_hand(self):
        print("{name}'s hand: {hand} ({value})".format(name = self.name,
                                               hand = self.hand,
                                               value = self.hand.best_hand))

dealer.py

from hand import Hand
from player import Player

class Dealer():
    """The dealer of the blackjack game. The dealer doesn't actually deal
    cards, but holds a hand for the purpose of the game.

    Responsibilities:

    * Holds a Hand of Cards
    * Hits until 17 or more, if over 21 returns bust
    * Has facedown card

    Collaborators

    * Gets a hand of cards from the deck
    """

    def __init__(self, hand = None):
        if hand == None:
            hand = Hand([])
        self.name = "Dealer"
        self.hand = hand


    def hit(self, deck):
        self.hand.draw(deck)


    def play_out_hand(self, deck):
        while self.hand.soft_total < 17:
            if self.hand.hard_total >= 17 and self.hand.hard_total <= 21:
                return self.hand.hard_total
                break
            self.hand.draw(deck)
        return self.hand.soft_total

    def show_hand(self):
        print("{name}'s hand: XX {hand}".format(name = self.name, hand = self.hand.hand[1]))

    def reveal_hand(self):
        print("{name}'s hand: {hand} ({value})".format(name = self.name,
                                               hand = self.hand,
                                               value = self.hand.best_hand))

deck.py

from card import Card
from random import shuffle


#assign unicode chararcters for each suit
Clubs, Diamonds, Hearts, Spades = u'\u2663', u'\u2666', u'\u2665', u'\u2660'
SUITS = (Clubs, Diamonds, Hearts, Spades)
RANKS = (2, 3, 4, 5, 6, 7, 8, 9, 10, 'J', 'Q', 'K', 'A')


class Deck:
    """A playing card deck.

    Responsibilities:

    * Can hold cards.
    * New deck should have all 52 cards.
    * Should allow others to draw cards.
    * Should be able to reshuffle itself
    * Should be able to report its current size

    Collaborators:

    * Can hold cards.
    * Can be put into shoe for dealing and shuffling
    """
    def __init__(self):
        self._cards = [Card(rank, suit)
                      for rank in RANKS
                      for suit in SUITS]


    def draw(self):
        """Take a card off the top of the deck and return it"""
        return self._cards.pop()


    def shuffle(self):
        """Shuffles cards in the deck"""
        shuffle(self._cards)
        return self


    def __eq__(self, other):
        return self._cards == other._cards

    def __len__(self):
        return len(self._cards)

    def __str__(self):
        deck_list = [str(card) for card in self._cards]
        return ", ".join(deck_list)

    def __repr__(self):
        return self.__str__()

hand.py

class Hand:
"""An assortment of cards

Responsibilitites:

* Can hold x cards
* Has a hard or soft total for hand, aces depend on hand
* Can add cards to hand
* Check if hand is busted (over 21)

Collaborators:

* A Hand is made up of Cards
* A Player and Dealer can hold a hand
* The shoe can deal to the hand
"""

def __init__(self, hand):
    self.hand = hand


@property
def hard_total(self):
    hard_total = 0
    for card in self.hand:
        if type(card.rank) == str:
            if card.rank == 'A':
                hard_total += 11
            else:
                hard_total += 10
        else:
            hard_total += card.rank
    return hard_total


@property
def soft_total(self):
    soft_total = 0
    for card in self.hand:
        if type(card.rank) == str:
            if card.rank == 'A':
                soft_total += 1
            else:
                soft_total += 10
        else:
            soft_total += card.rank
    return soft_total

@property
def best_hand(self):
    if self.hard_total > 21:
        return int(self.soft_total)
    else:
        return int(self.hard_total)



def bust(self):
    if self.soft_total > 21:
        return True
    else:
        return False


def draw(self, deck):
    card = deck.draw()
    self.hand.append(card)


def new_hand(self, deck):
    self.draw(deck)
    self.draw(deck)


def reset_hand(self):
    self.hand = []
    print(self.hand)



def __str__(self):
    hand_list = [str(card) for card in self.hand]
    return str(', '.join(hand_list))


def __repr__(self):
    return self.__str__()

card.py

from collections import namedtuple

"""A playing card.

Responsibilities:

* Has a rank and suit

Collaborators:

* Collected into a Deck
* Collected into a Hand for each player and a Hand for the dealer.
"""

class Card(namedtuple('card', ['rank', 'suit'])):

    __memory__ = () #assure instance is stored as a tuple in memory

    def __str__(self):
        return "{}{}".format(self.rank, self.suit)

    def __repr__(self):
        return self.__str__()
\$\endgroup\$
4
\$\begingroup\$

I haven't ran pyflakes or a PEP8 checker on it yet, so keep that in mind!

Uhm, why haven't you? That's a rhetorical question. You should have run those, and your statement of having not run doesn't help the reviewer in anyway, except that one might think you're deliberately careless.

Strange things

This is a bit odd:

    if game.dealer.hand.best_hand > 21:
        print("Dealer busts!, you win!")
        game.player.stack += game.pot * 2
    else:
        game.check_for_winner(game.dealer, game.player)

The if branch prints something, the else branch doesn't... Taking a quick look at game.check_for_winner:

def check_for_winner(self, dealer, player):
    if self.player.hand.best_hand < self.dealer.hand.best_hand:
        return("You Lose")
    elif self.dealer.hand.best_hand < self.player.hand.best_hand:
        print("You Win!")
        self.player.stack += self.pot * 2
    elif self.dealer.hand.best_hand == self.player.hand.best_hand:
        print("Push!")
        self.player.stack += self.pot

The method sometimes returns a string, sometimes prints a string. You should make it behave consistently. It's probably a typo, you meant to print instead of return. But even so, this is not good: the printing of game results is split in two places: main and game. It would be better to keep main fully in charge of that, having the logic in one place.

Also, the last elif should really be a simple else.


In game.place_bet this is really strange:

    try:
        int(amount)
        if int(amount) <= self.player.stack:
            self.player.stack -= int(amount)
            self.pot += int(amount)
        else:
            self.place_bet(input("Not enough funds! You have {} dollars. "
                                 "Place a bet: ".format(self.player.stack)))
    except ValueError:
        self.place_bet(input("Place a bet: "))

Why call int(amount) multiple times, re-parsing a string to integer multiple times? Better do it once and save in a variable.

Naming

stack is a strange name for a pot or bet. It's especially bad because in programming the term "stack" evokes a specific data structure, making the code misleading and harder to read. Even if "stack" is the correct and single best obvious term in Black Jack (I don't know), I suggest to find something else.


This is a bit confusing:

    game.dealer.reveal_hand()
    game.player.show_hand()

"show" and "reveal" are synonyms, which makes these statements confusing. It's not great when I have to open the implementation of two functions to see the difference of what they do.


new_turn in game can be misleading. It's really a new game. But that would be misleading too, because a method starting with "new" usually returns a new instance of something. reset_game would be better.

Other things

I know this is Black Jack and the goal to reach must be 21, but it would be fun sometimes to mess with the system and see how the game dynamics change if we use a different number, like 31. To make such future extensions easier, I'd put 21 in a global constant, and call it for example BLACKJACK. In any case I think it's a good natural reflex to develop to always replace repeated things with constant variables.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Thanks, I cleaned up my code at the places you suggested! \$\endgroup\$ – John Mitsch Jan 26 '15 at 2:42
  • \$\begingroup\$ You're welcome! If/when you are satisfied, don't forget to mark the answer accepted (see the pipe-shaped button under the voting buttons, next to my answer, at the top left) \$\endgroup\$ – janos Jan 26 '15 at 6:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.