I've been working with Python for a couple years on the side, and I'm looking to get feedback on this Blackjack game I made for some OOP practice. The code executes as expected (and I know the game is incomplete - no splitting, etc.).
https://github.com/josh9730/misc/blob/main/blackjack.py
Code:
import cards
class Blackjack:
game_over = False
def __init__(self):
self.deck = cards.Deck()
self.money = 100
self.money_lost = 0
self.bet = 0
def _calc_points(self, hand: list) -> int:
"""Calculate points for the hand supplied."""
num_aces = 0
points = 0
for card in hand:
if card.value in ("Jack", "Queen", "King"):
points += 10
elif card.value == "Ace":
num_aces += 1
points += 11
else:
points += int(card.value)
while points > 21 and num_aces:
points -= 10
num_aces -= 1
return points
def _test_game_over(self):
"""Check if game_over (i.e. break conditions):
- player has 21
dealer will hit until equal to 21 or bust
- player has over 21
player busts
"""
if self.dealer_points < self.player_points == 21:
self.dealer_hit()
elif self.player_points > 21:
self.game_over = True
def _hit(self, hand: list) -> int:
"""Hit and return calculated points.
Args:
hand [list]: list of Card objects. Expect either self.player_hand or
self.dealer_hand
"""
new_card = self.deck.deal_cards(1)
hand.extend(new_card)
return self._calc_points(hand)
def _show_last_card(self):
"""Draw last card on lose or draw."""
return self.deck.deal_cards(1)
def _print_cards_per(self, player: str, hand: list):
"""Print cards and points per-player.
- if player or game_over == True, then all cards will be shown and the point
total displayed.
- if dealer and not game_over, then only the dealer's first card will be shown.
The point total for the dealer will be only an empty string.
Args:
player [str]: 'dealer' or 'player'. Used to determine which hand to display.
hand [list]: list of Card objects. Expect either self.player_hand or
self.dealer_hand
"""
points = ""
if player == "player" or self.game_over:
cards_str = ", ".join(str(i) for i in hand)
points = self.player_points if player == "player" else self.dealer_points
else:
cards_str = f"{hand[0]}, UNKNOWN"
print(
f"{player.capitalize()} Hand:\n\t{cards_str}",
f"\n\tPoints: {points}",
)
def _laugh_at_player(self):
"""Show last card to the player to see if they would have won."""
last_card = self._show_last_card()
print(f"\tThe next card was the {str(last_card[0])}\n")
self.player_hand.extend(last_card)
points = self._calc_points(self.player_hand)
if 21 >= points > self.dealer_points:
print("\tHaha, you would have won, sucker!")
def calc_bet(self, win=False):
"""Calculate winnings/losings."""
if win:
self.money += self.bet
else:
self.money -= self.bet
self.money_lost += self.bet
def show_me_the_money(self):
"""Function to print current money pot."""
print(f"\nYou have: ${self.money}")
print(f"You have lost: ${self.money_lost}\n")
def deal(self):
"""Deal new hand to players.
- deal 2 to player and dealer
- Calculate points for each player
- Test if player has 21, no need to prompt for stay/hit
"""
self.dealer_hand = self.deck.deal_cards(2)
self.player_hand = self.deck.deal_cards(2)
self.dealer_points = self._calc_points(self.dealer_hand)
self.player_points = self._calc_points(self.player_hand)
self._test_game_over()
def player_hit(self):
"""Hit player and test game_over."""
self.player_points = self._hit(self.player_hand)
self._test_game_over()
def dealer_hit(self):
"""Hit dealer, looping until dealer_points are equal to or greater
than the player_points, then changing game_over to True."""
while self.player_points > self.dealer_points:
self.dealer_points = self._hit(self.dealer_hand)
self.game_over = True
def show_cards(self):
"""Show the cards & points for both the dealer and player."""
self._print_cards_per("dealer", self.dealer_hand)
self._print_cards_per("player", self.player_hand)
print("")
def show_winner(self):
"""Determine winner, print GAME OVER string."""
msg = "\n***** Game Over -- {} *****\n"
if self.player_points == self.dealer_points:
print(msg.format("DRAW"))
print("At least you didn't lose any money.")
self._laugh_at_player()
elif self.player_points > 21 or 21 > self.dealer_points > self.player_points:
self.calc_bet()
print(msg.format("YOU LOSE"))
print(f"You lost ${self.bet}")
self._laugh_at_player()
else:
self.calc_bet(win=True)
print(msg.format("YOU WIN"))
print(f"You won ${self.bet}!")
#
# Pytests
#
def create_hand(hand: list) -> list:
"""Initialize card namedtuple."""
return [cards.Card(i, "Hearts") for i in hand]
def test_hands():
"""Test various hand conditions."""
hands = [
(12, ["Ace", "Ace", "10"]),
(13, ["Ace", "Ace", "Ace", "10"]),
(14, ["Ace", "Ace", "Ace", "Ace", "10"]),
(17, ["Ace", "Ace", "King", "5"]),
(18, ["Ace", "Ace", "Ace", "King", "5"]),
(26, ["Ace", "King", "Queen", "5"]),
]
game = Blackjack()
for hand_points in hands:
hand = create_hand(hand_points[1])
points = game._calc_points(hand)
assert points == hand_points[0]
def test_21():
"""Test if player score == 21 ends the game."""
game = Blackjack()
game.deal()
game.player_points = 21
game._test_game_over()
assert game.game_over
def test_lose_bet():
"""Test bet if lose."""
game = Blackjack()
game.bet = 10
game.calc_bet(win=False)
assert game.money == 90
assert game.money_lost == 10
def test_win_bet():
"""Test bet if win."""
game = Blackjack()
game.bet = 10
game.calc_bet(win=True)
assert game.money == 110
assert game.money_lost == 0
#
# End Pytests
#
def main():
game = Blackjack()
play = True
while play:
game.deal()
game.show_me_the_money()
bet = 0
while not bet:
bet_str = input("Place your bet: ")
try:
bet = int(bet_str)
except ValueError:
print("Must be valid bet.")
bet = False
else:
if bet > game.money:
print("You are too poor for that bet.")
bet = False
elif bet <= 0:
print("Stop betting nonsense.")
bet = False
elif bet == game.money:
print("Feeling cocky!")
print(f"You bet ${bet}\n")
game.bet = bet
game.show_cards()
while not game.game_over:
response = ""
while response not in ("hit", "stay"):
response = input("Hit or stay? ").lower()
if response == "hit":
print("")
game.player_hit()
game.show_cards()
elif response == "stay":
game.dealer_hit()
game.show_cards()
game.show_winner()
# play new game & reset
print("=" * 50)
if game.money == 0:
print("You're broke, get out of the casino!")
exit()
new_game = ""
while new_game not in ("yes", "no"):
new_game = input("New Game? ").lower()
print("")
if new_game == "no":
play = False
else:
game.game_over = False
winnings = game.money - game.money_lost
if winnings > 0:
print(f"Good Game! You won a total of ${winnings}.")
elif winnings < 0:
print(f"You're not very good. You lost a total of ${winnings}.")
else:
print("Yay... You broke even...")
if __name__ == "__main__":
main()
1 Answer 1
Overview
The game is easy to play because it is easy to understand the prompts and messages.
The code layout is good, and you used meaningful names for classes, functions and variables. It is great that you added docstrings for the functions.
Output
In main
, if winnings
is negative, the output looks odd:
You're not very good. You lost a total of $-7.
I suggest printing the amount out as a positive number using abs
:
You're not very good. You lost a total of 7ドル.
Change:
print(f"You're not very good. You lost a total of ${winnings}.")
to:
print(f"You're not very good. You lost a total of ${abs(winnings)}.")
Type
In main
, you assign both int
values, like 0, and boolean values,
like False
, to the bet
variable. This is confusing. Since you need
integer values, you should not assign booleans to that variable. Perhaps
you need a separate variable for storing boolean values.
Simpler
In main
, since you guarantee response
will be either "hit" or "stay",
you can change:
elif response == "stay":
to:
else
Readability
In the _test_game_over
function, I find this line hard to understand with the way the 2 operators are chained:
if self.dealer_points < self.player_points == 21:
To clarify the code, consider adding and
as follows:
if self.dealer_points < self.player_points and self.player_points == 21:
Possible bug
In the show_winner
function, this condition is also worth reviewing:
elif self.player_points > 21 or 21 > self.dealer_points > self.player_points:
I think it is a bug since dealer_points
could be 21.
Humor
For the function named show_me_the_money
,
did you ask Jerry Maguire
for permission to use his catch-phrase?
-
\$\begingroup\$ Instead of using
abs()
to format the losing quantity, we could use the fact that we already knowwinnings < 0
at that point and just use-
to get the positive equivalent. \$\endgroup\$Toby Speight– Toby Speight2025年02月06日 08:42:38 +00:00Commented Feb 6 at 8:42
Explore related questions
See similar questions with these tags.