XML.com

A Game in XForms

May 20, 2019

Steven Pemberton

Steven Pemberton continues his XForms series with an example of coding a game.

Introduction

My youngest asked me how you would program a game. I asked what sort of game, and he described the game where you have to slide numbered tiles around to get them in order.

Slide game

Take some data

We start with four rows of four cells, containing the numbers 1-15 in any old order, plus one blank cell:

<instance>
  <data xmlns="">
    <row><cell>1</cell><cell>5</cell><cell> 8</cell><cell>12</cell></row>
    <row><cell>2</cell><cell>6</cell><cell> 9</cell><cell>13</cell></row>
    <row><cell>3</cell><cell>7</cell><cell>10</cell><cell>14</cell></row>
    <row><cell>4</cell><cell>.</cell><cell>11</cell><cell>15</cell></row>
  </data>
</instance>

Display it

We can display the numbers like so (with a suitable bit of CSS to format the cells, setting height and width and adding a border, not shown here):

<repeat ref="row"> 
    <repeat ref="cell">
        <output value="."/>
    </repeat>
</repeat>

which would look like this:

Source

Adding interaction

We want to be able to click on the tiles in order to move them, so we wrap the output of the number with a trigger. This is the similar to a button in HTML, except that XForms tries to be representation-neutral, and so avoids using naming that suggests a particular representation:

<repeat ref="row"> 
    <repeat ref="cell">
        <trigger appearance="minimal">
            <label><output value="."/></label>
        </trigger>
    </repeat>
</repeat>

The appearance="minimal" is an indication that you don't want it formatted as a button, just as regular text, but still acting as a button.

However, this trigger doesn't do anything yet. To achieve this we add an action within the trigger:

<trigger appearance="minimal">
    <label><output value="."/></label>
    <action ev:event="DOMActivate">
        ...
    </action>
</trigger>

This responds to the DOMActivate event on the trigger, which is the event that occurs when a trigger is clicked on.

What we want the action to do is copy the value in the clicked-on cell to the empty cell:

<setvalue ref="//cell[.='.']" value="context()"/>

and make the clicked-on cell empty:

<setvalue ref="." value="'.'"/>

In total:

<trigger appearance="minimal">
    <label><output value="."/></label>
    <action ev:event="DOMActivate">
        <setvalue ref="//cell[.='.']" value="context()"/>
        <setvalue ref="." value="'.'"/>
    </action>
</trigger>

This then looks like this (try clicking on the cells):

Source

However, this allows you to click on any square, and we only want to allow swapping a square if the empty square is one of its (up to) four directly adjacent ones.

To do this, we add a condition to the action:

<action ev:event="DOMActivate" if="...">

The condition is the tricky bit. The preceding or following cell in a row is easy:

preceding-sibling::cell[1]='.' or
following-sibling::cell[1]='.'

The cell at the same position in the preceding or following row is slightly harder. The following row is:

../following-sibling::row[1]

We want to find the cell within that row at the same position:

../following-sibling::row[1]/cell[...position calculation here...]

The position in the row of the cell clicked on is one plus the number of preceding cells there are:

1+count(context()/preceding-sibling::cell)

So putting it together:

 ../following-sibling::row[1]/cell[1+count(context()/preceding-sibling::cell)]='.' or
 ../preceding-sibling::row[1]/cell[1+count(context()/preceding-sibling::cell)]='.'

Which gives us our final game. You can try it out:

Source

Moving the condition to the model

XForms splits processing in two parts, form and content: the model contains details of the data and its relationships, and the content contains the view onto the data and deals with interaction.

It is usually preferable to have as much of the data description as possible in the model. An example of this is the description of which cells may be clicked on. Having the condition on the action as we do now describes when the action can be carried out. However, we could move it to the model to describe which cells may be clicked on. We do this in the following way.

We add a click attribute to each cell. This will indicate whether the cell can currently be clicked on or not.

<instance>
  <data xmlns="">
    <row><cell click="">1</cell><cell click="">5</cell>...

etc.

We move the condition up to the model. Immediately after the instance, we add a bind:

<bind ref="row/cell/@click" type="boolean"
      calculate="..."/>

where the calculate attribute holds the condition, with one change since the context for this expression is slightly different: we are talking about the attribute, and on the action we were talking about the cell itself. So the condition has to be applied one level higher by adding a "../" before its elements.

The action in the content can now look like this:

<action ev:event="DOMActivate" if="@click = true()">

Since the click attribute now contains true or false, just for fun, and to demonstrate the subtle difference between describing which cells may be clicked on and when the action may be done, we can display the clickable cells differently by adding class="cell {@click}" to the cell output, and adding different formatting to the CSS for class="true", thus making it visually obvious which cells are clickable:

.true { ... }

Source

Change the output

"It would be more fun if it was like a jigsaw."

OK. Easy peasy. Almost a single change:

<output value="." mediatype="image/*"/>

This says, instead of outputting a value such as "1", interpret the 1 as a filename, and display that file as an image.

The only problem is that you are not allowed to use "." as a filename, so we'll have to catch that case, and we'll use "blank" as the filename instead:

<output value="if(.='.', 'blank', .)" mediatype="image/*"/>

That gives a version of the game as a sort of jigsaw (audience gasps):

Source