Adventure Authoring System XVAN v2.11 (now 2.3.4)

Hello all, I made a new version of the XVAN authoring system. And there’s also a tutorial now on how to write a story from scratch.

More information here

I appreciate all feedback.

–EDIT april 20 2019: the latest information about XVAN is in the Technical Development section

-edit: link updated to latest version

-edit: link updated to latest version

- edit: link updated to latest version

- edit: link updated to latest version

- edit: link updated to latest version

Hi Marnix,

Great job on your latest XVAN release. I think the documentation is great, something sorely lacking in many other systems.

I have some feedback/suggestions/requests;

Base library; lib.xvn

Is it possible that you could make a “common library”, ie “lib.xvn” that contains “standard” definitions of the usual verbs, flags, triggers etc.

And that games could just include this library to get started.

Going through your docs, you progressively make verbs like “get” better and better, handing all the cases and eventually cope with ambiguity too. This final version should be part of the lib, so that people can go right ahead and use this final version. Same with all the other standard verbs, attributes etc.

Your demo games would be refactored to use this lib, so that for example COD would fit on a single page.

I think that would help people get started a lot.

In general, can .xvn files include other files. it would be nice, not just for a std lib but so that large projects could be split into sections.

Tools

Can xc (compiler) be changed to work with the command line:

xv [-o <output file>] [-d] [other options] <xvnfile>

The way it currently prompts for the file name means that it can’t be added to an automatic build script.

There should be “debug” and “release” build modes (ie -d = debug), to add additional logging, tracing etc.

Also it would be nice to define debug verbs. For example; teleport and superget could be compiled into the debug version, but not release.

Embedding

This is a more wild suggestion, but it could be really useful;

Could it be possible to define functions inside the .XVN code that could be called from outside in “C”.

For example, define some .xvn functions as “exported” and the xc compiler would generate .h and .c linkage files that would link to the runtime.

To make things simple, it would not be necessary to have full argument passing and return. The functions could, for example, take a string and return a string. I think this would be enough to get started.

Why?

Supposing one wanted to make a fancy UI over the top of an xvan system. Say i want to show the current location somewhere on that UI or i want to show the current inventory in a sidebar, or perhaps a list of people you’d met in the game.

The UI could call string get_inventory("player") or something, that would wind up calling into the xvan code itself to determine the answer.

A watered down version of this suggestion is to have a set of handy functions already part of the xvan runtime. So you don’t get to define them, but there’s stuff already for things like:

  • save/load game
  • get inventory
  • get known map
  • get current location picture
  • get/set property
  • eval command(string)

Those are my thoughts anyhow.

Good luck with xvan.

Thank you for your detailed feedback. It gives me something to think about.

Including other files
The vocabulary file is/was meant to be sort of a library with all the words and verb code. But each author would add his own words to it so there would never be one version. I agree that it would be better to have one lib and put that under version control. And as some verbs do need flags (like ‘open’ needs flag f_open to test) the flags can be defined in the lib as well. Same for commonly used triggers like t_entrance and t_exit.
The extra verbs and words that the author defines for the story can than still be defined in a separate vocabulary file for that specific story.
I agree it would be nice to be able to split up files for larger games. For example, the Bronze port story file is 11,900 lines.

Tools
I think the command line options can be intercepted by using the argc and argv arguments from the main() program?
Hard coding the debug functions in the interpreter seems not like a very good idea. Suppose someone wants to implement a teleport function in the story they’re writing and there is already a dubug function with that name. Maybe I can make 2 libraries one with debug functions and one without. The -d commandline option would then tell the compiler which library to compile.

Embeddding is something I would really have to dive into. I never made something like it before. If I understand correctly, with this your Brahman app could create a map and other graphical stuff for an xvan story?

Thanks again for your thoughts

Hi Marnix,

regarding debug compile, to be clear, i wasn’t specifically suggesting there would be pre-defined debug verbs in the compiler or interpreter, but that a story could define its own debug verbs that would be marked as such (eg DEBUG keyword) and that these would only be compiled in with -d.

Of course, it could also be done with separate files for debug and release. But i think it would go nicer in one file and also the standard lib would support this with its own debug verbs.

a bit about the Brahman interface;

I’ve been steadily converging on a compact interface between a GUI front end and a self-contained game back end. XVAN is one of my experimental back ends.

Because i didn’t want a proliferation of functions, various features evolved into a JSON string interface. So, for example, there might be a function implemented by the back-end;

string getMap()

If the string is empty, the back-end does not support mapping. otherwise it returns the known map as a JSON string.

To take a concrete example, this map is drawn from this string:

{"places":
        [{"id":1,"name":"On The Path","gx":2,"gy":1,"exits":[12,15,2,5,11]},
        {"id":5,"name":"Rank Forest","gx":1,"gy":2,"exits":[11,1,2,3,6,8,7]},
        {"id":7,"name":"Forest Clearing","gx":0,"gy":2,"exits":[11,5,6,8,10,9]},
        {"id":11,"name":"Rank Forest","gx":0,"gy":0,"exits":[24,13,12,1,7,9,27]},
        {"id":12,"name":"On The Path","gx":2,"gy":0,"exits":[13,14,15,1,11,24]}]}

To put something like this in XVAN, the game code would “export” some properties of a given room such as exits, name and whether it has been explored, then some “C” glue code would make the string and return it.

That’s the sort of thing i had in mind, anyhow.

I’d really like to see a standard library for XVAN, as well as an update to the interpreter (so the file doesn’t have to be called “out.dat.”)

jkj yuio and Flathead, thanks for your feedback. I’ll start working on the library and as soon as I start working on the xvan sources, I’ll try to implement the argc/argv mechanism to read filenames and options from the command line.

First step will be to create a sample library file. It won’t compile in the current version but is merely to check if I’m in the right direction. Currently, I have the following assumptions for the library:

  1. The library file contains XVAN source code, human readable. It is not a compiled binary that is read by the interpreter.
  2. The library looks like the vocabulary file, but it also contains common descriptions, common flags, common attributes and common triggers. For example, the verb take will check for flag takable==1, so if the verb is in the library, the flag should be as well.
  3. From a version control point of view, I would not encourage authors to change the library file. This means they must have the possibility to redefine what’s in the library in their story file. E.g. if an author needs a different implementation of “get”, he would not hack the library file, but he would define a verb “get” in his story file and the compiler would know to compile that code and not the library code. Maybe calculate a checksum on the library and check it at compile time and throw a warning if it doesn’t match or would that be too strict?

I’m thinking about abandoning the vocabulary file at some point and replace it by the possibility for the author to include other files.

The Cloak of Darkness story file would then start something like this:

[code]TITLE “Cloak of Darkness”
VERSION “1.0”

$LIBRARY main_lib.xvn

following includes may also be replaced by the actual file content

$INCLUDE my_verbs.xvn
$INCLUDE my_enhanced_vocabulary.xvn # extra words that I need and that are not in the library
$INCLUDE my_common_descrs.xvn
$INCLUDE my_common_flags.xvn
$INCLUDE my_common_attribs.xvn
$INCLUDE my_common_triggers.xvn

#===========================

Locations part

#===========================

#-------------------------------------------------------------------------------
LOCATION l_foyer
DESCRIPTIONS
d_sys “the foyer hall”, “the foyer”, “a spacious hall”, “the opera house”

d_entr_long “You are standing in a spacious hall, splendidly decorated in red /
and gold, with glittering chandeliers overhead. The entrance from /
the street is to the north, and there are doorways south and west.”

d_entr_short “Foyer of the Opera House”

d_no_go “You’ve only just arrived, and besides, the wheather outside seems /
to be getting worse…”

EXITS
south -> l_bar
w -> l_cloakroom

FLAGS
f_lit = 1

TRIGGERS
“look” -> t_look
“examine [l_foyer]” -> t_look
“go north” -> t_no_go
“go to north” -> t_no_go

t_no_go
printcr(d_no_go)
disagree()

END_LOC
#-------------------------------------------------------------------------------
etc
etc[/code]

Would this make things easier?

definitely the way to go. I think the demo games will be a lot smaller and easier for people to understand the implementations.

if its any use, here are my own changes to xvan;

xc/init.c


@@ -26,13 +26,12 @@
 /*************************/
 /* Function declarations */
 /*************************/
 
 int32_t ExitMsg(void);
 int32_t PreDefs(void);
-int32_t main(void);
 
 
 /************************/
 /* Function definitions */
 /************************/
 
@@ -122,13 +121,14 @@
   if (!GetTriggerId("default", &id, COMMON_TRIGGERS, 1))
     return(ERROR);
 
   return(OK);
 }
 
-int32_t main() {
+int main(int argc, char** argv)
+{
   char      storyfile[MAX_ID_LENGTH+1] = "";
   char      vocabfile[MAX_ID_LENGTH+1] = "";
   int32_t   storyfile_linenum = 1;
   int32_t   err = 0;       /* No errors yet. */
   storyInfo story_info;
 
@@ -152,18 +152,39 @@
   printf("                           max  %d common triggers\n", LAST_COMMON_TRIGGER_ID-FIRST_COMMON_TRIGGER_ID+1);
   printf("                           max  %d local triggers\n", LAST_LOCAL_TRIGGER_ID-FIRST_LOCAL_TRIGGER_ID+1);
   printf("                           max %d words\n", LAST_WORD_ID-FIRST_WORD_ID+1);
   printf("                           max %d verbs\n", LAST_VERB_ID-FIRST_VERB_ID+1);
   printf("                           max  %d timers\n", LAST_TIMER_ID-FIRST_TIMER_ID+1);
 
+  int i;
+  storyfile[0] = 0;
+  for (i = 1; i < argc; ++i)
+  {
+      if (argv[i][0] == '-')
+      {
+          ErrHdr();
+          printf("unrecognised option '%s'\n", argv[i]);
+          ExitMsg();
+          return(ERROR);          
+      }
+      else
+      {
+          strncpy(storyfile, argv[i], sizeof(storyfile));
+          storyfile[sizeof(storyfile)-1] = 0; // ensure termination
+      }
+  }
 
-  /* Get storyfile name. */
-  printf("\n\nStoryfile: ");
-  scanf("%19s", storyfile);
-  storyfile[MAX_ID_LENGTH] = '\0';
-  printf("\n");
+  // if not provided, ask it.
+  if (!storyfile[0])
+  {
+      /* Get storyfile name. */
+      printf("\n\nStoryfile: ");
+      scanf("%19s", storyfile);
+      storyfile[MAX_ID_LENGTH] = '\0';
+      printf("\n");
+  }
 
   /* open the storyfile */
   strncpy(current_filename, storyfile, MAX_ID_LENGTH+1);
   if ((source_file = fopen(storyfile, "r")) == NULL) {
     ErrHdr();
     printf("Error opening %s\n", storyfile);
@@ -284,10 +305,10 @@
   /* release malloc'ed space */
   FreeTables();
 
   if (err)
     printf("\nCompilation aborted...\n");
   printf("%d lines compiled.\n", total_lines);
-  ExitMsg();
+  //ExitMsg();
 
   return(OK);
 }

xvan/Input.c

  char *line_buf;
 {
   int     gotline;
   int     len;
   event_t ev;
 
-  glk_request_line_event(mainwin, line_buf, 255, 0);
+  glk_request_line_event(mainwin, line_buf, INPUT_LINE_LEN, 0);
   gotline = 0;
 
   while (!gotline) {
     glk_select(&ev);
     switch (ev.type) {
       case evtype_LineInput:

Sorry for my ignorance but can you explain what the tekst like @@ -122,13 +121,14 @@ means?

You took a shot at the command line parameters. I’m thinkning about implementing max 3 parameters: inputfilename - outputfilename. When no inputfilename is given, ask for it and when no outputfilename is given it will be out.dat.

Regarding the input.c, you told me in the other forum as well. I did look into it but forgot to follow up. The INPUT_LINE_LEN definition is 80 chars. I saw in Zarf’s model.c example for glk that he called the function with 255 as parameter. That’s the reason I copied the 255 as I was afraid the function could return more than 80 characters.

Next thing, I will post the library example as soon as I finish it.

Hi Marnix,

Sorry those weird @@ things are because this is a patch diff. I just copied and pasted it from my editor. If you ignore those bits and just look at lines marked + and -, those show the changes i made.

Indeed, my argc, argv was a quick change to add just one thing, but i was hoping it would indicate the sort of thing i mean, not necessarily to be taken as an actual code change suggestion.

regarding the INPUT_LINE_LEN, if i pass in 255 the request_line_even function can fill up to 255 although the buffer is 80. On my system it crashes if i don’t make this INPUT_LINE_LEN.

good luck with the lib. I think it’s the way to go!.

I made a new version with some changes.

Command line:
The compiler and interpreter now accept inputfile and outputfile from the command line.

Windows: compiler -i <inputfile> -o <outputfile> interpreter <storyfile>

Linux: ./Compiler -i <inputfile> -o <outputfile> ./Interpreter <storyfile>
If you omit one or both filenames they will be prompted for.

Unfortunately, the Glk libraries don’t support argc/argv, so the Glk version of the interpreter still requires the storyfile to be named out.dat.

Insert
The compiler now understands 'insert '. It will then open the file and continue compiling from this file. After an EOF it will switch back to the original file. Insert can go one level deep. It is implemented at the file I/O level so the compiler logic is unaware of it. Theoretically, you can change one word in the source code by an insert statement with a file with that one word in it.
It’s not a full library yet, because for a library I would want to be able to redefine things from the library in the source without getting a ‘duplicate definition’ error. I’m still working on that but for now you can move big chunks of text from the source file into separate files.

I haven’t refactored the sample stories yet, I must finish the library thing first.

The current work is in version 2.2 that can be found here

As always, feedback is highly appreciated.

Hi, nice update.

I made some changes in GetFileNames, there are some bugfixes, but the usage message is optional but helpful.

[code]int32_t GetFileNames(nr_args, args, input, output)
int nr_args;
char **args;
char *input;
char *output;
{
int i = 1;
int ok = 1;

/* the command ‘compiler’ itself is stored in argv[0] */

input[0] = ‘\0’;
output[0] = ‘\0’;

while (i<nr_args) {
if (strcmp(args[i], “-i”) == 0) {
if (nr_args > i+1) {
/* inputfile was specified /
strncpy(input, args[++i], MAX_ID_LENGTH);
}
}
else
if (strcmp(args[i], “-o”) == 0) {
if (nr_args > i+1) {
/
outputfile was specified /
strncpy(output, args[++i], MAX_ID_LENGTH);
}
}
else {
if (strcmp(args[i], “-d”) == 0) {
/
set debug compile mode /
/
implement this later */
}
else {
printf(“Unknown commandline option: %s\n\n”, args[i]);
ok = 0;
}
}
++i;
}

if (!ok)
{
printf(“Usage: %s [-i ] [-o ] [-d ]\n”,
args[0]);
return ERROR;
}
[/code]

Thanks for pointing that out. I feel really stupid, don know how that could have slipped through :frowning:

I made a next version. The vocabulary file is no longer a mandatory separate file, there is now one story file that may include other files. But the biggest change is that there is no longer a prescribed layout for the story file.

In this new version, there are still sections for:

  • common flags
  • common descriptions
  • common triggers
  • common attributes
  • locations
  • objects
  • timers
  • verbs, nouns, adjective, …

But now different sections can be in arbitrary order and each section may occur multiple times in the story file and its included files. When the compiler must compile a specific section it will just browse through all the files and compile all occurences it finds of this specific section. To implement this, I had to prefix the section keywords with $ so the compiler can tell them from dictionary words.
So now you can isolate different section types in one file to create libraries.

Example: file status_window.lib contains object and timer to make a status window for the Glk version.

[spoiler][code]#============================================

Glk Status Window

#============================================

#-------------------------------------------------------------------------------
$OBJECT o_status_window

DESCRIPTIONS
d_nodescr “”

CONTAINED in o_player

FLAGS
f_hidden = 1

TRIGGERS
“restore” -> t_restore

t_entrance
agree()

t_exit
agree()

t_update
# updates the status window (only in Glk version)
# triggered by m_statuswindow timer
clearstatus()
setcursor(0,2)
if islit(l_location) then
printstatus(l_location)
else
printstatus(“Darkness”)
endif

t_restore
restore()
# timers are not called after save and restore
if trigger(t_update) then
agree()

END_OBJ
#-------------------------------------------------------------------------------

$TIMERS

m_status_window
init 0
step 0
direction up
interval 1
state go
trigger_at 0
execute o_status_window.t_update
#-------------------------------------------------------------------------------[/code][/spoiler]
If you want a status window, just add $insert “status_window.lib” somewhere in the story file and you get a status window.

I refactored the sample stories. This is what Cloak of Darkness looks like now.

[spoiler][code]#============================================

XVAN implementation of “Cloak of Darkness”

#============================================

TITLE “Cloak of Darkness”
VERSION “1.0”

for Linux, replace \ in pathnames with //

$insert “.\includes\dictionary.lib”
$insert “.\includes\actions.lib”
$insert “.\includes\commons.lib”
$insert “.\includes\common_triggers.lib”
$insert “.\includes\nosuchthing.xvn”
$insert “.\includes\status_window.lib”

#===========================

Locations part

#===========================

#-------------------------------------------------------------------------------
$LOCATION l_foyer
DESCRIPTIONS
d_sys “the foyer hall”, “the foyer”, “a spacious hall”, “the opera house”

d_entr_long “You are standing in a spacious hall, splendidly decorated in red /
and gold, with glittering chandeliers overhead. The entrance from /
the street is to the north, and there are doorways south and west.”

d_entr_short “Foyer of the Opera House”

d_no_go “You’ve only just arrived, and besides, the wheather outside seems /
to be getting worse…”

EXITS
south -> l_bar
w -> l_cloakroom

FLAGS
f_lit = 1

TRIGGERS
“look” -> t_look
“examine [l_foyer]” -> t_look
“go north” -> t_no_go
“go to north” -> t_no_go

t_no_go
printcr(d_no_go)
disagree()

END_LOC
#-------------------------------------------------------------------------------

#-------------------------------------------------------------------------------
$LOCATION l_cloakroom
DESCRIPTIONS
d_sys “the cloakroom”, “the wall”, “the walls”

d_entr_long “The walls of this room were clearly once lined with hooks, /
though now only one remains. The exit is a door to the east.”

d_entr_short “Cloakroom”

EXITS
e -> l_foyer

FLAGS
f_lit = 1

TRIGGERS
“look” -> t_look
“examine [l_cloakroom]” -> t_look

END_LOC
#-------------------------------------------------------------------------------

#-------------------------------------------------------------------------------
$LOCATION l_bar
DESCRIPTIONS
d_sys “the foyer bar”

d_entr_long “The bar, much rougher than you’d have guessed after the opulence /
of the foyer to the north, is completely empty. There seems to /
be some sort of message scrawled in the sawdust on the floor.”

d_entr_short “Foyer bar”

EXITS
n -> l_foyer

FLAGS
f_bypass = 1

TRIGGERS
“look” -> t_look
“x [l_bar]” -> t_look
“[dir]” -> t_move

t_move
if not(islit(%this)) then
if not(equal(%dir, north)) then
o_message.r_damage += 2
printcr(“Blundering around in the dark isn’t a good idea!”)
endif
else
nomatch()

t_default
if not(islit(%this)) then
o_message.r_damage += 1
printcr(“In the dark? You could easily disturb something!”)
disagree()
else
nomatch()

END_LOC
#-------------------------------------------------------------------------------

#======================

OBJECTS part

#======================

#-------------------------------------------------------------------------------
$OBJECT o_player

The o_player object is predefined and represents the human player.

DESCRIPTIONS
d_sys “You”

d_init "Hurrying through the rainswept November night, you’re glad to see the /
bright lights of the Opera House. It’s surprising that there aren’t /
more people about but, hey, what do you expect in a cheap demo game…?

/ Cloak of Darkness
/ A basic IF demonstration
/ "

CONTAINED in l_foyer

ATTRIBUTES
r_is = are
r_have = have
r_score = 0
r_max_score = 2

TRIGGERS
“[dir]” -> t_move # if the player enters a (compass) direction, execute trigger t_move

t_init
# This trigger initializes things. It is started by counter
# m_init that goes off right after starting the story.
background(blue)
printcr(d_init)
printcr("")
entrance(owner(%this))

t_entrance
agree() # prevents calling common t_entrance trigger for player.

t_move
if valdir(l_location, %dir) then
# it’s a valid direction
if exit(l_location) then
# no object objects to the player leaving the room
move(o_player, %dir) # move updates current location
entrance(l_location)
endif
else
nomatch() # let other objects or verb default code react.
endif
agree()

t_end
printcr("") printcr("")
printcr(“Your score is [r_score] out of a possible /
[r_max_score], in [m_init] moves.”)
quit()

END_OBJ
#-------------------------------------------------------------------------------

#-------------------------------------------------------------------------------
$OBJECT o_cloak

DESCRIPTIONS
d_sys “a black velvet cloak”

d_entr_short “There is [a] [o_cloak] here.”

d_exa “A handsome cloak, of velvet trimmed with satin, and slightly /
spattered with raindrops. Its blackness is so deep that it /
almost seems to suck the light from the room.”

d_drop “This isn’t the best place to leave a smart cloak lying around.”

CONTAINED in o_player

FLAGS
f_bypass = 1 # exclude from interpreter’s visibility check
f_takeable = 1
f_wearable = 1
f_worn = 1 # initially, the player is wearing the cloak

TRIGGERS
“take off [o_cloak]” -> t_take_off_cloak
“wear [o_cloak]” -> t_wear
“drop the [o_cloak]” -> t_drop
“hang the [o_cloak] on the [o_hook]” -> t_hang
“examine the [o_cloak]” -> t_exa
“look” -> t_look
“inventory” -> t_i

t_take_off_cloak
if testflag(f_worn) then
printcr("")
printcr(" [[Taking off [the] [o_cloak]]")
clearflag(f_worn)
endif

t_wear
if not(owns(o_player, o_cloak)) then
printcr(" [[picking up the cloak first]")
move(o_cloak, o_player)
endif
printcr(“Ok, you are now wearing [the] [o_cloak].”)
setflag(o_cloak.f_worn)
clearflag(l_bar.f_lit)

t_drop
if equal(l_location, l_cloakroom) then
if trigger(t_take_off_cloak) then
printcr(“You drop [the] [o_cloak] on the floor.”)
move(o_cloak, l_cloakroom)
setflag(l_bar.f_lit)
endif
else
printcr(d_drop)
endif

t_hang
if trigger(t_take_off_cloak) then
move(o_cloak, o_hook)
setflag(l_bar.f_lit)
o_cloak.r_preposition = on
printcr(“Ok, the cloak is now on the hook.”)
o_player.r_score += 1
printcr("")
printcr(" [[Your score has just gone up by 1 point]")

t_i
if not(owns(o_player, %this)) then
nomatch()
else
indent()
print("[a] [o_cloak]")
if testflag(f_worn) then
printcr(", being worn")
else
printcr("")
endif
agree()
endif

END_OBJ
#-------------------------------------------------------------------------------

#-------------------------------------------------------------------------------
$OBJECT o_hook

DESCRIPTIONS
d_sys “the small brass hook”

d_entr_short “There is [a] [o_hook] on one of the walls.”

CONTAINED in l_cloakroom

TRIGGERS
“look” -> t_look
“examine [o_hook]” -> t_exa
“take [o_hook]” -> t_take

t_exa
print("It’s just [a] [o_hook], ")
if owns(o_hook, o_cloak) then
printcr(“with [a] [o_cloak] hanging on it.”)
else
printcr(“screwed to the wall.”)
endif

t_take
printcr("[the] [o_hook] is firmly attached to the wall.")

END_OBJ
#-------------------------------------------------------------------------------

#-------------------------------------------------------------------------------
$OBJECT o_message

DESCRIPTIONS
d_sys “the scrawled message”

d_entr_short “There seems to be some sort of message scrawled in the sawdust on the floor.”

d_exa_won "The message, neatly marked in the sawdust, reads …

                 *** You have won ***"

d_exa_lost "The message has been carelessly trampled, making it difficult to read. You /
can just distinguish the words …

                 *** You have lost ***"

CONTAINED in o_sawdust

ATTRIBUTES
r_damage = 0

TRIGGERS
“examine [o_message]” -> t_exa
“read [o_message]” -> t_exa
“read [o_message] in [o_sawdust]” -> t_exa

t_exa
if lt(r_damage, 2) then
o_player.r_score += 1
printcr(d_exa_won)
else
printcr(d_exa_lost)
endif
if trigger(o_player.t_end) then
printcr("")
endif

END_OBJ
#-------------------------------------------------------------------------------

#-------------------------------------------------------------------------------
$OBJECT o_sawdust

DESCRIPTIONS
d_sys “the sawdust”, “the floor”

d_entr_short “”

d_take “That doesn’t sound like a good idea. It would /
scatter the message…”
d_exa “There is a message written in the sawdust.”

CONTAINED in l_bar

FLAGS
f_takeable = 1

TRIGGERS
“take [o_sawdust]” -> t_take
“x [o_sawdust]” -> t_exa

t_entrance
agree()

t_take
printcr(d_take)

t_exa
printcr(d_exa)

END_OBJ
#-------------------------------------------------------------------------------

#-------------------------------------------------------------------------------
$OBJECT O_foyer_scenery
Descriptions
d_sys “the red decoration”, “the gold decoration”, “the glittering chandeliers”

d_scenery_1 “You examine [the] [o_foyer_scenery] and on closer look decide that you /
don’t need to bother about it to complete this story.”

d_scenery_2 “Just looks like [o_foyer_scenery].”

d_scenery_3 “Beautiful [o_foyer_scenery], very contemporary.”

CONTAINED in l_foyer

FLAGS
f_hidden = 1
f_bypass = 1
f_swap = 1

ATTRIBUTES
r_random = 1

TRIGGERS
“examine [o_foyer_scenery]” -> t_exa

t_entrance
agree()

t_exa
r_random = rnd(1,3)
if equal(r_random, 1) then
printcr(d_scenery_1)
else
if equal(r_random, 2) then
printcr(d_scenery_2)
else
printcr(d_scenery_3)
endif
endif
agree()
END_OBJ
#-------------------------------------------------------------------------------

#===========

Timer part

#===========

$TIMERS

Timer m_init is used to start the game and keep track

of the number of moves

m_init
init 0
step 1
direction up
interval 1
state go
trigger_at 1
execute o_player.t_init[/code][/spoiler]
The new version 2.3 can be found here.

Next thing I want to work on is the embedding that jkj yuio suggested, so XVAN can serve as an engine to external (G)UIs.

Hi Marnix,

This is a major step forward! Looking now at, say, cloak.xvn it is just the story and all the boilerplate support is neatly tucked away in include. Also the annoying voc file has been eliminated.

For people that want to use XVAN, they just need to look at example STORY files and perhaps copy include into their projects.

Later, the stuff in include will converge to become a standard base lib for all games. Nice one.

So i have some fixes:

XC\init.c\GetFileNames

  int i = 1;

  /* the command 'compiler' itself is stored in argv[0] */

  input[0]  = '\0';
  output[0] = '\0';

  while (i<nr_args) {
    if (strcmp(args[i], "-i") == 0) {
      if (nr_args > i+1) {
        /* inputfile was specified */
        strncpy(input, args[++i], MAX_ID_LENGTH);
      }
    }
    else
      if (strcmp(args[i], "-o") == 0) {
        if (nr_args > i+1) {
          /* outputfile was specified */
          strncpy(output, args[++i], MAX_ID_LENGTH);
        }
      }
      else {
        if (strcmp(args[i], "-d") == 0) {
          /* set debug compile mode */
          /* implement this later   */
        }
        else {
          printf("Unknown commandline option: %s\n\n", args[i]);
        }
      }
    ++i;
  }

To see why, try running “xc -fish”

XVAN runtime; cleanup.c

void FreeTriggers(start_trigger)
 triggerInfo *start_trigger;
{
  if (start_trigger == NULL)
    return;
  else {
    FreeTriggers(start_trigger->next);
    if (start_trigger->code != NULL)   // <<<<<<---- look here
      free(start_trigger->code);
    free(start_trigger);
  }
}

other stuff

I haven’t forgotten to get back to you re the embedding ideas.

Basically, and this applies to anyone else interested as well, I’m thinking of making a thin JSON interface that can be attached to a back-end engine (eg XVAN) that exposes various necessary concepts for a GUI.

These are things like:

  • inventory
  • map
  • pictures
  • sounds
  • options
  • product info
  • settings
  • inputs to the back-end that aren’t text commands.
    etc.

The idea is to take what I’ve ended up with in Brahman and make that the start point for further expansion.

You might think that inventory is just a list of strings - so did i at first! or perhaps pictures are just a URL (you’d think!). What happens, is quite quickly the information content needs to be expanded;

for example, inventory needs to also tell the UI; things worn, held in hand, inside other things etc. as well as fancy display labels and styles.

The idea is to start by defining a schema for the information and then move on to building a small library to implement it.

Underneath that, considering XVAN for example, it will then need to fill in the JSON structures. For example, i had a look at how XVAN might implement player inventory;

There appears to be utils to find things “owned” by other things. However, is the “player” a special object in the back-end, or just a general object? Could there be a util to find the list of things owned by the “player”. From that we could build the inventory JSON. at least as a start. Then we’d need to look into how each such object is related to the player (eg worn, held etc.).

I need to read the code a bit more however…

Hi jkj yuio,

Thank you for your comments (how did you spot that semicolon???).

XVAN has an Output() function, that can:

  • print text to the console window (printf() )
  • send text to a Glk library function, for printing in a Glk window
  • write text to a file (when transcript mode is on)

I think I can add JSON options for output: CreateJSON(), ExpandJSON(), … Starting the interpreter with a -e option would then invoke JSON mode.

Now about the inventory (I assume you mean a special command issued by the GUI to retrieve the player’s inventory, not the regular ‘i’ that the user would type from the command line):

Regarding the player object, it’s a normal object but it is mandatory because the interpreter refers to it in some cases (for example to update the current location variable).

The interpreter maintains 2 arrays of type dirInfo, called loc_dir and obj_dir. Each location and object has an entry in one of the arrays, indexed by their id. A dirInfo struct has a member called contained_objs, that holds all the contained objects. So, if you look up the player object in obj_dir, you can retrieve the contained_objs struct. But… I think there is a better way.

Let me elaborate a bit about how the ‘inventory’ user command (or any other user input) from the command line is executed, as this is also important for the special inventory command.

After the user input is parsed, the interpreter will create an array of object/location ids who must respond to the user input. Simplified: it looks up the current location in loc_dir, gets the contained_objs struct and for each contained object it looks up the contained objects etc etc. In the end, an array of object ids will result.
Next, the parsed user input is offered to each object in the array to let it respond (or decide not to).

So, how does this work for “inventory” ?

First, the interpreter will query the inventory verb whether it has a prologue. The prologue, if present, is always the first thing to execute.
Inventory has a prologue and it will print the string “You are carrying:”

Next, the user input is offered to all objects in the array I described earlier. Each object will check if it must do something. Basically, it will check whether it is held by the player and if so, it will print the text "an ". Objects know about special states; the cloak knows it can be worn and will check for it. If worn it will add “(being worn)” to the text. Likewise, a lamp could add “(providing light)” to the response.

After all objects have had their turn, the interpreter will check if anyone responded. If not, the inventory verb default will be executed, which will print “nothing, you are empty-handed.”

My point is that many things are handled at the object level. The interpreter does not know about worn, lit, opened, closed, whether an object is in/on/under/… another object etc. It can only parse user input and give it to the objects for processing.

If we want a special inventory command that can be issued by the GUI so it can display the player’s inventory in a special window, I think we come to your last bullet (inputs to the back-end that aren’t text commands). Info must still come from the objects, but it should be in a different type of JSON structs, so the GUI knows it must not be printed as a normal reply.

We could even consider to expand the functionality of the Inventory command, so it can also return the inventory from a location. This could be helpful when drawing the map: items that are in the locations and already discoverd by the player could be depicted in the map.

What’s the license on the source code?

I’m currently looking which license fits best. I Posted a request for advice in General Design Discussions.

In the meantime, if you want to do something with the sources, drop me a PM and we’l work something out.

Thanks for the detailed explanation.

I see it now; for the embedded commands, we need to have them implemented by XVAN story/lib code to make them general.

global triggers

are there/can there be global triggers. There are global trigger handlers, for example here’s a global handler for “inventory”

 t_i
    if owns(o_player, %this) then
      indent()
      print("[a] [this]")
      if testflag(%this.f_worn) then
         print(", being worn")
      endif
      printcr("")
    else
      nomatch()

But to get to this code, each object has to have something like;

 TRIGGERS
   ...
   "inventory"                          -> t_i

My question is, could there be a global version of the above line, ie on all objects unless overridden.

embedded JSON commands

The XVAN language, therefore needs to implement handlers that build JSON responses for our embedded commands. Taking the example as “inventory”, we need a pseudo-command; (eg) inv-json that implements this.

pseudo commands

These will look like regular commands, but they can’t be typed in from the command line interface, only from the embedded interface. The command should have a consistent naming convention, eg;

  • inv-json or json-inv
  • #inv
  • _inv

Special characters like “_” or “#” are attractive since they are easy to rule out from the CLI version, but obviously the choice must be a character allowed in command strings internally.

Otherwise fall back on names like “inv-json” or even “invjson”, but these will be harder to exclude from CLI. plus the fact that they imply JSON (will that always be true?)

pseudo command handlers

These could be implemented like any normal handler

t_#inv
  ...

but instead they build a json reply. one way to do this is to have print, printcr etc write to a buffer, another option is to have printjson etc. A third way is to not use print at all, but introduce a set of primitive JSON building functions; jsonbegin, jsonadd, … jsonend etc.

Issues with XVAN itself

When overriding triggers/actions, can they call the base version as part of their logic.

Example;

here is my generic library drop verb:

$VERB drop
  "drop"
    printcr("What do you want to [action]?")
    getsubject()

  "drop [o_subject]"
    if testflag(o_subject.f_worn) then
       printcr("  [[Taking off [the] [o_subject]]")
       clearflag(o_subject.f_worn)
    endif
    if (owns(o_actor, o_subject)) then
      clearflag(o_subject.f_bypass)
      move(o_subject, owner(owner(o_subject)))
      printcr("[o_subject]: dropped.")
    else
      printcr("One must hold something before it can be dropped...")

  DEFAULT
    printcr("You don't need that to finish this story.")
ENDVERB

Notice it will automatically remove something worn, then drop. Handy logic that you don’t want to keep copying about.

When overriding drop for, say, the cloak;

TRIGGERS
   "drop the [o_cloak]"                 -> t_drop
    ...

   t_drop
     if equal(l_location, l_cloakroom) then
       if trigger(t_take_off_cloak) then
         printcr("You drop [the] [o_cloak] on the floor.")
         move(o_cloak, l_cloakroom)
         setflag(l_bar.f_lit)
       endif
     else
       printcr(d_drop)
     endif

You really want to be able to CALL the base version and test the result. Something like;

TRIGGERS
   "drop the [o_cloak]"                 -> t_drop
    ...

   t_drop
     if super.drop then
         setflag(l_bar.f_lit)
     endif

Otherwise you basically have to copy the whole drop verb implementation to just add one line to setflag on the end. Furthermore bugs creep in because, you notice that the current “drop cloak” code does not check you’re carrying the cloak (oops!)

BTW, you can also “hang cloak on hook” over and over (even though it’s already there) and get any score you like… :slight_smile:

That’s why you have to be able to call the base lib versions that do all the testing and act on their result.

Hi jkj yuio,

My replies to the things you raised.

global triggers

Yes, I think so. Let me explain how the trigger mechanism works (this is also important for JSON implementation).
At compile time, the trigger string, in your example “inventory -> t_i”, to the left of the Arrow is parsed by the compiler as if it were user input (it ‘borrows’ some modified interpreter code). It is then stored in what I call a computer action record:

typedef struct ar1 { /* Used to store compiler generated action records read from */ /* the datafile. These action records have only one subject. */ int32_t actor; int32_t action; adverbInfo adverbs; int32_t q_word; int32_t direction; int32_t subject; int32_t specifier; preposInfo prepositions; int32_t value; /* make this a long. */ int32_t ordinal; int32_t execute[2]; struct ar1 *next; /* This must remain the last field. */ } compActionRec;
For “inventory -> t_i”, the actor would be the player object id, action would be the word id for inventory and execute would be the trigger (handler) id for t_i. This computer action record is stored with the object in the story file.

During play, the interpreter will parse the user input and create a user action record from it:

typedef struct ar2 { /* Used to store action records generated from user input */ /* These action records may have more than one subject. */ int32_t actor; int32_t action; adverbInfo adverbs; int32_t q_word; int32_t direction; int32_t subject[MAX_SUBJECTS]; int32_t specifier; preposInfo prepositions; int32_t value; /* make this a long. */ int32_t ordinal; struct ar2 *next; /* This must remain the last field. */ } usrActionRec;
For the “Inventory” command, the actor would be the player object id and the action would be set to the word id for inventory.

Next, for each object in scope (see one of my earlier replies), the user action record will be compared to all the object’s computer action records until there is a hit for the relevant fields. In case of a hit the interpreter will execute the trigger (handler) that is stored in the execute[2] field.

To implement the global trigger mechanism, I would define a new section in the input files, similar to the TRIGGERS sections within objects and locations.

"inventory" -> t_i "look" -> t_look "etc" -> t_etc
The computer action records generated from this section would be stored with ALL objects and locations, so there will always be a match with similar user input.

Overriding will then be done by redefining the exact same text in the object’s TRIGGERS section. Before adding the resulting computer action record, the compiler will check if there already is a similar global one and if so, overwrite it with the local one (which will have a different trigger to execute).

So, yes it can be done. But for now I consider it a nice-to-have, I really want to put my effort in the JSON stuff :slight_smile:

embedded JSON commands

pseudo commands

pseudo command handlers

Still thinking about how to implement this. You see, the runtime only works with identifiers. Object identifiers, location identifiers, word identifiers, trigger identifiers, etc. And Brahman would not know about these. It would send a string with the name of a command to execute, not a trigger id. I haven’t thought it over thoroughly, but I’m thinking about creating an additional type of action record, the JSONactionRec. There would be a type field in the JSON object that Brahman sends over to tell that it contains a Brahman command and not user input. This would tell the interpreter to not create a user action record but a JSON action record.
Next, define a new verb in the dictionary to name the JSON command and define triggers for the objects and location to match with the JSON command.

I think something like this could work:

1.Brahman sends a JSON object with {type = JSON, command = “inventory_j”, id = 23} (id is some internal Brahman thing to identify responses)
2. In the XVAN vocabulary is a $verb inventory_j ENDVERB definition (without code,we only need the definition for the parser).
3. In the applicable objects is something like:

TRIGGERS "inventory_j" -> t_i_j t_i_j SendJASONdata("name", %this) # will send the name of the object
4. The interpreter will create a JSON action record from the object that Brahman sends. Processing the action record will trigger t_i_j
Just my initial thoughts, I need to think it over further.

Issues with XVAN itself

This is how XVAN was designed. The verb code is a last resort, when you end up there it means there was no handler for the user input with any object or location.
But what you want can be realized in another way. Move the code from the verb to a common trigger (from a design perspective this is actually better, as the verb code should really be a default message). Now you can call the common trigger by using the trigger() function. So:

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

t_drop_cloak
if trigger(t_drop) then # t_drop must be defined as a common trigger
setflag(l_bar.f_lit)
endif[/code]
But I would advise to have the check for f_worn with the object and not in a common trigger or verb. Why? Here we only have state worn, but there could objects with other states. As an example let’s take ‘tied to’.

Example:

[code]> drop hook
As the hook is tied to the rope, you untie the hook first and then drop it.

– this would require addtl code in the drop verb:
if not(equal(o_subject.r_tied_to, %none)) then
# it’s tied to something
printcr(“As [the] [o_subject] is tied to [the] [o_subject.r_tied_to], you untie [the] [o_subject] first and then drop it.”)
o_subject.r_tied_to = %none
endif
[/code]
If you want to cover all possibilities in the verb code or a common trigger, with each new state, the library would change to a new version because the verbs must change to cover the new state. An XVAN principle is that objects are in charge of their own stuff. Cover the object specific stuff in a trigger with the object and if applicable, call a common trigger before or after to handle generic stuff.

Finally, There’s another mechanism I’d like to show. Normal evaluation order for a user action record is:

  1. local trigger
  2. if there is no local trigger, common trigger
  3. if there is no common trigger, verb code.

Now suppose you’re in the local trigger and after some tests decide that the local trigger should not have fired after all. If you just stop, evaluation for this object will end. To stop the trigger but continue evaluation of the user input you can use nomatch(). This indicates to the interpreter that it should proceed as if there were no local trigger. It will then continue with the common trigger. If there is no common trigger, or the common trigger also returns nomatch(), the interpreter will continue with the verb code. This mechanism also saves you from copying code.

An example:
PRIOR NOTE: PLSE DOWNLOAD the compiler executable again because I fixed a bug when I tested this. If you create your own executable, you must replace prsetrig.c from the compiler source code.

Copy the below text in a file test.xvn and $insert “test.xvn” somewhere in cloak.xvn. Then run it with f_condition set to 1 and then to 0 and see what happens.

[spoiler][code]$verb test
DEFAULT
printcr(“in verb default code”)
ENDVERB

$COMMON_TRIGGERS
t_test
if not(testflag(o_test.f_condition)) then
printcr(“in common trigger, condition not true”)
nomatch()

$OBJECT o_test
DESCRIPTIONS
d_dummy “”
CONTAINED in o_player
FLAGS
f_condition = 1 # set to 1 and 0 and check the behavior
TRIGGERS
“test” -> t_test

t_test
if not(testflag(f_condition)) then
printcr(“in local trigger, condition not true”)
nomatch()
else
printcr(“in local trigger, condition is true, execution ends”)
END_OBJ[/code][/spoiler]

Apologies for the lengthy reply, thanks for bearing with me.

I’ve done some thinking about what the JSON objects might look like. Assuming this is going to be a generic interface and that you also want to interface with other runtimes, there must be no XVAN specific semantics like identifiers going over the interface.

I identified 2 types of data exchange between Brahman and XVAN:

  1. user data, i.e. input that the user enters through the Brahman GUI that is sent to XVAN and output that XVAN generated and that is sent to the Brahman GUI for display.
  2. commands from Brahman to XVAN and replies that are of no concern to the user. E.g. Brahman asking XVAN for inventory and map data to continuously display in separate windows on the screen.

I drafted some sample JSON messages that could go back on forth over the interface.

In each message, the first string indicates the type of message.

Type 1: user data

Sent from Brahman to XVAN:

{"USER INPUT" : "examine the lamp"}

From “USER INPUT”, XVAN would know that the next string is user input that it must process.

Reply from XVAN to Brahman:

{"USER OUTPUT" : "The lamp is lit."}

From “USER OUTPUT” Brahman would know it must display the string that follows in the output window.

Type 2: commands

I made samples for 3 commands that Brahman could send to XVAN, with corresponding XVAN replies.

Sent from Brahman to XVAN

{"COMMAND" : "DIRECTIONS"}

As the number of directions in a game may very, Brahman uses this command to ask XVAN about which directions are supported by the game.

Reply from XVAN (sample):

{"COMPASS" : ["North" , "East" , "South" , "West"] , "OTHER" : ["Up" , "Down" , "In" , "Out"] }
Brahman may want to draw a map, so there must be some conventions: the array following “COMPASS” contains compass directions, starting at 0 degrees and evenly spread going clockwise from 0 to 360.
The array following “OTHER” contains non-compass directions that the player may move in.

Sent from Brahman to XVAN

{"COMMAND : "EXITS"}

This tells XVAN to send information on the possible exits for the current location.

Reply from XVAN (sample):

{"LOCATION" : "South Hallway" , "COMPASS" : ["North Hallway" , true , false , "Kitchen"] , "OTHER" : [true , false , false , false] }
Again, some conventions here.

  • the string following “LOCATIONS” is the name of the current location.
  • the arrays are ordered according to the exit schema from the DIRECTIONS command. So here going north goes to the North Hallway and going west ends up in the Kitchen.
  • Location names in the arrays indicate that the location has been visited before, so it’s name can be disclosed.
  • ‘true’ means that there is an exit in that direction but that the player has not been there before. So in this example the player may go east and up but doesn’t know yet where he will end up.
  • ‘false’ means that there is no exit in that direction.

Sent from Brahman to XVAN:

{"COMMAND" : "INVENTORY"}

This tells XVAN to send an overview of the objects the player is carrying. This one is a bit tricky. Because objects may contain other objects, there is nesting of objects.

Reply from XVAN (sample):

{"OBJECT" : "You" , "CONTAINS" : [ {"OBJECT" : "a lamp" , "CONTAINS" :[null] } , {"OBJECT" : "a book", "CONTAINS" : [null] } , {"OBJECT" : "a wallet" , "CONTAINS" : [ {"OBJECT" : "an old coin" , "CONTAINS" : [null] } , {"OBJECT" : "a photo" , "CONTAINS" : [null] } ] } {"OBJECT" : "a sword" , "CONTAINS" : [null] } ] }

  • the string following “OBJECT” is the name of the container.
  • the array after “CONTAINS” is the list of contained objects.
  • if the array has one element ‘null’, there are no contained objects.

So here the player contains a lamp, a book, a wallet and a sword. And the wallet contains a coin and a photo.