Adventure Authoring System XVAN v2.11 (now 2.3.4)

Hi Marnix,

Thanks for your replies and explanation. Here, I’m going to pick up on your reply to my original XVAN problems, later I’ll write about the JSON ideas;

Firstly, thanks for explaining how it works. Following your ideas (working with the COD example), i factored t_drop and t_take_off into common triggers as suggested, thus:

  t_take_off
    if testflag(%this.f_worn) then
       printcr("You take off [the] [%this]")
       clearflag(%this.f_worn)
    else
       printcr("You're not wearing [the] [%this]")
       disagree()
    endif

  t_drop
    if testflag(%this.f_worn) then
#       trigger(t_take_off)
       printcr("  [[Taking off [the] [%this]]")
       clearflag(%this.f_worn)
    endif
    if (owns(o_player, %this)) then
      clearflag(%this.f_bypass)
      move(%this, owner(o_player))
      printcr("[%this]: dropped.")
    else
      printcr("One must hold something before it can be dropped.")
      disagree()
    endif

Now, as you mentioned, i can just call these from the objects;

$OBJECT o_cloak
   ...
   d_drop       "This isn't the best place to leave a smart cloak lying around."

 TRIGGERS
   "take off [o_cloak]"                 -> t_take_off
   "drop the [o_cloak]"                 -> t_drop_cloak
   ...

   t_drop_cloak
     if equal(l_location, l_cloakroom) then
       if trigger(t_drop) then
         setflag(l_bar.f_lit)
       endif
     else
       printcr(d_drop)
     endif

This works neatly, where the bulk of the logic is factored out of the verb into the common triggers, yet i can still affect the bar.f_lit as required.

Quick question; looking at my definition of t_drop above, why can’t i call trigger(t_take_off) here (commented out), would be nice to do this and factor more code.

I understand your explanation of how you might implement global triggers. When moving logic (such as drop above) into common triggers, the global trigger would be nice and would avoid having to have “drop the X” -> t_drop on every object that can be dropped. same for “get” if that were factored.

handler ordering

Thanks for this. I see how it goes; object->common->verb. I can also see how i might call trigger(t_something) and make use of the result. But what would be nice is, if a handler could call the next in the chain (the one that it goes to for nomatch) but it be a call and not a nomatch because nomatch does not come back!

What’s happening is; without being able to manually call (and return from) the super chain, logic must be factored into common handlers like i have done above for “drop”. Since pretty much everything will sooner or later need to be called from an object handler, all verb logic will eventually be in common handlers and none in verbs - which would be a rather perverse state of affairs.

If the version in the verb could be invoked from the object (or common), then it would not have to be factored out.

That’s just my view of it. Of course, it still works by factoring to common triggers.

Will address the JSON ideas next…

Just a quick question concerning XVAN object IDs and JSON;

Because XVAN compiles, the runtime does not have access to object names in a convenient way, only identifiers. understood.

But, could the compiler have an option to emit some kind of “symbol table”. Something that connects XVAN IDs with identifiers; object identifiers, location identifiers, word identifiers, trigger identifiers.

Perhaps this “symbol table” could also be a JSON object and Brahman could load this in, so that it can work with JSON messages to and from XVAN using IDs.

We could design this symbol table to be an optional offering from a (arbitrary) back-end, whenever string identifiers were not to be used (in the absence of such a table, the corresponding JSON messages would have identifier strings instead of, say, ID numbers).

Good to see you got it to work.

Quick question
The trigger() function is what I call a testfunction. It is always used in an IF clause. So if you change your t_drop trigger a bit, calling t_take_off will work.

t_drop if testflag(%this.f_worn) then if not(trigger(t_take_off)) then # something happened in t_take_off and we must stop disagree() endif endif if (owns(o_player, %this)) then clearflag(%this.f_bypass) move(%this, owner(o_player)) printcr("[%this]: dropped.") else printcr("One must hold something before it can be dropped.") disagree() endif
Come to think of it, maybe move the ‘worn’ test inside the ‘owns’ test.

t_drop if (owns(o_player, %this)) then if testflag(%this.f_worn) then if not(trigger(t_take_off)) then # something happened in t_take_off and we must stop disagree() endif endif clearflag(%this.f_bypass) move(%this, owner(o_player)) printcr("[%this]: dropped.") else printcr("One must hold something before it can be dropped.") disagree() endif
To keep in mind: with common triggers the definition also is the declaration. If you call one common trigger from another, the called trigger must be defined before the calling trigger. If you move t_drop to above t_take_off in the file, the compiler will assume t_take_off is a local trigger and it will throw a ‘missing local trigger owner’ error. I can fix this by making a separate declaration section for common triggers before defining them, I’ll do that when I implement your global trigger suggestion.

Manually calling and returning from the super chain
I have to check the XVAN source (don’t have access right now) but I think I can make runverb() and runcommon() functions that can be called from a trigger. Runverb() would call the verb default code and runcommon() would call a local trigger’s common counterpart.
There would be some restrictions, though. Runverb() may not be called from the verb code itself and runcommon() may only be called from a local trigger to prevent looping.
Your earlier example would look like this:

[code]TRIGGERS
“drop the [o_cloak]” -> t_drop

t_drop

run the verb code

if runverb() then
setflag(l_bar.f_lit)
endif[/code]

I’ll get back on the JSON thing later.

Regarding the JSON interface, there’s no need for the compiler to spit out a symbol table, the interpreter can do it as well. Internally, the interpreter works with ids, but it also has all the info to print an object’s real world name.

The data sources the interpreter uses to convert location and object ids to text strings are:

  • location directory
  • object directory
  • word table

Here’s how it works. Suppose we have an object identifier. The interpreter would use the identifier to access the object directory and find the struct for this specific object. In the struct, among other things, is the object’s extended system description, which in it’s most extended way looks like:

[code][article id] [adjective ids] [noun id] [preposition id] [adjective ids] [noun id]

e.g. “the old grumpy man with the shiny sword”[/code]
Next, the interpreter would look up the word ids in the word table and print the associated text string (or send it to a Glk output handler).

So, by JSONifying (?) the location and object directories and the word table, Brahman should be able to convert an id to the text string.

But would this not be too much interpreter knowledge in the GUI? By only communication through text strings, Brahman could interface with any interpreter without knowing about how it’s structured internally.

W.r.t. running verb and common trigger code, I completed the runverb() function. You can now call the verb code and after execution it will return to the caller. The code for the runcommon() function is almost completed.

Are you on windows or linux?

Well, I made a next version. It can be found here:

XVAN 2.3.1

I added:
runverb() function
runcommon() function
JSON mode and library

runverb() and runcommon()
As per jkj yuio’s suggestion I’ve added these functions. Runverb() and runcommon() can be called from a trigger and will execute the verb default code for the action and the common trigger for the local trigger, respectively.

  • Both functions must be used in an if … then statement.
  • Runverb() cannot be called from verb code.
  • Runcommon cannot be called from verb code or from a common trigger.
  • In case runcommon() is called from a local trigger that has no common trigger, the interpreter will throw a runtime error.

JSON mode and library
I found that building the JSON text strings can be done from normal XVAN code. I wrote json.lib to create json strings for
sending the supported compass directions
sending the valid exits for the current location
sending the player’s inventory

The library:

[spoiler][code]$VERB json
PROLOGUE
if not(testflag(o_player.f_json_mode)) then
printcr(“The interpreter received a JSON command but JSON mode is /
not active. Ignoring the command.”)
disagree()
endif

DEFAULT
printcr(“Unknown JSON object received. Ignoring…”)
ENDVERB

$COMMON_FLAGS
f_first_json = 1

$COMMON_TRIGGERS
t_i_json
if not(testflag(owner(%this).f_first_json)) and not(equal(%this, o_player)) then
# there was already a reply from another contained object
print(" , ")
else
clearflag(owner(%this).f_first_json)
endif

print(“{‘OBJECT’ : ‘[this]’ , ‘CONTAINS’ : [[”)
setflag(f_first_json)

if equal(synchronize(%this, t_i_json, f_takeable, 1, 1), 0) then
# there were no contained objects
print(“null]}”)
else
# there was at least one contained object
print(“]}”)
endif
agree()

$OBJECT o_json_handler
DESCRIPTIONS
d_no_descr “”
CONTAINED in o_player
ATTRIBUTES
r_destination = %none
TRIGGERS
“json 1” → t_directions_json
“json 2” → t_exits_json
“json 3” → o_player.t_i_json

t_directions_json
print(“{‘DIRECTIONS’ : [[‘North’ , ‘South’ , ‘East’ , ‘West’]}”)
disagree()

t_exits_json
print(“{‘LOCATION’ : ‘[l_location]’ , ‘EXITS’ : [[”)
if valdir(l_location, north) then
r_destination = dest(l_location, north)
print("‘[r_destination]’ , ")
else
print("null , “)
endif
if valdir(l_location, south) then
r_destination = dest(l_location, south)
print(”‘[r_destination]’ , ")
else
print("null , “)
endif
if valdir(l_location, east) then
r_destination = dest(l_location, east)
print(”‘[r_destination]’ , ")
else
print(“null , “)
endif
if valdir(l_location, west) then
r_destination = dest(l_location, west)
print(”‘[r_destination]’]}”)
else
print(“null]}”)
endif
disagree()
END_OBJ[/code][/spoiler]

As I do not have a GUI to talk to, there are some test commands to generate the json text strings:
json 1 creates and prints the compass directions
json 2 creates and prints the valid directions for the current location
json 3 generates and prints the player’s inventory.

The json library checks for flag f_json_mode. It will be set automatically when you start the interpreter from the command line with the ‘-e’ option, but you can also set it to 1 in the library code for testing.

Here´s the output from a small test story with 3 locations and a nested inventory in which I included the json library:

[spoiler][code]XVAN transcript for: JSON demo
version: 1.0

l
Testlab
You are in the testlab.

json 1
{‘DIRECTIONS’ : [‘North’ , ‘South’ , ‘East’ , ‘West’]}

json 2
{‘LOCATION’ : ‘testlab’ , ‘EXITS’ : [null , ‘hallway’ , null , null]}

s
Hallway
You are in the hallway between the testlab and the storage.

json 2
{‘LOCATION’ : ‘hallway’ , ‘EXITS’ : [‘testlab’ , ‘storage’ , null , null]}

s
Storage
You are in the storage.

json 2
{‘LOCATION’ : ‘storage’ , ‘EXITS’ : [‘hallway’ , null , null , null]}

i
You are carrying:
a lamp, providing light
a book
a leather wallet
in the leather wallet is an old coin
in the leather wallet is a photo
an ancient sword

json 3
{‘OBJECT’ : ‘you’ , ‘CONTAINS’ : [{‘OBJECT’ : ‘lamp’ , ‘CONTAINS’ : [null]} , {
‘OBJECT’ : ‘book’ , ‘CONTAINS’ : [null]} , {‘OBJECT’ : ‘leather wallet’ , ’
CONTAINS’ : [{‘OBJECT’ : ‘old coin’ , ‘CONTAINS’ : [null]} , {‘OBJECT’ : 'photo
’ , ‘CONTAINS’ : [null]}]} , {‘OBJECT’ : ‘ancient sword’ , ‘CONTAINS’ : [null]}
]}

drop wallet
Leather wallet: dropped.

i
You are carrying:
a lamp, providing light
a book
an ancient sword

json 3
{‘OBJECT’ : ‘you’ , ‘CONTAINS’ : [{‘OBJECT’ : ‘lamp’ , ‘CONTAINS’ : [null]} , {
‘OBJECT’ : ‘book’ , ‘CONTAINS’ : [null]} , {‘OBJECT’ : ‘ancient sword’ , ’
CONTAINS’ : [null]}]}

get wallet
Leather wallet: taken.

json 3
{‘OBJECT’ : ‘you’ , ‘CONTAINS’ : [{‘OBJECT’ : ‘lamp’ , ‘CONTAINS’ : [null]} , {
‘OBJECT’ : ‘book’ , ‘CONTAINS’ : [null]} , {‘OBJECT’ : ‘ancient sword’ , ’
CONTAINS’ : [null]} , {‘OBJECT’ : ‘leather wallet’ , ‘CONTAINS’ : [{‘OBJECT’ :
‘old coin’ , ‘CONTAINS’ : [null]} , {‘OBJECT’ : ‘photo’ , ‘CONTAINS’ : [null]}]
}]}

transcript
Turning off transcript mode.
[/code][/spoiler]

In the final version of the interpreter, the -e option will also tell the interpreter to not accept input from the keyboard or send output to the screen, but to receive from and send to the GUI application.

Thanks for reading, I appreciate all feedback.

Here’s another update on XVAN.

I finished version 2.3.2 with:

  1. additional language support (currently Dutch);
  2. starter kit with predefined actions and dictionary (English and Dutch version);
  3. possibilities to redefine verbs and triggers, so you don’t have to hack the starter kit if you need different behavior;
  4. JSON object handler.

1. Additional language
I did not want separate compiler and interpreter instances for each different language. The language can be set with 2 parameters in the story input file:

  • XVAN_LANGUAGE tells the compiler the language of the programming interface. If set to Dutch, all XVAN keywords like if, then, else, and functions like IsLit(), CanSee(), Owner() are replaced by Dutch translations. Also, the error messages are in Dutch, then.
  • 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 how to parse input text.

The 2 language keywords work independently, so you can have for example an English programming interface with a Dutch story.

Why is there an option to change the XVAN programming language? Well, I would not use it myself but I think it may be useful for educational purposes. Suppose you’re a teacher and you want to demonstrate programming concepts to your audience, It might be handy if the keywords and messages are in their native language. And in some countries it’s a best practice to just translate everything :slight_smile:

Is it easy to add another language? Adding another language would mean translating tables with keywords and error messages and making another state machine to parse that language.

2. Starter Kit
The starter kit is a folder with a set of files that contain:

  • XVAN code for 30+ most commonly used verbs in IF;
  • a dictionary with single words (nouns, adjectives, etc);
  • status window object for Glk;
  • flags, attributes, triggers, and some more stuff to get everything working.

The Starter Kit is not required to use XVAN (you may define everything yourself) but it gives a head start.

The Starter Kit folder must be in the same directory as the compiler (it is not needed by the interpreter) and can be included in the story file by an insert statement. The Starter Kit has an English and a Dutch version.

3. Redefine verbs and triggers
This works pretty neat, I think.
As an example, suppose the Starter Kit has a verb ‘push’ with ‘move’ as a synonym:

$VERB move SYNONYM push … move verb code … ENDVERB

So pushing something and moving something is the same action with the same code.

Now, in my story, pushing something does not mean moving it, but pressing it (I have a button puzzle or something).
I can easily change the Starter Kit code: remove “SYNONYM push” and create a new verb push in my story code. But from a version control perspective this would not be desirable: there is now another version of the Starter Kit.

This is where we can use redefine. In our story source we redefine the ‘push’ verb:

$REDEFINE_VERB push SYNONYM press … push verb code … ENDVERB

The net effect is that:

  • the Starter Kit code is not modified;
  • the ‘move’verb is still linked to the ‘move’ verb code from the Starter Kit;
  • the ‘push’ verb is no longer linked to the ‘move’ verb code from the Starter Kit but to the code from the redefined ‘push’ verb.

The same can be done for common triggers, with the $REDEFINE_TRIGGERS keyword.

4. JSON object handler
The JSON object handler is a ‘normal’ XVAN object that can:

  • receive user input;
  • send the results of processed user input;
  • send compass directions supported by the story;
  • send possible exits from the player’s current location;
  • send the player’s inventory.

as text formatted as JSON structs.

The purpose is to (hopefully) interface with a GUI in the future, where XVAN can serve as an engine to do the processing where the GUI will do the fancy presentation stuff.

Version 2.3.2 can be downloaded from here.

For now I got 2 more things on my wish list:

  • possibility to run XVAN as an engine for a GUI (I need help with that, I program just C, no fancy graphic libraries and stuff).
  • write a killer story, so people actually want to use XVAN.

Thanks for reading…

I made version 2.3.3 with some new things:

  • The XVAN compiler can handle Windows, Linux and macOS text file line delimiters (“\r\n”, “\n” and “\r”). It is no longer necessary to convert the source file to the OS that is used for the compiler.

  • The interpreter now understands ‘all’ and ‘it’, predefined in Starter Kit 1.1.

  • Fixed some bugs and probably introduced some new ones.

  • Added the ‘try()’ function. With try() you can issue a new user command from within the story code as if it were entered from the keyboard. I use it to make a ‘use’ function, that depends on the state of an object. Example (with simplified code):

... “use [o_lamp]” if testflag(o_lamp.f_lit) then try(“turn off lamp”) else try(“turn on lamp”) ...
The nice thing is that try() invokes all tests that are done by an action. So if in this example the battery would be dead, that would be in the response when the player would type “use lamp”.

Xvan 2.3.3 can be found here.

The following is work in progress, not yet released:
I am now working with jkj yuio and strand games to make an xvan interpreter version that will serve as a back-end engine to the Brahman GUI. Communication between xvan and Brahman will be all json. We got the communication part between Brahman and xvan going and are now working on the set of json messages that can be exchanged.
I’ve seen some screen shots already and the nice thing is we can use markup to make object names clickable in the GUI. If you click an object name, Brahman will send a ‘use object’ command to xvan, which in turn will use the try() function I explained above to pick the best action for the current state of the object. And this is all in normal xvan code (not hard coded in the interpreter), so the story author can change the response to his liking.

Thanks for reading.