mirror of
https://github.com/coding-horror/basic-computer-games.git
synced 2025-12-21 23:00:43 -08:00
339 lines
10 KiB
Python
339 lines
10 KiB
Python
"""
|
|
BUG (overengineered)
|
|
|
|
Overengineered version of bug game
|
|
Demonstrates function-based Model View Controller pattern
|
|
|
|
Ported by Peter Sharp
|
|
"""
|
|
|
|
from collections import namedtuple
|
|
from random import randint
|
|
|
|
PAGE_WIDTH = 64
|
|
|
|
|
|
def main(states, data):
|
|
"""
|
|
Starts the game loop using given states and data
|
|
|
|
Uses a modified version of the MVC (Model View Controller) pattern that uses functions instead of objects
|
|
|
|
each state in the game has one of each of the following:
|
|
View, displays data
|
|
Control, converts raw command from user into something the model understands
|
|
Model, updates game data based on action received from controller
|
|
"""
|
|
|
|
while True:
|
|
if "exit" == data["state"]:
|
|
break
|
|
view, control, model = states[data["state"]]
|
|
cmd = view(data)
|
|
action = control(cmd)
|
|
data = model(data, action)
|
|
|
|
|
|
Bodypart = namedtuple("Bodypart", ["name", "count", "depends"])
|
|
|
|
|
|
def print_start(_):
|
|
"""
|
|
Prints start message
|
|
"""
|
|
print_centered("BUG")
|
|
print_centered("CREATIVE COMPUTING MORRISTOWN, NEW JERSEY")
|
|
print()
|
|
print()
|
|
print()
|
|
print("THE GAME BUG")
|
|
print("I HOPE YOU ENJOY THIS GAME.")
|
|
print()
|
|
return input("DO YOU WANT INSTRUCTIONS? ")
|
|
|
|
|
|
def control_start(cmd):
|
|
"""
|
|
Controls the start state
|
|
"""
|
|
if cmd.lower() in ("y", "yes"):
|
|
action = "instructions"
|
|
else:
|
|
action = "game"
|
|
return action
|
|
|
|
|
|
def print_instructions(data):
|
|
"""
|
|
Prints game instructions
|
|
"""
|
|
print("THE OBJECT OF BUG IS TO FINISH YOUR BUG BEFORE I FINISH")
|
|
print("MINE. EACH NUMBER STANDS FOR A PART OF THE BUG BODY.")
|
|
print("I WILL ROLL THE DIE FOR YOU, TELL YOU WHAT I ROLLED FOR YOU")
|
|
print("WHAT THE NUMBER STANDS FOR, AND IF YOU CAN GET THE PART.")
|
|
print("IF YOU CAN GET THE PART I WILL GIVE IT TO YOU.")
|
|
print("THE SAME WILL HAPPEN ON MY TURN.")
|
|
print("IF THERE IS A CHANGE IN EITHER BUG I WILL GIVE YOU THE")
|
|
print("OPTION OF SEEING THE PICTURES OF THE BUGS.")
|
|
print("THE NUMBERS STAND FOR PARTS AS FOLLOWS:")
|
|
|
|
print_table(
|
|
[
|
|
("NUMBER", "PART", "NUMBER OF PART NEEDED"),
|
|
*[
|
|
(i + 1, part.name, part.count)
|
|
for i, part in enumerate(data["partTypes"])
|
|
],
|
|
]
|
|
)
|
|
print()
|
|
print()
|
|
return ""
|
|
|
|
|
|
def goto_game(_):
|
|
"""
|
|
Returns game
|
|
"""
|
|
return "game"
|
|
|
|
|
|
def update_state(data, action):
|
|
"""
|
|
sets game state to given player value
|
|
"""
|
|
return {**data, "state": action}
|
|
|
|
|
|
def update_game(data, action):
|
|
"""
|
|
Updates game data for player turns until one player successfully gets a body part
|
|
"""
|
|
# stores logs of what happened during a particular round
|
|
logs = []
|
|
|
|
if "pictures" == action:
|
|
data["state"] = "pictures"
|
|
else:
|
|
partAdded = False
|
|
while partAdded == False:
|
|
for player, parts in data["players"].items():
|
|
# rolls the dice for a part
|
|
newPartIdx = randint(1, 6) - 1
|
|
|
|
# gets information about the picked part
|
|
partType = data["partTypes"][newPartIdx]
|
|
|
|
# gets the number of existing parts of that type the player has
|
|
partCount = parts[newPartIdx]
|
|
|
|
logs.append(("rolled", newPartIdx, player))
|
|
|
|
# a new part can only be added if the player has the parts
|
|
# the new part depends on and doesn't have enough of the part already
|
|
overMaxParts = partType.count < partCount + 1
|
|
missingPartDep = (
|
|
partType.depends != None and parts[partType.depends] == 0
|
|
)
|
|
|
|
if not overMaxParts and not missingPartDep:
|
|
# adds a new part
|
|
partCount += 1
|
|
logs.append(("added", newPartIdx, player))
|
|
partAdded = True
|
|
elif missingPartDep:
|
|
logs.append(("missingDep", newPartIdx, player, partType.depends))
|
|
if overMaxParts:
|
|
logs.append(("overMax", newPartIdx, player, partCount))
|
|
|
|
data["players"][player][newPartIdx] = partCount
|
|
data["logs"] = logs
|
|
|
|
# checks if any players have finished their bug
|
|
finished = get_finished(data)
|
|
if len(finished) > 0:
|
|
# and sets the state to 'won' if that's the case
|
|
data["finished"] = finished
|
|
data["state"] = "won"
|
|
return data
|
|
|
|
|
|
def get_finished(data):
|
|
"""
|
|
Gets players who have finished their bugs
|
|
"""
|
|
totalParts = sum(partType.count for partType in data["partTypes"])
|
|
finished = []
|
|
for player, parts in data["players"].items():
|
|
if sum(parts) == totalParts:
|
|
finished.append(player)
|
|
return finished
|
|
|
|
|
|
def print_game(data):
|
|
"""
|
|
Displays the results of the game turn
|
|
"""
|
|
for log in data["logs"]:
|
|
code, partIdx, player, *logdata = log
|
|
partType = data["partTypes"][partIdx]
|
|
|
|
if "rolled" == code:
|
|
print()
|
|
print(f"{player} ROLLED A {partIdx + 1}")
|
|
print(f"{partIdx + 1}={partType.name}")
|
|
|
|
elif "added" == code:
|
|
if "YOU" == player:
|
|
if partType.name in ["FEELERS", "LEGS", "TAIL"]:
|
|
print(f"I NOW GIVE YOU A {partType.name.replace('s', '')}.")
|
|
else:
|
|
print(f"YOU NOW HAVE A {partType.name}.")
|
|
elif "I" == player:
|
|
if partType.name in ["BODY", "NECK", "TAIL"]:
|
|
print(f"I NOW HAVE A {partType.name}.")
|
|
elif partType.name == "FEELERS":
|
|
print("I GET A FEELER.")
|
|
|
|
if partType.count > 2:
|
|
print(
|
|
f"{player} NOW HAVE {data['players'][player][partIdx]} {partType.name}"
|
|
)
|
|
|
|
elif "missingDep" == code:
|
|
(depIdx,) = logdata
|
|
dep = data["partTypes"][depIdx]
|
|
print(
|
|
f"YOU DO NOT HAVE A {dep.name}"
|
|
if "YOU" == player
|
|
else f"I NEEDED A {dep.name}"
|
|
)
|
|
|
|
elif "overMax" == code:
|
|
(partCount,) = logdata
|
|
if partCount > 1:
|
|
num = "TWO" if 2 == partCount else partCount
|
|
maxMsg = f"HAVE {num} {partType.name}S ALREADY"
|
|
else:
|
|
maxMsg = f"ALREADY HAVE A {partType.name}"
|
|
print(f"{player} {maxMsg}")
|
|
|
|
return input("DO YOU WANT THE PICTURES? ") if len(data["logs"]) else "n"
|
|
|
|
|
|
def print_pictures(data):
|
|
"""
|
|
Displays what the bugs look like for each player
|
|
"""
|
|
typeIxs = {partType.name: idx for idx, partType in enumerate(data["partTypes"])}
|
|
PIC_WIDTH = 22
|
|
for player, parts in data["players"].items():
|
|
print(f"*****{'YOUR' if 'YOU' == player else 'MY'} BUG*****")
|
|
print()
|
|
print()
|
|
if parts[typeIxs["BODY"]] > 0:
|
|
if parts[typeIxs["FEELERS"]] > 0:
|
|
F = " ".join(["F"] * parts[typeIxs["FEELERS"]])
|
|
for _ in range(4):
|
|
print(" " * 9 + F)
|
|
if parts[typeIxs["HEAD"]] > 0:
|
|
print_centered("HHHHHHH", PIC_WIDTH)
|
|
print_centered("H H", PIC_WIDTH)
|
|
print_centered("H O O H", PIC_WIDTH)
|
|
print_centered("H H", PIC_WIDTH)
|
|
print_centered("H V H", PIC_WIDTH)
|
|
print_centered("HHHHHHH", PIC_WIDTH)
|
|
if parts[typeIxs["NECK"]] > 0:
|
|
for _ in range(2):
|
|
print_centered("N N", PIC_WIDTH)
|
|
print_centered("BBBBBBBBBBBB", PIC_WIDTH)
|
|
for _ in range(2):
|
|
print_centered("B B", PIC_WIDTH)
|
|
|
|
if parts[typeIxs["TAIL"]] > 0:
|
|
print("TTTTTB B")
|
|
print_centered("BBBBBBBBBBBB", PIC_WIDTH)
|
|
if parts[typeIxs["LEGS"]] > 0:
|
|
L = "L" * parts[typeIxs["LEGS"]]
|
|
for _ in range(2):
|
|
print(" " * 5 + L)
|
|
print()
|
|
|
|
|
|
def control_game(cmd):
|
|
"""
|
|
returns state based on command
|
|
"""
|
|
if cmd.lower() in ("y", "yes"):
|
|
action = "pictures"
|
|
else:
|
|
action = "game"
|
|
return action
|
|
|
|
|
|
def print_winner(data):
|
|
"""
|
|
Displays the winning message
|
|
"""
|
|
for player in data["finished"]:
|
|
print(f"{'YOUR' if 'YOU' == player else 'MY'} BUG IS FINISHED.")
|
|
print("I HOPE YOU ENJOYED THE GAME, PLAY IT AGAIN SOON!!")
|
|
|
|
|
|
def exit_game(_):
|
|
"""
|
|
Exists the game regardless of input
|
|
"""
|
|
return "exit"
|
|
|
|
|
|
def print_centered(msg, width=PAGE_WIDTH):
|
|
"""
|
|
Prints given message centered to given width
|
|
"""
|
|
spaces = " " * ((width - len(msg)) // 2)
|
|
print(spaces + msg)
|
|
|
|
|
|
def print_table(rows):
|
|
for row in rows:
|
|
print(*row, sep="\t")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
# The main states in the game
|
|
states = {
|
|
# Initial state of the game
|
|
"start": (print_start, control_start, update_state),
|
|
# displays game instructions
|
|
"instructions": (print_instructions, goto_game, update_state),
|
|
# the main game state
|
|
"game": (print_game, control_game, update_game),
|
|
# displays pictures before returning to game
|
|
"pictures": (print_pictures, goto_game, update_state),
|
|
# Displays the winning players and message
|
|
"won": (print_winner, exit_game, update_state),
|
|
}
|
|
|
|
# body part types used by the game to work out whether a player's body part can be added
|
|
partTypes = (
|
|
Bodypart(name="BODY", count=1, depends=None),
|
|
Bodypart(name="NECK", count=1, depends=0),
|
|
Bodypart(name="HEAD", count=1, depends=1),
|
|
Bodypart(name="FEELERS", count=2, depends=2),
|
|
Bodypart(name="TAIL", count=1, depends=0),
|
|
Bodypart(name="LEGS", count=6, depends=0),
|
|
)
|
|
|
|
# all the data used by the game
|
|
data = {
|
|
"state": "start",
|
|
"partNo": None,
|
|
"players": {"YOU": [0] * len(partTypes), "I": [0] * len(partTypes)},
|
|
"partTypes": partTypes,
|
|
"finished": [],
|
|
"logs": [],
|
|
}
|
|
main(states, data)
|