Structured blackjack game in Python 3

I am new to programming, and I am doing some homework to get more hands on coding experience. I have written a blackjack game in Python 3 and would like a code review of any and all of my code.

# Simple program simulates Blackjack game.
#   Using method: Top-Down design, spiral development

from random import randrange

def main():
printIntro()
player_hand = player()
dealer_hand = dealer()
player_win, dealer_win = compare_between(player_hand, dealer_hand)
printResult(player_hand, dealer_hand, player_win, dealer_win)

def printIntro():
print("Blackjack (twenty-one) is a casino game played with cards.")
print("the goal of game is to draw cards that total as close to 21 points, as possibale")
print("without going over( whose hand > 21 will bust). All face cards count as 10 points,")
print("aces count as 1 or 11, and all other cards count their numeric value.")

def player():
hand = []
ans = "hit"
hand.append(card())
# Ask user whether Hit or Stand?
# Condition True, if user want to Hit.
while ans[0] == "h" or ans[0] == "H":
hand.append(card())
hand = eval_ace(hand)
print("Your hand: {0} total = {1}".format(hand, sum(hand)))
if bust(hand):
break
if blackjack(hand):
break
ans = input("Do you want to Hit or Stand (H or S)? ")
return hand

def card():
# get arbitrary card from 2 to 11.
shuffle_card = randrange(2, 11 + 1)
return shuffle_card

def eval_ace(hand):
# Determine Ace = 1 or 11, relying on total hand.
total = sum(hand)
for ace in hand:
if ace == 11 and total > 21:
# at position, where Ace == 11, replace by Ace == 1.
position_ace = hand.index(11)
hand[position_ace] = 1
return hand

def bust(hand):
# Condition True: if the hand of player (or dealer) > 21.
total = sum(hand)
if total > 21:
return True

def blackjack(hand):
# Condition True: if the hand of player (or dealer) == 21.
total = sum(hand)
if total == 21:
return True

def dealer():
hand = []
hand.append(card())
while sum(hand) < 18:
hand.append(card())
hand = eval_ace(hand)
return hand

def compare_between(player, dealer):
total_player = sum(player)
total_dealer = sum(dealer)
player_bust = bust(player)
dealer_bust = bust(dealer)
player_blackjack = blackjack(player)
dearler_blackjack = blackjack(dealer)
player_win = 0
dealer_win = 0
# when player (dealer) bust.
if player_bust:
if not dearler_blackjack and total_dealer < 21:
dealer_win += 1
if dealer_bust:
if not player_blackjack and total_player < 21:
player_win += 1
if player_bust and dealer_bust:
if total_player > total_dealer:
player_win += 1
elif total_dealer > total_player:
dealer_win += 1
else:
player_win == dealer_win
# when player (dealer) get blackjack.
if player_blackjack:
player_win += 1
if dearler_blackjack:
dealer_win += 1
if player_blackjack and dearler_blackjack:
player_win == dealer_win
# when total hand of player (dealer) < 21.
if total_player < 21 and total_dealer < 21:
if total_player > total_dealer:
player_win += 1
elif total_dealer > total_player:
dealer_win += 1
else:
player_win == dealer_win
return player_win, dealer_win

def printResult(player_hand, dealer_hand, player_win, dealer_win):
print("\nWe have the result: ")
print("Player has: {0} total = {1}".format(player_hand, sum(player_hand)))
print("Dealer has: {0} total = {1}".format(dealer_hand, sum(dealer_hand)))
print("player: {} | dealer: {}".format(player_win, dealer_win))

if __name__ == "__main__": main()


Update

After a few hour to learn and apply PEP8. Here is my version 2:

# Program simulate Blackjack game.
#   with multiple game
#   Using method: Top-Down design, spiral development

from random import randrange

def main():
print_intro()
player_win, dealer_win, game = play_multiple_game()
final_result(player_win, dealer_win, game)

def print_intro():
print("Blackjack (twenty-one) is a casino game played with cards.")
print("the goal of game is to draw cards that total as close to 21 points, as possibale")
print("without going over (whose hand > 21 will is_bust). All face cards count as 10 points,")
print("aces count as 1 or 11, and all other cards count their numeric value.")
return None

def play_multiple_game():
player_win = 0
dealer_win = 0
game = 0
play_again = "yes"
# Ask user whether continue another game or stop
# Condition True, if user want to play.
while (play_again[0] == "y" or play_again[0] == "Y"):
player_hand = player_turn()
dealer_hand = dealer_turn()
player_score, dealer_score = compare_between(player_hand, dealer_hand)
result_of_this_game(player_hand, dealer_hand)
if (player_score > dealer_score):
print("\nPlayer win!")
player_win += 1
elif (dealer_score > player_score):
print("\nDealer win!")
dealer_win += 1
else:
print("\nThis game end in a tie!")
player_win == dealer_win
game += 1
play_again = input("\nDo you want to continue (Y or N)? ")
return player_win, dealer_win, game

def player_turn():
hand = []
ans = "hit"
hand.append(take_card())
# Ask user whether Hit or Stand?
# Condition True, if user want to Hit.
while (ans[0] == "h" or ans[0] == "H"):
hand.append(take_card())
hand = eval_ace(hand)
print("\nYour hand: {0} total = {1}".format(hand, sum(hand)))
if (is_bust(hand) or
is_blackjack(hand)):
break
ans = input("Do you want to Hit or Stand (H or S)?")
return hand

def take_card():
# get arbitrary card from 2 to 11.
shuffle_card = randrange(2, 11 + 1)
return shuffle_card

def eval_ace(hand):
# Determine Ace = 1 or 11, relying on total hand.
total = sum(hand)
for ace in hand:
if (ace == 11 and total > 21):
# at position, where Ace == 11, replace by Ace == 1.
position_ace = hand.index(11)
hand[position_ace] = 1
return hand

def is_bust(hand):
# Condition True: if the hand of player (or dealer) > 21.
total = sum(hand)
if total > 21:
return True
return None

def is_blackjack(hand):
# Condition True: if the hand of player (or dealer) == 21.
total = sum(hand)
if total == 21:
return True
return None

def dealer_turn():
hand = []
while sum(hand) < 18:
hand.append(take_card())
hand = eval_ace(hand)
return hand

def compare_between(player, dealer):
total_player = sum(player)
total_dealer = sum(dealer)
player_bust = is_bust(player)
dealer_bust = is_bust(dealer)
player_blackjack = is_blackjack(player)
dearler_blackjack = is_blackjack(dealer)
player_score = 0
dealer_score = 0
# when player (dealer) is_bust.
if player_bust:
if (not dearler_blackjack and
total_dealer < 21):
dealer_score += 1
if dealer_bust:
if (not player_blackjack and
total_player < 21):
player_score += 1
if (player_bust and
dealer_bust):
if (total_player > total_dealer):
player_score += 1
elif (total_dealer > total_player):
dealer_score += 1
else:
player_score == dealer_score
# when player (dealer) get blackjack.
if player_blackjack:
player_score += 1
if dearler_blackjack:
dealer_score += 1
if (player_blackjack and
dearler_blackjack):
player_score == dealer_score
# when total hand of player (dealer) < 21.
if (total_player < 21 and
total_dealer < 21):
if (total_player > total_dealer):
player_score += 1
elif (total_dealer > total_player):
dealer_score += 1
else:
player_score == dealer_score
return player_score, dealer_score

def result_of_this_game(player_hand, dealer_hand):
print("\nWe have the result: ")
print("Player has: {0} total = {1}".format(
player_hand, sum(player_hand)))
print("Dealer has: {0} total = {1}".format(
dealer_hand, sum(dealer_hand)))
return None

def final_result(player_win, dealer_win, game):
print("\nThe Final after {} games:".format(game))
print("player: {} | dealer: {}".format(
player_win, dealer_win))
return None

if __name__ == "__main__": main()

• Would you appreciate an answer that recommends to use a class? If you don't would you downvote an answer because it uses one? Feb 23, 2018 at 9:58
• I am very appreciate if you use "Class". Because I will learn about "Class" tomorrow, after finish some homework :) Feb 23, 2018 at 10:03
• Welcome to codereview.stackexchange. It is best not to edit your question once answers have been given. What you could do is to open a new question with the new updated code (it is best to wait for more answers on this question first). Enjoy Feb 23, 2018 at 21:24

Your code looks nice, well documented and is split into small functions. Also, you've used the if __name__ == "__main__" which is a nice touch hardly ever found in beginniner's code. Congratulations!

Let's try to see if the code can be improved anyway :)

Style

There is an official standard Python style guide called PEP 8. This is highly recommended reading. It gives guidelines to help writing code that is both readable and consistent. The Python community tries to follow these guidelines, more or less strictly (a key aspect of PEP 8 is that it provides guidelines and not strict rules to follow blindly).

It deals with various aspects of the code style: naming conventions, indentation convention, etc.

You'll find various tools to try to check whether your code is PEP 8 compliant and if is it not, to try and fix this:

In your case, there is not much to be changed except for the function names which do not follow the snake_case naming convention. For instance, printIntro should be named print_intro.

Also, another (recent) part of PEP 8 is not followed by your code:

Be consistent in return statements. Either all return statements in a function should return an expression, or none of them should. If any return statement returns an expression, any return statements where no value is returned should explicitly state this as return None, and an explicit return statement should be present at the end of the function (if reachable).

In particular, the bust and blackjack functions should have an explicit return None at the end (even though it should be even better to use return False).

Docstrings

Docstrings are string literals used at the beginning of functions/classes/modules to give some explanation to anyone reading your code. They are also handled in a special way to be accessible via the __doc__ member of the function/class/module. You can see it as special form of documentation found only at the beginning of definitions and that is easy to access (which is not the case for # comments like this) for instance with help.

Also, there are conventions for Python docstrings described in PEP 257. As far as I can tell, it is not followed as strongly by the community as PEP 8 can be but it is still a reference worth reading.

Here again, you'll find tools to help you such as pydocstyle (formerly known as pep257).

(Also, for more comprehensive documentation, you may want to go further than bland text and use the reStructuredText markup. PEP 287 give more details about this but it is not something I am familiar with).

In your case, writing docstring could help you by forcing you to find how to describe your functions, their inputs, they return valuem etc.

Improving bust (and blackjack) with previous comments and more

From the function:

def bust(hand):
# Condition True: if the hand of player (or dealer) > 21.
total = sum(hand)
if total > 21:
return True


It looks like we could be adding a return None at the end that does not return the function behavior. However, if we were to think about what the function returns, True or None seems a bit un-natural; True or False makes more sense. Changing this and documenting it properly, you'd have something like:

def bust(hand):
"""Return True if the hand value is bigger than 21, False otherwise."""
total = sum(hand)
if total > 21:
return True
return False


But then, this can actually be improved considerably by simply returning total > 21.

def bust(hand):
"""Return True if the hand value is bigger than 21, False otherwise."""
return sum(hand) > 21


Also, as suggested in the other excellent answer, the names of the functions could be improved to be more explicit. The same comments applies to blackjack.

Handling user input

You perform check like: play_again[0] == "y" or play_again[0] == "Y" to handle the user input.

You could simplify the logic here in 2 different ways: play_again[0] in ("y", "Y") or play_again[0].lower() == 'y'.

Then, a few details could be improved to make the interactions with the user clearer. It could be nicer to ask again if the user gives an invalid answer rather than taking a default decision (an empty input could be considered as valid if the prompts says so explicitely). If you do so, it is a good idea to define a new functions wrapping input to handle user interactions and validation of input retrieved instead of having your input validation logic and your game logic mixed up.

I'll try to continue this later...

Bug in the distribution of the cards

As far as I can understand, whenever a new card is given, you just pick a value at random. Then you'll get a behaviour which is not quite the one you'd have with a deck of cards. Indeed, in your case, events are independent. With a deck of cards, the probability of getting a given card depends on previous cards given (for an extreme example, once you've drawn 4 aces, the probability to pick a fifth is zero).

Your code looks good! Here's a few points and a bug:

Variables vs functions

I've seen this go both ways, but I think if your functions are small, it's probably more readable and cleaner to just us the functions return value in the if/else conditionals.

def compare_between(player, dealer):
# *you'll have a problem here if you're expecting more than one person to ever
# have more than one win because you've defined player_win and dealer_win
# locally so they'll never be more than 1
player_win, dealer_win = 0, 0

# I prefer parenthesis, i'm not sure pep8's opinion, and my coworkers hate them :(
if (bust(player)):
if (not blackjack(dealer) and sum(dealer) < 21):
dealer_win += 1
elif (bust(dealer)):
if (not blackjack(player) and sum(player) < 21):
player_win += 1
...
return player_win, dealer_win


Bug

*See the comment in the code above. In order to play multiple games then, and have more than one win, you could add a loop in main and then return the win counts and add it to the player_wins and dealer_wins inside the main() function body.

Loops

Here you make a variable at the start, but you can just execute the loop and then set ans. This will also allow you to cleanup your exit condition:

Player's Turn

def player():
hand = []
while(True):
ans = input("Hit or stay: ")
if(ans=="stay"):
break

hand.append(card())
hand = eval_ace(hand)
print("Your hand: {} total = {}".format(hand, sum(hand)))

# combine conditions with 'and' - 'or'
if(bust(hand) or blackjack(hand)):
break
return hand


Dealer's Turn

def dealer():
hand = []
# sum([]) == 0
while (sum(hand) < 18):
hand.append(card())
hand = eval_ace(hand)
return hand


Naming

It's small but id try to be a bit more descriptive with your function names, that is most functions have a verb style to them. player() should be something like players_turn() and card() could be draw_card(). To build off of @Josay answer if your returning 0/1 or true/false then changing functions to hint at the return type is helpful too: bust() becomes is_bust().

This is for readability and will save you some variable names. For example maybe you want to use card as a variable somewhere more appropriate.