Tic-Tac-Toe Game


Tic-Tac-Toe

Start Game or read how to play.

Want a suggestion for a move?






Tic-Tac-Toe with gameQuery

*If this post is broken, please try again later. (full disclosure: so far the game works, but I’m still working on the tutorial…)

This is a simple tic-tac-toe game. Its purpose is to demonstrate gameQuery.

Important, the game has been moved here to separate it from the tutorial.

How to make a game like this?

The following sections discuss how this tic-tac-toe game was made. It is meant to be a tutorial for creating simple two-dimensional, sprite-based games with Javascript, jQuery and gameQuery. If you just want to look at the code, it is here.

Getting setup

Before you begin writing a JavaScript game with gameQuery, you need a few things:

  • A web browser
  • A text editor
  • The files

What files do I need?

To run the tic-tac-toe example, you will need:

If you go to the tic-tic-toe game page and view the source, you should see files and where to get them. Get a copy of all the files and put them in a directory.

How do I use the files

To use the JavaScript files, download test.htm and save it in the same directory as the .js files. Open test.htm in your browser. You should see the game board and instructions.

What do you want to play?

The first, and arguably hardest, step in designing a game is deciding what kind of game you want to make.
Fortunately for this example, I picked a well-known game with simple rules.
This allows the focus to be on implementing the game with gameQuery, and less on the rules of the game.

What does it look like?

Now that we’ve established the game type, we can focus on the first visual elements.
The game of tic-tac-toe has three main visual objects: the board, an ‘X’, and an ‘O’.
I’ve provided some images for these objects. (For the record, these are original content and free for your use. I think the brilliant minds that make Photoshop would be proud. ;-))
The images are shown below.

xogame board

Defining the game board

Tic-tac-toe is played on a 3×3 rectangular grid. A JavaScript function object is used to define the board. This object will hold the board ‘state’, and contain methods to change the board state. The Board object was intentionally designed to be as simple as possible for the purposes of this tutorial.

// The game board object, holds current game state.
this.Board = function() {

	// allows nested functions to refer to this object
	var self = this;

	// define board state as a 3x3 array
	this.state = [['','',''],['','',''],['','','']];

	// tests if a move is legal.
	// Move is an object like {row: 0, col: 0}
	this.isLegalMove = function(move) {
		// ...
	};

	// add a move to the board.
	// Move is an object like {row: 0, col: 0, player: 'x'}
	this.addMove = function(move) {
		// ...
	};

	// returns false if no winner, otherwise return player value
	this.isWinner = function() {
		// ...
	};

	return this;
};

The Board object has a simple interface and encapsulates the game logic. Notice that the Board doesn’t include any code to display itself. This is also by design, since there are countless ways to display a tic-tac-toe game.

Using JSON for configuring the game

When designing a game, it is nice to be able to specify and configure the game objects separate from the game logic. The JSON notation is convenient for use with JavaScript. The JSON used to define the game is below.

// game settings in compact JSON format.
this.json = {
	board_groups: {
		groups: {
			background: {
				size: {width: WIDTH, height: HEIGHT},
				sprites: {
					background_sprite: {
						animation: { imageURL: rc+'board.png'}
					}
				}
			}
		}
	},
	prototypes: {
		x: {
			size:{width: 128, height: 128},
			sprites: [{ animation: { imageURL: rc+'x.png'}}]
		},
		o: {
			size:{width: 128, height: 128},
			sprites: [{animation: { imageURL: rc+'o.png'}}]
		}
	}
};

Notice the .json object is divided into two parts, a board_groups and prototypes. The board_groups object is essentially the game ‘world’ (its a small world when you play tic-tac-toe ;-)). The prototypes object contains various objects, in JSON format, that are created dynamically throughout a game.

The jQuery.extend() method can be used to generate a new prototype object that can be used to create the animation groups.

// create a copy of the json prototype
var proto = $j.extend({playground: self.playground}, self.json.prototypes[self.turn]);

// add animation group for new move
var group = $j.gameQuery.createAnimationGroup('turn'+String(self.turn_count), proto);

Setting up the playground

To begin the game, the TicTacToe.setupGame() function is called.  This function will create a new Board object to store the current game state.

this.setupGame = function(callback) {
	var $j = jQuery.noConflict();

	self.board = new self.Board();  // create a new board to play on
	self.turn = 'x';				// x always goes first
	self.turn_count = 0;

	// create the gameQuery playground
	self.playground = $j("#playground").playground({width: WIDTH, height: HEIGHT});

	// create the gameQuery groups for the game board
	$j.gameQuery.createAnimationGroups($j.extend({playground: self.playground}, self.json.board_groups));	

	// assign mouse event handler
	self.playground.click(self.placeMark);
};

Making your mark

When a player clicks on an empty square on the tic-tac-toe board, a new animationGroup is created and added to the playground. The jQuery.fadeIn() animation is used to display the mark.

Notice that the ‘click’ handler is unbound first. This stops a user from accidentally double-clicking the board while the mark is animated. After the animationGroup is faded in, if there is no winner then the .placeMark function is re-added as a .click handler.

this.placeMark = function (e) {
	// unbind the click handler (avoids user double-clicks)
	self.playground.unbind('click');

		// don't do anything if the game is over
		if (self.isGameOver()) return;

		// compute the row and column where user clicked
		var d = self.playground.offset();

		var r = Math.floor((e.pageY - d.top)/(HEIGHT/3));
		var c = Math.floor((e.pageX - d.left)/(WIDTH/3));

		if (self.board.addMove({row: r, col: c, player: self.turn})) {

			// create a copy of the json prototype
			var proto = $j.extend({playground: self.playground}, self.json.prototypes[self.turn]);

			// add animation group for new move
			var group = $j.gameQuery.createAnimationGroup('turn'+String(self.turn_count), proto);

			// move group to position
			group.css({'left': c*(WIDTH/3), 'top': r*(HEIGHT/3)});

			// show the group
			group.fadeIn('slow', function() {
				// see if there was a winner
				if (self.isGameOver()) {
					if (self.winner == '') {
						$j('#tictactoe_winner_text').text("It was a tie, better luck next time ...");
					} else {
						$j('#tictactoe_winner_text').text("The winner is player " + String(self.turn).toUpperCase() + "!");
					}
					$j('#tictactoe_results').slideDown('slow');
				} else { // no winner yet
					// increment turn count
					self.turn_count = self.turn_count + 1;

					// set next player
					if (self.turn == 'x') {
						self.turn = 'o';
					} else {
						self.turn = 'x';
					}

					// re-enable user click
					self.playground.click(self.placeMark);
				}
			});
		}
	}