The editor
Python
The box on the right is real Python with a fence around it. Lessons only ask for one call at a time. This page is the rest of what that box will run: the buttons, the two ways a snippet is treated, every builtin, and every word it refuses.
The panel
Creation, town, the wilds, and a fight all use the same box. It sits on the right. The text area blinks a caret. A grey line is the current lesson’s example. It is a hint, not code, until you type it. The equip lesson is the exception: it writes your class weapon into the box for you.
Under the text is an output pane. Run starts it with the word Running and a copy of what you sent. A failure turns that pane red and says Code Error. The longer story — look, combat, gather — also lands in the status log under the map. You can select that log and copy it. When you change rooms, the last snippet is put back in the box.
Escape pauses in town, the wilds, and combat. It does nothing on the title, on creation, or inside Settings. Pause offers Resume, Settings, and Quit to Title. Settings opened from pause returns you to the same room and opens pause again. Quit on the title closes the window. Quit to Title does not.
Two ways a snippet runs
The box does not treat every script the same. A stack of plain calls is checked one line at a time. The moment the box contains an assignment, an if, a for, a while, a def, a lambda, or a comprehension, the whole snippet switches to the Python runner. A comment or a blank line does not switch it. Creation never switches: that screen only accepts the four lesson calls, and a variable will not stand in for a quoted word.
hero.look()
hero.move_right(2)Those two lines stay on the plain path. Add one name and the same calls run as Python instead:
steps = 2
hero.look()
hero.move_right(steps)Shape of a Python snippet
Lines run from top to bottom. A colon starts a block, and the next lines indent four spaces: if, elif, else, for, while, def, and try. Mix tabs and spaces and the box reports a syntax error. A line that starts with # is a comment and does nothing. Blank lines are fine.
Names you invent live only until the snippet ends. The hero does not. A function you define is gone the next time you press Run, unless you paste it again. If a line fails, the rest of that snippet does not run. Steps that already happened stay happened. Fix the line and send it again.
The name box on creation cannot be typed in. set_name("Aldric") writes it. Back to Menu on that screen throws away a half-finished hero and returns to the title.
Values you can write
Decisions
if runs a block only when the test is true. elif tries the next test. else runs when none of them were. The test itself is not a call you have to wrap.
hp = hero.hp
if hp < 8:
hero.use("minor_hp_potion")
elif hero.in_combat():
hero.attack()
else:
hero.look()Repeating
for walks a list or a range. while repeats until its test is false. break leaves the loop early. continue skips to the next pass. A while that can never become false, and that never calls the hero, does not hit the 200-call stop. It will sit there. Give every while a counter or a test that can fail.
for n in range(3):
hero.move_right()
steps = 0
while steps < 4 and hero.hp > 0:
hero.move_down()
steps = steps + 1range(3) yields 0, then 1, then 2. It does not include 3. hero.move_right(3) is different: that one call walks three tiles.
Functions you define
def names a block you can run more than once in the same snippet. return hands a value back and leaves the function. Arguments can have defaults. lambda is a one-line function, useful for sorted or filter. It cannot hold a hero call comfortably; use def for that.
def walk(n):
for i in range(n):
hero.move_right()
walk(3)
double = lambda n: n * 2
print(double(4))try and a bare except catch a failure so the snippet can choose another call. The name Exception is not in the box, so write except: with nothing after the colon.
try:
hero.use("minor_hp_potion")
except:
hero.attack()Lists the hero hands back
Several calls return data you can store, but only on the Python path, because storing uses an assignment. inventory_list returns rows of id, name, and qty. foes returns name, hp, and max_hp, in the order attack uses. recipe_available, recipe_list, enchant_list, shop, look_stations, and skills return ids. look_nodes returns names. gather, attack, skill, and use return True or False.
Read a field with square brackets: row["id"]. A dot on that row is refused, so row.get("id") and items.append(x) do not work. Build a new list with + . A method on a value written right there is allowed, such as "Bread".lower() or (1, 2, 3)[0], because the quotes or the brackets are in the line. A method on a name you created is not.
bag = hero.inventory_list()
ids = []
for row in bag:
ids = ids + [row["id"]]
print(row["name"], row["qty"])
recipes = hero.recipe_available()
if recipes:
hero.craft(recipes[0])Builtins
These names exist only on the Python path. They are not hero calls, and they do not count toward the 200. map and filter and reversed hand back a walker, not a list. Wrap them in list() if you want to index them.
| Word | Use |
|---|---|
| Write a line into the status log. print(hero.hp) or print("ready"). | |
| range | A count for a for loop. range(3) is 0, 1, 2. range(1, 4) is 1, 2, 3. range(0, 8, 2) steps by 2. |
| len | How many items are in a string, list, dict, tuple, or set. |
| int | Whole number. int("3") is 3. int(2.9) cuts the fraction. |
| float | A number with a fraction. float("1.5") is 1.5. |
| str | Turn a value into text. str(hero.gold) is a string of digits. |
| bool | True or False. bool(0) and bool("") are False. Anything else is usually True. |
| min | The smaller of the values. min(hero.hp, 10). |
| max | The larger of the values. max(1, hero.level). |
| abs | Drop the minus sign. abs(-4) is 4. |
| round | Nearest whole number, or round(1.234, 2) for two decimals. |
| divmod | Pair of quotient and remainder. divmod(7, 3) is (2, 1). |
| pow | Power. pow(2, 3) is 8, the same as 2 ** 3. |
| sum | Add a list of numbers. sum([1, 2, 3]) is 6. |
| chr | The letter for a number. chr(65) is "A". |
| ord | The number for a letter. ord("A") is 65. |
| list | Make a list. list(range(3)) is [0, 1, 2]. |
| dict | Make a dict. dict(name="Aldric") is {"name": "Aldric"}. |
| tuple | Make a tuple, a list you do not change. tuple([1, 2]) is (1, 2). |
| set | Make a set: each value once, no order you can count on. |
| enumerate | Walk a list with a counter. for i, row in enumerate(bag): |
| zip | Walk two lists in pairs. for a, b in zip(left, right): |
| sorted | A new list, smallest first. sorted(numbers, reverse=True) flips it. |
| reversed | Walk a list from the end. Wrap it with list() if you need to store it. |
| any | True if one item is true. any(row["qty"] > 0 for row in bag) needs a script. |
| all | True only if every item is true. |
| map | Apply a function to each item. Store it with list(map(...)). |
| filter | Keep the items that pass a test. Store it with list(filter(...)). |
| isinstance | Ask the kind. isinstance(hero.hp, int) is True. |
| True | The yes value. Not a call. Written with a capital T. |
| False | The no value. Capital F. |
| None | No value yet. Capital N. A call that has nothing to give back returns None. |
What you may read on the hero
A dot is allowed on the word hero, and only one dot deep. hero.hp is your current hit points. Also plain values: hero.gold, hero.level, hero.name, hero.mana, hero.stamina, hero.x, and hero.y. x and y are the column and the row of the tile you stand on. hero.in_combat() is True while a foe is still standing.
hero.race.something and hero.equipment.something are a second dot, so they are refused. Use the list calls and square brackets instead. You can overwrite hero.hp or hero.gold with an assignment. That skips shops and damage. Play with the calls.
if hero.hp < 10 and "minor_hp_potion" in [row["id"] for row in hero.inventory_list()]:
hero.use("minor_hp_potion")
else:
hero.attack()Words the box refuses
| Word | Why |
|---|---|
| import | No modules. Everything you may use is already in the box. |
| with | Not available. Open and close things with a hero call instead. |
| async, await, yield | No paused or background functions. |
| class | A class tries to put a dot on self. The box only allows a dot on hero. |
| Names starting with _ | Private. _floor, __init__, and hero._hp are refused. |
| open, input | No files, and the box cannot stop to ask you to type. |
| exec, eval, compile | You cannot run a second hidden program. |
| help, dir, globals, locals, vars | The builtin help is blocked. Use hero.help() or Get Help. |
| getattr, setattr, hasattr, delattr | No looking up a name from a string. |
| type, object, super, property | No building new kinds of objects. |
| id, hash, repr, ascii, format | Inspection and low-level text tools are blocked. |
| bytes, bytearray, memoryview, slice | Raw memory tools are blocked. Use a list and square brackets. |
| exit, quit, breakpoint | Those stop a terminal, not this game. hero.quit() closes the window. |
| classmethod, staticmethod, callable | Not part of the fenced Python. |
A dot on any name except hero fails with “Only attributes on hero are available.” That includes a string you stored, a list you stored, and self inside a class.
How a failure reads
Three snippets
Each one is a Python snippet because it uses a name or a for. Paste one, press F5, and read the status log.
Walk a short path
for step in range(3):
hero.move_right()
hero.move_down()Drink, or swing
if hero.hp < 8:
hero.use("minor_hp_potion")
else:
hero.attack()Make the first recipe the bag can afford
recipes = hero.recipe_available()
if recipes:
hero.craft(recipes[0])
else:
print("Nothing to craft.")The lesson order, and what each screen is asking for, stays on Lessons. Every hero call, room by room, stays in the command book.