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 random import randint
from typing import Any, Dict
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"]
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
@@ -35,10 +61,7 @@ def main(states, data) -> None:
data = model(data, action)
Bodypart = namedtuple("Bodypart", ["name", "count", "depends"])
def print_start(_) -> str:
def print_start(_: Any) -> str:
"""
Prints start message
"""
@@ -53,7 +76,7 @@ def print_start(_) -> str:
return input("DO YOU WANT INSTRUCTIONS? ")
def control_start(cmd):
def control_start(cmd: str) -> str:
"""
Controls the start state
"""
@@ -64,7 +87,7 @@ def control_start(cmd):
return action
def print_instructions(data) -> str:
def print_instructions(data: DataDict) -> str:
"""
Prints game instructions
"""
@@ -92,26 +115,25 @@ def print_instructions(data) -> str:
return ""
def goto_game(_):
"""
Returns game
"""
def goto_game(_: Any) -> Literal["game"]:
return "game"
def update_state(data, action):
def update_state(data: DataDict, action: Action) -> DataDict:
"""
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
"""
# 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":
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
# the new part depends on and doesn't have enough of the part already
overMaxParts = part_type.count < part_count + 1
missingPartDep = (
missing_part_dep = (
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
part_count += 1
logs.append(("added", new_part_idx, player))
part_added = True
elif missingPartDep:
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))
@@ -159,7 +181,7 @@ def update_game(data, action):
return data
def get_finished(data):
def get_finished(data: DataDict) -> List[str]:
"""
Gets players who have finished their bugs
"""
@@ -171,7 +193,7 @@ def get_finished(data):
return finished
def print_game(data) -> str:
def print_game(data: DataDict) -> str:
"""
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"
def print_pictures(data) -> None:
def print_pictures(data: DataDict) -> None:
"""
Displays what the bugs look like for each player
"""
@@ -261,7 +283,7 @@ def print_pictures(data) -> None:
print()
def control_game(cmd):
def control_game(cmd: str) -> Literal["pictures", "game"]:
"""
returns state based on command
"""
@@ -269,10 +291,10 @@ def control_game(cmd):
action = "pictures"
else:
action = "game"
return action
return action # type: ignore
def print_winner(data) -> None:
def print_winner(data: DataDict) -> None:
"""
Displays the winning message
"""
@@ -281,30 +303,25 @@ def print_winner(data) -> None:
print("I HOPE YOU ENJOYED THE GAME, PLAY IT AGAIN SOON!!")
def exit_game(_):
"""
Exists the game regardless of input
"""
def exit_game(_: Any) -> Literal["exit"]:
"""Exist the game regardless of input"""
return "exit"
def print_centered(msg, width=PAGE_WIDTH) -> None:
"""
Prints given message centered to given width
"""
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) -> None:
def print_table(rows: List[Any]) -> None:
for row in rows:
print(*row, sep="\t")
if __name__ == "__main__":
def main() -> None:
# The main states in the game
states = {
states: Dict[Action, StateFunctions] = {
# Initial state of the game
"start": (print_start, control_start, update_state),
# displays game instructions
@@ -317,23 +334,17 @@ if __name__ == "__main__":
"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
data: Dict[str, Any] = {
"state": "start",
"partNo": None,
"players": {"YOU": [0] * len(part_types), "I": [0] * len(part_types)},
"partTypes": part_types,
"finished": [],
"logs": [],
}
main(states, data)
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()

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