|
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:
- Mapping from the view coordinate space to the document space.
- Locating the table data item at that location.
- 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.
|