Rez v1.2.8 — open source engine for choice-based HTML games

One of the things that I think has so much potential about behaviour trees is the ability for authors to create NPC behaviours that they be unlikely to code for themselves. Especially with a little editor support.

Writing a complex behaviour in Javascript quickly gets unwieldy, even for an experienced developer. The same behaviour as a behaviour tree is easier to reason about and adjust. It’s composed from smaller behavioural units that are easier to write.

With an, outline based, node editor that shows composite structure you could go a long way. I’ve thought about exporting behaviour tree attributes as OPML to create some kind of bi-directional editing support but perhaps a custom interface wouldn’t be too much work.

1 Like

Kind of unrelated, but what does Rez stand for? Why is the IF engine named Rez?

1 Like

It doesn’t stand for anything.

It was originally called Twist (since it started as a variation on the Twine concept) but I came to my senses.

Then for quite a while it was called Grue. Only Robin released Gruescript before I released anything.

I flailed around trying to come up with another name but pretty much everything was used in some way.

I think I’d watched Tron quite recently and the name Rez suggested itself and was fairly short.

2 Likes

I’ve just committed v1.2.3. The main change is to the @rel (relationship) element. The old syntax forced a specific way to express relationships with an affinity constrained to the range -5.0 … +5.0:

@rel #player #kaspar_gutman -2.5 #{:suspicious}

and I’ve changed my mind. Now @rel works more or less like any other element exception that, as before, you don’t specify an id, rather source & target ids (relationships are unidirectional).

@rel #player #kaspar_gutman {
  affinity: -1.0
}

Of course now you can call affinity whatever you like and use any scale that makes sense to you (e.g. -1.0 … +1.0, or even 0 … 100).

@rel #player #miss_wonderly {
  love: 75
  trust: 20
}

If you want tags just use a tags: attribute as normal:

@rel #joel_cairo #kaspar_gutman {
  tags: #{:suspicious}
}

It’s also worth mentioning that relationships can be specified from any element to any element so:

@actor player {…}
@actor joel_cairo {…}
@faction gutman {…}
@item falcon {…}

@rel #player #gutman {…}
@rel #gutman #falcon {…}
@rel #joel_cairo #player {…}

Are all legal. Express any kind of relationship, between any kind of element, that makes sense in your game.

In game, you can obtain a relationship using the API:

$game.getRelationship(source_id, target_id)

or, if you have a reference to an element:

element.getRelationshipWith(target_id)

If you have a RezRelationship for the relationship A->B you can obtain the inverse relationship B->A using:

rel.inverse

Although I’ve not tested it yet, I am pretty sure that you can add a container: attribute to a relationship and specify an @inventory that could, for example, contain @item elements defined as rumours, facts, or memories. Here I am thinking about the question I was pondering earlier about enabling players to ask NPC’s questions about the world.

2 Likes

I’ve managed to create a nice dev workflow, figure its worth sharing.

I run the following command in my game project folder:

fswatch -o src | xargs -n1 ./build

On macOS fswatch is a utility for monitoring changes to the file system. Linux/Windows have inotify/inotify-win instead.

My build script is:

#!/bin/bash
clear
echo "Files change. Rebuilding."
rez compile src/rotn.rez && osascript -e 'tell application "Google Chrome" to reload active tab of front window'

Whenever I save any of the source files I am working on this will recompile the game and reload it in the Chrome window.

If I need to test a specific scene or card I just set initial_scene_id: or initial_card_id: appropriately.

1 Like

As a concrete example of how I am using behaviour trees:

In my city I have stores which may, or may not, be open. Here are a couple of simplified examples (I tend to use a relevant prefix such as v_ or s_ to make it clear when I am talking about, e.g. a vendor or scene):

@vendor v_apothecary {
  name: "The Apothecary"
  description: "Potions, herbs, poultices and such like"
  scene_id: #s_use_vendor
  available_from: [08 00]
  available_until: [17 00]
  player_knows: true
}

@vendor v_inn {
  name: "The Inn"
  description: "A comfortable bed and a meal."
  scene_id: #s_use_vendor
  available_from: [06 30]
  available_until: [23 00]
  player_knows: true
}

@vendor v_black_market {
  name: "Black market peddlar"
  description: "Dangerous if caught by the city guard but some things you cannot acquire elsewhere."
  scene_id: #s_use_vendor
  available_from: [22 00]
  available_until: [03 00]
  player_knows: false
}

Now I want to display a list of vendors that the player knows about and a link to use them, assuming they are open. Time is represented like this:

@object clock {
  time: [08 00]
  day: 1
  week: 1
  cycle: 1
}

As you can see I am using lists to represent times, e.g. [08 00] is 8:00am.

Rez provides a handy object called RezDecision which exposes an API hide(), no(reasons), yes(). We can pass a RezDecision to any code that should decide something for us.

  • hide() means this option shouldn’t be seen by the player, e.g. the player doesn’t yet know about this vendor
  • no(reasons) means this option should be shown to the player but disabled with an optional reason why they can’t select it, e.g. it’s too early
  • yes() means this option should be shown and available to the player

So, we could write something like this:

@object t_vendor {
  $template: true
  is_available: function(decision) {
    decision.default_yes();
    if(!this.player_knowns) {
      decision.hide();
    } else {
      const clock = $("clock");
      if(clock.before(this.available_from)) {
        decision.no("Not open yet");
      } else if(clock.after(this.available_until)) {
        decision.no("Closed for the day");
      }
    }
  }
}

@vendor v_apothecary<t_vendor> {
  ...
}

And now when we go through the list of vendors and generate the appropriate markup depending on whether the player can see vendor, and visit them, or not.

$if (decision.result) -> {%
  <a  data-event="switch" data-target="${vendor.scene_id}" data-vendor="${vendor.id}" data-tooltip="${vendor.description}">${vendor.name}</a>
%}
(decision.hide) -> {%%}
() -> {%
<a href="javascript:void(0)" data-tooltip="${decision.reason}" class="disabled">${vendor.name}</a> 
%}

Not that the is_available function above is particularly difficult to write or understand, but the code itself slightly obscures what is going on. And if we were to add a few more conditions this could become more complex and harder to write & maintain. Using behaviour trees (which are really decision trees) it could look like:

@object t_vendor {
  is_available: ^[$select
    [$sequence [$invert [player_knows_vendor]] [decision_hide]]
    [$sequence [vendor_before_open] [decision_no msg="Not open yet"]]
    [$sequence [vendor_after_close] [decision_no msg="Closed for the day"]]
    [decision_yes]
  ]
}

Once you understand some basic behaviours such as $select, $sequence, and $invert the structure of the decision becomes much clearer.

It means implementing behaviours such as player_knows_vendor, vendor_before_open, decision_hide and so on. But these can be quite compact:

@behaviour vendor_before_open {
  execute: (behaviour, wmem) => {
    return {
      success: $clock.before(wmem["vendor"].available_from),
      wmem: wmem
    };
  }
}

@behviour decision_no {
  execute: (behaviour, wmem) => {
    const decision = wmem["decision"];
    decision.no(behaviour.option("msg"));
    return {
      success: true,
      wmem: wmem
    }
  }
}

Once you have defined these behaviours they are easily composed and their implementation doesn’t get in the way. The behaviour tree more clearly (at least in my view) exposes the logic.

I have this view that an author with less technical expertise could assemble these behaviours to create the meaning and rely on a developer, more adept with Javascript, to build the implementation of the various behaviours for them. Madness? I don’t know.

1 Like

Welcome to the freak show. :crazy_face:

All you can do is make it the best it can be and true to your vision. In my research, I’ve found an appreciation for the divide between narrative games and simulation games. I think the tools in Rez lean towards a simulated game world to that of what parser games typically offer, yet Rez is more likely to be used in a choice-based interface design. I think Rez is filling a void in the IF authoring community. Even if Rez remains a niche solution, in a niche gaming genre, I think you’re on to something worthwhile. :slight_smile:

2 Likes

Thanks :slight_smile:

You’re on the money that the games I am building are IF/procgen/simulations with emergent narrative, rather than heavily story-based games.

I think Rez is well suited to be a Twine++ type of tool: all you need to master is @scene and @card, and HTML, absolute no scripting required.

Though a Rez source file does look different and perhaps a little daunting to someone without programming experience?

I don’t have any frame of reference to work out if Rez is easy to learn or not. Perhaps alternatives like ifinity seem more familiar? Or appear to have a shallower on-ramp?

I do know that it offers a lot of power for a relatively low investment of effort compared to building the same systems for yourself.

But I will keep plugging away and, of course, producing an actual game wouldn’t hurt :slight_smile:

3 Likes

This is the most important thing a new IF framework can do. “Show, don’t tell” is the mantra for good movie directors/writers… why not for game engine programmers too? :wink:

3 Likes

I’ve published the v1.2.5 release including binaries for macOS, linux, and Windows.

The main change is an overhaul and fixes to behaviour trees that have come from my now using them to convert some scripted behaviors in my own game.

In the future I am going to look for a way to allow authors to ‘compose’ behaviours more easily.

3 Likes

I think, I hope, I have come up with an elegant solution to this problem.

I’m writing behaviour trees which have common elements. For example “Does the player have enough money for the cost of this item?” ends up being a behaviour that most items share:

^[$sequence [query elem=#player if={player.cash < obj.cost}] [decide_no msg="You cannot afford it."]]

But we might have another condition: Does the vendor like you enough to sell this to you?

^[$sequence [query mem=:vendor if={vendor.relationshipWith("player").affinit < -0.1}] [decide_no msg="They are not willing to sell this to you."]]

Or, perhaps, the item is of the wrong class:

^[$sequence [query elem=#player if={player.class != obj.class}] [decide_hide]]

Having to write these out each time gets to be a pain and error prone. So, what I’ve done is introduce the @behaviour_template directive. Example:

@behaviour_template player_cannot_afford ^[$sequence [query elem=#player if={player.cash < obj.cost}] [decide_no msg="You cannot afford it."]]

And this is now used in a behaviour tree:

@item long_sword {
  is_available: ^[$select &player_cannot_afford
                          &player_is_wrong_class
                          [...]
                          [decide_yes]]
}

Templates can also contain other templates allowing complex behaviours to be built from less complex behaviours.

Also if you’re still unsure about what behaviour trees are, or how they work, this video does a pretty good job outlining this key AI technique. Most examples are some kind of real-time FPS game but the same techniques can work just as well in an IF simulation.

3 Likes

How I am working with nested objects right now.

Rez supports table attributes, so you can write:

@actor player {
  attrs: {
    str: {
      cur: 3d6
    }
    dex: {
      cur: 3d6
    }
    con: {
      cur: 3d6
    }
  }
}

The downside is that the attrs: attribute is a JS object representing the entire table of nested values. If you want to change the current value of str: you have to get the attrs object, futz with the nested value, then put the whole object back.

This is not a problem, per se, but I don’t feel its a good solution (esp. as Javascript doesn’t have great support for working with nested values (where Clojure for example has excellent support).

A while back I realised that I’d made Rez into an “id based” programming system. Every element has an id, there is the $(id) function that returns the object for a given id. Then I implemented a feature that automatically generates properties for attributes with an _id suffix.

So, if you have an attribute attrs_id: #attrs_obj it will automatically create a companion JS property attrs that is a reference to whatever object represents #attrs_obj.

So now I would write this:

@actor player {
  attrs_id: #player_attrs
}

@object player_attrs {
  str_id: #player_str
  dex_id: #player_dex
  con_id: #player_con
}

@object player_str {
  cur: 3d6
}

Now I have objects delegating to each other, I can write $player.attrs.str.cur += 1 and the lookups happen in the background.

There is a serialisation benefit underlying this, because references are “by id” there is no possibility of cyclic references. Rez actually deals with references as part of its built in save/load mechanic but it’s finicky and I’m looking to simplify it.

2 Likes

Released v1.2.6 with binaries for macOS, Linux, and Windows.

The main feature added was the @behaviour_template directive for specifying chunks of behaviours to share between different objects.

I’ve also been doing a lot of work on the documentation including starting on an authoring guide which I am writing in conjunction with building, which should be less overtly about technical detail and more leading you through the process of creating a game with the tool.

1 Like

In v1.2.7 I’ve removed a thorn from my paw. When you make bindings you now use a list, which means that one binding can depend upon another:

@card foozle {
  bindings: [
    game: #game
    title: game.title
  ]
  content: ```
  ${title}
  ```
}

In such a simple case you’d probably be okay writing ${game.title} but in more nested contexts (game.player.attributes.strength.current_value) I’ve found it gets tedious and this makes life a little easier.

This wasn’t possible before because bindings were specified using a table (map) value and JS objects do not guarantee order, so that you could not be sure that title wouldn’t be evaluated before game was, causing an exception.

1 Like

But they do?

Object.keys():

Object.keys() returns an array whose elements are strings corresponding to the enumerable string-keyed property names found directly upon object. This is the same as iterating with a for...in loop, except that a for...in loop enumerates properties in the prototype chain as well.

for … in loop:

The traversal order, as of modern ECMAScript specification, is well-defined and consistent across implementations. Within each component of the prototype chain, all non-negative integer keys (those that can be array indices) will be traversed first in ascending order by value, then other string keys in ascending chronological order of property creation.

And if I interpret the ECMAScript standard correctly, the order of property creation using an {...} is also guaranteed to be in-order.

Thanks. Of course I could be wrong, but while for…in is guaranteed because it is enumerating array indices, does Object.keys make guarantees about the order in which properties are enumerated? My understanding is not.

Also, I didn’t mention that Elixir maps (how the Rez compiler represents tables internally) do not guarantee order. So even if the compiled JS object does, I can’t guarantee that object has the right order in the first place.

As a rider, I think conceptually a list is a more appropriate choice given order dependency. It doesn’t then depend upon an implementation detail of maps. I didn’t think too hard about this when I first introduced bindings. A table was obvious (for defining key pairs) but wrong.

My quotes are from the MDN documentation of the JS functions. for...in doesn’t just work for arrays, but also for objects. In that case it returns the keys of the properties. And since for...in guarantees order, so does Object.keys().

Oh, so Rez is implemented in Elexir which is compiled to JS?

I’m on my phone so a bit linited right now but it may be that when i researched this i came up with out of date info.

You got it. Rez is an Elixir compiler app that produces HTML scaffold, JS source, and associated resources.

I looked it up, Elixir maps are un-ordered. But surely there is an ordered map implementation for Elixir as a library - even C++'s notoriously lacking standard library has an ordered map.

Yes, I did find someone had built an OrderedMap implementation that was a little old but serviceable. But essentially what this is doing is adding a List that holds the insertion order of keys.

Switching to a List is conceptually simpler and, as I mentioned above, would have been the sounder choice in the first place (esp when you consider bindings: to be a Clojure let).

1 Like