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:
Martin Thoma
2022-03-29 06:56:06 +02:00
parent 6f9cb1bac2
commit 17a75813b1
32 changed files with 1 additions and 2352 deletions

View File

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

View File

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