I'm working on a practice project dealing with graphs. It's an interactive adventure game where the user inputs choices and tries to escape the wilderness, however, right now, when the user chooses an answer for the first prompt, it ends the game without prompting the other choices to make the game go on longer and make more sense to the user. I used tuples to represent a branching narrative, but I think I'm using it incorrectly. If anyone has a second to look through this or has any tips, I would greatly appreciate it!!
Here's the main.py file that creates the root node of the story tree and initiates the game:
# Import TreeNode class and story_data from respective files
from story_data import story_data
from tree_node import TreeNode
def main():
# Create the root node with the initial story piece
story_root = TreeNode(story_data.story_piece, story_data.choices)
# Print the initial story piece
print("Once upon a time...")
print(story_root.story_piece)
# Begin the story traversal
story_root.traverse()
# Check if this file is being run directly
if __name__ == "__main__":
main()
Here's the tree_node.py file that represents nodes in the story tree:
class TreeNode:
def __init__(self, story_piece, choices=None):
self.story_piece = story_piece
self.choices = choices or []
def add_child(self, node):
self.choices.append(node)
def traverse(self):
story_node = self
while story_node.choices:
choice = input("Enter 1 or 2 to continue the story: ")
if choice not in ["1", "2"]:
print("Invalid choice. Try again.")
else:
chosen_index = int(choice) - 1
chosen_child = story_node.choices[chosen_index]
print(chosen_child.story_piece)
story_node = chosen_child
And lastly, here's the story_data.py file that defines the structure of the story, creating instances of the tree node to represent different parts of the story:
from tree_node import TreeNode
story_data = TreeNode("""
You are in a forest clearing. There is a path to the left.
A bear emerges from the trees and roars!
Do you:
1 ) Roar back!
2 ) Run to the left...
""")
choice_a_1 = TreeNode("""
The bear returns and tells you it's been a rough week. After
making peace with
a talking bear, he shows you the way out of the forest.
YOU HAVE ESCAPED THE WILDERNESS.
""")
choice_a_2 = TreeNode("""
The bear returns and tells you that bullying is not okay before
leaving you alone
in the wilderness.
YOU REMAIN LOST.
""")
choice_b_1 = TreeNode("""
The bear is unamused. After smelling the flowers, it turns
around and leaves you alone.
YOU REMAIN LOST.
"""
)
choice_b_2 = TreeNode("""
The bear understands and apologizes for startling you. Your new
friend shows you a
path leading out of the forest.
YOU HAVE ESCAPED THE WILDERNESS.
"""
)
story_data.add_child(choice_a_1)
story_data.add_child(choice_a_2)
story_data.add_child(choice_b_1)
story_data.add_child(choice_b_2)
as stated in the comments,
You have only added child nodes to
story_data
. None of the other nodes contain child nodes, so thewhile
loop quits.
To give a bit more detail, after executing the code in story_data.py
the TreeNode
s look like this:
story_data.story_piece = "You are in a forest [..]"
story_data.choices = [choice_a_1, choice_a_2, choice_b_1, choice_b_2]
choice_a_1.story_piece = "The bear [..] tells you it's been a rough week [..]"
choice_a_1.choices = []
choice_a_2.story_piece = "The bear [..] tells you bully is not okay [..]"
choice_a_2.choices = []
choice_b_1.story_piece = "The bear is unamused [..]"
choice_b_1.choices = []
choice_b_2.story_piece = "The bear understands [..]"
choice_b_2.choices = []
The first iteration of the while
loop works because the conditional, story_node.choices
, finds a non-empty list
which evaluates to True
.
When the user chooses a value from ["1","2"]
, they are effectively choosing between choice_a_1
and choice_a_2
. For both of these, story_node.choices
will be an empty list
, which evaluates as False
, ending the loop without a second iteration.
If you're ever unsure about a python program's behavior, you can always debug it with an IDE like VS Code/PyCharm/etc., or use print
statements right before and/or after wherever your suspect the problem is.
So in this case, try running the program with the following change:
class TreeNode:
# .. (unchanged)
def traverse(self):
story_node = self
while story_node.choices:
print("story_node.choices before choice " + story_node.choices) # <-- added
choice = input("Enter 1 or 2 to continue the story: ")
if choice not in ["1", "2"]:
print("Invalid choice. Try again.")
else:
chosen_index = int(choice) - 1
chosen_child = story_node.choices[chosen_index]
print(chosen_child.story_piece)
story_node = chosen_child
print("story_node.choices after choice " + story_node.choices) # <-- added
def __repr__(self): # <-- added
return self.story_piece # <-- added
With this you would get something like the following output:
Once upon a time...
1) [..]
2) [..]
story_node.choices before choice [
The bear returns and tells you it's been a rough week. [..]
,
The bear returns and tells you that bullying is not okay [..]
,
The bear is unamused. [..]
,
The bear understands and apologizes [..]
]
Enter 1 or 2 to continue the story: 1
The bear returns and tells you it's been a rough week. [..]
story_node.choices after choice []
notice the last line of output is an empty list
, which is likely leading to the behavior you mention.
As an aside, ["1", "2"]
is semantically a list
, a tuple
would look like ("1", "2")
, but the behavior is identical in this instance as there are no assignments or hashing, you can read a bit about differences)
You may also want to check out the visitor
design pattern as it would let you move your processing of each TreeNode
outside the object itself, since after the first iteration, there's no reason for the traverse
method to be inside TreeNode
. Example here