Sun Java Solaris Communities My SDN Account Join SDN
 
Article

TicTacToe Revisited

 

TicTacToe Revisited
Exploring Dynamic HTML in 1.3

By Scott Violet

Those of you who have played around with the Java platform for any length of time have likely encountered the TicTacToe demo that has shipped since the earliest release. As you could probably guess, the TicTacToe applet allows you to play a game of tic-tac-toe against the computer. The TicTacToe demo is implemented by subclassing the Applet class and handling the painting of the board within that subclass. This article takes the different approach of using an HTML document to render the current state of the tic-tac-toe board. Along the way, some of the new API in the Java 2 SDK, Standard Edition, v1.3 Release, to dynamically update an HTML document, will be discussed.

For those anxious to look at the source, or download the demo, here are the necessary files:

Displaying the Board

As previously mentioned, the rendering of the board, as well as its current state, will be handled by an HTML document. Swing contains the class JEditorPane which can be used to display HTML text. To configure a JEditorPane to display an HTML document is as simple as:

    JEditorPane jep = new JEditorPane();
    jep.setPage(new URL("http://java.sun.com"));

JEditorPane allows for displaying and editing of various content types by way of a pluggable EditorKit (a full discussion of an EditorKit is beyond the scope of this article). The setPage method of JEditorPane obtains the content type from the URLConnection method getContentType (a URLConnection can be obtained from a URL via the method openConnection). The content type is then mapped to an EditorKit. In the previous example, the content type for the passed in URL is HTML text. JEditorPane then plugs in an EditorKit for HTML text, and is then able to correctly render the page.

In this example, the HTML document will be distributed with the example, so that the following code will be used:

    JEditorPane jep = new JEditorPane();
    jep.setPage(getClass().getResource("TicTacToe.html"));

At this point, JEditorPane has done a number of things, one of which is to create a model of the specified HTML file. The model is represented by the Document subclass HTMLDocument. The current look and feel (by way of the EditorKit) has created a view of the model that is rendered by a set of View classes. The model is internally represented as a set of hierarchical Elements that can be accessed either by Element, or by a particular offset (an integer) into the model. The UI, by way of the View it has created, is able to map from a visual space (in x, y coordinates) to the model space.

HTML supports many possible approaches to render a page that looks like a tic-tac-toe board. This example uses a simple table with three rows and three columns, with an attribute on each table cell to indicate the board location it represents. The state of a particular cell on the tic-tac-toe board will be rendered by one of three images:

  • The user has selected the cell:
  • The computer has selected the cell:
  • The cell is available (an empty image):

The following shows one row of the HTML table in the initial state of the game. Note the id attribute of the td tags that are used to identify the board location the table data item, td, represents:

    <table>
      <tr>
	<td id=0><img src=emptyImage.gif></td>
	<td id=1><img src=emptyImage.gif></td>
	<td id=2><img src=emptyImage.gif></td>
      </tr>
    

Interacting With The User

To correctly update the state of the game, we need a way to determine when and where the user clicks on a particular board cell. This can be handled by a MouseListener installed on the JEditorPane. When the mousePressed method is invoked, the selected cell is determined and, if necessary, the state of the game is updated.

Once the location of the MouseEvent has been determined the next step is to locate which cell, if any, the user clicked on. The process to determine the table cell is:

  1. Mapping from the view coordinate space to the document space.
  2. Locating the table data item at that location.
  3. Extracting its id attribute.

The first step is mapping from the view coordinate space to the model coordinate space, which is handled by the following code:

    Position.Bias bias[] = new Position.Bias[1];
    int offset = jep.getUI().viewToModel(jep, new Point(x, y), bias);
    

viewToModel returns the offset closest to the specified location as well as a Bias to further narrow down the location in the model. In the context of the text classes, an offset (often times represented as an instance of Position) into the Document model represents a position between two characters, not a particular character. From the rendering perspective an offset can often times be ambiguous, especially in the case of bi-directional text. Consider the following diagrams:

The diagram on the left shows how the text is modeled, while the diagram on the right shows hows the text is actually rendered. If the user clicks between the b and D it becomes ambiguous (with an offset alone) as to where new text should be added. Should new text be added after the b or after the D? That is, which of the following shows how the model should look after the insertion:

A Bias is used to distinguish between these two possibilities. Inserting after the b is represented as offset 2 with a backward Bias, while inserting after the D is represented as offset 4 with a backward Bias. The visual position to the right of the C would then be 2 with a forward bias. A Bias is typically only of interest to the visual (view) side of text components. The model is not aware of Bias, it simply models the text as a set of Elements mapped onto a set of characters.

The next step is to locate the table data item using offset, the offset into the Document. We do this by starting at the deepest Element and iterating through the parent Elements until a table data Element is located, or an Element has no parent, indicating the user clicked on an area that is not part of the board. The StyledDocument method getCharacterElement returns the deepest Element at a particular offset. The following code uses getCharacterElement and getParentElement to iterate through the Elements looking for a table data Element at offset:

    Element e = htmlDoc.getCharacterElement(offset);
    while (e != null && e.getAttributes().getAttribute
                (StyleConstants.NameAttribute) != HTML.Tag.TD) {
        e = e.getParentElement();
    }                        

At this point we now potentially have the Element representing the cell on the board the user selected. We need to do one additional check. viewToModel returns the document offset that is closest to the specified visual location. We need to make sure the that bounds of the table cell actually contain the mouse location. We can use modelToView for this, as the following code shows:

    int start = e.getStartOffset();
    int end = e.getEndOffset();
    Rectangle r1 = jep.getUI().modelToView(jep, start, Position.Bias.Forward);
    Rectangle r2 = jep.getUI().modelToView(jep, end, Position.Bias.Backward);

    if (bounds.union(r1, r2).contains(x, y)) ...
    

If the rectangle does in fact contain the mouse location, the last step is to determine the actual cell on the board. As previously mentioned, the cell ID is stored as an attribute in the table data tag. The following code shows how to extract the cell from the Element representing the table cell:

    Object boardLocation = e.getAttributes().getAttribute(HTML.Attribute.ID);
    int boardCell = Integer.parseInt((String)boardLocation);                        

We now know which cell the user has selected. The next step is to the update the display accordingly.

Dynamically Updating an HTML Document

The Swing text package has always had the ability for text to be dynamically updated. Typically a developer would insert a string via the Document method insertString, or delete text via the Document method remove. However, when you are working with structured text, such as HTML, you often want to operate at a lower functional level. For example, it would be convenient to have methods for adding a new Element to a particular Element. While this type of operation has always been possible, it previously required a fair amount of effort to implement.

If you're familiar with dynamical HTML, you've probably encountered operations for altering an existing HTML element with new HTML text. For 1.3, we have added similar operators to HTMLDocument. The new methods all work on the same principal -- altering a particular Element to contain new HTML text. The methods differ only in where the HTML text is inserted. The following diagrams show the state before and after using these new methods.

A good way to visualize these insertion methods is to think of them as tree operations. Each method takes an Element identifying a node in a tree, the name of the method identifies the operation to perform on the node, and text identifies the new nodes that are to be inserted. The following table shows the state before and after using these methods. The bolded node in the second column identifies the Element passed into the method, and the italicized node in the fourth column identifies the newly inserted Elements.

Initial Document HTML Resulting Document
setOutterHTML <p>content
setInnerHTML <tr><td>
insertBeforeStart <tr><td>
insertAfterStart <td>
insertBeforeEnd <td>
insertAfterEnd <tr><td>

For this demo, when a cell on the board is changed, we take the approach of completely replacing the table cell. For this we use the setOuterHTML method. Once we have located the Element, as shown, the code to reset the table cell element looks like this:

    htmlDoc.setOuterHTML(e, html);
    

This immediately changes the model and updates the display as necessary.

The Computer Opponent

We won't go into the details as to how the computer opponent chooses a particular location; refer to the source code if you are interested. Once the the computer decides which cell to choose, we again need to update the HTML document. We will take the same approach as before, that is, to replace the table cell element representing the board location using the setOuterHTML method.

The interesting thing to note here is how we locate the Element representing the table data tag corresponding to the location on the board which the computer opponent has selected. There are a number of ways to locate this table cell. The easiest way is to use the HTMLDocument method getElement (this too debuts in 1.3). The HTMLDocument's getElement method takes a String and returns the Element with an id attribute equal to the specified String. There is also a more general getElement method that allows you to specify any attribute you want to search for and its corresponding value. The code to locate the table cell for a particular cell on the board and change its content would be:

    Element e = htmlDoc.getElement(Integer.toString(boardLocation));
    htmlDoc.setOuterHTML(e, html);
    

Conclusions

Admittedly, a tic-tac-toe game written using dynamic HTML is unlikely to be implemented in the real world, but it was an easy way to illustrate new API in 1.3 that greatly simplifies dynamically updating an HTML document. We have not touched on how the Swing HTML document model differs from the more traditional HTML model; for that you can read the article Understanding the Element Buffer. For more information, see the HTMLDocument class.

Oracle is reviewing the Sun product roadmap and will provide guidance to customers in accordance with Oracle's standard product communication policies. Any resulting features and timing of release of such features as determined by Oracle's review of roadmaps, are at the sole discretion of Oracle. All product roadmap information, whether communicated by Sun Microsystems or by Oracle, does not represent a commitment to deliver any material, code, or functionality, and should not be relied upon in making purchasing decisions. It is intended for information purposes only, and may not be incorporated into any contract.