What is Rez?


Rez is a language and compiler for creating “hypermedia games”.

At its simplest, this means games implemented using HTML, Javascript, CSS, images, audio, and movies, where the primary way the player interacts with the game is by clicking on links.

There is a substantial overlap with ‘interactive fiction‘, a broad genre encompassing visual novels and games. However, sometimes the games will be played by typing instructions rather than clicking links. Zork is an early example of a hugely popular IF game. These were often called “adventure games”. Twine and Inform are popular tools for creating interactive fiction.

Rez is designed to sit between Twine and Inform in terms of complexity. It makes it easy to do things that would be a lot of work in Twine, but its bounded nature and event callbacks (written in Javascript) likely make it more accessible than Inform.

You write Rez in source code form. Here’s an illustrative example to give you a flavour of what a Rez game could look like:

@game begin
  name: "The Maltese Parrot"
  author: "Matt Mower"
  IFID: "D2050DE2-97A2-1ED1-4CCA-AF9D3B0DD883"
  created: "2022-08-31 22:13:43.830755Z"
  version: 1
  initial_scene: #meet_sam_spade

  @actor player begin
    name: "Miss ..."
    intuition: 12
    deceit: 15
    playfulness: 17
    has_parrot: false
    container: #player_carrying
  end

  @actor sam_spade begin
    height: 180
    eyes: "piercing, yellow grey"
    intuition: 17
    gunplay: 12
    callousness: 14
    has_parrot: false
    container: #sams_stuff
  end

  @derive :small_item :item
  @derive :gun :small_item
  @derive :gun :weapon
  @derive :big_item :item
  @derive :hat :big_item
  @derive :hat :clothing
  @derive :parrot :big_item
  @derive :parrot :treasure

  @slot on_head begin
    name: "Head"
    accepts: :hat
    capacity: 1
  end

  @slot in_valise begin
    name: "Valise"
    accepts: :big_item
    capacity: 2
  end

  @slot inside_pocket begin
    accepts: :small_item
  end

  @slot in_wallet begin
    accepts: :bank_note
  end

  @inventory sams_stuff begin
    slots: [#on_head #inside_pocket #in_wallet #in_valise]
    initial_on_head: #brown_fedora
  end

  @item sams_valise begin
    type: :big_item
    description: "A valise, obviously well used judging by how battered and frayed it is."
    container: #valise_contents
  end

  @item maltese_parrot begin
    type: :parrot
    eponymous: true
    description: (game, item) => {
      if(item.flag("enamelled")) {
        return "A parrot, covered in black enamel. Quite who would want to do that to a parrot is beyond you.";        
      } else {
        return "A plain plaster parrot of no obvious value unless you are an ornithophile. You regard it with some disdain.";
      }
    }
    enamelled: true
  }

  @asset brown_hat_pic begin
    file: "brown_hat.jpg"
  end

  @item brown_fedora begin
    type: :hat
    description: "brown fedora"
    wearable: true
    asset: #brown_hat_pic
    description: "A Messer wool fedora hat. Classy."
  end

  @item suspicious_about_player begin
    type: :concept
    flavour_text: "Sam raises an eyebrow just a little"
  end

  @item heard_of_parrot begin
    type: :concept
    flavour_text: "Ah yes, **that** parrot. Very valuable so they say."
  end

  @slot known_concept begin
    name: "Concept"
    accepts: :concept
    capacity: 999
  end

  @inventory sam_knows begin
    description: "Things that Sam knows about"
    slots: [#known_concept]
  end

  @scene meet_sam_spade begin
    layout_mode: :continuous
    initial_card: #sam_at_his_desk
    location: #sams_office
    blocks: [#sidebar]
    layout: """
    <div class="sidebar">{{sidebar}}</div>
    <div class="main">{{content}}</div>
    """
    on_enter: (game, event) => {
      const sam = $("sam_spade");
      const intuition = sam.getAttribute("intuition");
      if(roll("d20") < intuition) {
        const sam_knows = $("sam_knows");
        const idea = $("suspicous_about_player");
        sam_knows.addForSlot("concepts", idea);
      }
    }
  end

  @card sam_at_his_desk begin
    bindings: [#player]
    player_named: false
    content: """
      You enter the room to see a man, probably 6', with pale brown, almost blonde hair. He looks up with yellowish grey eyes that don't miss a beat.

      "Come in miss...", he says to you.

      [[* say_name]] [[Enter the Office]] [[>Turn Around|this_didnt_happen_in_the_movie]]
    """
    say_name: (choice) => {
      if(choice.card.flag("named")) {
        choice.hide();
      } else {
        const player = $("player");
        const playfullness = player.getAttribute("playfullness");
        if(roll("d20") < playfullness) {
          choice.allow("Make up a name", lie_to_sam);
        } else {
          choice.allow("I am Miss...", tell_sam_name);
        }
      }      
    }
  end

  @card tell_sam_name begin
    content: """
    You tell Sam your name
    <form rez-live name="player_name">
      <input rez-live type="text" name="edit_name" />
    </form>
    """
    on_edit_name: (game, event) => {
      $("player").setAttribute("name", "Miss " + event.target.value);
      $("player").setAttribute("named", true);
    }
    on_player_name: (game, event) => {
      game.currentScene().playCard("sam_at_his_desk");
    }
  end

  @list made_up_names begin
    values: [
      "Cairo",
      "Gutman",
      "Wilmer"
    ]
  end

  @card lie_to_sam begin
    bindings: [#player]
    suspicion_text: ""
    on_render: (game, card) => {
      const list = $("made_up_names");
      const name = list.randomElement();
      $("player").setAttribute("name", "Miss " + name);
      $("player").setAttribute("named", true);

      const sam_knows = $("sam_knows");
      if(sam_knows.itemInSlot("suspicious_of_player", "known_concept")) {
        card.setAttribute("suspicion_text", "Does he raise an eyebrow just a fraction when you say that?");
      }
    }
    content: """
    You're not quite ready to share that yet so you make up a name. I'm {{$ player "name"}} you tell him. {{$ this "suspicion_text"}}
    [[Enter the Office]] [[>Turn Around|this_didnt_happen_in_the_movie]]
    """
  end

  @card enter_the_office begin
    ...
  end

  @scene this_didnt_happen_in_the_movie begin
    ...
  end

  ...
end

A game is described in terms of elements like actor, item, inventory, card, scene, faction, effect, location, and so on.

Most of these are described with attributes. For example, the content attribute of a card specifies (using Markdown + Handlebars) the HTML that will be rendered when the card is displayed.

Dynamic behaviours are implemented using Javascript callbacks. In this example, when the player enters the scene, #meet_sam_spade we check Sam’s intuition to see if he is immediately suspicious of us. We also use a dynamic link to decide whether the player will share their real name or use an alias. Many event hooks are available for implementing the dynamic parts of the game.

Think of scenes like scenes in a movie where a specific type of action takes place. Some scenes could be pretty generic, some very specific. Every scene has a layout, and playing cards add content that may replace or append to what content has already been displayed.

To play a game, you compile it. This creates a dist folder containing the index.html file and all the Javascript, CSS, and asset files (e.g. images) necessary to play the game. You can then distribute the dist folder.


Leave a Reply

Your email address will not be published. Required fields are marked *