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()