mirror of
https://github.com/coding-horror/basic-computer-games.git
synced 2025-12-22 07:10:42 -08:00
Remove tests and alternative Python implementations
Ad discussed here: https://github.com/coding-horror/basic-computer-games/issues/548#issuecomment-1081008471
This commit is contained in:
@@ -1,350 +0,0 @@
|
||||
"""
|
||||
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
|
||||
from typing import Any, Callable, Dict, List, Literal, Optional, Tuple, TypedDict, Union
|
||||
|
||||
PAGE_WIDTH = 64
|
||||
|
||||
OneParamFunc = Callable[[Any], Any]
|
||||
TwoParamFunc = Callable[[Any, Any], Any]
|
||||
StateFunctions = Tuple[OneParamFunc, OneParamFunc, TwoParamFunc]
|
||||
Action = Literal["instructions", "game", "pictures", "won", "start", "exit"]
|
||||
|
||||
Bodypart = namedtuple("Bodypart", ["name", "count", "depends"])
|
||||
|
||||
# body part types used by the game to work out whether a player's body part can be added
|
||||
part_types = (
|
||||
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),
|
||||
)
|
||||
|
||||
|
||||
class DataDict(TypedDict):
|
||||
state: Action
|
||||
partNo: Optional[Any]
|
||||
players: Dict[str, List[int]]
|
||||
partTypes: Tuple[Bodypart, ...]
|
||||
finished: List[Any]
|
||||
logs: List[Any]
|
||||
|
||||
|
||||
def game_loop(states: Dict[Action, StateFunctions], data: DataDict) -> None:
|
||||
"""
|
||||
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 data["state"] == "exit":
|
||||
break
|
||||
view, control, model = states[data["state"]]
|
||||
cmd = view(data)
|
||||
action = control(cmd)
|
||||
data = model(data, action)
|
||||
|
||||
|
||||
def print_start(_: Any) -> str:
|
||||
"""
|
||||
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: str) -> str:
|
||||
"""
|
||||
Controls the start state
|
||||
"""
|
||||
if cmd.lower() in ("y", "yes"):
|
||||
action = "instructions"
|
||||
else:
|
||||
action = "game"
|
||||
return action
|
||||
|
||||
|
||||
def print_instructions(data: DataDict) -> str:
|
||||
"""
|
||||
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(_: Any) -> Literal["game"]:
|
||||
return "game"
|
||||
|
||||
|
||||
def update_state(data: DataDict, action: Action) -> DataDict:
|
||||
"""
|
||||
sets game state to given player value
|
||||
"""
|
||||
return {**data, "state": action} # type: ignore
|
||||
|
||||
|
||||
def update_game(data: DataDict, action: Action) -> DataDict:
|
||||
"""
|
||||
Updates game data for player turns until one player successfully gets a body part
|
||||
"""
|
||||
# stores logs of what happened during a particular round
|
||||
Log1 = Tuple[str, int, Any]
|
||||
Log2 = Tuple[str, int, Any, Any]
|
||||
logs: List[Union[Log1, Log2]] = []
|
||||
|
||||
if action == "pictures":
|
||||
data["state"] = "pictures"
|
||||
else:
|
||||
part_added = False
|
||||
while not part_added:
|
||||
for player, parts in data["players"].items():
|
||||
# rolls the dice for a part
|
||||
new_part_idx = randint(1, 6) - 1
|
||||
|
||||
# gets information about the picked part
|
||||
part_type = data["partTypes"][new_part_idx]
|
||||
|
||||
# gets the number of existing parts of that type the player has
|
||||
part_count = parts[new_part_idx]
|
||||
|
||||
logs.append(("rolled", new_part_idx, 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 = part_type.count < part_count + 1
|
||||
missing_part_dep = (
|
||||
part_type.depends is not None and parts[part_type.depends] == 0
|
||||
)
|
||||
|
||||
if not overMaxParts and not missing_part_dep:
|
||||
# adds a new part
|
||||
part_count += 1
|
||||
logs.append(("added", new_part_idx, player))
|
||||
part_added = True
|
||||
elif missing_part_dep:
|
||||
logs.append(("missingDep", new_part_idx, player, part_type.depends))
|
||||
if overMaxParts:
|
||||
logs.append(("overMax", new_part_idx, player, part_count))
|
||||
|
||||
data["players"][player][new_part_idx] = part_count
|
||||
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: DataDict) -> List[str]:
|
||||
"""
|
||||
Gets players who have finished their bugs
|
||||
"""
|
||||
total_parts = sum(part_type.count for part_type in data["partTypes"])
|
||||
finished = []
|
||||
for player, parts in data["players"].items():
|
||||
if sum(parts) == total_parts:
|
||||
finished.append(player)
|
||||
return finished
|
||||
|
||||
|
||||
def print_game(data: DataDict) -> str:
|
||||
"""
|
||||
Displays the results of the game turn
|
||||
"""
|
||||
for log in data["logs"]:
|
||||
code, part_idx, player, *logdata = log
|
||||
part_type = data["partTypes"][part_idx]
|
||||
|
||||
if code == "rolled":
|
||||
print()
|
||||
print(f"{player} ROLLED A {part_idx + 1}")
|
||||
print(f"{part_idx + 1}={part_type.name}")
|
||||
|
||||
elif code == "added":
|
||||
if player == "YOU":
|
||||
if part_type.name in ["FEELERS", "LEGS", "TAIL"]:
|
||||
print(f"I NOW GIVE YOU A {part_type.name.replace('s', '')}.")
|
||||
else:
|
||||
print(f"YOU NOW HAVE A {part_type.name}.")
|
||||
elif player == "I":
|
||||
if part_type.name in ["BODY", "NECK", "TAIL"]:
|
||||
print(f"I NOW HAVE A {part_type.name}.")
|
||||
elif part_type.name == "FEELERS":
|
||||
print("I GET A FEELER.")
|
||||
|
||||
if part_type.count > 2:
|
||||
print(
|
||||
f"{player} NOW HAVE {data['players'][player][part_idx]} {part_type.name}"
|
||||
)
|
||||
|
||||
elif code == "missingDep":
|
||||
(dep_idx,) = logdata
|
||||
dep = data["partTypes"][dep_idx]
|
||||
print(
|
||||
f"YOU DO NOT HAVE A {dep.name}"
|
||||
if player == "YOU"
|
||||
else f"I NEEDED A {dep.name}"
|
||||
)
|
||||
|
||||
elif code == "overMax":
|
||||
(part_count,) = logdata
|
||||
if part_count > 1:
|
||||
num = "TWO" if part_count == 2 else part_count
|
||||
maxMsg = f"HAVE {num} {part_type.name}S ALREADY"
|
||||
else:
|
||||
maxMsg = f"ALREADY HAVE A {part_type.name}"
|
||||
print(f"{player} {maxMsg}")
|
||||
|
||||
return input("DO YOU WANT THE PICTURES? ") if len(data["logs"]) else "n"
|
||||
|
||||
|
||||
def print_pictures(data: DataDict) -> None:
|
||||
"""
|
||||
Displays what the bugs look like for each player
|
||||
"""
|
||||
typeIxs = {part_type.name: idx for idx, part_type in enumerate(data["partTypes"])}
|
||||
PIC_WIDTH = 22
|
||||
for player, parts in data["players"].items():
|
||||
print(f"*****{'YOUR' if player == 'YOU' 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: str) -> Literal["pictures", "game"]:
|
||||
"""
|
||||
returns state based on command
|
||||
"""
|
||||
if cmd.lower() in ("y", "yes"):
|
||||
action = "pictures"
|
||||
else:
|
||||
action = "game"
|
||||
return action # type: ignore
|
||||
|
||||
|
||||
def print_winner(data: DataDict) -> None:
|
||||
"""
|
||||
Displays the winning message
|
||||
"""
|
||||
for player in data["finished"]:
|
||||
print(f"{'YOUR' if player == 'YOU' else 'MY'} BUG IS FINISHED.")
|
||||
print("I HOPE YOU ENJOYED THE GAME, PLAY IT AGAIN SOON!!")
|
||||
|
||||
|
||||
def exit_game(_: Any) -> Literal["exit"]:
|
||||
"""Exist the game regardless of input"""
|
||||
return "exit"
|
||||
|
||||
|
||||
def print_centered(msg: str, width: int = PAGE_WIDTH) -> None:
|
||||
"""Print given message centered to given width."""
|
||||
spaces = " " * ((width - len(msg)) // 2)
|
||||
print(spaces + msg)
|
||||
|
||||
|
||||
def print_table(rows: List[Any]) -> None:
|
||||
for row in rows:
|
||||
print(*row, sep="\t")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
# The main states in the game
|
||||
states: Dict[Action, StateFunctions] = {
|
||||
# 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),
|
||||
}
|
||||
|
||||
# all the data used by the game
|
||||
data = DataDict(
|
||||
state="start",
|
||||
partNo=None,
|
||||
players={"YOU": [0] * len(part_types), "I": [0] * len(part_types)},
|
||||
partTypes=part_types,
|
||||
finished=[],
|
||||
logs=[],
|
||||
)
|
||||
game_loop(states, data)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,25 +0,0 @@
|
||||
import io
|
||||
from typing import Callable
|
||||
|
||||
import pytest
|
||||
from _pytest.monkeypatch import MonkeyPatch
|
||||
|
||||
from bug import main
|
||||
from bug_overengineered import main as overengineered_main
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"main",
|
||||
[main, overengineered_main],
|
||||
)
|
||||
def test_main(monkeypatch: MonkeyPatch, main: Callable[[], None]) -> None:
|
||||
monkeypatch.setattr("time.sleep", lambda n: n)
|
||||
instructions = "Y"
|
||||
pictures = "Y"
|
||||
monkeypatch.setattr(
|
||||
"sys.stdin",
|
||||
io.StringIO(
|
||||
f"{instructions}\n{pictures}\nN\nN\nN\nN\nN\nN\nN\nN\nN\nN\nN\nN\nN\nN\nN\nN\nN\nN\nN\nN\nN\nN\n"
|
||||
),
|
||||
)
|
||||
main()
|
||||
Reference in New Issue
Block a user