-- everything is a location, an object or a timer --
This document gives a general overview of the XVAN Interactive Fiction
Authoring System. It describes the basic concepts in terms of locations,
objects and timers and also addresses the vocabulary and actions.
Examples are throughout the text.
A tutorial that describes how to create an XVAN story from scratch is
available in a separate document.
Apart from the tutorial there are (of course) a Cloak of Darkness
implementation and a medium-size sample story called Escape!. Escape!
Was entered in the Sprint Thing 2019 competition and received badges for
best NPC and best puzzles.
Platforms – Linux, Windows and macOS
Common Descriptions, Flags, Attributes and Triggers
Wildcards with (common) triggers and verbs
Predefined descriptions, flags, attributes and triggers
XVAN is an interpreter-based interactive fiction authoring system. It is
a compiler, an interpreter and an authoring language.
The XVAN compiler takes a story source file written in the XVAN language
and creates a binary game file. The interpreter reads the game file and
lets the user play the story.
The XVAN
compiler is
a console application with a command line interface. The XVAN
interpreter comes
in 3 versions: a console version, a Glk version and as a back-end that
is used with a separate GUI: IFI-XVAN.
The first version of XVAN was built in the 90’s of the previous century,
when internal computer memory was not sheer unlimited (my Atari has 1 MB
of RAM which was a lot back then). I therefore built it to be low on
memory requirements. A flag, for example, occupies 1 bit of internal
memory and XVAN has a mechanism for swapping objects and locations in
and out of internal memory.
XVAN is written in C. The console version of compiler and interpreter
does not use any external libraries or interfaces other than the
standard C include files.
The Glk version of the interpreter uses the Glk libraries. Glk is mainly intended for applications with text user interfaces (like interactive fiction). It gives more possibilities than a console or terminal window. More information on Glk can be found at http://www.eblong.com/zarf/glk/.
As of version 2.3.4, IFI-XVAN is available. This version of the
interpreter uses the IFI (Interactive Fiction Interface) to connect to
the Brahman GUI, which supports in-game graphics, graphical map display,
clickable links etc. Both desktops and mobile devices are supported.
More information on IFI and Brahman can be found at
Strandgames' blog.
XVAN started its life on my Atari 1040st back in the 90s. After the
Atari went obsolete, I ported XVAN to Windows. I ran it on W95 and XP,
and currently on W7 and W10.
I also made a version for Linux. I used the Linux Mint Rebecca
distribution.
I made macOS versions on High Sierra.
As of version 2.3.4, there is IFI-XVAN that runs on Windows, Linux, and
macOS.
Binaries and save files are portable between the operating systems. What
you compile or save on one platform will work on the others.
My sample game Escape! Was tested on windows with NVDA by a
vision-impaired person. I tested Escape! On linux Mint with the Orca
screenreader and it seems to work.
XVAN is designed around Locations, Objects and Timers. In XVAN story
source files there is no main flow, it’s just the collection of a number
of locations, objects and timers in one or more text files. At playtime,
the user input is offered to each location and object that is in scope
and each decides whether and how to respond to the input.
Locations and objects have a number of artifacts available that they can
use and manipulate:
The XVAN world model is made up of a number of locations that can be
connected, so the player can travel through the world. Locations cannot
contain other locations but they can contain objects (for objects, see
below). Anything tangible that is not contained in something else is a
location.
Example:
$LOCATION l_kitchen
DESCRIPTIONS
d_sys “the kitchen”, “the dining room”
END_LOC
l_kitchen is the
location identifier
that is used in the source code to refer to the kitchen. d_sys is the so
called
system description,
a list of friendly names for the location.
Objects are items in the world that can be manipulated. Objects can be
moved around and may contain other objects. At the top level, an object
is contained in a location.
Example:
$OBJECT o_lamp
DESCRIPTIONS
d_sys “the lamp”, “the flashlight”, “the brass lantern”
CONTAINED on o_table
END_OBJ
o_lamp is the
object identifier
that is used in the source code to refer to the lamp. The
contained line
tells which object or location holds the lamp. In this example, the lamp
object is
on the
table object. The table object is defined elsewhere in the source and is
referred to by its
object identifier here.
XVAN has four predefined objects:
Timers are used to trigger events at certain moments. The moment is
determined by a threshold value. At the end of each turn, all timers
are updated according to the instructions stored with the timer. If the
threshold value is reached, the timer will trigger an associated chain
of actions. Timer
ids have
format m_timername.
m_battery
value 100
step 1
direction down
interval 1
state go
trigger_at 0
execute o_lamp.t_empty
This timer will count down until it reaches zero and then executes the
lamp's t_empty trigger.
XVAN has no default start routine when the interpreter starts , but a
timer can be used to kick off the story.
Create a timer with value 0
that triggers at value 1. After the interpreter starts the story, the
timers get updated and the timer initiates a series of actions that display
the opening screen etc.
value 0
step 1
direction up
interval 1
state go
trigger_at 1
execute o_player.t_init
Trigger t_init will print the game's opening message, start the moves
counter and perform other initial activities for the story.
In order to run the game, locations and objects use Descriptions, Flags,
Attributes and Triggers.
Action records will be described later on in this document.
Descriptions are blocks of text. A
description identifier is
used to refer to the description in the source code.
Description identifiers have
format d_descriptionname.
Example:
d_exa_locked "The old chest seems to be locked."
About text strings
So, as we are creating Interactive Fiction works, text will be an
important part of the story. Text strings can be long, which makes the
story’s source code harder to read. XVAN has a number of mechanisms to
format text strings so the story source is easier to read, without
affecting the way how the string is printed.
Each string must end with a “ or a /:
All carriage returns and spaces after a ‘/’ will be ignored up to the
next non-<cr>-or-space character.
Example:
d_long_descr | " | This is a very very long description that / |
goes on and on and on over several / | ||
lines in the source file, but it will print / | ||
as one line on the screen.” |
Will print as:
This is a very very long description that goes on and on and on over
several lines in the source file, but it will print as one line on the
screen.”
The same effect can be achieved by ending the string with an end quote
and start a new string on the next line. Consecutive strings will be
combined to 1 string.
Example:
d_long_descr | "This is a very very long description that” |
“ goes on and on and on over several“ | |
“ lines in the source file, but it will print” | |
“ as one line on the screen.” |
Will print as:
This is a very very long description that goes on and on and on over
several lines in the source file, but it will print as one line on the
screen.
String formatting characters:
The following string formatting characters are available:
Example:
d_descr "This is a string with a \n, a \t, a \” and a \\ in it.”
Will print as:
This is a string with a
, a , a “ and a \ in it.
A flag can be either up (true) or down (false).
Flag identifiers have
format f_flagname.
Examples of situations where a flag would be used by a location or an
object to keep track of things are:
Attributes are used to store information that is dynamically generated
during the game (i.e. not known at compile time) and that must be used
at a later moment in the game.
Attributes can be used to remember different types of information:
Attribute identifiers have
format r_attributename.
An attribute can remember all of the above types, but it will keep track
of the type that you put in.
When an attribute is used as a parameter in a function, the interpreter
will check if the attribute type matches with the function parameter.
Example:
r_some_item = o_lamp
move(r_some_item, o_player) # moves the lamp into the player’s
inventory.
r_some_item = 10
move(r_some_item, o_player) # will cause a runtime error
Both lines will be accepted by the compiler because at compile time it
is unknown what will eventually be put in the attribute. It’s the
author’s responsibility to prevent runtime errors.
Examples of the use of attributes are:
Triggers are small programs – written by the story author or in a
library – that allow locations and objects to manipulate their flags and
attributes and print information.
Locations and objects have triggers
depending on the tasks they must perform.
Trigger identifiers have
format t_triggername.
An example of a trigger defined with an object:
$OBJECT o_lamp
DESCRIPTIONS
d_sys “the lamp”, “the flashlight”, “the brass lantern”
d_exa “An ancient lamp made of brass.”
CONTAINED on o_table
FLAGS
f_lit = 1
ATTRIBUTES
r_power_left = 100
TRIGGERS
“light [o_lamp]” -> t_light
t_light
# the user has indicated he wants to light the lamp
IF testflag(f_lit) THEN
printcr("But the lamp is already lit!")
ELSE
setflag(f_lit)
printcr("Ok, the lamp is now lit.")
ENDIF
END_OBJ
Words like printcr, testflag and setflag are functions that perform
specific tasks.
For example, the function setflag() sets the indicated
flag to value 1. XVAN has several functions that can be used to write
the story.
There is a separate document with detailed descriptions of the functions
that are available in XVAN. The functions document is quite large, but
it is intended as reference when you need information on how a specific
function works and what parameters it expects. It is not for reading
cover to cover, you may just flip through it to see what’s available.
The same t_light trigger, but now combined with the earlier described
timer m_battery (bold italic parts),
to make the behavior of the lamp object a bit more realistic:
t_light
# the user has indicated he wants to light the lamp
IF testflag(f_lit) THEN
printcr("But the lamp is already lit!")
ELSE
IF equal(m_battery, 0) THEN
printcr("Nothing happens. The battery must be empty.")
ELSE
setflag(f_lit)
starttimer(m_battery)
# start draining the battery
printcr("Ok, the lamp is now lit")
ENDIF
ENDIF
agree()
There are descriptions, flags, attributes and triggers for which it is
obvious that they will be necessary for all locations and objects. For
example, a description d_exa that contains the text to be printed when
an object is examined. Or a trigger t_look that allows an object or
location to print a description about itself when the user types ‘look’.
Such common artifacts need only be defined once in a dedicated section
of the story file. The compiler will then add them to each object and
location. The definition is a default definition for all locations and
objects. When necessary a common definition can be simply redefined by
defining it again locally in a location or object. This
local definition
will then replace the
common definition.
Locations and objects use triggers to respond to certain events. The
actual data for the event (which objects, which location, which
direction, etc) will be determined during play time. XVAN has the
possibility to use wildcards in trigger code that will be filled in with
the actual data when the trigger is executed.
Following wildcards are available:
The %-character is to allow XVAN to tell the wildcards from normal
vocabulary words when used as function parameters. When used in a
string, wildcards must be put in [ ] and the '%'-sign may be omitted.
Examples:
move(o_subject, %this)
"There is [a] [this] here."
The following function example is not valid:
move(o_subject, this) # this must be written as move(o_subject, %this).
An example of using wildcards
t_wildcard_example
# This trigger illustrates the use of wildcards
# This trigger is fired by all user input with syntax
# “ask [o_fred]
about [o_specifier]”
# Suppose the user has entered the command
# ‘Ask Fred about the dial,
then
# during execution of the trigger [o_specifier] will be
# substituted with
‘dial’
# This makes that we only need to define the trigger
# once to work for
all objects.
IF equal (o_specifier, o_dial) THEN
printcr(“The keys with 3, 5, 6 and 9 are a bit more worn than the
others.“)
ELSE
printcr("I don't know anything about the [o_specifier]!")
ENDIF
agree() # let other objects react
XVAN has a number of predefined descriptions, flags, attributes and
triggers. They are used internally by the interpreter but are also
available to the story author.
Following description is predefined:
Following flags are predefined:
Following attributes are predefined:
Following triggers are predefined:
To interact with the player, XVAN needs a vocabulary. It is best to
store XVAN’s vocabulary of words in one or more separate files, so it
can be used with multiple stories by including the vocabulary files in
the story file. The idea is that this vocabulary file grows with each
new story that you write so XVAN will learn more words with each new
story and existing words can be reused.
A word section starts with the section keyword, followed by the words.
E.g:
$PREPOSITIONS
at, behind, in, on, under
A word section – and other sections as well – can be in any file, as
long as it starts with the section keyword.
XVAN has an understanding of the syntax of English (and Dutch) sentences
so it is able to evaluate the user’s input.
XVAN allows words to be of more than one type. For example ‘light’ can
be:
When parsing user input, the parser starts with the first available type
for each word. In case a syntax error is encountered during the parsing
process, XVAN has a trial-and-error mechanism to deduce a valid input.
It will go back one step and see if the conflicting word has another
type that might fit.
So, for example, the sentence “light light light”
is valid user input (meaning “ignite the not so heavy lamp”).
For verbs, extra functionality can be coded. Verbs may have their own
actions, comparable to triggers. These actions will be used as a default
scenario. In case none of the locations or objects responded to the user
input, the verb’s actions will be searched for a match.
It is good practice to not refer to specific locations or objects but
use wildcards (this, actor, subject, specifier, etc) to ensure that the
vocabulary remains independent of specific stories.
A verb can have a prologue. Before the user input is offered to the
locations and objects, the interpreter checks if there is a prologue for
the verb. If so, the prologue will be executed. A possible result of the
prologue is that the input will not be offered to the locations and
objects.
Example of a prologue for the verb ‘look’:
PROLOGUE
If not(islit(o_player)) then
printcr(“It is pitch black.”)
disagree() # stop further processing user input
ELSE
agree() # let locations and objects react
A verb’s epilogue (if present) will be executed after all locations and
objects have had the opportunity to respond to the user’s input.
Example of an epilogue for the verb ‘close’:
EPILOGUE
# they may have closed the object with the light source in it
IF not(islit(o_actor)) THEN
printcr(“It is now pitch black.”)
ENDIF
agree()
In case none of the locations and objects respond to the user input and
there are no default actions defined with the verb, the verb may have a
default
default action defined. The purpose of this default is to have a sort of
last resort response available so the user doesn’t get the feeling the
author forgot to implement something.
In case the verb does not have a default section, the interpreter will
not print a response.
Example of default section for the verb ‘charge’:
DEFAULT
printcr(“I only understood you as far as wanting to charge
something.”)
agree()
$verb inventory SYNONYM i # 'i' may be used as a synonym for inventory
PROLOGUE
printcr(“You are carrying:”)
indent(2) # from now on, the indent() function will print 2 white
spaces
agree()
# after the prologue and before the epilogue, all locations and objects
will respond to the command
EPILOGUE
indent (-2) # remove indent
DEFAULT
# in case there were no responses, the default section will be
executed.
printcr(“Nothing, you are empty-handed.”).
ENDVERB
The location’s and object’s responses to the “inventory” command could
be coded in the story file as the following common trigger:
IF owns(o_player, %this) THEN
printcr(“[a] [this]”)
ELSE # player is not carrying this object
nomatch() # Return nomatch() to inform the interpreter to forget this
object had
# a matching action record. Otherwise the verb default code will not
# be executed.
The above example shows that there's no loop or similar construction to
list the inventory. The prologue prints an opening message and then each
location and object will decide to contribute or not. If nobody
responds, the verb default section will respond.
There is some hierarchy, however. The location will always be the first
to receive the user input and containing objects go before their
contained objects.
XVAN wants to give full control to the author. But if you don’t want to
create your own framework and build everything from scratch, there is
the Library that contains predefined verbs, flags, triggers and
vocabulary words to handle most common tasks.
The Library is described in separate documents.
As of version 2.3.2, XVAN also supports the Dutch language (next to
English). There are two keywords that you can use in story files to
select the language:
XVAN_LANGUAGE tells
the compiler the language of the programming interface. If set to Dutch,
all XVAN keywords like if, then, else, prologue and functions like
IsLit(), CanSee(), Owner() are replaced by Dutch translations. Also, the
error messages are translated.
STORY_LANGUAGE tells
the compiler and interpreter in which language the story must be played.
The story language is stored in the compiled file, so the interpreter
knows which state machine to select to parse input text.
An example:
# XVAN language sample
TITLE “Sample to show language selection”
VERSION “1.0”
XVAN_LANGUAGE english # may also be eng or engels
STORY_LANGUAGE nederlands # may also be nl or dutch
XVAN consists of a compiler and an interpreter (and a language
definition).
The compiler needs an input file with the story (locations, objects,
timers etc) and the vocabulary (verbs, nouns, adjectives etc). The
compiler supports multiple files, but one file must be leading.
It is
advised to use separate files for things that can be reused with other
games. E.g, create a separate vocabulary file with generic verb
definitions that can be reused with new stories. Game specific code is
put in the main story file. In earlier versions from XVAN the separate
vocabulary file was mandatory.
From the input file(s), the compiler creates an output file that
contains the complete story in binary format. The output file can be
played using the interpreter.
The interpreter reads the output file created by the compiler and offers
the user a prompt (‘>’) to enter commands to play the story.
For each
turn, the user will enter a sentence at the command line. E.g. "Open the
mailbox", "Take the leaflet", etc. This sentence will eventually cause
locations and objects to execute certain triggers to progress the game.
How do locations and objects know which trigger corresponds to the
user's input? This is where action records come in.
When the story was compiled and the output file was created, the
compiler created and stored so called action records for the objects and
locations.
An action record is a standard set of data with information about:
Such information is of the format:
"unlock [o_chest] with [o_key]” -> t_unlock
In the above example the compiler will create an action record in the
output file with:
and link it to the trigger t_unlock.
This action record will be stored with the location or object.
When playing the game, the interpreter ‘disassembles’ (parses) the
user's input line and creates an action record from it.
The interpreter
will offer the action record to each location and object. The locations
and objects will search through their own set of action records and will
try to find a matching record. If a matching record is found, this
record will contain information on the trigger that must be executed.
With this mechanism, XVAN can turn the user's input into a series of
triggers that will be executed by locations and objects.
The above is a simplified description of the mechanism; it explains the basics, but there's more to it.
To elaborate a little bit, the
interpreter doesn't actually offer the action record to all locations
and objects from the story, only to those that qualify (are in scope).
An object may tell the interpreter to stop offering the action record to
other objects, an object or location may tell the interpreter to ignore
that it had a matching action record so a default trigger will be
called, etc, etc.
Verbs also have action records. In case none of the locations or objects
respond to an action record, the interpreter will check the applicable
verb whether it has a matching action record with default code to handle
the user input in a more general way.
It is advised to keep the verb
code as general as possible (default messages), so a verb can be used
with different story files.
From the above examples, it shows that the interpreter imposes
priorities in executing the action record created from the user input. A
graphical representation of the evaluation priority mechanism is
depicted in the Syntax document.
In a bullet list, the evaluation priority is as follows:
The interpreter will:
Depending on the outcome of a step execution of further steps may be
cancelled.
Sometimes the user input will contain insufficient information for the
parser to determine which object(s) the user is referring to: an
ambiguous command. In such a case, the interpreter will ask for
additional information so it can map the command to the right objects.
Consider this:
Which cube do you mean? The red cube, the green cube or the blue cube?
> red
Red cube: taken
Nothing wrong with that. But now consider the following:
There is a red cube here.
There is a green cube here.
> I
You are holding:
a blue cube.
a yellow cube.
a lamp (providing light)
> drop cube
Which cube do you mean? The red cube, the green cube, the blue cube or
the yellow cube?
This makes less sense. The interpreter also asks about the red and green cubes although the player isn't holding them. We want some way to tell the interpreter that an item must be held before it can be dropped.
This
is where disambiguation rules come in.
Disambiguation rules are defined in the verb code..
….
"[o_actor], drop [o_subject]"
DISAMBIGUATION_RULES
if owns(o_actor, o_subject) then score(5)
END_RULES
if not(owns(o_actor, o_subject)) then
printcr("But [the] [o_actor] is not holding [the] [o_subject].")
else
move(o_subject, owner(owner(o_subject))
endif
…..
ENDVERB
If there are disambiguation rules, the parser will create one action
record for each possible actor, subject and specifier. In our cube
example, 4 action records will be created with different subjects.
Each action record is then ran by the disambiguation rules. An action
record can earn points if it complies with the rules. In the end, the
action record with the most points wins.
The number of created action records can be large. Suppose we have 2
possibilities for the actor, 3 for the subject and 3 for the specifier
then the parser will create 2 x 3 x 3 = 18 action records.
In the example, the disambiguation rules tell the parser that a
situation where the actor holds the subject gets 5 extra points. If
there are more winners, the interpreter will ask the user, but only for
the winners (in our example only for the blue and yellow cubes).
It is important to realize that the disambiguation rules will only be
executed in case the interpreter cannot decide which item the user
means.
Again, in our example, if the user would type 'drop red cube'
although he is not holding it, the interpreter would not consult the
disambiguation rules, because it can map the text 'red cube' to one
unique object. That's why we still need the code after the END_RULES
keyword in the verb default code.
From version 2.5, XVAN supports plurality. Plurality implementation is based on the following starting points:
This defines oxen and cattle as plural for ox. All actions released on the oxen/cattle will be split up into actions for each individual ox. E.g, in case there are three oxen, “examine oxen” would be similar to “examine white ox, grey ox, black ox”.
There is a red cube here
There is a green cube here.
> i
You are holding:
a blue cube.
a yellow cube.
a lamp (providing light).
> get cubes
red cube: taken.
green cube: taken.
> drop cubes
blue cube: dropped.
yellow cube: dropped.
red cube: dropped.
green cube: dropped
The disambiguation rules exclude the blue and yellow cubes from the ‘get’ action.
The
interpreter will always try to bind nouns and adjectives from the user
input to objects and locations in the story. If it cannot find a
matching object or location it will print a “You cannot see that here…”
message.
But what if
we have a valid expression whose noun cannot be bound to an object? E.g
“Get some rest”, “hit the road” (go on your way) or the like.
We don’t want something like:
You don't see that here.There is a green cube here.
>
So, in order
to deal with this we have:
An example:
if equal(%action, hit) and equal(r_unbound, road) and equa
l(o_spec, %none) then
printcr("Please use compass directions to move.")
agree()
endif
if equal(%action, get) and equal(r_unbound, rest) and equa
l(o_spec, %none) then
printcr("Resting costs you 2 moves.")
wait(2)
agree()
endif
disagree()
>
This ends the explanation of XVAN’s basic concepts. In the Syntax
document, examples of a larger location, object and verb are included,
as well as the layouts for story files.
A detailed description of all of XVAN’s available functions is available
in a separate document.
.