1
\$\begingroup\$

[Edited] formatting and Flake8 linter corrections

Four days ago I posted my first object oriented programming project. Everything I have learned to date is self-taught and posting project to receive criticism is the best mentor I currently have.

The feedback on posts like this is integral to me, so please use a fine-tooth comb as you are willing. The more detailed, the more minute the mistake, the better you help me become.

Github: BlackjackV2

V1 of the project is here

The first bit of code is the code to be inherited.

import abc
import itertools
import random
from enum import Enum
from dataclasses import dataclass


class User(metaclass=abc.ABCMeta):

    def __init__(self):

        self.name = ""
        self.score = 0
        self.hand = []
        self.balance = 0

    @abc.abstractmethod
    def define_name(self):
        """Set either the dealer or player name"""
        return

    def print_hand(self, cards_to_be_shown):
        if cards_to_be_shown > len(self.hand):
            print("Too many cards requested to be shown")
            return

        counter = 0
        print(f"\n\n{self.name}'s HAND:")
        for card in self.hand:
            print(card)
            counter += 1
            if counter >= cards_to_be_shown:
                print('', end="\r")
                break

    def discard_hand(self):
        self.hand = []

    def calculate_score(self):
        self.score = 0
        for card in self.hand:
            self.score += card.rank.points

    def print_score(self):
        print(f"Total Score: {self.score}")

    def add_card_to_hand(self, card):
        self.hand.append(card)


class Player(User):

    def __init__(self):
        super(Player, self).__init__()
        self.balance = 200
        self.define_name()
        self.bet = self.place_bet()

    def define_name(self):

        while True:

            user_name = input("Your Name:  ")
            confirmation = input(f"Your name is {user_name}? Y/N: ")
            confirmation = confirmation.lower().strip()

            if confirmation == "y":
                self.name = user_name
                print("username set!")
                break

    def place_bet(self):

        while True:
            response = input(
                f"BALANCE: {self.balance}\nMake Bet (1/2/5/10/25/50/100): ")

            try:
                current_bet = int(response)
            except ValueError:
                print("Please Enter a Valid number")

            else:
                if current_bet not in (1, 2, 5, 10, 25, 50, 100, 1000):
                    print(
                        "Sorry, only bets of exactly 1, 2, 5, 10, 25, 50 & 100 are allowed.")
                else:
                    if current_bet <= self.balance:
                        self.balance -= current_bet
                        self.bet = current_bet
                        print(f"${self.bet} BET PLACED")
                        break
                    else:
                        print(
                            f"Amount Entered Higher than Balance. MAXIMUM BET = {self.balance}")

    def reset_bet(self):
        self.bet = 0

    def get_user_action(self):
        likely_action = ''

        while True:
            actions = ("hit", "stand")
            response = input("HIT OR STAND?:  ")
            response = response.lower().strip()

            counter = 0
            for i in range(len(response)):
                try:
                    if response[i] == actions[0][i]:
                        counter += 1
                        likely_action = actions[0]
                    elif response[i] == actions[1][i]:
                        counter += 1
                        likely_action = actions[1]
                except ValueError:
                    "just keep swimming. I feel like its bad practice but I used the try loop "

            if counter < 2:
                print("Please Re-enter: ")
            else:
                return likely_action
                break

    # What can a player do that a dealer cannot?
    # makebet


class Dealer(User):

    def __init__(self):
        super(Dealer, self).__init__()
        self.balance = 1000000
        self.define_name()

    def define_name(self):
        self.name = "Dealer"

    # what can a dealer do that a player cannot?
    # show one card on deal


class Rank(Enum):
    TWO = ('2', 2)
    THREE = ('3', 3)
    FOUR = ('4', 4)
    FIVE = ('5', 5)
    SIX = ('6', 6)
    SEVEN = ('7', 7)
    EIGHT = ('8', 8)
    NINE = ('9', 9)
    TEN = ('10', 10)
    JACK = ('J', 10)
    QUEEN = ('Q', 10)
    KING = ('K', 10)
    ACE = ('A', 11)

    @property
    def symbol(self):
        return self.value[0]

    @property
    def points(self):
        return self.value[1]


class Suits(Enum):
    CLUBS = ('Clubs', '♣')
    DIAMONDS = ('Diamonds', '♦')
    HEART = ('Hearts', '♥')
    SPADES = ('Spades', '♠')

    @property
    def suit_name(self):
        return self.value[0]

    @property
    def symbol(self):
        return self.value[1]


@dataclass(frozen=True)
class Card:
    rank: Rank
    suit: Suits

    def point_value(self):
        return self.rank.points

    def __str__(self):
        return f"{self.rank.symbol} of {self.suit.suit_name} {self.suit.symbol}"


class Deck:
    def __init__(self):
        self.deck = [Card(rank, suit)
                     for rank, suit in itertools.product(Rank, Suits)]

        random.shuffle(self.deck)

    def reveal_deck(self):

        for card in self.deck:
            card = str(card)
            print(card)


class Shoe:

    def __init__(self):

        self.new_shoe = []
        for i in range(5):
            for card in Deck().deck:
                self.new_shoe.append(card)

    def reveal_shoe(self):

        print(self.new_shoe)

    def deal_one_card(self):
        card_dealt = self.new_shoe[0]
        self.new_shoe.pop(0)
        return card_dealt


class Game:

    def __init__(self, player, dealer, shoe):
        self.player = player
        self.dealer = dealer
        self.game_shoe = shoe

    def deal_cards(self, cards_per_player):

        for i in range(cards_per_player):
            self.player.add_card_to_hand(self.game_shoe.deal_one_card())
            self.dealer.add_card_to_hand(self.game_shoe.deal_one_card())

        # delete this (prints out the current hand data)
        # print(self.player.hand)
        # print(self.dealer.hand)

    def hit(self, person):

        person.add_card_to_hand(self.game_shoe.deal_one_card())
        person.calculate_score()
        person.print_hand(len(person.hand))
        person.print_score()

    def stand(self):
        print("Chose to stay")

    def play_action(self, action, person):
        if action == "hit":
            self.hit(person)
        elif action == "stand":
            self.stand()
        else:
            ("Somehow a bad action was passed")

    def player_turn(self):

        while True:
            action = self.player.get_user_action()
            self.play_action(action, self.player)
            if action == "stand":
                break
            elif self.player.score > 21:
                print("Ya busted early bud\nGAME OVER")
                self.player.reset_bet()
                self.reset_hands()
                self.keep_playing()
                break

    def dealer_turn(self):
        self.dealer.print_hand(len(self.dealer.hand))
        self.dealer.print_score()

        while True:
            self.dealer.calculate_score()
            if self.dealer.score < 16:
                self.play_action("hit", self.dealer)

            elif self.dealer.score >= 16 and self.dealer.score < 22:
                self.play_action("stand", self.dealer)
                break

            elif self.dealer.score >= 22:
                print("Dealer BUSTS!")
                self.payout()
                self.player.reset_bet()
                self.reset_hands()
                self.keep_playing()
                break

    def reset_hands(self):
        self.player.discard_hand()
        self.dealer.discard_hand()

    def payout(self):
        print(self.player.bet)
        self.dealer.balance -= self.player.bet
        self.player.balance += (self.player.bet * 2)

    def game_begin(self):
        if self.player.bet == 0:
            self.player.place_bet()
        self.deal_cards(2)
        self.player.print_hand(2)
        self.player.calculate_score()
        self.player.print_score()
        self.dealer.print_hand(1)
        self.dealer.calculate_score()

    def middle_game(self):
        self.player_turn()
        if self.player.score < 22:
            self.dealer_turn()
            if self.dealer.score < 22:
                self.end_game()
            else:
                # ask next game function
                pass

    def end_game(self):

        if self.player.score > self.dealer.score:
            print("YOU WIN!")
            self.payout()
            # payout to player
            pass
        elif self.player.score >= self.dealer.score:
            print("DEALER WINS!!")
        self.reset_hands()
        self.player.reset_bet()
        keep_playing_variable = self.keep_playing()
        # determine winner
        # payout winner
        # reset_bet()
        # ask next game function
        return keep_playing_variable

    def keep_playing(self):

        response = input("Would you like to keep playing? Y/N: ")
        response = response.lower().strip()

        if response == "n":
            return False

The second bit of code is the code I actually execute. I will say, something about how I set-up and execute the gameplay below just doesn't feel right, although I have come to no conclusion as to why.

from blackjackhelpers import Player, Dealer, Deck, Shoe, Game

keep_playing_variable = True
player1 = Player()
dealer1 = Dealer()
game_shoe = Shoe()

new_game = Game(player1, dealer1, game_shoe)

while keep_playing_variable:
    new_game.game_begin()
    new_game.middle_game()
\$\endgroup\$
5
  • 1
    \$\begingroup\$ Given that we've found an error when you enter "stand" (and by my reading, anything with four or more letters), I think this is off topic. How did you not try "stand"? It also looks like "sit" and "stt" will cause behavior you didn't expect. \$\endgroup\$
    – Teepeemm
    Oct 8, 2022 at 20:38
  • \$\begingroup\$ Fair enough. And noted \$\endgroup\$ Oct 8, 2022 at 21:12
  • \$\begingroup\$ I fixed the loop and I immensely appreciate that flaw you pointed out. Mostly because it clearly exposes a lack of proper testing. Something that should probably become a standard practice for me henceforth. Thanks mate \$\endgroup\$ Oct 9, 2022 at 1:56
  • \$\begingroup\$ Please see What to do when someone answers. I have rolled back Rev 6 → 4. \$\endgroup\$ Oct 9, 2022 at 6:59
  • 1
    \$\begingroup\$ One habit that would be really good to develop to improve your testing is adopting Test-Driven-Development (TDD), where you write your tests BEFORE you write the production code. While writing your tests, make them as complete a test suite as possible (including all edge cases, etc). This way your tests focus on what the production code SHOULD be doing rather than testing what you have written - it really focuses your mind on all the requirements. \$\endgroup\$
    – racraman
    Oct 10, 2022 at 5:53

2 Answers 2

1
\$\begingroup\$

Bug!

You have a bug!

I was just playing your game, and this happened:

Your Name:  Thonnu
Your name is Thonnu? Y/N: y
username set!
BALANCE: 200
Make Bet (1/2/5/10/25/50/100): 100
$100 BET PLACED


Thonnu's HAND:
5 of Hearts ♥
3 of Clubs ♣
Total Score: 8


Dealer's HAND:
3 of Diamonds ♦
HIT OR STAND?:  hit


Thonnu's HAND:
5 of Hearts ♥
3 of Clubs ♣
9 of Clubs ♣
Total Score: 17
HIT OR STAND?:  stand
Traceback (most recent call last):
  File "main.py", line 12, in <module>
    new_game.middle_game()
  File "blackjackhelpers.py", line 318, in middle_game
    self.player_turn()
  File "blackjackhelpers.py", line 266, in player_turn
    action = self.player.get_user_action()
  File "blackjackhelpers.py", line 111, in get_user_action
    if response[i] == actions[0][i]:
IndexError: string index out of range

The fix

In this section of your code (lines 110 to 118):

            for i in range(len(response)):
                try:
                    if response[i] == actions[0][i]:
                        counter += 1
                        likely_action = actions[0]
                    elif response[i] == actions[1][i]:
                        counter += 1
                        likely_action = actions[1]
                except ValueError:
                    "just keep swimming. I feel like its bad practice but I used the try loop "

Your try/except block catches ValueErrors, but this was an IndexError. Instead, use:

                except (ValueError, IndexError):

to catch them both

\$\endgroup\$
3
  • 1
    \$\begingroup\$ Even better would be to diagnose why there's an index error. \$\endgroup\$
    – Teepeemm
    Oct 8, 2022 at 13:44
  • 1
    \$\begingroup\$ I actually am super curious why there is an index error. Perhaps there was empty space that wasn't cut off before setting it to the variable. After work 100% I will be checking this out AND fixing the try/except block. \$\endgroup\$ Oct 8, 2022 at 17:52
  • \$\begingroup\$ I assessed the situation and it was quite apparent what the issue was. Instead of adding IndexError to the except, I added the ability for the if/elif to filter, and then gathered a list of likely actions everytime we made a comparison. Then whichever seemed most likely, I returned. \$\endgroup\$ Oct 9, 2022 at 2:26
1
\$\begingroup\$

Game ending

When the player reaches $0, this happens:

Would you like to keep playing? Y/N: y
BALANCE: 0
Make Bet (1/2/5/10/25/50/100): 100
Amount Entered Higher than Balance. MAXIMUM BET = 0
BALANCE: 0
Make Bet (1/2/5/10/25/50/100): 0
Sorry, only bets of exactly 1, 2, 5, 10, 25, 50 & 100 are allowed.
BALANCE: 0
Make Bet (1/2/5/10/25/50/100):

It just goes in an endless loop and the player is forced to re-run the program.

Instead, I would suggest adding a feature at the start of the place_bet function (line 72), like this:

    def place_bet(self):

        if self.balance == 0:
            # Exit the game here
\$\endgroup\$
1
  • 1
    \$\begingroup\$ Good catch my friend. I'll add that here in a bit. \$\endgroup\$ Oct 8, 2022 at 16:41

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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