๐Ÿ“– Adventure Terminal Documentation

Complete guide: playing, uploading, writing games, and using Python callbacks.

Jump to: โšก Quickstart โ–ถ Playing โฌ† Uploading โœ Editor ๐Ÿ† Gallery โœ Writing a Game ๐Ÿ“š Examples ๐Ÿ”— Callbacks ๐Ÿ Python Cookbook ๐Ÿ”ง Troubleshooting

โšก Quickstart

New here? These two cards get you running in under a minute.

โ–ถ Play an existing game

  1. Open the Terminal page.
  2. Pick a game from the dropdown.
  3. Click โ–ถ Play, then type commands and press Enter.

โฌ† Upload your own game

  1. Create mygame.txt (game definition) and mygame.py (Python runner).
  2. Click โฌ† Upload and drop both files.
  3. Select your game from the dropdown and click โ–ถ Play.

โœ๏ธ Write in the browser

  1. Go to ๐Ÿ  Home and create your project.
  2. Open the โœ๏ธ Editor and create my_game.txt and my_game.py.
  3. Click โ–ถ Run โ€” the Terminal opens and plays automatically.

๐Ÿ† Explore the Gallery

  1. Go to ๐Ÿ† Gallery.
  2. Click โ–ถ Play on any card to play it instantly.
  3. Click Copy to My Project on an example to use it as a starting point.
๐Ÿ’ก Minimal .py launcher โ€” your .py file can be as short as two lines:
from advengine import Game
Game('mygame.txt').run()

1 Playing a Game

The toolbar at the top of the Terminal page controls everything:

๐ŸŽฎ Adventure Terminal โ–ถ Play โ–  Stop โฌ† Upload
  1. Pick a game from the dropdown menu.
  2. Click โ–ถ Play โ€” the game starts in the terminal below.
  3. Read what the game tells you, then type a command and press Enter.
  4. Click โ–  Stop to quit at any time.
๐Ÿ’ก Clear on input: Check Clear on input in the toolbar to wipe the screen before each command. Many students find this keeps the display tidy.

In-game commands

Commands are typed at the > prompt. They are not case-sensitive.

CommandWhat it does
lookDescribe the current room again.
go north / south / east / westMove in that direction (also: n s e w).
take <thing>Pick up a thing in the room.
drop <thing>Leave a thing in the current room.
use <thing>Use a thing from your inventory.
attack <person>Fight a person (if they have hp > 0).
talk <person>Talk to a person.
check selfShow health, inventory, and score.
save / loadSave or restore progress.
menu / mShow all available commands.
quit / qExit the game.

2 Uploading Your Own Game

A game consists of two files with matching names:

โš  Both files are required. The game cannot start without the .txt definition.
  1. Click โฌ† Upload in the toolbar.
  2. Drag and drop both files onto the upload area โ€” or click to browse.
  3. A green checkmark confirms each file was received.
  4. Close the dialog. Your game now appears in the dropdown.
โœ“ File rules: Only .py and .txt files accepted. Maximum 2 MB per file. Names may only contain letters, numbers, hyphens, underscores, and dots.

5 Writing a Game

The .txt game file uses an indented hierarchy. Top-level entries use no indentation; their properties are indented with one tab; sub-items (exits, things inside a location) are also indented one tab, and their properties take two tabs.

โš  Use real tab characters, not spaces. Most text editors let you press Tab to insert a tab. Spaces will silently be ignored by the parser.

Game header

title: My Adventure
description: A short game to learn the engine.
start: Forest Path       # name of the first location
end: Treasure Chamber  # reaching this location wins the game

player:
	health: 100
	strength: 10
	max_weight: 20

Places (locations)

place: Forest Path
	# Properties indented one tab
	description: A winding dirt path through tall oaks. Sunlight filters through.
	image: forest.txt        # optional ASCII art file
	play: t120 ~triangle v6 c4q e4q g4q r  # optional music loop

	exit: north to Old Mill
	exit: east to River Crossing

	thing: lantern
		description: A brass oil lantern. Its flame is steady.
		weight: 2

	person: wolf
		description: A gray wolf with yellow eyes.
		hp: 20
		conflict_damage: 4

Entity keywords

KeywordSynonymsUse forKey properties
place:location:A room or area the player can be indescription, image, play
exit:direction:A way to move from one place to anotherto, hidden, locked, key, description
thing:item:, object:Objects the player can carry or interact withweight, heals, attack_power, play
person:npc:, character:Characters โ€” peaceful or hostile depending on attributeshp (set 0 for peaceful), conflict_damage

The play: field (synthesised music)

Any place or thing can have a play: field. For places the music plays on entry; for things it plays when the player uses them.

play: t120 ~sine v7 c4q e4q g4q c5dh r   # loops forever
play: t160 ~square v8 c4s e4s g4s c5q r3  # repeats 3 times then stops
TokenMeaningExample
t<bpm>Set tempo (beats per minute)t120
~waveWaveform: sine triangle square sawtooth~triangle
v<0-9>Volume (0 = silent, 9 = loudest)v6
<note><oct><dur>Note: aโ€“g, optional #/b, octave 0โ€“8, duration w h q e s tc4q f#5e
d suffixDotted note (ร—1.5 duration)c4dq
s<dur>Silence (rest)sq
r<n>Repeat preceding section n timesr3
rLoop forever (until player presses Enter)r

ASCII art in descriptions

Wrap art in --- fences inside a description:. No indentation needed inside the fence.

place: Dragon's Lair
	description: You enter the cave. ...The heat is intense.
---
     /\_____/\
    /  o   o  \
   ( ==  ^  == )
---
	A dragon eyes you suspiciously.
๐Ÿ’ก ASCII art tool: Visit the Utilities page to convert any image into ASCII art you can paste directly into a description.

The .py runner

from advengine import Game

# Minimal โ€” just load and run
Game('mygame.txt').run()

# Or save the game object to attach callbacks first
game = Game('mygame.txt')
game.add_callback('use', 'magic wand', wand_callback)
game.run()

6 Example Gallery

Seventeen ready-to-run examples ship with the engine. Load any of them from the dropdown on the Terminal page. Each file pair is in the examples/ folder.

#TitleWhat it demonstrates
01 Minimal The fewest lines possible: two rooms, one exit, one thing. Start here.
02 Items Picking up, dropping, and using items. Inventory weight limits. takedropuse
03 Hidden Secrets Revealing hidden objects and concealed exits when conditions are met. hiddenreveal()
04 Combat Fighting persons with hp and conflict_damage. Defeat scoring. personattackhp
05 Dramatic Pauses Using ... in descriptions to create suspense and pacing. storytelling
06 Callbacks Adding custom Python code that fires when the player does something. Core feature of the engine. add_callbackuse
07 Multiple Enemies Rooms with several hostile persons. Organizing combat encounters. persondefeat_score
08 Complex Puzzles Chained multi-step puzzles: unlock door A with key B found behind secret C. hiddencallbacks
09 Fatigue System Health decreases each move, creating urgency. Time-pressure mechanics. fatiguehealth
10 Complete Adventure Full game combining combat, puzzles, ANSI color, high scores, and multiple paths. showcase
11 Space Exploration Sci-fi themed adventure. A captain exploring alien worlds. themecallbacks
12 Treasure Hunt Score tracking, high score file, callbacks that award points on treasure pickup. scorehighscorescallbacks
13 Color Quest ANSI 256-color and 24-bit color in game text. Using ansi_colors.py. ansicolor
14 Color Markup Inline color markup: [red]warning![/red] in description text. color markup
15 ASCII Art (mono) Embedding large monochrome ASCII art in room descriptions. ascii artimage
16 ASCII Art (color) Same art as #15 but with ANSI 24-bit color added via the Utilities converter. ascii artansi
17 Sound Demo Synthesised music with the play: field on locations and items. The Enchanted Conservatory puzzle. play:music
18 Haunted House A full 18-room classic adventure. Navigate a condemned Victorian house, survive the killer, rescue the crying boy, and escape before your health runs out. Features room ASCII art, health-draining fatigue, and Python callbacks. full gamefatiguecallbacksASCII artpictures

7 Callback System

Callbacks let you attach custom Python functions to in-game events. The function is called automatically whenever the player performs the matching action. This is how you add puzzles, scoring, conversations, and any custom logic beyond what the .txt file alone can express.

Registering a callback

# Specific: fires only when the player uses the 'golden key'
game.add_callback('use', 'golden key', use_golden_key)

# Generic: fires when the player uses ANY item
game.add_callback('use', use_any_item)

# Move: fires when the player moves (any direction)
game.add_callback('move', on_move)

Callback function signature

def my_callback(game, command, target):
    # game    โ€” the Game object (access player, locations, etc.)
    # command โ€” string like 'use', 'take', 'drop', 'move'
    # target  โ€” the thing name, direction, or place name
    print("Custom logic here!")
    return True   # True = suppress default engine action
                  # False = let engine also handle the command

Hookable events

EventFires whenโ€ฆtarget value
usePlayer types use <thing>thing name
takePlayer types take <thing>thing name
dropPlayer types drop <thing>thing name
movePlayer moves to a new roomdirection string ('north' etc.)

Useful game object attributes

ExpressionWhat you get
game.player.healthCurrent player health (int)
game.player.max_healthMaximum player health (int)
game.player.scoreCurrent score (int)
game.player.inventoryList of Thing objects in inventory
game.player.has_thing("sword")True if "sword" is in inventory
game.player.current_locationThe current Location object
game.player.current_location.get_thing("key")Find a Thing in the current room
game.player.current_location.get_exit("north")Find an Exit in the current room
thing.reveal()Make a hidden thing visible
exit.reveal()Make a hidden exit visible

8 Python Callback Cookbook

Each entry below shows a self-contained callback snippet illustrating one Python topic in a game context. Click any topic to expand it. Drop the code into your .py file โ€” it's ready to adapt!

1 Printing โ€” display extra flavor text when the player examines a torch

print() is how callbacks communicate with the player. You can print multiple lines, use escape sequences for spacing, and combine it with f-strings for dynamic messages. Return True from the callback to prevent the engine from also printing its default description.

from advengine import Game

def examine_torch(game, command, thing_name):
    print("\nThe torch flickers in a sudden draught.")
    print("Its orange glow illuminates carved runes on the wall:")
    print("  'NORTH LEADS TO PERIL โ€” SOUTH TO REFUGE'")
    print()  # blank line for spacing
    return True  # suppress the default "You use the torch." message

game = Game('mygame.txt')
game.add_callback('use', 'torch', examine_torch)
game.run()
2 Variables & Types โ€” count gold coins collected with an integer counter

Variables outside your functions are module-level variables that persist across the whole game session. Python has several built-in types: int for counting, str for text, bool for True/False flags, and float for decimal numbers.

from advengine import Game

gold_count = 0       # int โ€” counts coins
player_title = ""   # str โ€” earned title
has_crown = False   # bool โ€” tracks a one-time event

def take_coin(game, command, thing_name):
    global gold_count, player_title
    gold_count += 1
    if gold_count == 1:
        player_title = "Apprentice"
    elif gold_count == 5:
        player_title = "Treasure Seeker"
    elif gold_count >= 10:
        player_title = "Master of Gold"
    print(f"\nCoins collected: {gold_count}  |  Title: {player_title}")
    return False  # also let the engine pick up the coin normally

game = Game('mygame.txt')
game.add_callback('take', 'gold coin', take_coin)
game.run()
3 User Input โ€” a riddle door that asks a question before opening

input() pauses the game and waits for the player to type something, then returns it as a str. Use .strip().lower() to normalize the answer so capitalisation and extra spaces don't matter.

from advengine import Game

def use_riddle_door(game, command, thing_name):
    print("\nThe stone door rumbles. A voice echoes:")
    print("  'What has teeth but cannot bite?'")
    answer = input("Your answer: ").strip().lower()

    if answer == "comb":
        print("The door grinds open! 'Correct,' the voice whispers.")
        door = game.player.current_location.get_thing("riddle door")
        north = game.player.current_location.get_exit("north")
        if north:
            north.reveal()
    else:
        print(f"'{answer}?' the door cackles. 'Wrong! Try again.'")
    return True

game = Game('mygame.txt')
game.add_callback('use', 'riddle door', use_riddle_door)
game.run()
4 Math Operators โ€” a healing potion that restores exactly 25% of max health

Python's arithmetic operators โ€” + - * / // % ** โ€” work naturally on integers and floats. Integer division (//) is handy when you need a whole number result (e.g., a quarter of max health). min() / max() prevent values from going out of range.

from advengine import Game

def drink_potion(game, command, thing_name):
    max_hp = game.player.max_health
    current_hp = game.player.health

    heal_amount = max_hp // 4        # integer division: 25% of max
    new_hp = current_hp + heal_amount  # addition
    new_hp = min(new_hp, max_hp)      # cap at max โ€” can't overheal
    actual_healed = new_hp - current_hp

    game.player.health = new_hp
    print(f"\nYou drink the potion. Healed {actual_healed} HP.")
    print(f"Health: {new_hp} / {max_hp}")
    return True

game = Game('mygame.txt')
game.add_callback('use', 'potion', drink_potion)
game.run()
5 String Operators โ€” greet a king with a personalized banner built from strings

Strings support + for concatenation, * for repetition, and f-strings (f"...") for embedding expressions. Common methods: .upper(), .lower(), .strip(), .replace(), .split().

from advengine import Game

hero_name = "Adventurer"   # could be set from input() earlier

def talk_to_king(game, command, thing_name):
    border = "*" * 40          # string repetition
    greeting = "Hail, " + hero_name + "!"  # concatenation
    title = hero_name.upper()  # string method

    print(f"\n{border}")
    print(f"  {greeting}")
    print(f"  'We have been expecting you, {title}.'")
    print(f"  'The kingdom is yours to save.'")
    print(border)
    return True

game = Game('mygame.txt')
game.add_callback('talk', 'king', talk_to_king)
game.run()
6 Comments โ€” document a multi-phase mill puzzle so teammates can understand it

Comments (lines starting with #) are notes for human readers โ€” Python ignores them completely. Good comments explain why code does something, not just what it does. Docstrings ("""...""" right after a def) describe what a function does and appear in help tools.

from advengine import Game

# Phase tracking: False = puzzle not yet solved
lever_pulled = False

def use_lever(game, command, thing_name):
    """Pull the mill lever to grind the grain and reveal the hidden passage."""
    global lever_pulled

    # Once pulled, the lever is stuck โ€” don't allow a second pull
    if lever_pulled:
        print("The lever is already in position.")
        return True

    # Mark the puzzle phase as complete
    lever_pulled = True

    print("\nThe millstone grinds to life โ€” revealing a hidden passage below!")

    # Reveal the concealed exit so the player can go down
    passage = game.player.current_location.get_exit("down")
    if passage:
        passage.reveal()

    return True

game = Game('mygame.txt')
game.add_callback('use', 'lever', use_lever)
game.run()
7 Comparison & Logical Operators โ€” a drawbridge that only opens if you have the key AND enough health

Comparison operators (== != < > <= >=) return True or False. Logical operators (and or not) combine those booleans. in tests membership in a sequence.

from advengine import Game

def use_drawbridge(game, command, thing_name):
    has_key   = game.player.has_thing("iron key")    # bool
    low_hp    = game.player.health < 20              # comparison
    full_hp   = game.player.health == game.player.max_health

    if has_key and not low_hp:
        print("You unlock the drawbridge and stride across confidently.")
        game.player.current_location.get_exit("north").reveal()
    elif has_key and low_hp:
        print("You limp across the drawbridge. Better find a healer soon!")
        game.player.current_location.get_exit("north").reveal()
    elif not has_key:
        print("The drawbridge is locked. You need an iron key.")
    return True

game = Game('mygame.txt')
game.add_callback('use', 'drawbridge', use_drawbridge)
game.run()
8 Floating Point & Rounding โ€” show health as a percentage with one decimal place

Division (/) always returns a float in Python 3. Use round(value, digits) to control decimal places. Be aware of floating-point imprecision: 0.1 + 0.2 is 0.30000000000000004 โ€” round() is your friend.

from advengine import Game

def check_vitals(game, command, thing_name):
    hp = game.player.health
    max_hp = game.player.max_health

    ratio = hp / max_hp          # float division, e.g. 0.6666...
    pct   = round(ratio * 100, 1)  # e.g. 66.7

    # Determine status string based on percentage
    if pct >= 75.0:
        status = "Excellent condition"
    elif pct >= 50.0:
        status = "Lightly wounded"
    elif pct >= 25.0:
        status = "Badly hurt"
    else:
        status = "Near death!"

    print(f"\n[Vitals] HP: {hp}/{max_hp}  ({pct}%)  โ€” {status}")
    return True

game = Game('mygame.txt')
game.add_callback('use', 'mirror', check_vitals)
game.run()
9 While Loops โ€” an oracle that keeps asking riddles until you answer correctly

A while loop repeats as long as its condition is True. It's useful when you don't know in advance how many times to repeat โ€” for example, retrying until the player gives the right answer.

from advengine import Game

RIDDLES = [
    ("What runs but has no legs?",              "water"),
    ("What has a head but no face?",            "coin"),
    ("The more you take, the more you leave behind.", "footsteps"),
]

def consult_oracle(game, command, thing_name):
    print("\nThe oracle speaks: 'Answer my riddles to earn a reward.'")
    score = 0
    i = 0
    while i < len(RIDDLES):
        question, correct = RIDDLES[i]
        answer = input(f"Riddle {i+1}: {question}\n> ").strip().lower()
        if answer == correct:
            print("'Correct!' the oracle chimes.")
            score += 1
        else:
            print(f"'Wrong โ€” the answer was {correct!r}.'")
        i += 1

    print(f"\nYou scored {score} / {len(RIDDLES)}.")
    if score == len(RIDDLES):
        print("'Worthy!' A gem appears at your feet.")
    return True

game = Game('mygame.txt')
game.add_callback('talk', 'oracle', consult_oracle)
game.run()
10 For Loops โ€” a crystal ball that lists every item in your inventory

A for loop iterates over any sequence โ€” a list, a string, a range. game.player.inventory is a list of Thing objects, which makes it a natural target for iteration. Use enumerate() to get both the index and value in one loop.

from advengine import Game

def use_crystal_ball(game, command, thing_name):
    inventory = game.player.inventory

    if not inventory:
        print("\nThe crystal ball shows... nothing. Your pack is empty.")
        return True

    print("\nThe crystal ball reveals your possessions:")
    total_weight = 0
    for i, item in enumerate(inventory, 1):
        print(f"  {i}. {item.name} โ€” {item.description}")
        total_weight += item.weight

    print(f"Total weight carried: {total_weight}")
    return True

game = Game('mygame.txt')
game.add_callback('use', 'crystal ball', use_crystal_ball)
game.run()
11 Break & Continue โ€” an armory scanner that finds your first weapon and skips non-weapons

break exits a loop immediately. continue skips the rest of the current iteration and moves to the next. Together they let you write efficient searches and filters inside loops.

from advengine import Game

WEAPONS = ["sword", "dagger", "bow", "axe", "spear"]

def use_armory_mirror(game, command, thing_name):
    print("\nThe armory mirror scans your kit...")
    best_weapon = None

    for item in game.player.inventory:
        if item.name not in WEAPONS:
            continue      # skip anything that isn't a weapon
        best_weapon = item.name
        break           # stop after the first weapon found

    if best_weapon:
        print(f"Your primary weapon: {best_weapon}")
    else:
        print("You carry no weapons! The dungeon ahead is dangerous.")
    return True

game = Game('mygame.txt')
game.add_callback('use', 'armory mirror', use_armory_mirror)
game.run()
12 Functions & Parameters โ€” a helper function that returns a health status string, reused by multiple callbacks

Define reusable logic in helper functions with descriptive parameters. A function is a named block of code; parameters are the inputs it accepts, and arguments are the actual values passed when calling it. Helper functions keep callbacks short and readable.

from advengine import Game

def health_status(hp, max_hp):
    """Return a descriptive string for the given health values."""
    pct = hp / max_hp * 100
    if pct > 75:  return "You feel strong and ready for anything."
    if pct > 50:  return "You have a few cuts but press on."
    if pct > 25:  return "You are badly hurt. Find a healer."
    return "You are on the brink of collapse!"

def use_mirror(game, command, thing_name):
    msg = health_status(game.player.health, game.player.max_health)
    print(f"\nThe mirror reflects your weary face. {msg}")
    return True

def enter_inn(game, command, direction):
    # Reuse the same helper function in a different callback
    msg = health_status(game.player.health, game.player.max_health)
    print(f"\nThe innkeeper looks you over. {msg}")
    return False

game = Game('mygame.txt')
game.add_callback('use', 'mirror', use_mirror)
game.add_callback('move', 'Inn', enter_inn)
game.run()
13 Namespaces in Functions โ€” a magic fountain that behaves differently on each visit using global

Variables defined inside a function are local โ€” they disappear after the function returns. Variables defined at the top of the file are global. To modify a global variable from inside a function, declare it with the global keyword first; otherwise Python creates a new local variable with the same name and the global stays unchanged.

from advengine import Game

drink_count = 0  # global โ€” tracks how many times the fountain was used

def use_fountain(game, command, thing_name):
    global drink_count  # required to modify the global variable
    drink_count += 1

    if drink_count == 1:
        print("\nYou drink. The water tastes sweet and cool.")
        game.player.health = min(game.player.health + 10, game.player.max_health)
    elif drink_count == 2:
        print("\nYou drink again. A warm tingling fills your hands.")
        game.player.health = min(game.player.health + 5, game.player.max_health)
    else:
        print("\nThe fountain has run dry. Not even a drip remains.")

    print(f"Health: {game.player.health} / {game.player.max_health}")
    return True

game = Game('mygame.txt')
game.add_callback('use', 'fountain', use_fountain)
game.run()
14 Return Values โ€” a helper function checks all vault conditions and returns True/False

A return statement sends a value back to the caller and exits the function. Functions that return True or False (predicates) are especially useful for checking game-state conditions in a readable way. Callbacks themselves return True (handled) or False (pass through).

from advengine import Game

def can_open_vault(game):
    """Return True only when all vault conditions are met."""
    has_gem    = game.player.has_thing("blue gem")
    has_scroll = game.player.has_thing("ancient scroll")
    alive      = game.player.health > 0
    return has_gem and has_scroll and alive  # returns a bool

def use_vault_door(game, command, thing_name):
    if can_open_vault(game):  # call the helper and check its return value
        print("\nThe vault door clicks and swings open!")
        east = game.player.current_location.get_exit("east")
        if east:
            east.reveal()
    else:
        print("\nThe vault door won't budge. Something is still missing...")
    return True

game = Game('mygame.txt')
game.add_callback('use', 'vault door', use_vault_door)
game.run()
15 Exceptions โ€” read a quest journal from file and handle missing or corrupt data gracefully

An exception is an error that occurs at runtime. A try / except block lets you catch specific exceptions instead of crashing. Always catch the most specific exception type you expect (e.g., FileNotFoundError, ValueError) rather than using a bare except:.

import json
from advengine import Game

NOTES_FILE = "quest_notes.json"

def use_journal(game, command, thing_name):
    try:
        with open(NOTES_FILE) as f:
            data = json.load(f)
        print("\n=== Quest Journal ===")
        for entry in data["entries"]:
            print(f"  โ€ข {entry}")
    except FileNotFoundError:
        print("\nThe journal is blank โ€” no entries recorded yet.")
    except json.JSONDecodeError:
        print("\nThe journal pages are smudged and unreadable.")
    except KeyError:
        print("\nThe journal format is unfamiliar. Nothing to read.")
    return True

game = Game('mygame.txt')
game.add_callback('use', 'journal', use_journal)
game.run()
16 Tuples โ€” track the player's (x, y) grid position as an immutable coordinate pair

A tuple is an ordered, immutable sequence: once created it cannot be changed. This makes tuples ideal for fixed-structure data like coordinates, RGB colors, or paired values you want to unpack in one line: x, y = position. Tuples use parentheses: (x, y).

from advengine import Game

# Map each direction to a (dx, dy) offset โ€” tuples are immutable
COMPASS = {
    "north": (0,  1),
    "south": (0, -1),
    "east":  (1,  0),
    "west":  (-1, 0),
}

player_pos = (0, 0)  # starting position as a tuple

def track_position(game, command, direction):
    global player_pos
    dx, dy = COMPASS.get(direction, (0, 0))  # unpack the tuple
    x, y = player_pos                           # unpack current position
    player_pos = (x + dx, y + dy)               # create a new tuple
    print(f"  [Map position: {player_pos}]")
    return False

game = Game('mygame.txt')
game.add_callback('move', track_position)
game.run()
17 Lists โ€” record every room the player visits in an exploration log

A list is an ordered, mutable sequence of values. Unlike tuples, you can add, remove, and change items after creation. Lists are created with square brackets: [1, 2, 3]. Use in to test membership and len() for the count.

from advengine import Game

visited_rooms = []  # mutable list โ€” grows as the player explores

def log_visit(game, command, direction):
    location_name = game.player.current_location.name

    if location_name not in visited_rooms:  # membership test
        visited_rooms.append(location_name)
        print(f"  [New discovery! Rooms explored: {len(visited_rooms)}]")
    return False

def use_explorer_badge(game, command, thing_name):
    print("\nRooms you have explored:")
    for room in visited_rooms:
        print(f"  โœ“ {room}")
    print(f"Total: {len(visited_rooms)}")
    return True

game = Game('mygame.txt')
game.add_callback('move', log_visit)
game.add_callback('use', 'explorer badge', use_explorer_badge)
game.run()
18 For Loops & Lists โ€” a cursed altar that checks your inventory for dangerous items

Combining for loops with lists is one of Python's most used patterns. You can loop over a list to search, filter, transform, or accumulate values. Building a new list inside the loop (instead of modifying the original while iterating) avoids subtle bugs.

from advengine import Game

CURSED_KEYWORDS = ["skull", "shadow", "cursed", "dark"]

def use_altar(game, command, thing_name):
    print("\nThe altar glows blue, scanning your belongings...")

    cursed_items = []  # build a new list of matches
    for item in game.player.inventory:
        for keyword in CURSED_KEYWORDS:
            if keyword in item.name.lower():
                cursed_items.append(item.name)
                break  # only add the item once even if multiple keywords match

    if cursed_items:
        print("Cursed objects detected:")
        for name in cursed_items:
            print(f"  โš  {name}")
        print("The altar trembles with disapproval.")
    else:
        print("Your pack is clean. The altar hums approvingly.")
    return True

game = Game('mygame.txt')
game.add_callback('use', 'altar', use_altar)
game.run()
19 List Methods โ€” a quest log that uses append, sort, and remove to manage entries

Lists have many built-in methods: .append(x) adds to the end, .remove(x) deletes the first occurrence, .sort() sorts in place, .pop() removes and returns the last item, .index(x) finds a position, and .count(x) counts occurrences. These all modify the list in place โ€” no assignment needed.

from advengine import Game

quest_log = []  # shared list โ€” no global needed if we only mutate, not reassign

def take_map(game, command, thing_name):
    quest_log.append("Found the treasure map")       # append
    quest_log.sort()                                    # sort alphabetically
    print(f"Quest log updated ({len(quest_log)} entries).")
    return False

def take_key(game, command, thing_name):
    quest_log.append("Obtained the bronze key")
    quest_log.sort()
    print(f"Quest log updated ({len(quest_log)} entries).")
    return False

def use_campfire(game, command, thing_name):
    print("\n=== Quest Log ===")
    if not quest_log:
        print("  (empty)")
    for i, entry in enumerate(quest_log, 1):
        print(f"  {i}. {entry}")
    return True

game = Game('mygame.txt')
game.add_callback('take', 'map', take_map)
game.add_callback('take', 'bronze key', take_key)
game.add_callback('use', 'campfire', use_campfire)
game.run()
20 File I/O โ€” a trophy room that saves and loads a high-score table from a JSON file

Use the built-in open() function to read and write files. The with statement ensures the file is closed automatically. json is the standard library module for reading and writing JSON โ€” a simple, human-readable format for structured data. Always use try / except around file operations.

import json
from advengine import Game

SCORE_FILE = "trophy_scores.json"

def load_scores():
    """Read scores from file; return [] if file missing or invalid."""
    try:
        with open(SCORE_FILE) as f:
            return json.load(f)
    except (FileNotFoundError, json.JSONDecodeError):
        return []

def save_score(name, score):
    """Append a new score, sort descending, keep only top 10, write to file."""
    scores = load_scores()
    scores.append({"name": name, "score": score})
    scores.sort(key=lambda s: s["score"], reverse=True)
    with open(SCORE_FILE, "w") as f:
        json.dump(scores[:10], f, indent=2)  # write top 10 only

def use_trophy(game, command, thing_name):
    name = input("Enter your name for the scoreboard: ").strip()
    if name:
        save_score(name, game.player.score)
        print(f"Score of {game.player.score} saved!")

    print("\nโ”€โ”€ High Scores โ”€โ”€")
    for i, entry in enumerate(load_scores(), 1):
        print(f"  {i:2}. {entry['name']:<15}  {entry['score']}")
    return True

game = Game('mygame.txt')
game.add_callback('use', 'trophy', use_trophy)
game.run()

9 Troubleshooting

ProblemFix
Game not in the dropdown Upload the .py file (it needs a few seconds to appear), then re-open the Upload dialog and close it to refresh.
"Did you forget to upload the .txt file?" error Upload the matching .txt definition โ€” it must have the same base name as the .py file.
play: field is silent / empty Check that the line uses a real tab character for indentation, not spaces. Spaces are silently ignored by the parser.
Can't type in the terminal Click inside the black terminal area first to focus it.
Screen looks garbled / misaligned Resize the browser window slightly โ€” this forces the terminal to recalculate its layout.
Callback fires but the target doesn't match The target string must exactly match the thing's name in the .txt file (case-insensitive, but spelling matters).
Global variable not updating between calls Add global my_variable as the first line inside the function whenever you assign to a module-level variable.
Game hangs / won't stop Click โ–  Stop to force-quit the Python process.