Tic-tac-toe: an Ajax approach

Posted:


As part of my technological foraging, I have been playing around with Ajax as presented through the new hotness that is jQuery. The result is a very humble, but all Ajax, tic-tac-toe game. This replaces the all flash version I had on this site for a while. Not only did I improve the AI of the computer player (thanks CS210!), but I have completely validated HTML and CSS files to boot.

Part of my inspiration comes from my current nighttime read, jQuery in Action by Bibeault and Katz. Besides gently easing the reader into jQuery concepts, the authors turned me on to the concept of unobtrusive javascript, which is the idea that HTML, CSS and javascript should be kept separate from each other, thus simplifying development considerably. One way to intrepret this concept is that HTML, CSS and Javascript all need to be keep in discrete files. That means no “onClick” attributes in the HTML, please. In structure, one can find liberation.

The architecture of my tic-tac-toe game follows a pretty standard client-server model. The server bit consists of a single PHP script that handles game session and AI. The client bit is simply an HTML file supported by CSS and Javascript. The client API is narrowly defined to prevent obvious forms of cheating and to enforce the idea that the client is a display and input mechanism that has little idea about how to play tic-tac-toe. The game logic is enforced by the PHP script. I’ll discuss the design of the game engine later, but let’s look at the client first.

The entire source for tic-tac-toe can be found here.

The place to begin with the tic-tac-toe client is the index.html file. This is a validating HTML 4.01 Transitional document. It has no TABLE tags at all. It depends on CSS float to make the playing board and handle the rest of the page layout. Because of some problems with Internet Explorer, there is a bit of IE-specific goo that forces a warning to appear in that browser. That will be the subject I return to last.

That HTML file is so clean, you could eat off it. Anyone who has done PHP or even a lot of javascripting will be surprised that such a simple file could be the basis of anything complex. As far as the game is concerned, the most interesting elements in the HTML file are the DIVs that have an ID attribute beginning with “s”. In traditional video game parlance, these function as both buttons and sprites. I could have used HTML buttons here. Part of me still thinks that’s the right way to go, but just using DIVs looks a lot more “gamey” to me. All the wiring of event handlers for these DIVs is handled in the ttt.js file, described shortly. The look and layout of the client is provided by the ttt.css file, which holds no surprises.

The client-side magic happens in the javascript. As it promises to do, the jQuery library greatly reduced the burden of locating and attaching elements, attaching click handlers and Ajax processing. Because the jQuery library is loaded before ttt.js, jQuery functionality is accessible in this file. The first four lines of code are pretty harmless:

var Game = new Object();
Game.timeout = 5000;
Game.square_clicker_enabled = false;

$(document).ready(init);

A global object called Game is created that will hold client-side game state. Scoping in javascript is a little primative, so having one global object in which to store various bits of information helps to reduce namespace clutter. The game property ‘square_clicker_enabled’ is set to false to prevent undesirable button clicking later on. I’ll get to that. The most powerful statement here is the ready() function that calls init() (not shown yet) when the DOM is fully constructed in the browser. As any primer on jQuery will tell you, the more traditional onLoad() event for BODY elements does not run untill all the graphics are loaded on the page. The ready() function is ideal of initialization code. Speaking of which…

function init() {
   $("#newGame").click(new_game_clicker);
   var arg = new Object();
   arg.get_board = 1;
   $.ajax({url: 'move.php',
           type: 'GET',
           data: arg,
           dataType: 'json',
           timeout: Game.timeout,
           error: bomb,
           success: function(a) { paint_board(a);}
        });
}

The init() function sets up the click handler for the “New Game?” button/DIV that ensures a new session and a blank board on the server. The fancy jQuery selector simply looks for an element with the ID of newGame. An object is created to hold parameters to be passed in the following Ajax request. The call to “get_board” simply requests the board state of the current game, if applicable. If the call is successful, the paint_board() function is invoked with the structure returned from the server. Even though the server returns a JSON serialized string, jQuery deserializes this structure and passed it to the function. That’s some pretty terse code for an RPC mechanism!

function new_game_clicker() {
   if (confirm("Really start a new game?")) {
     var arg = new Object();
     arg["new"] = 1;
     $.ajax({url: 'move.php',
            type: 'POST',
            data: arg,
            dataType: 'json',
            timeout: Game.timeout,
            error: bomb,
            success: function(a) { paint_board(a);}
        })

   }
}

There’s little new code here at all. All veteran javascripters will have seen the confirm() dialog function. The click handler for the new game button is mostly another Ajax call. This one creates a new session and empty tic-tac-toe board on the server. Again, whenever the state of the board might have changed, paint_board() needs to be invoked.

function square_clicker(e) {
   if (Game.square_clicker_enabled) {
        Game.square_clicker_enabled = false;

        var arg = new Object;
        arg.pos = this.id.charAt(1);
        $.ajax({url: 'move.php',
           type: 'GET',
           data: arg,
           dataType: 'json',
           timeout: Game.timeout,
           error: bomb,
           success: function(a) { paint_board(a);}
        });
    } else {
        alert("Thinking!");
    }
}

The click handler of each game board square is a little trickier. At its heart, the handler is merely responsible for sending the human’s move to the computer using an Ajax call. When the player moves, the computer also makes a move and the new board state is returned and passed to paint_board(). Again, there are a lot of details behind that tiny bit of code!

The Ajax call is protected by a boolean. The idea is to prevent humans from wildly click on squares before the computer returns with the new game state. The opening move for the computer can take a few seconds to calculate, even when the depth of recursion is limited! I suppose that’s why algorithms with a runtime performs of O(n!) are frown upon.

As simple as this mechanism is, Internet Explorer does not seem to like it very much. When I use that browser, I get all kinds of weird board states. It is a mystery to me why this happens, but this is why I include a warning to IE users in the HTML document.

function paint_board (a) {
   if (a.board == null) {
     $("#status").text("No game in progress");
     Game.square_clicker_enabled = false;
   } else {
     for (var i=0; i < a.board.length; i++) {
        $("#s" + i).empty();
        if (a.board.charAt(i) == '0') {
            var events = $("#s"+i).data("events");
            if (events == null) {
                $("#s"+i).click(square_clicker);
            }
        } else {
            $("#s"+i).unbind('click',square_clicker);
        var e = a.board.charAt(i).toUpperCase() 
            e = $("<span></span>").text(e).addClass("p");
            $("#s"+i).append(e);
        }
     }
     Game.square_clicker_enabled = true;
     if (a.msg == null) {
        a.msg = " ";
     }
     $("#status").text(a.msg + " ");
   }
}

Finally, the heart of the client code appears in paint_board. Given a structure from the server, it displays the game state on the board to the human. The board’s state is presented as a nine character string where each position represents a place on the board. A zero is an unoccupied space, an ‘x’ represents the human’s move and a ‘y’ represents the computer’s. Notice that the client doesn’t even know that much. It simply knows that 0 states are presented one way and non-zero another.

When the board is painted, the square_clicker_enabled flag is set to true, allowing the human to make a new move. If an open square is indicated by the board state, a click handler is installed if one does not exist. If the square is occupied, the click_handler is removed. Doing this without jQuery would have been horrible.

And there’s the client! Amazingly small and even valid HTML. Will wonders never cease?

This post is already long. At some point, I’ll go into the server code which has some moderated clever AI and some awful code to determine a win condition. Keep reachin’ for the stars!