intfiction.org

The Interactive Fiction Community Forum
It is currently Thu Nov 23, 2017 9:10 pm

All times are UTC - 6 hours [ DST ]




Post new topic Reply to topic  [ 25 posts ]  Go to page Previous  1, 2, 3  Next
Author Message
PostPosted: Wed Aug 09, 2017 6:08 pm 
Offline
User avatar

Joined: Sat Jun 25, 2016 12:13 pm
Posts: 153
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 <inputfile>] [-o <outputfile>] [-d <level>]\n",
             args[0]);
      return ERROR;
  }


Top
 Profile Send private message  
Reply with quote  
PostPosted: Tue Aug 15, 2017 7:29 am 
Offline

Joined: Wed Aug 26, 2015 11:18 am
Posts: 88
Location: The Netherlands
Thanks for pointing that out. I feel really stupid, don know how that could have slipped through :-(


Top
 Profile Send private message  
Reply with quote  
PostPosted: Fri Sep 22, 2017 8:22 am 
Offline

Joined: Wed Aug 26, 2015 11:18 am
Posts: 88
Location: The Netherlands
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: show
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
#-------------------------------------------------------------------------------

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: show
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

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.


Top
 Profile Send private message  
Reply with quote  
PostPosted: Fri Sep 22, 2017 4:28 pm 
Offline
User avatar

Joined: Sat Jun 25, 2016 12:13 pm
Posts: 153
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

Code:
  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

Code:
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...


Top
 Profile Send private message  
Reply with quote  
PostPosted: Mon Sep 25, 2017 8:31 am 
Offline

Joined: Wed Aug 26, 2015 11:18 am
Posts: 88
Location: The Netherlands
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 <object name>". 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.


Top
 Profile Send private message  
Reply with quote  
PostPosted: Wed Sep 27, 2017 11:37 am 
Offline

Joined: Thu Aug 13, 2015 1:34 am
Posts: 97
Location: Kemerovo
What's the license on the source code?


Top
 Profile Send private message  
Reply with quote  
PostPosted: Thu Sep 28, 2017 5:43 am 
Offline

Joined: Wed Aug 26, 2015 11:18 am
Posts: 88
Location: The Netherlands
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.


Top
 Profile Send private message  
Reply with quote  
PostPosted: Thu Sep 28, 2017 8:22 am 
Offline
User avatar

Joined: Sat Jun 25, 2016 12:13 pm
Posts: 153
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"

Code:
 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;

Code:
 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

Code:
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:

Code:
$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;

Code:
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;

Code:
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... :-)

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


Top
 Profile Send private message  
Reply with quote  
PostPosted: Sat Sep 30, 2017 8:17 am 
Offline

Joined: Wed Aug 26, 2015 11:18 am
Posts: 88
Location: The Netherlands
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:

Spoiler: show
Code:
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:

Spoiler: show
Code:
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.

Code:
"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 :-)

## 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:
    Code:
    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

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

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: show
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


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


Top
 Profile Send private message  
Reply with quote  
PostPosted: Wed Oct 04, 2017 10:06 am 
Offline

Joined: Wed Aug 26, 2015 11:18 am
Posts: 88
Location: The Netherlands
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:
Code:
{"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:
Code:
{"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
Code:
{"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):
Code:
{"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
Code:
{"COMMAND : "EXITS"}

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

Reply from XVAN (sample):
Code:
{"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:
Code:
{"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):
Code:
{"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.


Top
 Profile Send private message  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 25 posts ]  Go to page Previous  1, 2, 3  Next

All times are UTC - 6 hours [ DST ]


Who is online

Users browsing this forum: No registered users and 2 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
cron
Powered by phpBB® Forum Software © phpBB Group