# 21 Game (Nim) as Finite State Machine in Python

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.""",
"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"
}
}

os.system('clear')  # Windows version. Use "cls" on Linux/Mac

def get_integer():
number = None
while number is None:
prompt = settings["text"]["number_prompt"]
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)))

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)

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():
global current_total
global state

current_total = 0

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

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