Search code examples
pythontuplesgraph-theory

Adventure Story Graph Tuple Bug


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)

Solution

  • as stated in the comments,

    You have only added child nodes to story_data. None of the other nodes contain child nodes, so the while loop quits.

    To give a bit more detail, after executing the code in story_data.py

    the TreeNodes 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