Bug (Python): Refactoring

- Rename files to follow PEP8 convention / make them testable
- Add type annotations
- Fix Flake8 issues
- Variable naming
- Add unit test
This commit is contained in:
Martin Thoma
2022-03-24 11:34:19 +01:00
parent e8ba8bdf24
commit 8d5e3d1d0f
4 changed files with 326 additions and 363 deletions

View File

@@ -1,307 +0,0 @@
import random
import time
def print_n_whitespaces(n: int) -> None:
print(" " * n, end="")
def print_n_newlines(n: int) -> None:
for _ in range(n):
print()
def print_feelers(n_feelers, is_player: bool = True) -> None:
for _ in range(4):
print_n_whitespaces(10)
for _ in range(n_feelers):
print("A " if is_player else "F ", end="")
print()
def print_head() -> None:
print(" HHHHHHH")
print(" H H")
print(" H O O H")
print(" H H")
print(" H V H")
print(" HHHHHHH")
def print_neck() -> None:
print(" N N")
print(" N N")
def print_body(has_tail: bool = False) -> None:
print(" BBBBBBBBBBBB")
print(" B B")
print(" B B")
print("TTTTTB B") if has_tail else ""
print(" BBBBBBBBBBBB")
def print_legs(n_legs: int) -> None:
for _ in range(2):
print_n_whitespaces(5)
for _ in range(n_legs):
print(" L", end="")
print()
def main() -> None:
print_n_whitespaces(34)
print("BUG")
print_n_whitespaces(15)
print("CREATIVE COMPUTING MORRISTOWN, NEW JERSEY")
print_n_newlines(3)
print("THE GAME BUG")
print("I HOPE YOU ENJOY THIS GAME.")
print()
want_instructions = input("DO YOU WANT INSTRUCTIONS? ")
if want_instructions != "NO":
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:")
table = [
["NUMBER", "PART", "NUMBER OF PART NEEDED"],
["1", "BODY", "1"],
["2", "NECK", "1"],
["3", "HEAD", "1"],
["4", "FEELERS", "2"],
["5", "TAIL", "1"],
["6", "LEGS", "6"],
]
for row in table:
print(f"{row[0]:<16}{row[1]:<16}{row[2]:<20}")
print_n_newlines(2)
A = 0
B = 0
H = 0
L = 0
N = 0
P = 0
Q = 0
R = 0 # NECK
S = 0 # FEELERS
T = 0
U = 0
V = 0
Y = 0
while Y <= 0:
Z = random.randint(1, 6)
print()
C = 1
print("YOU ROLLED A", Z)
if Z == 1:
print("1=BODY")
if B == 1:
print("YOU DO NOT NEED A BODY.")
# goto 970
else:
print("YOU NOW HAVE A BODY.")
B = 1
C = 0
# goto 970
elif Z == 2:
print("2=NECK")
if N == 1:
print("YOU DO NOT NEED A NECK.")
# goto 970
elif B == 0:
print("YOU DO NOT HAVE A BODY.")
# goto 970
else:
print("YOU NOW HAVE A NECK.")
N = 1
C = 0
# goto 970
elif Z == 3:
print("3=HEAD")
if N == 0:
print("YOU DO NOT HAVE A NECK.")
# goto 970
elif H == 1:
print("YOU HAVE A HEAD.")
# goto 970
else:
print("YOU NEEDED A HEAD.")
H = 1
C = 0
# goto 970
elif Z == 4:
print("4=FEELERS")
if H == 0:
print("YOU DO NOT HAVE A HEAD.")
# goto 970
elif A == 2:
print("YOU HAVE TWO FEELERS ALREADY.")
# goto 970
else:
print("I NOW GIVE YOU A FEELER.")
A = A + 1
C = 0
# goto 970
elif Z == 5:
print("5=TAIL")
if B == 0:
print("YOU DO NOT HAVE A BODY.")
# goto 970
elif T == 1:
print("YOU ALREADY HAVE A TAIL.")
# goto 970
else:
print("I NOW GIVE YOU A TAIL.")
T = T + 1
C = 0
# goto 970
elif Z == 6:
print("6=LEG")
if L == 6:
print("YOU HAVE 6 FEET ALREADY.")
# goto 970
elif B == 0:
print("YOU DO NOT HAVE A BODY.")
# goto 970
else:
L = L + 1
C = 0
print(f"YOU NOW HAVE {L} LEGS")
# goto 970
# 970
X = random.randint(1, 6)
print()
time.sleep(2)
print("I ROLLED A", X)
if X == 1:
print("1=BODY")
if P == 1:
print("I DO NOT NEED A BODY.")
# goto 1630
else:
print("I NOW HAVE A BODY.")
C = 0
P = 1
# goto 1630
elif X == 2:
print("2=NECK")
if Q == 1:
print("I DO NOT NEED A NECK.")
# goto 1630
elif P == 0:
print("I DO NOT HAVE A BODY.")
# goto 1630
else:
print("I NOW HAVE A NECK.")
Q = 1
C = 0
# goto 1630
elif X == 3:
print("3=HEAD")
if Q == 0:
print("I DO NOT HAVE A NECK.")
# goto 1630
elif R == 1:
print("I HAVE A HEAD.")
# goto 1630
else:
print("I NEEDED A HEAD.")
R = 1
C = 0
# goto 1630
elif X == 4:
print("4=FEELERS")
if R == 0:
print("I DO NOT HAVE A HEAD.")
# goto 1630
elif S == 2:
print("I HAVE TWO FEELERS ALREADY.")
# goto 1630
else:
print("I GET A FEELER.")
S = S + 1
C = 0
# goto 1630
elif X == 5:
print("5=TAIL")
if P == 0:
print("I DO NOT HAVE A BODY.")
# goto 1630
elif U == 1:
print("I ALREADY HAVE A TAIL.")
# goto 1630
else:
print("I NOW HAVE A TAIL.")
U = 1
C = 0
# goto 1630
elif X == 6:
print("6=LEG")
if V == 6:
print("I HAVE 6 FEET.")
# goto 1630
elif P == 0:
print("I DO NOT HAVE A BODY.")
# goto 1630
else:
V = V + 1
C = 0
print(f"I NOW HAVE {V} LEGS")
# goto 1630
# 1630
if (A == 2) and (T == 1) and (L == 6):
print("YOUR BUG IS FINISHED.")
Y = Y + 1
if (S == 2) and (P == 1) and (V == 6):
print("MY BUG IS FINISHED.")
Y = Y + 2
if C == 1:
continue
want_pictures = input("DO YOU WANT THE PICTURES? ")
if want_pictures != "NO":
print("*****YOUR BUG*****")
print_n_newlines(2)
if A != 0:
print_feelers(A, is_player=True)
if H != 0:
print_head()
if N != 0:
print_neck()
if B != 0:
print_body(True) if T == 1 else print_body(False)
if L != 0:
print_legs(L)
print_n_newlines(4)
print("*****MY BUG*****")
print_n_newlines(3)
if S != 0:
print_feelers(S, is_player=False)
if R == 1:
print_head()
if Q != 0:
print_neck()
if P != 0:
print_body(True) if U == 1 else print_body(False)
if V != 0:
print_legs(V)
if Y != 0:
break
print("I HOPE YOU ENJOYED THE GAME, PLAY IT AGAIN SOON!!")
if __name__ == "__main__":
main()

234
16_Bug/python/bug.py Normal file
View File

@@ -0,0 +1,234 @@
import random
import time
from dataclasses import dataclass
from typing import Literal
@dataclass
class State:
is_player: bool
body: int = 0
neck: int = 0
head: int = 0
feelers: int = 0
tail: int = 0
legs: int = 0
def is_finished(self) -> bool:
return (
self.feelers == 2
and self.tail == 1
and self.legs == 6
and self.head == 1
and self.neck == 1
)
def display(self) -> None:
if self.feelers != 0:
print_feelers(self.feelers, is_player=self.is_player)
if self.head != 0:
print_head()
if self.neck != 0:
print_neck()
if self.body != 0:
print_body(True) if self.tail == 1 else print_body(False)
if self.legs != 0:
print_legs(self.legs)
def print_n_whitespaces(n: int) -> None:
print(" " * n, end="")
def print_n_newlines(n: int) -> None:
for _ in range(n):
print()
def print_feelers(n_feelers: int, is_player: bool = True) -> None:
for _ in range(4):
print_n_whitespaces(10)
for _ in range(n_feelers):
print("A " if is_player else "F ", end="")
print()
def print_head() -> None:
print(" HHHHHHH")
print(" H H")
print(" H O O H")
print(" H H")
print(" H V H")
print(" HHHHHHH")
def print_neck() -> None:
print(" N N")
print(" N N")
def print_body(has_tail: bool = False) -> None:
print(" BBBBBBBBBBBB")
print(" B B")
print(" B B")
print("TTTTTB B") if has_tail else ""
print(" BBBBBBBBBBBB")
def print_legs(n_legs: int) -> None:
for _ in range(2):
print_n_whitespaces(5)
for _ in range(n_legs):
print(" L", end="")
print()
def handle_roll(diceroll: Literal[1, 2, 3, 4, 5, 6], state: State) -> bool:
who = "YOU" if state.is_player else "I"
changed = False
print(f"{who} ROLLED A", diceroll)
if diceroll == 1:
print("1=BODY")
if state.body:
print(f"{who} DO NOT NEED A BODY.")
else:
print(f"{who} NOW HAVE A BODY.")
state.body = 1
changed = True
elif diceroll == 2:
print("2=NECK")
if state.neck:
print(f"{who} DO NOT NEED A NECK.")
elif state.body == 0:
print(f"{who} DO NOT HAVE A BODY.")
else:
print(f"{who} NOW HAVE A NECK.")
state.neck = 1
changed = True
elif diceroll == 3:
print("3=HEAD")
if state.neck == 0:
print(f"{who} DO NOT HAVE A NECK.")
elif state.head:
print(f"{who} HAVE A HEAD.")
else:
print(f"{who} NEEDED A HEAD.")
state.head = 1
changed = True
elif diceroll == 4:
print("4=FEELERS")
if state.head == 0:
print(f"{who} DO NOT HAVE A HEAD.")
elif state.feelers == 2:
print(f"{who} HAVE TWO FEELERS ALREADY.")
else:
if state.is_player:
print("I NOW GIVE YOU A FEELER.")
else:
print(f"{who} GET A FEELER.")
state.feelers += 1
changed = True
elif diceroll == 5:
print("5=TAIL")
if state.body == 0:
print(f"{who} DO NOT HAVE A BODY.")
elif state.tail:
print(f"{who} ALREADY HAVE A TAIL.")
else:
if state.is_player:
print("I NOW GIVE YOU A TAIL.")
else:
print(f"{who} NOW HAVE A TAIL.")
state.tail = 1
changed = True
elif diceroll == 6:
print("6=LEG")
if state.legs == 6:
print(f"{who} HAVE 6 FEET ALREADY.")
elif state.body == 0:
print(f"{who} DO NOT HAVE A BODY.")
else:
state.legs += 1
changed = True
print(f"{who} NOW HAVE {state.legs} LEGS")
return changed
def main() -> None:
print_n_whitespaces(34)
print("BUG")
print_n_whitespaces(15)
print("CREATIVE COMPUTING MORRISTOWN, NEW JERSEY")
print_n_newlines(3)
print("THE GAME BUG")
print("I HOPE YOU ENJOY THIS GAME.")
print()
want_instructions = input("DO YOU WANT INSTRUCTIONS? ")
if want_instructions != "NO":
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:")
table = [
["NUMBER", "PART", "NUMBER OF PART NEEDED"],
["1", "BODY", "1"],
["2", "NECK", "1"],
["3", "HEAD", "1"],
["4", "FEELERS", "2"],
["5", "TAIL", "1"],
["6", "LEGS", "6"],
]
for row in table:
print(f"{row[0]:<16}{row[1]:<16}{row[2]:<20}")
print_n_newlines(2)
player = State(is_player=True)
opponent = State(is_player=False)
bugs_finished = 0
while bugs_finished <= 0:
diceroll = random.randint(1, 6)
print()
changed = handle_roll(diceroll, player) # type: ignore
diceroll = random.randint(1, 6)
print()
time.sleep(2)
changed_op = handle_roll(diceroll, opponent) # type: ignore
changed = changed or changed_op
if player.is_finished():
print("YOUR BUG IS FINISHED.")
bugs_finished += 1
if opponent.is_finished():
print("MY BUG IS FINISHED.")
bugs_finished += 1
if not changed:
continue
want_pictures = input("DO YOU WANT THE PICTURES? ")
if want_pictures != "NO":
print("*****YOUR BUG*****")
print_n_newlines(2)
player.display()
print_n_newlines(4)
print("*****MY BUG*****")
print_n_newlines(3)
opponent.display()
if bugs_finished != 0:
break
print("I HOPE YOU ENJOYED THE GAME, PLAY IT AGAIN SOON!!")
if __name__ == "__main__":
main()

View File

@@ -9,12 +9,38 @@ Ported by Peter Sharp
from collections import namedtuple from collections import namedtuple
from random import randint from random import randint
from typing import Any, Dict from typing import Any, Callable, Dict, List, Literal, Optional, Tuple, TypedDict, Union
PAGE_WIDTH = 64 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"]
def main(states, data) -> None: 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 Starts the game loop using given states and data
@@ -35,10 +61,7 @@ def main(states, data) -> None:
data = model(data, action) data = model(data, action)
Bodypart = namedtuple("Bodypart", ["name", "count", "depends"]) def print_start(_: Any) -> str:
def print_start(_) -> str:
""" """
Prints start message Prints start message
""" """
@@ -53,7 +76,7 @@ def print_start(_) -> str:
return input("DO YOU WANT INSTRUCTIONS? ") return input("DO YOU WANT INSTRUCTIONS? ")
def control_start(cmd): def control_start(cmd: str) -> str:
""" """
Controls the start state Controls the start state
""" """
@@ -64,7 +87,7 @@ def control_start(cmd):
return action return action
def print_instructions(data) -> str: def print_instructions(data: DataDict) -> str:
""" """
Prints game instructions Prints game instructions
""" """
@@ -92,26 +115,25 @@ def print_instructions(data) -> str:
return "" return ""
def goto_game(_): def goto_game(_: Any) -> Literal["game"]:
"""
Returns game
"""
return "game" return "game"
def update_state(data, action): def update_state(data: DataDict, action: Action) -> DataDict:
""" """
sets game state to given player value sets game state to given player value
""" """
return {**data, "state": action} return {**data, "state": action} # type: ignore
def update_game(data, action): def update_game(data: DataDict, action: Action) -> DataDict:
""" """
Updates game data for player turns until one player successfully gets a body part Updates game data for player turns until one player successfully gets a body part
""" """
# stores logs of what happened during a particular round # stores logs of what happened during a particular round
logs = [] Log1 = Tuple[str, int, Any]
Log2 = Tuple[str, int, Any, Any]
logs: List[Union[Log1, Log2]] = []
if action == "pictures": if action == "pictures":
data["state"] = "pictures" data["state"] = "pictures"
@@ -133,16 +155,16 @@ def update_game(data, action):
# a new part can only be added if the player has the parts # 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 # the new part depends on and doesn't have enough of the part already
overMaxParts = part_type.count < part_count + 1 overMaxParts = part_type.count < part_count + 1
missingPartDep = ( missing_part_dep = (
part_type.depends is not None and parts[part_type.depends] == 0 part_type.depends is not None and parts[part_type.depends] == 0
) )
if not overMaxParts and not missingPartDep: if not overMaxParts and not missing_part_dep:
# adds a new part # adds a new part
part_count += 1 part_count += 1
logs.append(("added", new_part_idx, player)) logs.append(("added", new_part_idx, player))
part_added = True part_added = True
elif missingPartDep: elif missing_part_dep:
logs.append(("missingDep", new_part_idx, player, part_type.depends)) logs.append(("missingDep", new_part_idx, player, part_type.depends))
if overMaxParts: if overMaxParts:
logs.append(("overMax", new_part_idx, player, part_count)) logs.append(("overMax", new_part_idx, player, part_count))
@@ -159,7 +181,7 @@ def update_game(data, action):
return data return data
def get_finished(data): def get_finished(data: DataDict) -> List[str]:
""" """
Gets players who have finished their bugs Gets players who have finished their bugs
""" """
@@ -171,7 +193,7 @@ def get_finished(data):
return finished return finished
def print_game(data) -> str: def print_game(data: DataDict) -> str:
""" """
Displays the results of the game turn Displays the results of the game turn
""" """
@@ -222,7 +244,7 @@ def print_game(data) -> str:
return input("DO YOU WANT THE PICTURES? ") if len(data["logs"]) else "n" return input("DO YOU WANT THE PICTURES? ") if len(data["logs"]) else "n"
def print_pictures(data) -> None: def print_pictures(data: DataDict) -> None:
""" """
Displays what the bugs look like for each player Displays what the bugs look like for each player
""" """
@@ -261,7 +283,7 @@ def print_pictures(data) -> None:
print() print()
def control_game(cmd): def control_game(cmd: str) -> Literal["pictures", "game"]:
""" """
returns state based on command returns state based on command
""" """
@@ -269,10 +291,10 @@ def control_game(cmd):
action = "pictures" action = "pictures"
else: else:
action = "game" action = "game"
return action return action # type: ignore
def print_winner(data) -> None: def print_winner(data: DataDict) -> None:
""" """
Displays the winning message Displays the winning message
""" """
@@ -281,30 +303,25 @@ def print_winner(data) -> None:
print("I HOPE YOU ENJOYED THE GAME, PLAY IT AGAIN SOON!!") print("I HOPE YOU ENJOYED THE GAME, PLAY IT AGAIN SOON!!")
def exit_game(_): def exit_game(_: Any) -> Literal["exit"]:
""" """Exist the game regardless of input"""
Exists the game regardless of input
"""
return "exit" return "exit"
def print_centered(msg, width=PAGE_WIDTH) -> None: def print_centered(msg: str, width: int = PAGE_WIDTH) -> None:
""" """Print given message centered to given width."""
Prints given message centered to given width
"""
spaces = " " * ((width - len(msg)) // 2) spaces = " " * ((width - len(msg)) // 2)
print(spaces + msg) print(spaces + msg)
def print_table(rows) -> None: def print_table(rows: List[Any]) -> None:
for row in rows: for row in rows:
print(*row, sep="\t") print(*row, sep="\t")
if __name__ == "__main__": def main() -> None:
# The main states in the game # The main states in the game
states = { states: Dict[Action, StateFunctions] = {
# Initial state of the game # Initial state of the game
"start": (print_start, control_start, update_state), "start": (print_start, control_start, update_state),
# displays game instructions # displays game instructions
@@ -317,23 +334,17 @@ if __name__ == "__main__":
"won": (print_winner, exit_game, update_state), "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
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),
)
# all the data used by the game # all the data used by the game
data: Dict[str, Any] = { data = DataDict(
"state": "start", state="start",
"partNo": None, partNo=None,
"players": {"YOU": [0] * len(part_types), "I": [0] * len(part_types)}, players={"YOU": [0] * len(part_types), "I": [0] * len(part_types)},
"partTypes": part_types, partTypes=part_types,
"finished": [], finished=[],
"logs": [], logs=[],
} )
main(states, data) game_loop(states, data)
if __name__ == "__main__":
main()

25
16_Bug/python/test_bug.py Normal file
View File

@@ -0,0 +1,25 @@
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()