LAB-5558: Developing Revolutionary Web Applications, Using Ajax Push or Comet

Expected Duration: 120 minutes

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

 

Step 1: create a new Web Application project

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.

  1. If NetBeans is not already running, start it.
  2. Choose File > New Project (Ctrl-Shift-N) from the main menu. Under Categories, select Java Web. Under Projects, select Web Application and click Next.

  3. Type tictactoe in the Project Name field. Note that the Context Path becomes /tictactoe.

  4. You may be prompted to enter the Glassfish username and password. If so they are:

    • Username: admin
    • Password: adminadmin
  5. Specify the Project Location to the exercise4  sub directory of this lab on your computer.

  6. 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.

  7. 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).)

    tictactoe project


Step 2: Creating the server side Web Component

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
  1. In the Projects window right click the Source Packages in your tictactoe node (from Step 1) and select New Java Package...

    Select New Java Package

  2. In the New Java Package Window, enter the Package name sample, and click Finish.

  3. In the Projects window right click the sample package and select New Java Class...

    Select New Java Class

  4. In the New Java Class Window, enter the Class Name TTTComet, and click Finish.

  5. Double click on the TTTComet.java file to open it in the editor. 

  6. 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 {
    }
    }

  7. Now we need to add the Comet libraries to our project. Right click the tictactoe node and select Properties.

  8. Find the project's Libraries in the left hand side of the window and select it.

  9. 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.

    Library Selection Window

  10. 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;

  11. 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";

  12. Add this line to init get an instance of the Comet engine.

    CometEngine engine = CometEngine.getEngine();

  13. Add these lines to the init method. This registers the component with the Comet engine.

    CometContext cometContext = engine.register(contextPath);
    cometContext.setExpirationDelay(120 * 1000);

  14. 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;

    }

  15. 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);
    }

  16. 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);
    }
    }

  17. 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);

  18. 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();
    }
  19. 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.

    Copy from Favorites window

  20. 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 }

Step 3: Creating the client pages for the Tic-Tac-Toe game

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.

  1. 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.

    Select New HTML File

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

    name: index

  3. Double click on the index.html file to open it in the editor. 

  4. 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.

  5. 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>
  6. 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.

  7. 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
    }
  8. 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
    }
    }
  9. 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.

    Paste resources folder



Step 4: Modifying the deployment descriptor.

Now we need to do is modify our deployment descriptor so that it knows about our servlet.

  1. 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.

    Open web.xml

    Change to Servlets View

  2. 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


    Add Servlet


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

    Select Pages button

  4. Change the Welcome Files from index.jsp to index.html.

  5. Save the changes you've made to your web.xml file.



Step 5: Enable Comet support in Glassfish (SKIP THIS STEP FOR JAVAONE 2009 MACHINE PROVIDED LAB).

See exercise 0 for detail on how to enable comet support in Glassfish if you haven't done so.

Step 6: Run the project.

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.

  1. 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.

    Run Project

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

    Tic Tac Toe Game Board




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