5
\$\begingroup\$

I came up with this solution to an exercise out of John Zelle's Python Programming book.

#bust.py --- Simulates multiple games of blackjack with each possible dealer starting card and estimates
#   the probability that the dealer will bust

import random

def intro():
    pass

def takecard():
    card = random.randint(1, 10)
    has_ace = False
    #Randomly choose if the 1 is an ace
    if card == 1:
        has_ace = random.choice([True, False])
    return card, has_ace

def sim_one_game(starter):
    #For simulating the ace
    if starter == 12:
        starter = 1
        has_ace = True

    total = starter

    while total <= 17:
        card, has_ace = takecard()
        total += card

        #Ace is valued at 11 when it would produce a stopping total
        if has_ace == True and total + 10 >= 17 and total + 10 <= 21:
            total += 10

    return total

def sim_n_games(starter, game_count):
    busts = 0
    for i in range(game_count):
        score = sim_one_game(starter)
        if score > 21:
            busts += 1
    return busts

def sim_each_card(game_count):
    bustsls = []
    for i in range(1, 12):
        bustsls.append(sim_n_games(i, game_count))

    return bustsls

def output(bustsls, game_count):
    print("Busts for Starting Card A: {} out of {} games. ({:0.1%})".format(bustsls[10], game_count, bustsls[10] / game_count))
    for card in range(1, 11):
        print("Busts for Starting Card {}: {} out of {} games. ({:0.1%})".format(card, bustsls[card - 1], game_count, bustsls[card - 1] / game_count))

def main():
    intro()
    game_count = int(input("\nHow many rounds to simulate? "))
    bustsls = sim_each_card(game_count)
    output(bustsls, game_count)

if __name__ == "__main__": main()

It works pretty well, although when profiling it with 100.000 iterations, it takes ~5 seconds to finish. When profiling, I noticed that all the calls to random take up quite some time.

Does anyone know a better way to solve this? Please note that I am a beginner, and there are a lot of things I haven't learned anything about yet, but I'll read all of your suggestions.

\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

The code has excellent layout, partitioning into functions, and you gave variables and functions meaningful names, for the most part.

Naming

Consider a better name for the bustsls variable. If it is just a list of busts, then busts would be good.

Input checking

It is good that you force the user input to be an int, but there is room for improvement. For example, the code does not behave well if the user inputs unexpected values like -5, 0 or a word like dog. You could extend the user input checking using a module like pyinputplus.

Lint check

I ran pylint, and it identified a few long lines of code.

It also recommends f-strings over format.

DRY

I created a new function (print_results) to avoid repeating the code that calculates the percentage and formats the output.

Documentation

You could use your intro placeholder function to print out some helpful information to the user. It should also include a better description of the output.


Here is the code with some of the suggestions:

import random
import pyinputplus as pyip

def intro():
    print(
        'Simulates multiple games of blackjack with each possible dealer\n'
        + 'starting card and estimates the probability that the dealer will bust.'
    )

def takecard():
    card = random.randint(1, 10)
    has_ace = False
    #Randomly choose if the 1 is an ace
    if card == 1:
        has_ace = random.choice([True, False])
    return card, has_ace

def sim_one_game(starter):
    #For simulating the ace
    if starter == 12:
        starter = 1
        has_ace = True

    total = starter

    while total <= 17:
        card, has_ace = takecard()
        total += card

        #Ace is valued at 11 when it would produce a stopping total
        if has_ace == True and total + 10 >= 17 and total + 10 <= 21:
            total += 10

    return total

def sim_n_games(starter, game_count):
    busts = 0
    for _ in range(game_count):
        score = sim_one_game(starter)
        if score > 21:
            busts += 1
    return busts

def sim_each_card(game_count):
    bustsls = []
    for i in range(1, 12):
        bustsls.append(sim_n_games(i, game_count))

    return bustsls

def print_result(card, busts, game_count):
    ''' Print results for one card '''
    pct = busts / game_count
    print(f"Busts for Starting Card {card}: {busts} out of {game_count} games. ({pct:0.1%})")

def output(bustsls, game_count):
    print_result('A', bustsls[10], game_count)
    for card in range(1, 11):
        print_result(card, bustsls[card - 1], game_count)

def main():
    intro()
    game_count = pyip.inputInt("\nHow many rounds to simulate? ", min=1)
    bustsls = sim_each_card(game_count)
    output(bustsls, game_count)

if __name__ == "__main__":
    main()
\$\endgroup\$
0

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.