Skip to content

Instantly share code, notes, and snippets.

@dalaing
Created March 16, 2023 05:18
Show Gist options
  • Save dalaing/f2de854c6e8e28f0838acdfe2434fc45 to your computer and use it in GitHub Desktop.
Save dalaing/f2de854c6e8e28f0838acdfe2434fc45 to your computer and use it in GitHub Desktop.

David Laing I liked the idea of the game "Yoda Stories". It was a procedurally generated adventure game in the Star Wars universe which could be played in under an hour and was still entertaining. It involved adventure game tropes like talking to characters and fetching items for them in order to get access to information or items that would drive the story forward. I'd like to write a game like that, can you help me?

Of course! I'd be happy to help you design a procedurally generated adventure game. Let's start by outlining the basic elements and structure of the game, and then dive into each component in more detail.

Game concept and setting: Choose a theme or setting for your game that appeals to you and your target audience. It could be an original universe or one inspired by popular franchises, like Star Wars, but ensure that you don't infringe on any copyrights.

Procedural generation: This will be the core of your game, determining the layout of the game world, characters, quests, items, and other elements. You can use algorithms like Perlin noise or Simplex noise to create diverse and interesting environments. The procedural generation should make each playthrough feel unique and engaging.

Characters: Create a variety of characters that players can interact with during their adventure. These characters can provide quests, offer information, or trade items. Make sure their personalities, dialogues, and roles are generated procedurally to keep the game fresh and engaging.

Quest system: Design a quest system that provides players with a range of objectives, such as fetching items, solving puzzles, or defeating enemies. Make sure these quests are generated procedurally based on the game world and characters, ensuring a unique experience for each playthrough.

Inventory and items: Develop an inventory system that allows players to collect, use, and trade items. Create a diverse set of items that have different effects and functions, and ensure they are procedurally generated within the game world.

Puzzles and challenges: Incorporate puzzles and challenges into your game, encouraging players to think critically and strategize. These can be environmental puzzles, combat encounters, or social challenges that require players to interact with characters in specific ways.

Progression and pacing: Ensure that your game has a well-balanced difficulty curve, with challenges and rewards that increase as players progress. Procedurally generate the pacing of the game so that it can be completed within a set timeframe, like an hour, while still offering a satisfying experience.

Art and sound: Design the visual and auditory elements of your game to create an immersive experience for the players. These can be simple pixel art or more complex 3D graphics, depending on your preference and skill level.

Development tools: Choose a game engine and programming language that suits your needs and skills. Popular engines like Unity or Unreal Engine offer a wealth of resources and support for creating adventure games. You may also consider using a scripting language like Lua or Python for the procedural generation aspects of your game.

Playtesting and feedback: As you develop your game, be sure to playtest it regularly to ensure it's enjoyable, balanced, and engaging. Gather feedback from players to help refine your design and make improvements.

Remember, designing a game is a complex and iterative process, but by focusing on these key elements, you'll be well on your way to creating a fun, procedurally generated adventure game that players can enjoy time and time again. David Laing Let's start in the Star Wars universe while we prototype, and we'll change it to an original setting once we have the code working. I'd be happy to start with a text based game using python in order to get started on the procedural generation and core pieces of the game.

That's a great plan! Starting with a text-based game in Python will allow you to focus on the procedural generation and core mechanics without worrying about graphics or other complexities. Here's a simple outline of how you can approach building the prototype:

Setup the project: Create a new Python project, and consider using a virtual environment to manage dependencies. You may want to use a version control system like Git to track your changes and collaborate with others.

Define the game world: Create classes or data structures for the various elements in your game, such as locations, characters, items, and quests. For example, you can define a Location class with properties like name, description, and connected locations.

Procedural generation: Implement algorithms for generating the game world, characters, quests, and items. You can use random number generation to create different elements, and techniques like Perlin noise for generating more structured content. For instance, you could create a function that generates a list of locations with randomized connections.

Characters and dialogues: Develop classes or data structures for characters, including their names, descriptions, dialogues, and roles in the game. To generate procedural dialogues, you could use a template-based approach with placeholders for character names, items, and other context-specific information.

Quest system: Implement a system for generating and managing quests. Create a Quest class with properties like name, description, objectives, and rewards. You can use procedural generation to create unique quests based on the game world and characters.

Inventory and items: Design an inventory system that allows players to collect, use, and trade items. Create an Item class with properties like name, description, and effects. You can generate items procedurally to ensure variety and balance in the game.

Game loop and user input: Implement the main game loop that handles user input and updates the game state. You can use a simple command-based interface for user input, such as "go north", "talk to character", or "use item". Process the input, update the game state, and display the results to the player.

Saving and loading: Add functionality for saving and loading the game state. You can use Python's built-in serialization libraries like pickle or json to store the game state in a file.

Testing and debugging: Test your game thoroughly to ensure it's engaging, balanced, and free of bugs. Use Python's built-in debugging tools, like pdb, to help diagnose and fix issues.

Refactoring and optimization: As you develop your prototype, keep an eye on the code quality and performance. Refactor and optimize your code to ensure it's maintainable, efficient, and scalable.

Once you have a working prototype, you can focus on refining the gameplay, adding new features, and eventually transitioning to an original setting. Good luck, and have fun building your procedurally generated adventure game!

David Laing: Can you tell me how to do step 1?

Sure! I'll guide you through setting up a new Python project with a virtual environment and Git for version control. This example assumes you're using the command line and have Python and Git already installed on your system.

Create a new directory for your project:

mkdir my_game
cd my_game

Initialize a Git repository:

git init

Create a .gitignore file to ignore files and directories that should not be tracked by Git. Open a new file called .gitignore in your favorite text editor, and add the following lines to it:

__pycache__/
*.pyc
*.pyo
*.egg-info/
dist/
*.log
*.log.*
*.swp
*.swo
*.swn
*.pyc
*.pyo
*.egg-info/
dist/
*.log
*.log.*
*.swp
*.swo
*.swn
venv/

Save the file and close the editor.

Set up a virtual environment for managing dependencies:

python -m venv venv

This creates a new directory called venv that will store your project's dependencies. To activate the virtual environment, run the following command:

On Windows:

venv\Scripts\activate

On macOS and Linux:

source venv/bin/activate

Your command prompt should now display the virtual environment's name, indicating that it's active.

Create a requirements.txt file to manage your project's dependencies. This file should list any external libraries you'll be using in your game. For now, you can leave it empty, but you can add dependencies later as needed.

Create a main.py file to serve as the entry point for your game:

touch main.py

Open main.py in your favorite text editor, and write a simple "Hello, World!" program to test your setup:

def main():
    print("Hello, World!")

if __name__ == "__main__":
    main()
Run the main.py file to make sure everything is set up correctly:
python main.py

You should see "Hello, World!" printed in your terminal.

Commit your initial project setup to the Git repository:

git add .
git commit -m "Initial project setup"

Now your project is set up with a Python virtual environment and Git for version control. As you work on your game, remember to commit your changes regularly to keep track of your progress.

David Laing: Can you show me how to do step 2?

Certainly! In step 2, we'll define the game world by creating classes or data structures for various elements like locations, characters, items, and quests. To get started, let's create a simple Location class to represent different places in your game world.

Add the following code to your main.py file:

class Location:
    def __init__(self, name, description, connections):
        self.name = name
        self.description = description
        self.connections = connections

    def __str__(self):
        return f"{self.name}: {self.description}"

    def get_connections(self):
        return self.connections

This Location class has three properties:

name: The name of the location.
description: A short description of the location.
connections: A dictionary representing connected locations and the directions to reach them.

The str method allows us to print the location's name and description easily, while the get_connections method returns the location's connections.

Now let's create a few example locations and connect them:

def create_example_locations():
    location1 = Location("Jungle", "A dense and humid jungle full of life.", {})
    location2 = Location("Desert", "A vast and scorching desert with endless dunes.", {})
    location3 = Location("Mountain", "A towering mountain range with snow-capped peaks.", {})

    location1.connections = {"north": location2, "west": location3}
    location2.connections = {"south": location1, "west": location3}
    location3.connections = {"east": location1, "east": location2}

    return location1  # Return the initial location for the player.

This function creates three example locations (Jungle, Desert, and Mountain) and connects them. The function returns the initial location (Jungle) for the player.

Now, let's create a simple game loop that allows the player to move between locations using text commands. Update the main function in main.py:

def main():
    current_location = create_example_locations()

    while True:
        print(current_location)
        connections = current_location.get_connections()
        print(f"Available directions: {', '.join(connections.keys())}")
        direction = input("Enter the direction you want to go (or type 'quit' to exit): ").lower()

        if direction == "quit":
            break
        elif direction in connections:
            current_location = connections[direction]
        else:
            print("Invalid direction. Please try again.")

This game loop prints the current location's name and description, as well as the available directions. It then prompts the player to enter a direction or type 'quit' to exit the game. If the player enters a valid direction, the game moves to the connected location.

Run the game by executing main.py, and you should be able to navigate between the example locations using text commands.

Now you have a basic game world with locations and connections. You can build upon this foundation by creating more locations, as well as adding characters, items, and quests to your game world. The next steps would involve adding procedural generation for creating a more dynamic and varied game world.

David Laing: Can you help me write the character, item and quest classes?

Of course! Let's create simple classes for Character, Item, and Quest to represent these elements in your game. Add the following classes to your main.py file:

class Character:
    def __init__(self, name, description, dialogue):
        self.name = name
        self.description = description
        self.dialogue = dialogue

    def __str__(self):
        return f"{self.name}: {self.description}"

    def talk(self):
        return self.dialogue


class Item:
    def __init__(self, name, description, effect):
        self.name = name
        self.description = description
        self.effect = effect

    def __str__(self):
        return f"{self.name}: {self.description}"


class Quest:
    def __init__(self, name, description, objectives, reward):
        self.name = name
        self.description = description
        self.objectives = objectives
        self.reward = reward

    def __str__(self):
        return f"{self.name}: {self.description}"

    def is_completed(self):
        return all(objective['completed'] for objective in self.objectives)

    def complete_objective(self, objective_name):
        for objective in self.objectives:
            if objective['name'] == objective_name:
                objective['completed'] = True
                break

Here's a brief explanation of each class:

Character: Represents non-player characters (NPCs) in the game. This class has a name, description, and dialogue that the character can say when the player interacts with them. The talk() method returns the character's dialogue.

Item: Represents items that the player can collect, use, or trade. This class has a name, description, and effect, which could be a function or a string describing the item's in-game effect.

Quest: Represents quests that the player can undertake. This class has a name, description, objectives, and reward. The objectives is a list of dictionaries, each containing a name and a completed boolean status. The is_completed() method checks if all objectives are completed. The complete_objective() method marks an objective as completed by its name.

Now you can create instances of these classes and add them to your game world. For example, you could add characters and items to locations, and let characters give quests to the player. To fully integrate these classes into your game, you'll need to extend the game loop to handle interactions with characters, item usage, and quest management. Additionally, you can work on procedural generation for creating characters, items, and quests dynamically to make the game more engaging and replayable.

David Laing: Let's extend the game loop to handle these interactions.

To extend the game loop to handle interactions with characters, items, and quests, we'll need to make some modifications to the existing Location, Character, and Item classes and update the game loop in main.

First, let's modify the Location class to include characters and items:

class Location:
    def __init__(self, name, description, connections, characters=None, items=None):
        self.name = name
        self.description = description
        self.connections = connections
        self.characters = characters or {}
        self.items = items or {}

    # ... (rest of the class remains the same)

Now, locations can contain characters and items. We've initialized them with empty dictionaries by default.

Next, let's update the main function to handle character interactions, item usage, and quest management. We'll add new commands for the player to interact with characters, pick up items, and check their inventory and active quests.

def main():
    current_location = create_example_locations()

    inventory = {}
    active_quests = []

    while True:
        print(current_location)
        connections = current_location.get_connections()
        print(f"Available directions: {', '.join(connections.keys())}")
        
        if current_location.characters:
            print("Characters here: ", ', '.join(character.name for character in current_location.characters.values()))

        if current_location.items:
            print("Items here: ", ', '.join(item.name for item in current_location.items.values()))

        command = input("Enter a command (or type 'quit' to exit): ").lower()

        if command == "quit":
            break
        elif command in connections:
            current_location = connections[command]
        elif command.startswith("talk to "):
            character_name = command[8:]
            character = current_location.characters.get(character_name)
            if character:
                print(character.talk())
                quest = character.give_quest()  # Assuming characters have a 'give_quest' method
                if quest:
                    active_quests.append(quest)
                    print(f"New quest: {quest}")
            else:
                print("Character not found.")
        elif command.startswith("pick up "):
            item_name = command[8:]
            item = current_location.items.get(item_name)
            if item:
                inventory[item_name] = item
                del current_location.items[item_name]
                print(f"Picked up {item_name}.")
            else:
                print("Item not found.")
        elif command == "inventory":
            print("Your inventory:")
            for item_name, item in inventory.items():
                print(f" - {item_name}: {item.description}")
        elif command == "quests":
            print("Your active quests:")
            for quest in active_quests:
                print(f" - {quest}")
        else:
            print("Invalid command. Please try again.")

Now the game loop allows players to interact with characters, pick up items, and check their inventory and quests. Players can talk to characters using the command "talk to [character_name]" and pick up items using "pick up [item_name]". The "inventory" command shows the player's inventory, and the "quests" command lists the player's active quests.

Keep in mind that this is a simple implementation, and there are many ways to extend and improve it. You could add more commands for using items, trading with characters, or completing quest objectives. Additionally, you'll need to create methods for giving quests from characters and populate your game world with procedurally generated characters, items, and quests to make the game more engaging and replayable.

David Laing: Can we extend the example_locations to have example characters, quests and items?

Sure! Let's create some example characters, quests, and items, and add them to the create_example_locations() function. We'll first create a give_quest method for the Character class that allows characters to give quests to the player.

Add the following method to the Character class:

def give_quest(self):
    if self.quest and not self.quest.is_completed():
        return self.quest
    return None

Now, let's extend the create_example_locations() function to include example characters, items, and quests:

def create_example_locations():
    # Create items
    item1 = Item("Healing Potion", "A potion that heals your wounds.", "heal")
    item2 = Item("Mysterious Artifact", "An ancient artifact with unknown powers.", "unknown")

    # Create quests
    quest1 = Quest("Lost Artifact", "Find and return the mysterious artifact.",
                   [{"name": "Find Artifact", "completed": False},
                    {"name": "Return Artifact", "completed": False}], item1)

    # Create characters
    character1 = Character("Mentor", "A wise mentor who guides adventurers.", "Greetings, young traveler. I have a quest for you.", quest1)

    # Create locations
    location1 = Location("Jungle", "A dense and humid jungle full of life.", {}, characters={"Mentor": character1}, items={"Healing Potion": item1})
    location2 = Location("Desert", "A vast and scorching desert with endless dunes.", {}, items={"Mysterious Artifact": item2})
    location3 = Location("Mountain", "A towering mountain range with snow-capped peaks.", {})

    # Connect locations
    location1.connections = {"north": location2, "west": location3}
    location2.connections = {"south": location1, "west": location3}
    location3.connections = {"east": location1, "east": location2}

    return location1  # Return the initial location for the player.

In this example, we've created two items (item1 and item2), one quest (quest1), and one character (character1). We added the character and one item to the first location (location1) and the second item to the second location (location2). The character has a quest for the player to find and return the mysterious artifact.

Now, when you run the game, you should be able to interact with the character, pick up items, and receive the quest. You can expand upon this example by creating more characters, items, and quests, and populating your game world with procedurally generated content.

David Laing: How could we add environmental or social puzzles to this game?

To add environmental or social puzzles to your game, you can create new classes or extend the existing classes to represent puzzles, and integrate them into your game world. Here's an outline of how you could approach this:

Create a Puzzle class to represent different types of puzzles in the game. The Puzzle class could have properties like name, description, solution, and a method to check if the player's input matches the solution.
class Puzzle:
    def __init__(self, name, description, solution):
        self.name = name
        self.description = description
        self.solution = solution

    def is_solved(self, player_input):
        return player_input == self.solution
Add puzzles to locations, characters, or quests. You can either extend the existing Location, Character, and Quest classes to include puzzles or add puzzles as separate elements in your game world.

For example, you could add a puzzle to a location by extending the Location class:

class Location:
    def __init__(self, name, description, connections, characters=None, items=None, puzzle=None):
        self.name = name
        self.description = description
        self.connections = connections
        self.characters = characters or {}
        self.items = items or {}
        self.puzzle = puzzle

    # ... (rest of the class remains the same)
Extend the game loop to handle puzzle interactions. You can add new commands for the player to interact with puzzles and solve them. For example, you could add a "solve puzzle" command that prompts the player to enter a solution, and then checks if the solution is correct using the is_solved method of the Puzzle class.

Here's an example of how you could handle puzzle interactions in the game loop:

# Inside the main function's game loop
elif command == "solve puzzle":
    if current_location.puzzle:
        print(current_location.puzzle.description)
        player_input = input("Enter your solution: ")
        if current_location.puzzle.is_solved(player_input):
            print("You solved the puzzle!")
            # Perform an action, like revealing a hidden item, unlocking a door, or progressing a quest
        else:
            print("Incorrect solution. Please try again.")
    else:
        print("There is no puzzle to solve here.")
Design and create environmental or social puzzles for your game. Environmental puzzles could involve manipulating objects in the game world, navigating through a maze, or solving riddles based on the environment. Social puzzles might involve interacting with characters, influencing their decisions, or unraveling mysteries through dialogue and observation.

For example, you could create a puzzle where the player has to arrange objects in a certain order based on the environment's clues, or a puzzle where the player must persuade a character to reveal important information by choosing the right dialogue options.

With these components in place, you can create various environmental and social puzzles to engage players and add depth to your game world. Remember to integrate these puzzles into the game world in a way that makes sense for the narrative and gameplay experience.

David Laing: Can you help me have the locations, characters, items and quests be procedurally generated?

Certainly! To create procedurally generated locations, characters, items, and quests, you'll need to establish a set of rules and randomness that define how these elements are generated. Here's a high-level approach to creating procedurally generated game content:

Define templates or patterns for locations, characters, items, and quests. These templates can include attributes, such as names, descriptions, effects, objectives, and dialogue options, that are combined in various ways to generate unique game elements.

Create functions that generate random instances of these templates, such as a function that generates a random location, character, item, or quest.

Integrate these generation functions into your game world, so they are called when needed. For example, you can generate new locations when the player enters a previously unexplored area, or generate new quests when the player talks to certain characters.

Here's an example of how you could create procedurally generated characters:

import random

# Define character templates
character_templates = [
    {
        "name": "Wise Elder",
        "description": "An old and wise elder with many stories to tell.",
        "dialogue": ["I have seen many things in my time.", "The world is full of mysteries."]
    },
    {
        "name": "Curious Child",
        "description": "A curious child with a thirst for adventure.",
        "dialogue": ["I want to explore the world!", "What's beyond the horizon?"]
    },
    {
        "name": "Merchant",
        "description": "A merchant who travels far and wide to trade goods.",
        "dialogue": ["I have many wares for sale!", "Looking for something specific?"]
    }
]

def generate_random_character():
    template = random.choice(character_templates)
    name = template["name"]
    description = template["description"]
    dialogue = random.choice(template["dialogue"])
    
    return Character(name, description, dialogue)

# Example usage
random_character = generate_random_character()
print(random_character)

You can follow a similar approach to create procedurally generated locations, items, and quests. Keep in mind that this is a simple example, and you can extend it to create more complex and interesting game content. You can use external data sources, like text files or databases, to store your templates, or create more sophisticated generation algorithms that take into account the player's actions, game world state, or other factors.

When you have procedural generation functions for locations, characters, items, and quests, you can replace the static content in your game world with these dynamic elements. This will make your game more replayable and engaging, as players will encounter new and different content each time they play.

David Laing: I just realised that we haven't used give_quest in the game loop yet!

Apologies for that oversight. You're right; we need to incorporate the give_quest method into the game loop. Let's update the game loop in the main function to handle quests given by characters:

def main():
    current_location = create_example_locations()

    inventory = {}
    active_quests = []

    while True:
        print(current_location)
        connections = current_location.get_connections()
        print(f"Available directions: {', '.join(connections.keys())}")

        if current_location.characters:
            print("Characters here: ", ', '.join(character.name for character in current_location.characters.values()))

        if current_location.items:
            print("Items here: ", ', '.join(item.name for item in current_location.items.values()))

        command = input("Enter a command (or type 'quit' to exit): ").lower()

        if command == "quit":
            break
        elif command in connections:
            current_location = connections[command]
        elif command.startswith("talk to "):
            character_name = command[8:]
            character = current_location.characters.get(character_name)
            if character:
                print(character.talk())
                quest = character.give_quest()
                if quest:
                    active_quests.append(quest)
                    print(f"New quest: {quest}")
            else:
                print("Character not found.")
        # ... (rest of the game loop)

Now, when the player talks to a character, the give_quest method is called, and if there's a quest available, it's added to the player's active_quests list. This allows players to receive quests from characters and keep track of their active quests. You can further extend this functionality to handle quest completion, rewards, and other quest-related features.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment