mirror of
https://github.com/coding-horror/basic-computer-games.git
synced 2025-12-05 20:40:25 -08:00
201 lines
7.0 KiB
Python
201 lines
7.0 KiB
Python
"""
|
||
Animal
|
||
|
||
From: Basic computer Games(1978)
|
||
|
||
Unlike other computer games in which the computer
|
||
picks a number or letter and you must guess what it is,
|
||
in this game you think of an animal and the computer asks
|
||
you questions and tries to guess the name of your animal.
|
||
If the computer guesses incorrectly, it will ask you for a
|
||
question that differentiates the animal it guessed
|
||
from the one you were thinking of. In this way the
|
||
computer "learns" new animals. Questions to differentiate
|
||
new animals should be input without a question mark.
|
||
This version of the game does not have a SAVE feature.
|
||
If your sistem allows, you may modify the program to
|
||
save array A$, then reload the array when you want
|
||
to play the game again. This way you can save what the
|
||
computer learns over a series of games.
|
||
At any time if you reply 'LIST' to the question "ARE YOU
|
||
THINKING OF AN ANIMAL", the computer will tell you all the
|
||
animals it knows so far.
|
||
The program starts originally by knowing only FISH and BIRD.
|
||
As you build up a file of animals you should use broad,
|
||
general questions first and then narrow down to more specific
|
||
ones with later animals. For example, If an elephant was to be
|
||
your first animal, the computer would ask for a question to distinguish
|
||
an elephant from a bird. Naturally there are hundreds of possibilities,
|
||
however, if you plan to build a large file of animals a good question
|
||
would be "IS IT A MAMAL".
|
||
This program can be easily modified to deal with categories of
|
||
things other than animals by simply modifying the initial data
|
||
in Line 530 and the dialogue references to animal in Lines 10,
|
||
40, 50, 130, 230, 240 and 600. In an educational environment, this
|
||
would be a valuable program to teach the distinguishing chacteristics
|
||
of many classes of objects -- rock formations, geography, marine life,
|
||
cell structures, etc.
|
||
Originally developed by Arthur Luehrmann at Dartmouth College,
|
||
Animal was subsequently shortened and modified by Nathan Teichholtz at
|
||
DEC and Steve North at Creative Computing
|
||
"""
|
||
|
||
from typing import Optional
|
||
|
||
|
||
class Node:
|
||
"""
|
||
Node of the binary tree of questions.
|
||
"""
|
||
|
||
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: 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
|
||
self.text = new_question
|
||
|
||
if answer_new_ques == "y":
|
||
self.yes_node = Node(new_animal, None, None)
|
||
self.no_node = Node(old_animal, None, None)
|
||
else:
|
||
self.yes_node = Node(old_animal, None, None)
|
||
self.no_node = Node(new_animal, None, None)
|
||
|
||
# the leafs have as children None
|
||
def is_leaf(self) -> bool:
|
||
return self.yes_node is None and self.no_node is None
|
||
|
||
|
||
def list_known_animals(root_node: Optional[Node]) -> None:
|
||
"""Traversing the tree by recursion until we reach the leafs."""
|
||
if root_node is None:
|
||
return
|
||
|
||
if root_node.is_leaf():
|
||
print(root_node.text, end=" " * 11)
|
||
return
|
||
|
||
if root_node.yes_node:
|
||
list_known_animals(root_node.yes_node)
|
||
|
||
if root_node.no_node:
|
||
list_known_animals(root_node.no_node)
|
||
|
||
|
||
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")
|
||
|
||
token = inp[0].lower() if len(inp) > 0 else ""
|
||
return token
|
||
|
||
|
||
def avoid_void_input(message: str) -> str:
|
||
answer = ""
|
||
while not answer:
|
||
answer = input(message)
|
||
return answer
|
||
|
||
|
||
def print_intro() -> 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")
|
||
|
||
|
||
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
|
||
print_intro()
|
||
while (
|
||
keep_playing := parse_input(
|
||
"Are you thinking of an animal? ", True, root
|
||
)
|
||
== "y"
|
||
):
|
||
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(f"{new_question}?", answer_new_question, new_animal)
|
||
|
||
else:
|
||
print("Why not try another animal?")
|
||
|
||
keep_asking = False
|
||
|
||
|
||
########################################################
|
||
# Porting Notes
|
||
#
|
||
# The data structure used for storing questions and
|
||
# animals is a binary tree where each non-leaf node
|
||
# has a question, while the leafs store the animals.
|
||
#
|
||
# As the original program, this program doesn't store
|
||
# old questions and animals. A good modification would
|
||
# be to add a database to store the tree.
|
||
# Also as the original program, this one can be easily
|
||
# modified to not only make guesses about animals, by
|
||
# modyfing the initial data of the tree, the questions
|
||
# that are asked to the user and the initial message
|
||
# function (Lines 120 to 130, 135, 158, 160, 168, 173)
|
||
|
||
########################################################
|
||
|
||
if __name__ == "__main__":
|
||
main()
|