Exercise 4: Build a Two Way Tic-tac-toe Game (40 minutes)
Using
AJAX and Comet, you will learn how to create a simple tic-tac-toe game
in which two people play while other people can watch the game via
their browser. This exercise involves building the servlet that
implements the Comet event handling and then building the client side
html code to play the game. You will learn how you can customize the
code on the server side to handle your specific application
requirements, and how you can use Comet technology to build back-end
capibilities enabling a two-player distributed game environment.
Background Information
This
exercise is going to use the long polling method to develop
our Comet-enabled web application. The client
side of our application, our tic-tac-toe board, is going to use some embedded JavaScript to allow us to connect to
the server. The server side of our application consists of a servlet
that listens for updates from clients (our players), processes the
player's moves, validates them, checks if there is a winner of the
game, and finally writes JavaScript code to the clients that updates
the board.
Steps to Follow
In
this portion of the exercise we will create the web application that
will contain our server side Comet application and our client side
tic-tac-toe game. This will all run on our familiar Glassfish server
and many of the steps you will be familiar with from previous exercises.
- If NetBeans is not already running, start it.
-
Choose File > New Project (Ctrl-Shift-N) from the main
menu. Under Categories, select Java Web. Under Projects, select Web
Application and click Next.
-
Type tictactoe in the Project Name field. Note
that the
Context Path becomes /tictactoe.
-
You may be prompted to enter the Glassfish username and password. If so they are:
- Username: admin
- Password: adminadmin
-
Specify the Project Location to the exercise4 sub
directory of this lab on your computer.
-
Under Server, select GlassFish v3. GlassFish is a Java
EE5-certified application server and is bundled with the Web and Java
EE insallation of NetBeans IDE.
-
Leave the Set as Main Project option selected and click
Finish. The IDE creates the tictactoe project folder. The project
folder contains all of your sources and project metadata, such as the
project's Ant build script. The tictactoe project opens in the IDE. The
welcome page, index.jsp, opens in the Source Editor in the main window.
You can view the project's file structure in the Files window (Ctrl-2),
and its logical structure in the Projects window (Ctrl-1).)

Now
we will begin building the servlet that will handle our Comet requests.
This will start out as a standard servlet but we will customize the doGet and doPost methods with our Comet code. Here are the general steps you will take to build your web component.
- Create a web component to support Comet requests.
- Register the component with the Comet engine.
- Define a Comet handler that updates the client
- Add the Comet handler to the Comet context
- Notify the Comet handler of an event using the Comet context
In the Projects window right click the Source Packages in your tictactoe node (from Step 1) and select New Java Package...

In the New Java Package Window, enter the Package name sample, and click Finish.
-
In the Projects window right click the sample package
and select New Java Class...

-
In the New Java Class Window, enter the Class Name TTTComet, and
click Finish.
-
Double click on the TTTComet.java file to open it in the
editor.
-
Copy
all of the code below and paste it to replace the contents of the
TTTComet.java file in the editor. This will be your web component that
supports Comet requests.
package sample;
import java.io.IOException; import java.io.PrintWriter; import javax.servlet.*; import javax.servlet.http.*;
public class TTTComet extends HttpServlet { private String contextPath = null; private static TTTGame game = new TTTGame();
@Override public void init(ServletConfig config) throws ServletException { }
@Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { }
@Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } }
Now we need to add the Comet libraries to our project. Right click the tictactoe node and select Properties.
Find the project's Libraries in the left hand side of the window and select it.
-
Click the add JAR/Folder button on the right side. Select the following two JAR's from the <lab_root>/exercises/exercise3 folder:
- grizzly-comet.jar
- grizzly-cometd.jar
Click OK to finish Library selection.

-
Add the following imports to your TTTComet.java file.
import com.sun.grizzly.comet.CometContext; import com.sun.grizzly.comet.CometEngine; import com.sun.grizzly.comet.CometEvent; import com.sun.grizzly.comet.CometHandler;
-
Now
that we have our servlet class we need to add our Comet functionality.
Start by copying and pasting the following code into the init method. This allows us to get the component's context path.
ServletContext context = config.getServletContext(); contextPath = context.getContextPath() + "/TTTComet";
-
Add this line to init get an instance of the Comet engine.
CometEngine engine = CometEngine.getEngine();
-
Add these lines to the init method. This registers the component with the Comet engine.
CometContext cometContext = engine.register(contextPath); cometContext.setExpirationDelay(120 * 1000);
-
Next
we need to create a private class that implements CometHandler in the
servlet class. Copy and paste this code inside your TTTComet class.
This is our Comet Handler that updates the client when events happen.
private class TTTHandler implements CometHandler<HttpServletResponse> {
private HttpServletResponse response; }
-
Now
copy and paste the following methods into your new TTTHandler private
class. We need to provide the implementations of all these methods
since our class implements CometHandler. The onInterrupt and onTerminate methods execute when certain changes occur in the status of the underlying TCP communication. The onInterrupt method executes when communication is resumed. The onTerminate method executes when communication is closed. Both methods call removeThisFromContext, which removes the CometHandler object from the CometContext object.
public void onInitialize(CometEvent event) throws IOException { }
public void onInterrupt(CometEvent event) throws IOException { removeThisFromContext(); }
public void onTerminate(CometEvent event) throws IOException { removeThisFromContext(); }
public void attach(HttpServletResponse attachment) { this.response = attachment; }
private void removeThisFromContext() throws IOException { response.getWriter().close(); CometContext context = CometEngine.getEngine().getCometContext(contextPath); context.removeCometHandler(this); }
-
The final method we need in our TTTHandler class is the onEvent method. This method defines what happens when an event occurs.
This method first checks if the event type is NOTIFY, which means that
the web component is notifying the CometHandler object that a client
has made a move. If the event type is NOTIFY, the onEvent method writes
out JavaScript to the client with the updated board information. The
JavaScript includes a call to the chImg function, which will update the
board on the clients' pages.
The last line resumes the Comet request and removes it from the list of
active CometHandler objects. By this line, you can tell that this
application uses the long-polling technique.
Copy and paste this code into your TTTHandler class.
public void onEvent(CometEvent event) throws IOException { if (CometEvent.NOTIFY == event.getType()) { PrintWriter writer = response.getWriter(); writer.write("<script type='text/javascript'>parent.chImg(" + game.getJSON() + ")</script>\n"); writer.flush(); event.getCometContext().resumeCometHandler(this); } }
-
Now we need to get an instance of the TTTHandler
and attach the response to it. We then get the Comet context and add
the handler to it. Copy and paste the following lines into your doGet method.
TTTHandler handler = new TTTHandler(); handler.attach(response);
CometEngine engine = CometEngine.getEngine(); CometContext context = engine.getCometContext(contextPath);
context.addCometHandler(handler);
-
When a user clicks the button, the doPost
method is called. The doPost method checks that the player has made a
valid move. It then obtains the current CometContext object and calls
its notify method. By calling context.notify, the doPost method triggers the onEvent method you created in the step before last. After onEvent executes, doPost checks if there was a winner in the game. Copy and paste the following code into your doPost method.
int cell = -1; String cellStr = request.getParameter("cell"); PrintWriter writer = response.getWriter(); writer.println("cell is '" + cellStr + "'"); if (cellStr == null) { writer.println("error - cell not set"); return; } try { cell = Integer.parseInt(cellStr); } catch (NumberFormatException nfe) { writer.println("error - cellStr not an int: " + cellStr); return; } if (!game.turn(cell)) { writer.println("warning - invalid move"); } writer.println(game.getJSON());
CometEngine engine = CometEngine.getEngine(); CometContext context = engine.getCometContext(contextPath); context.notify(null);
// Hideous hack, not threadsafe if (game.win() != -1) { try { Thread.sleep(2000); // sleep 2 sec } catch (InterruptedException ie) {} game = new TTTGame(); }
-
The final thing that we ned to add to our server side web application is a new class for our game. Copy the TTTGame.java file from <lab_root>/exercises/exercise4/ to your tictactoe project's sample package.
The
easiest way to do this is to open the Favorites window (Windows ->
Favorites). This should display your home directory, if not, Right
click and choose Add to Favorites and select the <lab_root>. Now you can navigate to the <lab_root>/exercises/exercise4/ folder and right click the TTTGame.java file. Choose Copy and go back to the Projects window. Right click the sample package and choose Paste.

-
If you look back at our onEvent code you will see that we call a function in our game class called getJSON. This is the code that you will need to implement for this section of the exercise. Let's take a brief walkthrough of our TTTGame class.
We start off with our class variables. The important thing to note here
is the representation of our board. It is an array with one element for
each position on the tic-tac-toe board. We also have an array with all
the possible winning combinations represented.
The first function that we have is called turn and checks whether a move is valid or not. It also checks whose turn it is.
Next we have the whoseTurn function, which does exactly what it sounds like, checking whose turn it is.
Our next important function is win. This function checks if there is a winner, no winner or a tie game.
And finally we have the function that you need to implement, the getJSON
function. This is called by onEvent which then sends this JSON
representation of the game board to the clients. You need to implement
a function that cycles through the elements in the board array and
prints out a JSON representation of the board in the format described
in the comments. Here it is again as an example as to how your JSON
string should be outputted.
Hint: You should use the win()
function in your implementation. If you get stuck, check out the
solutions folder for the answer.
/** * Create a JSON representation of the game state. It will look something like this: *
* { "win": "-1", "board": ["0","0","0","0","0","0","0","0","0"] }
*
* @return json
*/
synchronized public String getJSON() {
// CODE THIS FUNCTION
}
The
goal of this part of Exercise 4 is to create the client pages that will
represent our game. When a user clicks on a square a new POST request
will be created and sent with information as to which cell was clicked.
This will be caught and the doPost method on the server will run and update the game board and then notify all the clients of the new board.
-
The
first thing that we need to do is create a new HTML page that will
display our game board. In the Projects window right click the Web Pages node
of the tictactoe project and select New HTML File...
If you don't see New HTML File, select New -> Other. Change the Categories type to Other and select HTML File.

-
In the New HTML File Window, enter the name index, and
click Finish.

-
Double click on the index.html file to open it in the
editor.
-
Copy and paste the following code into your new index.html file between the opening <body> tag and the closing </body> tag.
<iframe name="hidden" src="TTTComet" frameborder="0" height="0" width="100%" onload="restartPoll()" onerror="restartPoll()" ></iframe>
<h1>Tic Tac Toe</h1> <table> <tr> <td id="cell0"><img id="img0" src="resources/0.gif" onclick=postMe("0")></td> <td id="cell1"><img id="img1" src="resources/0.gif" onclick=postMe("1")></td> <td id="cell2"><img id="img2" src="resources/0.gif" onclick=postMe("2")></td> </tr> <tr> <td id="cell3"><img id="img3" src="resources/0.gif" onclick=postMe("3")></td> <td id="cell4"><img id="img4" src="resources/0.gif" onclick=postMe("4")></td> <td id="cell5"><img id="img5" src="resources/0.gif" onclick=postMe("5")></td> </tr> <tr> <td id="cell6"><img id="img6" src="resources/0.gif" onclick=postMe("6")></td> <td id="cell7"><img id="img7" src="resources/0.gif" onclick=postMe("7")></td> <td id="cell8"><img id="img8" src="resources/0.gif" onclick=postMe("8")></td> </tr> </table> <h2 id="gstatus">Starting to watch the game.</h2>
This code contains a few important things. The first is the
<iframe> tag. This IFrame contains the JavaScript code to make a
connection to the servlet. When the page loads the connection is
initialized and the restartPoll() function is run to put the client
back into a waiting for more data mode. The next important thing to
note is the HTML table. This is our tic-tac-toe board. Whenever a cell
is clicked the postMe function is run with the associated cell number
passed as an argument to the server.
-
Inside the opening <head> and closing </head> tags paste the following code.
<link rel="stylesheet" type="text/css" href="resources/tictactoe.css"> <script type="text/javascript">
</script>
-
Now let's add our JavaScript functions. Paste this code inside the <script></script> tags that you added in the last step.
var url = "TTTComet"; function postMe(arg) { function createXMLHttpRequest() { try { return new XMLHttpRequest(); } catch(e) {} try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch (e) {} try { return new ActiveXObject("Microsoft.XMLHTTP"); } catch (e) {} alert("Sorry, you're not running a supported browser - XMLHttpRequest not supported"); return null; }; var xhReq = new createXMLHttpRequest(); xhReq.open("POST", url, false); xhReq.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xhReq.send("cell="+arg); };
This
function runs whenever a cell in the game board is clicked. It creates
a POST request and sends it to the Comet servlet with the cell number
that was clicked as an argument.
-
This is the next important JavaScript function. The chImg
function updates the board with the JSON data that is received from the
Comet servlet. It also alerts the clients of the status of the game and
if there is a winner. Finally it sets the IFrame url to the servlet url
which puts the client back into the waiting for more data mode. Copy
and paste this code inside the <script></script> tags after the last JavaScript function we inserted.
function chImg(args) { var data = eval(args); for (i = 0; i < 9; i++) { // 0 is blank, 10 is x, 1 is o document.getElementById("img"+i).src="resources/"+data.board[i]+".gif"; } var statusMsg; // -1 is unfinished, 0 is tie, 1 is X win, 2 is Y win if (data.win == 0) { statusMsg = "It's a tie!"; } else if (data.win == 1) { statusMsg = "X wins!"; } else if (data.win == 2) { statusMsg = "O wins!"; } else if (data.win == -1 && data.turn == 10) { statusMsg = "It's X's Turn"; } else if (data.win == -1 && data.turn == 1) { statusMsg = "It's O's Turn"; } else { statusMsg = "That's odd, it shouldn't get here"; } if (data.win != -1) { statusMsg = statusMsg + '<br><a href="index.html">Restart the game</a>'; }
// And write the status message out here - document.getElementById("gstatus").innerHTML = statusMsg; hidden.location = url; // restart the poll retries = 0; // reset retry number }
-
The final JavaScript that we need to make this game run is the restartPoll
function. This runs when the IFrame loads and verifies that there is a
connection to the servlet. Copy and paste this code inside the <script></script> tags after the last JavaScript function we inserted.
var retries = 0; function restartPoll() { if (retries++ > 10) { // stop the client from indefinate spin alert("The connection has errored out too many times - hit reload to retry"); } else { hidden.location = url; // restart the poll } }
-
Finally we need to copy the images for our game into our project. Copy the resources folder from <lab_root>/exercises/exercise4/ to your tictactoe project's Web Pages directory.

Now we need to do is modify our deployment descriptor so that it knows about our servlet.
-
Open web.xml which you can find in your tictactoe project under Web Pages -> WEB-INF folder. Once you have the file open change to the Servlets view by clicking the Servlets button in Netbeans.


-
Click the Add Servlet Element button. Enter the following information for your servlet:
- Servlet Name: TTTComet
- Servlet Class: Click the Browse button and find your sample.TTTComet class.
- JSP Class: LEAVE BLANK
- Description: LEAVE BLANK
- URL Pattern: /TTTComet


-
Now select the Pages button to move out of the Servlets section of the web.xml file.

-
Change the Welcome Files from index.jsp to index.html.
-
Save the changes you've made to your web.xml file.
See exercise 0 for detail on how to enable comet support in Glassfish if you haven't done so.
We
have now built our Tic Tac Toe application. Go ahead and run the
project and we will take a closer look at some of the code in a minute.
-
In the Projects window, right click your tictactoe
project. Select Run. Wait for the Glassfish server to start. Once the
browser window opens to your index.html page you can start playing the
game. However, to see Comet in action, you need to open another browser
and
copy and paste the URL to open your tictactoe game
(http://localhost:8080/TTTComet). This second browser can be another
instance of Firefox. Make sure you've setup mutiple profiles as
described in Exercise 0 and then when you start Firefox make sure to
select your second profile. Note that if you are using OpenSolaris, to
bring up the Profile Manager when Firefox is already running execute
this command in a terminal and then select your second profile:
firefox -P -no-remote
FOR JAVAONE 2009 MACHINE PROVIDED LABS:
You may type the shortcut ff into a terminal to start Firfox with the profile manager open.
Now try clicking on a box and you can
see each browser is updated as you click the different boxes. Once
there is a winner each browser will be notified as well.

Here is what the game should look like when it is running:

Summary
The
example application demonstrates the use of Comet's long-polling
technique. You learned how to build the servlet that implements
CometHandler to handle comet events. You also learned how the client
side IFrame technique is used to interact with the Comet server to
create an interactive two-player game.
See Also
For more information see the following resources:
Back to top
Next
exercise
|