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\$

0

Your Answer

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

Browse other questions tagged or ask your own question.