Xopus plays Tic-Tac-Toe

Usually Xopus keeps you within the bounds and rules of a structured document: bold is allowed in a paragraph, but not in a header, or a header must be followed by the name of the author.

A game like Tic-Tac-Toe has similar rules: cross and circle can only be put in an empty square, when a player puts a cross in one of the squares, that is followed by putting a circle in one of the other squares, etc.

By teaching Xopus these rules in much the same way as you would with document rules, Xopus can respond to a move. When a player (author) puts (inserts) a circle, Xopus knows it should respond (the document is not valid). Xopus will respond by putting (inserting) a cross in the right (valid) location.

Play Tic-Tac-Toe against Xopus!
(tested in IE6 and 7 and Firefox 2 and 3)

Hint: if you can't win, try undo.

How did we do it?

There are four key ingredients needed to teach Xopus to play this game:

  1. XSD describing the valid games states.
  2. XML document which contains the initial (empty) game state.
  3. XSL stylesheet to convert the game state to HTML.
  4. A few lines of Javascript.

The main intelligence is contained in the XSD. This contains the valid game states, Xopus will use this to calculate the transition from one valid game state to the next. The XSD was generated and tweaked so that Xopus is worthy opponent.

The XML starts out like this:

<games />

The XSL is fairly straightforward:

<xsl:stylesheet version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  
  <xsl:template match="/games">
    <xsl:apply-templates />
    <div class="game">
      <span class="at22">
        <img src="newgame.jpg" onclick="addElement(node, 'game')" />
      </span>
    </div>
  </xsl:template>
  
  <xsl:template match="game">
    <div class="game">
      <xsl:apply-templates />
    </div>
  </xsl:template>

  <xsl:template 
       match="at11|at21|at31|at12|at22|at32|at13|at23|at33">
    <span class="{name()}" style="z-index: {count(ancestor::*)}">
      <xsl:if test="not(O|X)">
        <!--  
          Make a move if the square is empty and 
          this game has no winner. 
        -->
        <xsl:attribute name="onclick">
          if (!node.hasChildNodes() &amp;&amp; 
              !node.selectSingleNode("ancestor::game//O_has_won|" +
                "ancestor::game//X_has_won"))
          { 
            addElement(node, 'O')
          }
        </xsl:attribute>
        <img src="_.jpg" style="cursor: pointer"/>
      </xsl:if>
      <xsl:apply-templates select="O|X" />
    </span>
    <xsl:apply-templates select="O/*|X/*" />
  </xsl:template>
  
  <xsl:template match="O">
    <img src="O.jpg" />
  </xsl:template>

  <xsl:template match="X">
    <img src="X.jpg" />
  </xsl:template>

  <xsl:template match="O_has_won">
    <div class="message">You have won!</div>
  </xsl:template>

  <xsl:template match="X_has_won">
    <div class="message">Xopus has won!</div>
  </xsl:template>

  <xsl:template match="draw">
    <div class="message">This game is a draw...</div>
  </xsl:template>
</xsl:stylesheet>

Then there is a little bit of Javascript to glue the components together. The new game button will call:

addElement(node, "game")

Where node is the games root element. Clicking an empty square to make a move will call:

addElement(node, "O")

Where node is the square that was clicked. The addElement function looks like this:

function addElement(parent, name) 
{ 
  var node = parent.getOwnerDocument().createElement(name); 
  parent.appendChild(node); 
  node.makeValid(); 
}

appendChild is used to either insert a new game or insert an O.
The magic is in makeValid which will initialize the game or make the next move according to the rules in the XSD.

That's all for now. We have to continue our work on Xopus 4.

Modified: July 10th 2008
By: Laurens van den Oever

ChrisEidhof
anonymous user
July 25th 2008
Haha, totally awesome! What's next, a FPS in Xopus? If it could be done performance-wise, that would be awesome. Use XSL to render your game-state into canvas-objects. Or SVG.