diff --git a/16_Bug/python/Bug.py b/16_Bug/python/Bug.py deleted file mode 100644 index 45ef86a1..00000000 --- a/16_Bug/python/Bug.py +++ /dev/null @@ -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() diff --git a/16_Bug/python/bug.py b/16_Bug/python/bug.py new file mode 100644 index 00000000..4d93c158 --- /dev/null +++ b/16_Bug/python/bug.py @@ -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() diff --git a/16_Bug/python/bug-overengineered.py b/16_Bug/python/bug_overengineered.py similarity index 78% rename from 16_Bug/python/bug-overengineered.py rename to 16_Bug/python/bug_overengineered.py index 35c5e420..bc12a3c3 100644 --- a/16_Bug/python/bug-overengineered.py +++ b/16_Bug/python/bug_overengineered.py @@ -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() diff --git a/16_Bug/python/test_bug.py b/16_Bug/python/test_bug.py new file mode 100644 index 00000000..0b5b008a --- /dev/null +++ b/16_Bug/python/test_bug.py @@ -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()