Minesweeper in XForms

Steven Pemberton, CWI, Amsterdam

The author

The Board

We're going to program for a 10×10 board of cells.We could just write the board out wholesale:

<instance>
  <game xmlns="">
    <row><c/><c/><c/><c/><c/><c/><c/><c/><c/><c/></row>
    <row><c/><c/><c/><c/><c/><c/><c/><c/><c/><c/></row>
    <row><c/><c/><c/><c/><c/><c/><c/><c/><c/><c/></row>
    <row><c/><c/><c/><c/><c/><c/><c/><c/><c/><c/></row>
    <row><c/><c/><c/><c/><c/><c/><c/><c/><c/><c/></row>
    <row><c/><c/><c/><c/><c/><c/><c/><c/><c/><c/></row>
    <row><c/><c/><c/><c/><c/><c/><c/><c/><c/><c/></row>
    <row><c/><c/><c/><c/><c/><c/><c/><c/><c/><c/></row>
    <row><c/><c/><c/><c/><c/><c/><c/><c/><c/><c/></row>
    <row><c/><c/><c/><c/><c/><c/><c/><c/><c/><c/></row>
  </game>
</instance>

But we won't.

Start with a 1 cell board

<instance>
  <game xmlns="">
    <row><c/></row>
  </game>
</instance>

and grow it by responding to the xforms-ready event:

<action ev:event="xforms-ready">
  ...
</action>

Insert

The simplest form of the <insert/> action just duplicates the last element of a (non-empty) list. So starting with

<row><c/></row>

the action

<insert ref="row/c"/>

gives

<row><c/><c/></row>

Insert

So we just do that until we have ten cells in the row, and then the same for the rows, giving us a 10×10 board:

<insert ref="row/c" while="count(//c) &lt; 10"/>
<insert ref="row" while="count(//row) &lt; 10"/>

Mines

Place 10 mines at random locations:

<setvalue
   ref="row[round(random()*9)+1]/c[round(random()*9)+1]"
   while="count(//c[.='💣']) &lt; 10">💣</setvalue>

Display

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

adding some suitable CSS:

Result

Counting

Fill each non-mine cell with the count of its mine-filled neighbours:

<bind ref="//c[.!='💣']" 
      calculate="count(...something here...)"/>

Neighbours

../preceding-sibling::row[1]/c[2+count(context()/preceding-sibling::c)] |
../preceding-sibling::row[1]/c[1+count(context()/preceding-sibling::c)] |
../preceding-sibling::row[1]/c[  count(context()/preceding-sibling::c)] |
   preceding-sibling::c[1] | 
   following-sibling::c[1] |
../following-sibling::row[1]/c[2+count(context()/preceding-sibling::c)] |
../following-sibling::row[1]/c[1+count(context()/preceding-sibling::c)] |
../following-sibling::row[1]/c[  count(context()/preceding-sibling::c)]

So:

<bind ref="row/c[.!='💣']" 
      calculate="count((neighbours)[.='💣'])"/>

Result

Interaction

Surround the output with a trigger that marks the cell as clicked:

<repeat ref="row"> 
   <repeat ref="cell" >
      <trigger appearance="minimal">
         <label><output value="."/></label>
         <setvalue ev:event="DOMActivate" ref="@clicked">yes</setvalue>
      </trigger>
   </repeat>
</repeat>

Change the board:

<instance>
  <game size="10" mines="10" xmlns="">
    <row><c clicked=""/></row>
  </game>
</instance>

Change the output

Cells are hidden until clicked

<output value="if(@clicked='', '□', .)"/>

Zeros are displayed as a (non-breaking) space:

<output value="if(@clicked='', '□', if(.=0, '&#x00A0;', .))"/>

Result

(All examples are live, so you can click on them)

Oil slick effect

If you click on a zero cell, neighbouring zeros get revealed as well.

Change the board:

<instance>
  <game size="10" mines="10" xmlns="">
    <row><c clicked="" revealed=""/></row>
  </game>
</instance>

Change the output slightly:

<output value="if(not(@revealed), '□', if(.=0, '&#x00A0;', .))"/>

And specify what it means for a cell to be revealed:

<bind ref="row/c/@revealed" type="boolean"
      calculate="../@clicked='yes' or neighbours[@revealed and .=0]"/>

Result

Bells and whistles

Keep track of state of play. You lose if a mine has been revealed; you win if the number of non-revealed cells equals the number of mines:

<bind ref="@state"
      calculate="if(//c[.='💣' and @revealed], 'lose', 
      if(count(//c[not(@revealed)])=count(//c[.='💣']), 'win',
      ''))"/>

Keep count of remaining non-mine squares:

<bind ref="@count" calculate="count(//c[not(@revealed)])-/game/@mines"/>

Add some fancy CSS

Result

About 50 lines of XForms

Source (may differ slightly from exposition above)