From 8cf8bab7421a5ca5d3d47382feb539dddf45068e Mon Sep 17 00:00:00 2001 From: Martin Thoma Date: Mon, 7 Mar 2022 22:00:41 +0100 Subject: [PATCH] MAINT: Add type annotations / use functions --- 01_Acey_Ducey/python/acey_ducey.py | 4 +- 01_Acey_Ducey/python/acey_ducey_oo.py | 30 ++-- 01_Acey_Ducey/python/aceyducey.py | 12 +- 02_Amazing/python/amazing.py | 241 +++++++++++++++----------- 03_Animal/python/animal.py | 151 ++++++++-------- 5 files changed, 240 insertions(+), 198 deletions(-) mode change 100644 => 100755 01_Acey_Ducey/python/acey_ducey.py diff --git a/01_Acey_Ducey/python/acey_ducey.py b/01_Acey_Ducey/python/acey_ducey.py old mode 100644 new mode 100755 index 8461dad1..550d94b2 --- a/01_Acey_Ducey/python/acey_ducey.py +++ b/01_Acey_Ducey/python/acey_ducey.py @@ -23,7 +23,7 @@ cards = { } -def play_game(): +def play_game() -> None: """Play the game""" cash = 100 while cash > 0: @@ -63,7 +63,7 @@ def play_game(): print("Sorry, friend, but you blew your wad") -def main(): +def main() -> None: """Main""" keep_playing = True diff --git a/01_Acey_Ducey/python/acey_ducey_oo.py b/01_Acey_Ducey/python/acey_ducey_oo.py index d7bed605..d983c8b2 100644 --- a/01_Acey_Ducey/python/acey_ducey_oo.py +++ b/01_Acey_Ducey/python/acey_ducey_oo.py @@ -11,41 +11,43 @@ # ###################################################### +from typing import List + class Card: - def __init__(self, suit, rank): + def __init__(self, suit: str, rank: int): self.suit = suit self.rank = rank - def __str__(self): - r = self.rank - if r == 11: + def __str__(self) -> str: + r = str(self.rank) + if r == "11": r = "J" - elif r == 12: + elif r == "12": r = "Q" - elif r == 13: + elif r == "13": r = "K" - elif r == 14: + elif r == "14": r = "A" return f"{r}{self.suit}" class Deck: def __init__(self): - self.cards = [] + self.cards: List[Card] = [] self.build() - def build(self): + def build(self) -> None: for suit in ["\u2665", "\u2666", "\u2663", "\u2660"]: for rank in range(2, 15): self.cards.append(Card(suit, rank)) - def shuffle(self): + def shuffle(self) -> None: import random random.shuffle(self.cards) - def deal(self): + def deal(self) -> Card: return self.cards.pop() @@ -58,7 +60,7 @@ class Game: self.money = 100 self.not_done = True - def play(self): + def play(self) -> None: while self.not_done: while self.money > 0: card_a = self.card_a @@ -90,9 +92,9 @@ class Game: print("Chicken!") print(f"Your deal should have been: {player_card}") if card_a.rank < player_card.rank < card_b.rank: - print(f"You could have won!") + print("You could have won!") else: - print(f"You would lose, so it was wise of you to chicken out!") + print("You would lose, so it was wise of you to chicken out!") self.not_done = False break diff --git a/01_Acey_Ducey/python/aceyducey.py b/01_Acey_Ducey/python/aceyducey.py index 87147181..fa112d8b 100644 --- a/01_Acey_Ducey/python/aceyducey.py +++ b/01_Acey_Ducey/python/aceyducey.py @@ -30,13 +30,13 @@ import random # to start with more or less than $100." DEFAULT_BANKROLL = 100 -# functions -def deal_card_num(): + +def deal_card_num() -> int: """Get card number""" return random.randint(0, 12) -def get_card_name(number): +def get_card_name(number: int) -> str: """Get card name""" card_names = ( " 2", @@ -56,7 +56,7 @@ def get_card_name(number): return card_names[number] -def display_bankroll(bank_roll): +def display_bankroll(bank_roll: int) -> None: """Print current bankroll""" if BANK_ROLL > 0: print("You now have %s dollars\n" % bank_roll) @@ -103,9 +103,9 @@ while KEEP_PLAYING: # Get and handle player bet choice BET_IS_VALID = False while not BET_IS_VALID: - curr_bet = input("What is your bet? ") + curr_bet_str = input("What is your bet? ") try: - curr_bet = int(curr_bet) + curr_bet = int(curr_bet_str) except ValueError: # Bad input? Just loop back up and ask again... pass diff --git a/02_Amazing/python/amazing.py b/02_Amazing/python/amazing.py index 56123c71..638acc57 100644 --- a/02_Amazing/python/amazing.py +++ b/02_Amazing/python/amazing.py @@ -1,118 +1,147 @@ import random +from typing import List, NamedTuple, Tuple # Python translation by Frank Palazzolo - 2/2021 -print(" " * 28 + "AMAZING PROGRAM") -print(" " * 15 + "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY") -print() -print() -print() -while True: - width, length = input("What are your width and length?").split(",") - width = int(width) - length = int(length) - if width != 1 and length != 1: - break - print("Meaningless dimensions. Try again.") +class Maze(NamedTuple): + used: List[List[int]] + walls: List[List[int]] + enter_col: int + width: int + length: int -# Build two 2D arrays -# -# used: -# Initially set to zero, unprocessed cells -# Filled in with consecutive non-zero numbers as cells are processed -# -# walls: -# Initially set to zero, (all paths blocked) -# Remains 0 if there is no exit down or right -# Set to 1 if there is an exit down -# Set to 2 if there is an exit right -# Set to 3 if there are exits down and right -used = [] -walls = [] -for i in range(length): - used.append([0] * width) - walls.append([0] * width) +def main() -> None: + welcome_header() + width, length = get_maze_dimensions() + maze = build_maze(width, length) + print_maze(maze) -# Use direction variables with nice names -GO_LEFT, GO_UP, GO_RIGHT, GO_DOWN = [0, 1, 2, 3] -# Give Exit directions nice names -EXIT_DOWN = 1 -EXIT_RIGHT = 2 -# Pick a random entrance, mark as used -enter_col = random.randint(0, width - 1) -row, col = 0, enter_col -count = 1 -used[row][col] = count -count = count + 1 - -while count != width * length + 1: - # remove possible directions that are blocked or - # hit cells that we have already processed - possible_dirs = [GO_LEFT, GO_UP, GO_RIGHT, GO_DOWN] - if col == 0 or used[row][col - 1] != 0: - possible_dirs.remove(GO_LEFT) - if row == 0 or used[row - 1][col] != 0: - possible_dirs.remove(GO_UP) - if col == width - 1 or used[row][col + 1] != 0: - possible_dirs.remove(GO_RIGHT) - if row == length - 1 or used[row + 1][col] != 0: - possible_dirs.remove(GO_DOWN) - - # If we can move in a direction, move and make opening - if len(possible_dirs) != 0: - direction = random.choice(possible_dirs) - if direction == GO_LEFT: - col = col - 1 - walls[row][col] = EXIT_RIGHT - elif direction == GO_UP: - row = row - 1 - walls[row][col] = EXIT_DOWN - elif direction == GO_RIGHT: - walls[row][col] = walls[row][col] + EXIT_RIGHT - col = col + 1 - elif direction == GO_DOWN: - walls[row][col] = walls[row][col] + EXIT_DOWN - row = row + 1 - used[row][col] = count - count = count + 1 - # otherwise, move to the next used cell, and try again - else: - while True: - if col != width - 1: - col = col + 1 - elif row != length - 1: - row, col = row + 1, 0 - else: - row, col = 0, 0 - if used[row][col] != 0: - break - -# Add a random exit -col = random.randint(0, width - 1) -row = length - 1 -walls[row][col] = walls[row][col] + 1 - -# Print the maze -for col in range(width): - if col == enter_col: - print(". ", end="") - else: - print(".--", end="") -print(".") -for row in range(length): - print("I", end="") - for col in range(width): - if walls[row][col] < 2: - print(" I", end="") - else: - print(" ", end="") +def welcome_header() -> None: + print(" " * 28 + "AMAZING PROGRAM") + print(" " * 15 + "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY") print() - for col in range(width): - if walls[row][col] == 0 or walls[row][col] == 2: - print(":--", end="") + print() + print() + + +def build_maze(width: int, length: int) -> Maze: + # Build two 2D arrays + # + # used: + # Initially set to zero, unprocessed cells + # Filled in with consecutive non-zero numbers as cells are processed + # + # walls: + # Initially set to zero, (all paths blocked) + # Remains 0 if there is no exit down or right + # Set to 1 if there is an exit down + # Set to 2 if there is an exit right + # Set to 3 if there are exits down and right + + used = [] + walls = [] + for _ in range(length): + used.append([0] * width) + walls.append([0] * width) + + # Use direction variables with nice names + GO_LEFT, GO_UP, GO_RIGHT, GO_DOWN = [0, 1, 2, 3] + # Give Exit directions nice names + EXIT_DOWN = 1 + EXIT_RIGHT = 2 + + # Pick a random entrance, mark as used + enter_col = random.randint(0, width - 1) + row, col = 0, enter_col + count = 1 + used[row][col] = count + count = count + 1 + + while count != width * length + 1: + # remove possible directions that are blocked or + # hit cells that we have already processed + possible_dirs = [GO_LEFT, GO_UP, GO_RIGHT, GO_DOWN] + if col == 0 or used[row][col - 1] != 0: + possible_dirs.remove(GO_LEFT) + if row == 0 or used[row - 1][col] != 0: + possible_dirs.remove(GO_UP) + if col == width - 1 or used[row][col + 1] != 0: + possible_dirs.remove(GO_RIGHT) + if row == length - 1 or used[row + 1][col] != 0: + possible_dirs.remove(GO_DOWN) + + # If we can move in a direction, move and make opening + if len(possible_dirs) != 0: + direction = random.choice(possible_dirs) + if direction == GO_LEFT: + col = col - 1 + walls[row][col] = EXIT_RIGHT + elif direction == GO_UP: + row = row - 1 + walls[row][col] = EXIT_DOWN + elif direction == GO_RIGHT: + walls[row][col] = walls[row][col] + EXIT_RIGHT + col = col + 1 + elif direction == GO_DOWN: + walls[row][col] = walls[row][col] + EXIT_DOWN + row = row + 1 + used[row][col] = count + count = count + 1 + # otherwise, move to the next used cell, and try again else: - print(": ", end="") + while True: + if col != width - 1: + col = col + 1 + elif row != length - 1: + row, col = row + 1, 0 + else: + row, col = 0, 0 + if used[row][col] != 0: + break + + # Add a random exit + col = random.randint(0, width - 1) + row = length - 1 + walls[row][col] = walls[row][col] + 1 + return Maze(used, walls, enter_col, width, length) + + +def get_maze_dimensions() -> Tuple[int, int]: + while True: + width_str, length_str = input("What are your width and length?").split(",") + width = int(width_str) + length = int(length_str) + if width > 1 and length > 1: + break + print("Meaningless dimensions. Try again.") + return width, length + + +def print_maze(maze: Maze) -> None: + for col in range(maze.width): + if col == maze.enter_col: + print(". ", end="") + else: + print(".--", end="") print(".") + for row in range(maze.length): + print("I", end="") + for col in range(maze.width): + if maze.walls[row][col] < 2: + print(" I", end="") + else: + print(" ", end="") + print() + for col in range(maze.width): + if maze.walls[row][col] == 0 or maze.walls[row][col] == 2: + print(":--", end="") + else: + print(": ", end="") + print(".") + + +if __name__ == "__main__": + main() diff --git a/03_Animal/python/animal.py b/03_Animal/python/animal.py index 63d4813e..a3cb509a 100644 --- a/03_Animal/python/animal.py +++ b/03_Animal/python/animal.py @@ -42,20 +42,26 @@ # ######################################################## +from typing import Optional + class Node: """ Node of the binary tree of questions. """ - def __init__(self, text, yes_node, no_node): + def __init__( + self, text: str, yes_node: Optional["Node"], no_node: Optional["Node"] + ): # the nodes that are leafs have as text the animal's name, otherwise # a yes/no question self.text = text self.yes_node = yes_node self.no_node = no_node - def update_node(self, new_question, answer_new_ques, new_animal): + def update_node( + self, new_question: str, answer_new_ques: str, new_animal: str + ) -> None: # update the leaf with a question old_animal = self.text # we replace the animal with a new question @@ -69,13 +75,13 @@ class Node: self.no_node = Node(new_animal, None, None) # the leafs have as children None - def is_leaf(self): - return self.yes_node == None and self.no_node == None + def is_leaf(self) -> bool: + return self.yes_node is None and self.no_node is None -def list_known_animals(root_node): +def list_known_animals(root_node: Optional[Node]) -> None: # Traversing the tree by recursion until we reach the leafs - if root_node == None: + if root_node is None: return if root_node.is_leaf(): @@ -89,91 +95,93 @@ def list_known_animals(root_node): list_known_animals(root_node.no_node) -def parse_input(message, check_list, root_node): - # only accepts yes or no inputs and recognizes list operation - correct_input = False - while not correct_input: - try: - inp = input(message) +def parse_input(message: str, check_list: bool, root_node: Optional[Node]) -> str: + """only accepts yes or no inputs and recognizes list operation""" + token = "" + while token not in ["y", "n"]: + inp = input(message) - if check_list and inp.lower() == "list": - print("Animals I already know are:") - list_known_animals(root_node) - print("\n") + if check_list and inp.lower() == "list": + print("Animals I already know are:") + list_known_animals(root_node) + print("\n") + if len(inp) > 0: token = inp[0].lower() - if token == "y" or token == "n": - correct_input = True - except IndexError: - pass + else: + token = "" return token -def avoid_void_input(message): +def avoid_void_input(message: str) -> str: answer = "" while answer == "": answer = input(message) return answer -def initial_message(): +def initial_message() -> None: print(" " * 32 + "Animal") print(" " * 15 + "Creative Computing Morristown, New Jersey\n") print("Play ´Guess the Animal´") print("Think of an animal and the computer will try to guess it.\n") -# Initial tree -yes_child = Node("Fish", None, None) -no_child = Node("Bird", None, None) -root = Node("Does it swim?", yes_child, no_child) - -# Main loop of game -initial_message() -keep_playing = parse_input("Are you thinking of an animal? ", True, root) == "y" -while keep_playing: - keep_asking = True - # Start traversing the tree by the root - actual_node = root - - while keep_asking: - - if not actual_node.is_leaf(): - # we have to keep asking i.e. traversing nodes - answer = parse_input(actual_node.text, False, None) - - if answer == "y": - actual_node = actual_node.yes_node - else: - actual_node = actual_node.no_node - else: - # we have reached a possible answer - answer = parse_input(f"Is it a {actual_node.text}? ", False, None) - if answer == "n": - # add the new animal to the tree - new_animal = avoid_void_input( - "The animal you were thinking of was a ? " - ) - new_question = avoid_void_input( - "Please type in a question that would distinguish a {} from a {}: ".format( - new_animal, actual_node.text - ) - ) - answer_new_question = parse_input( - f"for a {new_animal} the answer would be: ", False, None - ) - - actual_node.update_node( - new_question + "?", answer_new_question, new_animal - ) - - else: - print("Why not try another animal?") - - keep_asking = False +def main() -> None: + # Initial tree + yes_child = Node("Fish", None, None) + no_child = Node("Bird", None, None) + root = Node("Does it swim?", yes_child, no_child) + # Main loop of game + initial_message() keep_playing = parse_input("Are you thinking of an animal? ", True, root) == "y" + while keep_playing: + keep_asking = True + # Start traversing the tree by the root + actual_node: Node = root + + while keep_asking: + + if not actual_node.is_leaf(): + + # we have to keep asking i.e. traversing nodes + answer = parse_input(actual_node.text, False, None) + + # As this is an inner node, both children are not None + if answer == "y": + assert actual_node.yes_node is not None + actual_node = actual_node.yes_node + else: + assert actual_node.no_node is not None + actual_node = actual_node.no_node + else: + # we have reached a possible answer + answer = parse_input(f"Is it a {actual_node.text}? ", False, None) + if answer == "n": + # add the new animal to the tree + new_animal = avoid_void_input( + "The animal you were thinking of was a ? " + ) + new_question = avoid_void_input( + "Please type in a question that would distinguish a " + f"{new_animal} from a {actual_node.text}: " + ) + answer_new_question = parse_input( + f"for a {new_animal} the answer would be: ", False, None + ) + + actual_node.update_node( + new_question + "?", answer_new_question, new_animal + ) + + else: + print("Why not try another animal?") + + keep_asking = False + + keep_playing = parse_input("Are you thinking of an animal? ", True, root) == "y" ######################################################## @@ -193,3 +201,6 @@ while keep_playing: # function (Lines 120 to 130, 135, 158, 160, 168, 173) ######################################################## + +if __name__ == "__main__": + main()