Zaytun Handbook

The player's command language

Everything above describes the language you write a game in. This section describes the language the player types at the prompt, which is a different language with different rules, and both runtimes are held to it exactly.

It is written out in full because it has to be. Three defects have come from it being assumed rather than stated — talk to somebody matching nobody, go in parsing as a preposition with no object, and an item's own article defeating its name — and every one of them was wrong in both runtimes at once, which is the one kind of mistake that a test comparing the two cannot possibly catch.

The grammar

command  :=  VERB [ OBJECT [ PREPOSITION OBJECT ] ]

One verb, then up to two objects with a preposition between them. unlock door with key gives the verb unlock, the object door, the preposition with and the second object key. Inside an author verb these arrive as noun, prep and other.

A preposition at the front belongs to the verb

If the first word after the verb is a preposition, it is part of the verb phrase and the object is what follows it:

talk to the smith        → verb "talk",   object "the smith"
look at the door         → verb "look",   object "the door"

This is the rule whose absence made talk to <anyone> match nobody for nine days: without it the name landed in other, the object came out empty, and the most ordinary phrasing in English did nothing at all.

Unless there is nothing after it. A preposition on its own is an object, not a word in front of one, so a room with exit in -> cellar still answers to in and to go in.

The prepositions

in  into  inside  on  onto  at  to  with  from
under  behind  over  against  about  for  through

A word in that list separates two objects — but only when there is already an object in front of it. That is what makes go in a direction and put coin in box a two-object command.

A preposition inside a name is part of the name

The same six words are two different sentences, and the verb is the only thing that says which:

put board on the door           two objects — a board, and a door to put it on
examine board on the door       one — the thing called "a board on the door"

So the line is not cut where the first preposition falls. It is cut where the sentence says it should be:

Which verbs have a second object. PUT, ASK and TAKE ALL FROM x are the built-ins that do; every other built-in ignores the second slot entirely. A verb of your own has one exactly when something in it reads other — the compiler works that out by walking the verb and every proc it calls, so verb turn { if isnumber(other) ... } keeps TURN DIAL TO 7 as two objects while a verb that never mentions other gets the whole name in noun.

This is the rule the language already keeps for and: take salt and pepper takes the one thing when a thing is called that, and take coin and rope takes two. The author's name is what the words are measured against, and a separator is what is left over.

Without it a name with a preposition inside it could not be typed at all. A room would print You see here: a board on the door, and examine board on the door would answer about some other board — or, where nothing else answered, deny that the thing was there.

Articles are ignored on both sides

A leading the, a, an, some or my is stripped from what the player typed and from the thing's own name, so all of these find a grey cloak:

equip grey cloak
equip the grey cloak
equip cloak

Stripping only one side is a real bug and this project shipped it: an item whose title carried its own article could not be named by a player who typed one.

A direction is a name like any other, so the article comes off that too — and a preposition in front of it belongs to the verb, as it does everywhere else:

go north      go the north      go to the north      steer the north

all walk north, and a hull's own word takes the same phrasing: sail to the north. A preposition on its own is still a direction, because a room with exit in -> cellar answers to in and to go in. This too was shipped the wrong way round for a while: take the rope worked, go the north did not, and the refusal handed the article back — "You can't go the north from here."

Case is ignored on both sides, in every alphabet the engine knows

TALK LÚTHIEN, talk Lúthien and talk lúthien all reach the same person, and so do ЖУРНАЛ and журнал, ΣΟΦΊΑ and Σοφία. The rule is simple lowercase folding — one character in, one character out — applied to what the player typed and to the name it is compared against, for:

Latin-1 Supplement   Latin Extended-A   Latin Extended-B
Greek                Cyrillic           Latin Extended Additional (Vietnamese)

Anything outside those blocks is passed through unchanged, and matches when it is typed as the author wrote it.

The table is in the engine, not in a library. C's tolower works a byte at a time and JavaScript's toLowerCase folds the whole of Unicode, so handing this to either would mean a game that behaves differently in a terminal and in a browser — which is what it did: EXAMINE CAFÉ found the café in one and nothing in the other. The runs are generated by zaytunplay/tools/casefold.py and held identical in both runtimes by a check.

What the player sees of their own case. The echo above a reply is the line as they typed it — > LOOK, not > look — because that is what they read on their own keyboard a moment earlier. Anything the game quotes back at them is the folded line, because it is quoting the word the lookup used: I don't understand "blorp", There's no "xyzzy" here to take, You don't see "frobnitz" here. Both runtimes do both, in both places.

Two deliberate non-folds, because one character in and one out is what keeps this a table:

upper() and lower(), the language's own two functions, are not this: they stay ASCII-only, because converting text for display is a different job from deciding which name a word reaches.

Commands that cost no time

Most commands advance the clock, and a game's on turn block runs. These do not:

help    status    inventory    topics

They report on the state of things rather than changing it. Everything else costs a turn, even when it fails — fumbling with a locked door still takes a moment.

Naming an exit moves you

Typing an exit's name on its own moves you through it, so a room with exit boat -> ... or exit through -> ... answers to boat and through as well as to go boat and go through.

room shore "The Shore" { description: "Shingle, and a boat drawn up."  exit boat -> island }
room island "The Island" { description: "Gorse and wind."  exit back -> shore }

> boat
The Island
Gorse and wind.
Exits: back.

Nobody has to guess that go boat is the phrasing; the word on the Exits: line is the word that works.

A passage opened by open exit answers to its name the same way. Nothing on the Exits: line tells the player which of those names the room declared and which the game added while it ran, so neither does the parser — see Opening and closing a way out from a script.

Unless the word is already somebody's. The first word of a line is read against the game's own verbs and then the engine's commands before any room's exits, so a way out called take, look or read is listed and never reached by its name:

room a "A" { description: "..."  exit take -> b }

> look     Exits: take.
> take     Take what?
> go take  B

The compiler warns, naming the word, what has it, and the form that still walks it:

zaytunc: a.zaytun:3: warning: room "a" has a way out called "take", and "take" is one of the
engine's own commands — the word runs the command, so the way out is listed and cannot be
walked by name; "go take" still walks it, and a verb of your own called "take" would take
the word back

It is a warning and not a refusal: the game is not wrong, and GO TAKE works. There are two ways to answer it — rename the way out, or take the word back with verb take of your own, since a verb the game declares owns its word. A verb of yours shadowing an exit is warned about the same way, and there the rename is the only answer.

The compass is not a collision: north, up and the rest already mean walk that way, which is what an exit of that name is for.

Near misses on ASK

ask <person> about <subject> matches the subject against a topic's keywords exactly, and a near miss — a plural of a keyword, or a prefix of one — is answered as a near miss rather than as a blank. The rule, and what it means for choosing keywords, is under Near misses on a topic.

> ask queen about girdles
"That I will not answer," says the Queen.
(You have the right idea. Try another word for it.)

A fight, as the player sees it

attack <enemy> (also kill, fight, hit, punch) resolves one round of combat: you hit, and if the enemy survives it hits back, unless its retaliates: says otherwise. equip <item> / unequip <item> (also wield, wear) toggle a weapon or armor piece. status (also stats, health) prints current hp, effective attack and defense, and what is equipped — useful for tracking state without a health bar, and described in full under Status.

player { hp: 20  maxhp: 20  attack: 3  defense: 1 }
item sword "a sword" { weapon: true  damage: 4  aliases: "sword" }
enemy goblin "a goblin" { hp: 9  attack: 3  defense: 0  aliases: "goblin" }

> equip sword
You equip a sword. (+4 attack)
> status
HP: 20/20  |  Attack: 7 (base 3)  |  Defense: 1 (base 1)
Equipped — weapon: a sword  |  Turns: 2
> attack goblin
You hit a goblin for 7 damage.
A goblin hits you for 2 damage. (18/20 HP)
> attack goblin
You hit a goblin for 7 damage.
You have defeated a goblin!

The 7 is the player's 3 plus the sword's 4; the 2 the goblin lands is its 3 less the player's 1.

The built-in commands

Every word the parser accepts, with its synonyms — an abbreviation is not a lesser spelling, and a player who types x lamp should find it here.

EXAMINE reaches all four kinds of thing that can be standing in a room: an item, a creature, a vehicle and a person. The first three answer with their own examine: where they have one; a person has no such field and answers with their name, which is thin but is better than the alternative — see below.

Every other verb that acts on a thing names what it found, rather than saying the thing is not there, when the noun reaches one of the other kinds:

> open car
The Car is not something you can open.
> take dog
A dog is not something you can take.
> search bel
Fisher Bel is not something you can search.

A word that really does reach nothing still gets the old you don't see it here, because that one is true. In an unlit room nothing can be named at all, a vehicle included — the room will not list it, so the game will not act on it.

commandalso
lookl
examine <thing>x
go <direction>walk, move — or just north/south/east/west/up/down, n/s/e/w/u/d
take <item>get, grab — the floor and the hand; take <item> from <container> is the box
drop <item>
inventoryinv, i
use <item>
open <thing> / close <thing>shut
read <thing>peruse
search <thing>inspect, rummage, frisk
put <item> in <container>place, insert
enter <thing>board, embark, mount, ride, aboard, climb — see Vehicles
disembarkdismount, alight — see Vehicles
steer <direction>takes the vehicle you are in that way, and never the room — see Vehicles
talk <name>speak
ask <name> about <topic>question
topics [<name>]subjects
attack <enemy>kill, fight, hit, punch
equip <item> / unequip <item>wield, wear
waitz
statusstats, health
help?
againg
save [file] / restore [file]load (for restore)
quitexit — ends the session; the browser has a button instead

HELP only offers the ones the game can actually use — see Help.

take all and drop all move everything at once, one line each, in the order the place lists them. Scenery — anything fixed: — is skipped rather than refused once per object.

One command may name several objects, separated by and or by a comma:

> take coin, rope and lamp
Taken: a coin.
Taken: a rope.
Taken: a lamp.

A name that reaches nothing is reported for that name alone and the rest still happen. Scenery named outright is refused, unlike ALL, because the player said the word.

The whole phrase is tried against the item table before it is broken up, so an item you called "a jar of salt and pepper" is still one thing: a name you wrote beats a rule the parser invented.

Every verb that acts on one thing accepts a list: TAKE, DROP, EQUIP, UNEQUIP, EXAMINE, OPEN, CLOSE, READ, SEARCH and USE. equip sword and shield fills both slots, because a weapon and a piece of armour are worn independently.

The rest take a single object on purpose, and that is a judgement about what repeating the verb would mean rather than about grammar: a turn is one blow, so ATTACK names one enemy, and talking to two people at once is not a thing. EXAMINE looks at creatures as well as things, so a creature you titled "a cat and dog" wins over the grammar the same way an item does.

Items, creatures and people all answer to the same four things: their id, their title, their title without the article it carries, and any of their aliases: — and the player may put an article in front of any of them. So attack grey wolf, attack a grey wolf and attack the wolf all reach the same creature, and talk to tired guard, talk to a tired guard and talk to the tired guard all reach the same person.

The rule lives in the resolvers rather than in each verb, so it reaches the expressions too: noun_is_npc(guard) is true for any phrasing that TALK would accept.

it means whatever was last referred to, while that thing is still to hand: EXAMINE LAMP then TAKE IT. Out of the room it is nothing again, which is the rule a name already follows.

One line may hold several commands, separated by then or by a full stop:

> open hatch then climb down
> e.n.
> take lamp. north

Each part runs in order and costs its own turn, so on turn fires once per command. The line is echoed once, as it was typed.

A chain carries on into a room it walks into — N. TAKE LAMP takes the lamp in the new room, which is the point of the feature. It stops early when the story ends, when the player dies, when a command was not understood, and when one word named two things: in each of those the rest of the line was written on an assumption that has already failed.

Two things are deliberately not cut. A full stop between digits belongs to the number, so TURN DIAL TO 1.5 reaches the game whole; and SAVE, RESTORE and LOAD keep the rest of the line, because a path may have dots in it.

again (or g) repeats the last line, chain and all. It never repeats itself, and never repeats a save.

One word that names two things is a question, not a guess. A brass key and an iron key both answering to "key" used to mean TAKE KEY silently took whichever was declared first:

> take key
Which do you mean: a brass key or an iron key?

The question is asked by title, so the titles have to differ. Two things in one room both called a key produce a choice with one option in it, twice — Which do you mean: a key or a key? — and there is no word the player can type to settle it, because an id is not a word the player has: take keyA is answered with There's no "keya" here to take. Nothing about that is the engine's to fix; it is a room written so that the player cannot see two things apart, and the answer is to give them the names they should have had. An alias of its own is enough to make each one reachable — aliases: "brass" and aliases: "iron" — but the You see here: line will still read a key, a key, so distinct titles are the thing to write.

None of these is anything a game declares, so no game can add one — but a game can take the word. again, g, save, restore and load are read before the verb table, which is what makes them meta, and each of those interceptions asks the game's verbs first: verb save { ... } is yours, and this game then has no SAVE command. See A verb of your own owns its word.

quit and exit are the two exceptions, and the reason is mechanical rather than a matter of taste: they are read by the program's input loop, above the story entirely, so a verb of either name would compile and then never run — the silent failure the whole rule exists to prevent. There is always a way to stop playing. An alias is the same word as far as this goes: verb flee { aliases: "exit" } is refused too.

There is no undo. Going back is save and restore, which a player asks for deliberately. A game that wants a death to be recoverable says so itself — an on death block that puts the player somewhere and sets the world back is a few lines, and it is the author's decision how forgiving the story is rather than the runtime's.

undo is not a reserved word, so a game may define verb undo and mean whatever it likes by it.