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
- Open
text_adventure/story.py. - Find the scene function where the fight should happen, such as
forest_scene(player, shop_stock). - 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
- Open
web/app.js. - Find the matching scene method, such as
async forestScene(player, shopStock). - 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
- Open
text_adventure/data.py. - Find
MONSTERS = {. - Add the new monster inside that dictionary.
"bandit": {
"health": 28,
"damage": 8,
"attacks": ["rusty dagger", "cheap shot", "boot kick"],
},
Browser Website
- Open
web/app.js. - Find
const MONSTERS = {near the top of the file. - 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
- Open
text_adventure/story.py. - Find
SCENE_ORDER = (and add your new scene id where it belongs. - Find
SCENE_TITLES = {and add the readable checkpoint title. - Find
def _run_scene(scene_id, player, shop_stock):and add anelifbranch for the new id. - Add the new scene function near the other scene functions at the bottom of the file.
Browser Checklist
- Open
web/app.js. - Find
const SCENE_ORDER = [and add the same scene id in the same position. - Find
const SCENE_TITLES = {and add the same readable title. - Find
async runScene(sceneId, player, shopStock)and add anelse ifbranch for the new id. - Add the new
asyncscene 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
- Run the Python game with fast text:
TEXT_ADVENTURE_SPEED=instant python3 main.py. - Start a new game and play until the new scene.
- At the fight, choose
fight, then tryBasic Attackand at least one spell. - Test the run path if your scene has one.
- Open
index.htmlin a browser and repeat the same story path on the website version. - If you inserted the scene between existing checkpoints, start a new game for the cleanest test. Old saves continue from whatever
next_scenethey already stored.
Common Mistakes
- Adding a scene id but no dispatcher branch: the game reaches the checkpoint, then raises an unknown story checkpoint error.
- Calling a monster key that does not exist:
spell_fight("bandit", player)requires a matching"bandit"entry in Python and a matchingbanditentry in JavaScript. - Updating Python but not the website: the terminal game works, but the browser version does not know about the new scene or monster.
- Using Python syntax in JavaScript: Python uses
player["money"] += 10; JavaScript usesplayer.money += 10;. - Using JavaScript syntax in Python: JavaScript uses
await this.spellFight("bandit", player);; Python usesspell_fight("bandit", player). - Renaming existing scene ids: encrypted saves store
next_scene. If a save points to an id you removed or renamed, it will not load cleanly. - Adding unsupported spell effects: combat currently understands
burnandstun. New effect names need combat code in bothtext_adventure/combat.pyandweb/app.js.