intfiction.org

The Interactive Fiction Community Forum
It is currently Thu Jun 21, 2018 3:28 am

All times are UTC - 6 hours [ DST ]




Post new topic Reply to topic  [ 27 posts ]  Go to page Previous  1, 2, 3
Author Message
PostPosted: Wed Oct 04, 2017 5:24 pm 
Offline
User avatar

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

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

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


Top
 Profile Send private message  
Reply with quote  
PostPosted: Wed Oct 04, 2017 5:43 pm 
Offline
User avatar

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


Top
 Profile Send private message  
Reply with quote  
PostPosted: Thu Oct 05, 2017 8:19 am 
Offline

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

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

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


I'll get back on the JSON thing later.


Top
 Profile Send private message  
Reply with quote  
PostPosted: Mon Oct 09, 2017 10:00 am 
Offline

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

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?


Top
 Profile Send private message  
Reply with quote  
PostPosted: Mon Oct 23, 2017 12:25 pm 
Offline

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


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


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.


Top
 Profile Send private message  
Reply with quote  
PostPosted: Mon Jan 15, 2018 3:17 pm 
Offline

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

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:

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


Top
 Profile Send private message  
Reply with quote  
PostPosted: Wed Mar 28, 2018 3:05 pm 
Offline

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


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

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:  
Powered by phpBB® Forum Software © phpBB Group