-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcat.py
192 lines (175 loc) · 8.39 KB
/
cat.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
import os
import pickle
"""
A decision-tree based learning game that iteratively guesses objects by asking yes/no questions, adapting to new knowledge from user input
"""
class TreeNode:
"""
A node in the decision tree
@param question: The question to ask at this node
@param yes: The child node if the answer is yes
@param no: The child node if the answer is no
"""
# Initialize the node with a question and two child nodes
def __init__(self, question=None, yes=None, no=None):
# The question to ask at this node
self.question = question
# The child node if the answer is yes
self.yes = yes
# The child node if the answer is no
self.no = no
"""
Inserts a new node with a question and object as children of the parent node
@param parent_node: The parent node to insert the new node under
@param question: The question to ask at the new node
@param new_object: The object to guess at the new node
@return: The parent node with the new node inserted
"""
def insert_node(parent_node, question, new_object, is_yes):
# Creating a new node for the new object
new_object_node = TreeNode(new_object)
# Placing the new question and new object in the tree
if is_yes:
# if the answer is yes, insert the new question and object as children of the parent node for the yes branch
parent_node.yes = TreeNode(question, new_object_node, parent_node.no)
else:
# if the answer is no, insert the new question and object as children of the parent node for the no branch
parent_node.no = TreeNode(question, new_object_node, parent_node.yes)
"""
Prompts the user for the correct object and the difference between the correct object and the current object
@param node_question: The question at the current node
@return: The correct object and the new question based on the difference
"""
def learn_new_object(node_question):
# Prompt the user for the correct object
new_object = input("I give up. What is it? ").strip().lower()
# Guide the user to provide a characteristic that fits into a question
print(f"Tell me something that distinguishes {new_object} from {node_question}.")
print("For example, 'can fly', 'has feathers', or 'is bigger than a car'.")
difference = input("It ... ").strip().lower()
# Determine the appropriate question format based on the input
if difference.startswith("is "): # If the difference starts with "is", use a "Is it" question
new_question = "Is it " + difference + "?"
elif difference.startswith("can "): # If the difference starts with "can", use a "Can it" question
new_question = "Can it " + difference[4:] + "?"
elif difference.startswith("has "): # If the difference starts with "has", use a "Does it have" question
new_question = "Does it have " + difference[4:] + "?"
else:
# For inputs that don't fit the above patterns, use a generic "Does it" question
new_question = "Does it " + difference + "?"
# Return the correct object and the new question
return new_object, new_question
"""
Asks a question at the current node and recursively navigates the tree based on the user's input
@param node: The current node in the decision tree
@param parent: The parent node of the current node (default is None)
@return: The updated root node of the decision tree
"""
def ask_question(node, parent=None):
# Check if the node is a question node
if node.question.endswith('?'):
# Ask the question and get the user's input
answer = input(node.question + " (Y/N): ").strip().lower()
# Check only the first letter for simplicity
if answer.startswith('y'):
# If the answer is yes, navigate to the yes branch
if node.yes is None:
# If the yes branch is None, the object has been guessed correctly
print("I guessed right!")
return None
else:
# Recursively navigate to the yes branch
return ask_question(node.yes, node)
# If the answer is no, navigate to the no branch
elif answer.startswith('n'):
# If the no branch is None, the object has not been guessed correctly
if node.no is None:
# Learn the correct object and the difference
new_object, new_question = learn_new_object(node.question[:-1])
# Insert the new node with the new question and object
insert_node(node, new_question, new_object, False)
# return the updated node
return node
else:
# Recursively navigate to the no branch
return ask_question(node.no, node)
else:
# If the node is not a question node, prompt the user for the correct object
answer = input(f"Is it {node.question}? (Y/N): ").strip().lower() # Handle input in lowercase
# Check only the first letter for simplicity
if answer.startswith('y'): # If the answer is yes, the object has been guessed correctly
print("I guessed right!")
elif answer.startswith('n'): # If the answer is no, prompt the user for the correct object and the difference
# Learn the correct object and the difference
new_object, new_question = learn_new_object(node.question)
# Insert the new node with the new question and object
new_node = TreeNode(new_question, TreeNode(new_object), TreeNode(node.question))
# Check if the current node has a parent
if parent:
# Check if the current node is the yes branch or the no branch of the parent node
if parent.yes == node:
# Update the yes branch of the parent node with the new node
parent.yes = new_node
else:
# Update the no branch of the parent node with the new node
parent.no = new_node
else:
# Return the new node
return new_node
return None
"""
Save the decision tree to a file within the data directory.
@param root_node: The root node of the decision tree
@param directory: The directory to save the tree to (default is 'data')
@param filename: The name of the file to save the tree to (default is 'tree.pkl')
@return: None
"""
def save_tree(root_node, directory="data", filename='tree.pkl'):
# Check if the directory exists, create it if it doesn't
if not os.path.exists(directory):
os.makedirs(directory)
# Construct the file path to save the tree
filepath = os.path.join(directory, filename)
# Save the tree to the file
with open(filepath, 'wb') as file:
pickle.dump(root_node, file)
"""
Load the decision tree from a file or create a new one if the file doesn't exist or an error occurs.
@param directory: The directory to load the tree from (default is 'data')
@param filename: The name of the file to load the tree from (default is 'tree.pkl')
@return: The loaded tree or a new tree with "cat" as the root node
"""
def load_tree(directory="data", filename='tree.pkl'):
# Construct the file path to load the tree
filepath = os.path.join(directory, filename)
try:
# Load the tree from the file
with open(filepath, 'rb') as file:
# Return the loaded tree
return pickle.load(file)
except (FileNotFoundError, EOFError, pickle.UnpicklingError):
# If the file doesn't exist, or an error occurs, return a new tree with "cat" as the root node.
return TreeNode("a cat")
# Starting node of the tree
root = load_tree()
"""
Plays the game by asking questions and navigating the decision tree based on user input (infinitely until the user decides to stop)
@param root: The root node of the decision tree
"""
def play_game(root):
# Print the initial message
print("Think of something...")
# Start the game by asking questions and navigating the tree
new_root = ask_question(root)
# Return the updated root node
return new_root if new_root else root
# Play the game
while True:
# Play the game with the current root node
root = play_game(root)
# Save the tree after each game
save_tree(root)
# Ask the user if they want to play again
if not input("Do you want to play again? (Y/N): ").strip().lower().startswith('y'):
# If the user does not want to play again, break the loop
break