1
\$\begingroup\$

Feedback from previous posts of mine suggested that my approach to states was haphazard, which is not surprising since I hadn't really thought of games in terms of states before. I have since gone off and done some studying and implemented a version of "the 21 Game" using the finite state machine pattern. My code is procedural intentionally. I will try to use an OOP version of this pattern for my next project.

I would appreciate some feedback on how well I did with a first attempt using this pattern.

For example, did I identify all the states correctly? Is my implementation "clean"? How could it be improved?

"""
Count to 21 game (aka Nim)

Uses basic implementation of a finite state machine pattern for
game control

A variable named 'state' controls the game in the loop at the end
of the program
"""

import random
import sys
import os

settings = {
    "max_add": 3,  # Must be int >= 1
    "text": {
        "header": """ ___    __       _______      ___      .___  ___.  _______ 
|__ \  /_ |     /  _____|    /   \     |   \/   | |   ____|
   ) |  | |    |  |  __     /  ^  \    |  \  /  | |  |__   
  / /   | |    |  | |_ |   /  /_\  \   |  |\/|  | |   __|  
 / /_   | |    |  |__| |  /  _____  \  |  |  |  | |  |____ 
|____|  |_|     \______| /__/     \__\ |__|  |__| |_______|
""",
        "number_prompt": "Enter a number between 1 and #: ",
        "invalid": "Invalid input",
        "welcome": "Welcome to 21",
        "instructions": """You must add to the total to make 21 without going over.
The computer will try and beat you.""",
        "ready?": "Are you ready? ",
        "won": "Congratulations. You won",
        "lost": "You lost.",
        "again?": "Play again? ",
        "goodbye": "Thank you for playing.",
        "current_total": "The current total is #.",
        "player_turn": "Player turn",
        "computer_turn": "Computer turn"
    }
}


def print_header():
    os.system('clear')  # Windows version. Use "cls" on Linux/Mac
    print(settings["text"]["header"])


def get_integer():
    number = None
    while number is None:
        prompt = settings["text"]["number_prompt"]
        value = input(prompt.replace("#", str(settings["max_add"])))
        if value.isdigit() and int(value) <= settings["max_add"]:
            number = int(value)
        else:
            print(settings["text"]["invalid"])
    return number


def yes_no(txt):
    result = None
    while result is None:
        choice = input(txt)
        if choice in ["y", "Y"]:
            result = True
        elif choice in ["n", "N"]:
            result = False
        else:
            print(settings["text"]["invalid"])
    return result


def player_turn():
    global state
    global current_total

    print("")
    print(settings["text"]["player_turn"])
    print(settings["text"]["current_total"].replace("#", str(current_total)))

    to_add = get_integer()
    if to_add in range(1, settings["max_add"] + 1):
        current_total += to_add
    if current_total == 21:
        state = "PLAYER_WINS"
        return
    elif current_total > 21:
        state = "PLAYER_LOSES"
        return
    state = "COMPUTER_TURN"
    return


def computer_turn():
    print("")
    print(settings["text"]["computer_turn"])

    global state
    global current_total

    computer_choice = random.randrange(1, settings["max_add"] + 1)

    print("Computer adds {}".format(computer_choice))
    current_total += computer_choice
    print(settings["text"]["current_total"].replace("#", str(current_total)))
    if current_total == 21:
        state = "PLAYER_LOSES"
        return
    elif current_total > 21:
        state = "PLAYER_WINS"
        return
    state = "PLAYER_TURN"
    return


def start():
    print_header()
    global current_total
    global state

    current_total = 0

    print(settings["text"]["welcome"])
    print(settings["text"]["instructions"])

    result = yes_no(settings["text"]["ready?"])
    if result:
        state = "PLAYER_TURN" if random.randrange(2) == 0 else "COMPUTER_TURN"
    else:
        state = "STOP"
    return


def player_wins():
    global state
    print(settings["text"]["won"])
    state = "END"
    return


def player_loses():
    global state
    print(settings["text"]["lost"])
    state = "END"
    return


def end():
    global state
    print("")
    again = yes_no(settings["text"]["again?"])
    if again:
        state = "START"
    else:
        state = "STOP"
    return


def stop():
    print(settings["text"]["goodbye"])
    sys.exit()


# Main game loop
state = "START"

while True:
    if state == "START":
        start()
    elif state == "PLAYER_TURN":
        player_turn()
    elif state == "COMPUTER_TURN":
        computer_turn()
    elif state == "PLAYER_WINS":  # Synonymous with "COMPUTER_LOSES"
        player_wins()
    elif state == "PLAYER_LOSES":  # Synonymous with "COMPUTER_WINS"
        player_loses()
    elif state == "END":
        end()
    elif state == "STOP":
        stop()
\$\endgroup\$

Your Answer

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

Browse other questions tagged or ask your own question.