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.
<instance> <game xmlns=""> <row><c/></row> </game> </instance>
and grow it by responding to the xforms-ready
event:
<action ev:event="xforms-ready"> ... </action>
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>
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) < 10"/> <insert ref="row" while="count(//row) < 10"/>
Place 10 mines at random locations:
<setvalue ref="row[round(random()*9)+1]/c[round(random()*9)+1]" while="count(//c[.='💣']) < 10">💣</setvalue>
<repeat ref="row"> <repeat ref="c"> <output value="."/> </repeat> </repeat>
adding some suitable CSS:
Fill each non-mine cell with the count of its mine-filled neighbours:
<bind ref="//c[.!='💣']" calculate="count(...something here...)"/>
../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)[.='💣'])"/>
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>
Cells are hidden until clicked
<output value="if(@clicked='', '□', .)"/>
Zeros are displayed as a (non-breaking) space:
<output value="if(@clicked='', '□', if(.=0, ' ', .))"/>
(All examples are live, so you can click on them)
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, ' ', .))"/>
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]"/>
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