Adventure Game Story And Fight Instructions

Use this when you want to add new story content, new monsters, and new combat encounters. The Python game and the browser website are separate copies of the game logic, so changes must be made in both places if you want both versions to match.

Files You Edit

Goal Python game Browser website
Add or reorder story scenes text_adventure/story.py web/app.js
Add monster stats text_adventure/data.py web/app.js
Add spell stats text_adventure/data.py web/app.js
Change combat rules for every fight text_adventure/combat.py web/app.js

Important: the website is not generated from the Python files. If you add a monster or scene only in Python, python3 main.py can use it but the website cannot. If you add it only in web/app.js, the website can use it but the Python game cannot.

Add A Fight With An Existing Monster

Use this when the monster already exists in MONSTERS, such as goblin, troll, skeleton, werewolf, ogre, witch, or vampire.

Python

  1. Open text_adventure/story.py.
  2. Find the scene function where the fight should happen, such as forest_scene(player, shop_stock).
  3. Add this pattern inside the function at the exact point where the fight should start.
say("\nA vampire drops from the ceiling.", "beat")
if fight_or_run("\nThe vampire blocks your path. Do you fight or run? ") == "run":
    say("\nThe vampire catches you before you reach the door.", "beat")
    game_over(player)

spell_fight("vampire", player)
offer_potions(player)

Browser Website

  1. Open web/app.js.
  2. Find the matching scene method, such as async forestScene(player, shopStock).
  3. Add the JavaScript version at the same story point.
this.say("\nA vampire drops from the ceiling.");
if (await this.fightOrRun("\nThe vampire blocks your path. Do you fight or run? ") === "run") {
  this.say("\nThe vampire catches you before you reach the door.");
  this.gameOver(player);
}

await this.spellFight("vampire", player);
await this.offerPotions(player);

Add A New Monster

A monster needs three fields: health, damage, and attacks. The fight command uses the monster key, not the display text. For example, spell_fight("bandit", player) uses the "bandit" monster entry.

Python

  1. Open text_adventure/data.py.
  2. Find MONSTERS = {.
  3. Add the new monster inside that dictionary.
    "bandit": {
        "health": 28,
        "damage": 8,
        "attacks": ["rusty dagger", "cheap shot", "boot kick"],
    },

Browser Website

  1. Open web/app.js.
  2. Find const MONSTERS = { near the top of the file.
  3. Add the same monster using JavaScript object syntax.
    bandit: {
      health: 28,
      damage: 8,
      attacks: ["rusty dagger", "cheap shot", "boot kick"],
    },

Add A New Story Scene

A normal scene is one checkpoint. The game autosaves after each scene, then moves to the next id in SCENE_ORDER. Do not rename existing scene ids unless you also want to break old saves that point to those ids.

Python Checklist

  1. Open text_adventure/story.py.
  2. Find SCENE_ORDER = ( and add your new scene id where it belongs.
  3. Find SCENE_TITLES = { and add the readable checkpoint title.
  4. Find def _run_scene(scene_id, player, shop_stock): and add an elif branch for the new id.
  5. Add the new scene function near the other scene functions at the bottom of the file.

Browser Checklist

  1. Open web/app.js.
  2. Find const SCENE_ORDER = [ and add the same scene id in the same position.
  3. Find const SCENE_TITLES = { and add the same readable title.
  4. Find async runScene(sceneId, player, shopStock) and add an else if branch for the new id.
  5. Add the new async scene method near the other scene methods.

Full Copy-Paste Example: Add A Bandit Camp Scene

This example adds a new bandit_camp checkpoint after forest and before twin_doors. It also adds a new bandit monster and starts a fight in the new scene.

1. Python Monster

In text_adventure/data.py, add this inside MONSTERS:

    "bandit": {
        "health": 28,
        "damage": 8,
        "attacks": ["rusty dagger", "cheap shot", "boot kick"],
    },

2. Python Scene Order

In text_adventure/story.py, update SCENE_ORDER so the new scene is after "forest":

SCENE_ORDER = (
    "intro",
    "wizard",
    "locked_door",
    "first_goblin",
    "village",
    "forest",
    "bandit_camp",
    "twin_doors",
    "witch",
)

3. Python Scene Title

In SCENE_TITLES, add this row:

    "bandit_camp": "Bandit Camp",

4. Python Scene Dispatcher

In _run_scene(), add this branch between the forest and twin_doors branches:

    elif scene_id == "bandit_camp":
        bandit_camp_scene(player)

5. Python Scene Function

Add this new function near the other scene functions in text_adventure/story.py:

def bandit_camp_scene(player):
    """Optional camp encounter after the forest."""
    say("\nA campfire flickers beside the trail.")
    say("A bandit steps out with a crooked dagger.", "beat")

    if fight_or_run("\nThe bandit blocks the road. Do you fight or run? ") == "run":
        say("\nYou sprint through the brush and drop $5 in the panic.", "beat")
        player["money"] = max(0, player["money"] - 5)
        return

    spell_fight("bandit", player)
    reward = random.randint(8, 14)
    player["money"] += reward
    say(f"\nYou search the camp and find ${reward}.", "beat")
    print_stats(player)
    offer_potions(player)

6. Browser Monster

In web/app.js, add this inside const MONSTERS = {:

    bandit: {
      health: 28,
      damage: 8,
      attacks: ["rusty dagger", "cheap shot", "boot kick"],
    },

7. Browser Scene Order

In web/app.js, update SCENE_ORDER so the new scene is after "forest":

  const SCENE_ORDER = [
    "intro",
    "wizard",
    "locked_door",
    "first_goblin",
    "village",
    "forest",
    "bandit_camp",
    "twin_doors",
    "witch",
  ];

8. Browser Scene Title

In SCENE_TITLES, add this row:

    bandit_camp: "Bandit Camp",

9. Browser Scene Dispatcher

In async runScene(sceneId, player, shopStock), add this branch between the forest and twin_doors branches:

      } else if (sceneId === "bandit_camp") {
        await this.banditCampScene(player);

10. Browser Scene Method

Add this new method near the other scene methods in web/app.js:

    async banditCampScene(player) {
      this.say("\nA campfire flickers beside the trail.");
      this.say("A bandit steps out with a crooked dagger.");

      if (await this.fightOrRun("\nThe bandit blocks the road. Do you fight or run? ") === "run") {
        this.say("\nYou sprint through the brush and drop $5 in the panic.");
        player.money = Math.max(0, player.money - 5);
        return;
      }

      await this.spellFight("bandit", player);
      const reward = randomInt(8, 14);
      player.money += reward;
      this.say(`\nYou search the camp and find $${reward}.`);
      this.printStats(player);
      await this.offerPotions(player);
    }

Test Your Changes

  1. Run the Python game with fast text: TEXT_ADVENTURE_SPEED=instant python3 main.py.
  2. Start a new game and play until the new scene.
  3. At the fight, choose fight, then try Basic Attack and at least one spell.
  4. Test the run path if your scene has one.
  5. Open index.html in a browser and repeat the same story path on the website version.
  6. If you inserted the scene between existing checkpoints, start a new game for the cleanest test. Old saves continue from whatever next_scene they already stored.

Common Mistakes