![]() |
TicTacToe is a game of complex deception and trickery. |
Here is a short tutorial on how to write a TicTacToe game in JavaScript using an Object-Oriented(OO) Approach in a Model-View-Controller(MVC) Pattern, using the Publish-Subscribe Model, Namespacing, and Immediately-Invoked-Function-Expression(IIFE).
If you just want to make a TicTacToe game for you and your partner's amusement, this is not the place you want to be. A simple TicTacToe game can be written without using such complicated designing patterns. You should read this, however, if you are trying to learn how to write programs - not just stupid TicTacToe - in JavaScript using these patterns.
1 & 2. The Object-Oriented Approach and the Model-View-Controller Design Pattern
Before diving into the incomprehensible marsh of JavaScript, let's plan out what we are going to do first. What objects do we want? Let's see: what's in a TicTacToe?
- The players.
- The board.
- The squares that make up the board.
- The "idea" of TicTacToe, or the rules.
The first three are tangible objects. Models tend to be tangible in a physical sense, although this is not always the case. But this train of thought works here, so these objects will be the models. We will name them Player, Board, and Square, respectively.
On the other hand, the fourth one on our list is not a tangible object. You cannot touch the rules of TicTacToe just like you can't touch gravity or entropy or your endless depression and self-hatred. So, this one will be our controller. We will name it Game.
So we have models and a controller, but the view is missing. This is the visual representation of the game that the player can actually look at and click on: the Graphical User Interface(GUI). That will be our fifth object. Here is what it will look like:
![]() |
Beautiful, is it not? |
So, here are our objects:
- Player
- Board
- Square
- Game
- Gui
Another thing to consider is how to determine who the winner is. We could go the stupid way and check every condition where one of the players has three symbols vertically or in a row/column. Admittedly, there wouldn't be too many solutions because we're making just TicTacToe. Still, being called stupid doesn't feel good. So let's go the not stupid way and come up with a civilized solution.
What we can do is give each of the squares in the cell a value, like this:
1
|
2
|
4
|
8
|
16
|
32
|
64
|
128
|
256
|
You have noticed that each of the cells contains a power of 2. The first one is 2^0=1, the second one is 2^1=2, the third one is 2^2=4, and so on. Now, every time a player occupies a cell with a X or an O, we will give the player a score according to the value in the square. For example:
In this situation, player O has a score of 1 + 32 + 64 = 97, and player X has a score of 4 + 8 + 16 = 28.
This ensures a unique value for each combination of O's or X's. That means that there are only 8 possible values for a winning combination: 7, 56, 448, 73, 146, 292, 273, and 84. See how civilized this is? In fact, it is so civilized that we will have to use the bitwise operator "&" to actually take advantage of this solution. But more on that later; we're not coding just yet.
We will use Class Responsibility Collaborator(CRC) cards to elucidate what each of our objects should be doing. (Note: JavaScript doesn't have classes, so we're really just using ORC cards. Orc cards. It's like a fantasy trading card game.)
Left side: Responsibilities - Right side: Collaborators
Board
|
|
Knows number of squares
Creates list of squares
Listens for click
|
Square
|
Square
|
|
Knows its point value
Knows and sets if empty or marked
|
Player
|
|
Knows its symbol
Knows and sets its score
Knows winning conditions
Determines if winner
|
Game
|
|
Creates players
Create board
Knows and sets current player
Starts a new game
|
Gui (also the driver)
| |
Creates a game
Displays the board
Lets the player interact with the board
| Game |
3. The Publish-Subscribe Model
Also, we want to know what a Publish-Subscribe model is. It is similar to the Observer pattern, but it is even more loosely coupled. We want to use this to implement the last responsibility of the Gui: to let the player interact with the board. The Gui will subscribe to some events, and the Game will be the publisher for those same events. What events?
Well, let's take a look. Where do we need a publisher and a subscriber?
For the player to click on the board and the game to respond, we need a clickedOnEvent. For the game to tell the Gui the symbol of the current player, we need a symbolChangedEvent. For the game to tell the Gui the index of the cell to be changed, we need a changeCellSymbolEvent.
Just hold on to this; we'll get to it later when we're actually writing the code. For now, remember we are going to use three events to subscribe and publish to.
4. Namespacing
JavaScript, unlike Java, does not have packages. But we can use Namespaces to simulate packages. A namespace is a variable, declared like so:
var NAMESPACE = NAMESPACE || {};
This makes a new NAMESPACE variable if the variable didn't previously exist, initializing it as an empty object. This is what the right side of the || signifies. If the variable already exists, JavaScript takes the existing NAMESPACE and dumps it onto the new NAMESPACE. Keep in mind that these variables are still not exactly the same; their values are the same, but their pointers are not.
5. Immediately-Invoked-Function-Expression
The IIFE defines a function and invokes it as soon as it is defined. It is used to reduce the amount of bits the code occupies. For further explanation, follow the link here.
So what we have now is like a map to Mordor that will guide us through the dark entrapping realm of JavaScript.
We're all set. Let's now move on from the soft and tingling Georgia font to the cold and hard Courier.
Here is our Square object. Not too intimidating, is it?
The only thing that might be a bit confusing is "Object.defineProperty". Here's a nice tutorial that deals with this topic.
If you don't feel like reading the link, suffice it to know that it is a way to make local variables accessible to other objects. So, even a Board or Gui object could access the Square's private variable "marked" or "value" using Object.defineProperty. The catch is you need to use the name of the property, as shown in the quotation marks, instead of the name of the variable.
Here is our Board object. Nothing too special. Let's move along.
Here's our last model, the Player object.
The only pieces of code that defy comprehension at first sight is "isWinner()". This is the part where we have to use the bitwise operator "&" to tell who the winner is.
Here is a simple explanation of what "&" does. If you don't want to click on the link, I'll explain it to you bit by bit, quite literally. Don't bite me:
11010 and 10100 are binary numbers.
11010 & 10100 is 10000. This is easier to comprehend when you lay out the numbers vertically:
11010&
10100
_____
10000
What is happening is that the truth value of each of the digits is being evaluated, bit by bit. 1 is true, and 0 is false.
True AND True is True. 1 & 1 = 1.
True AND False is False. 1 & 0 = 0.
False AND True is False. 0 & 1 = 0.
True AND False is False. 1 & 0 = 0.
False AND False is False. 0 & 0 = 0.
Now that we have an understanding of "&", let's look at the function again.
this.isWinner = function() {
for (var i = 0; i < threeInARowConditions.length; i += 1) {
if ((threeInARowConditions[i] & score) === threeInARowConditions[i]) {
return true;
}
}
return false;
};
Using the for loop, we go through each of the elements in the list threeInARowConditions. If [[any one of the winning values] evaluated with [the bitwise operator &]] is equal to [any one of the winning values], then the function returns true. In other words, it means you have won. Let's see how this works.
Pick a winning value that is not in the list of winning values. For example, you might have the top three cells and the bottom leftmost cell filled in; then, the score would be 2^0 + 2^1 + 2^2 + 2^6 = 1 + 2 + 4 + 64 = 71. This is 1000111 in binary form.
Now, the top three cells give you a value of 7. This is 111 in binary form.
Let's evaluate these two numbers with a & operator:
1000111&
0000111
_______
0000111
We get 111, which is 7, which is one of the values in our list.
So, (threeInARowConditions[i] & score) === threeInARowConditions[i]) is true.
So, the function correctly determines that the player with a score of 71 has won the game. Magic!
Let's move on.
Here's the Game. The most convoluted method is takeaTurn(), as you can see. Let's dissect it:
this.takeaTurn = function(squareIndex) {
if (!gameEnded) {
if (theBoard.isSquareEmpty(squareIndex)) {
theBoard.setSquareOccupied(squareIndex);
currentPlayer.updateScore(theBoard.getValue(squareIndex));
this.publishSymbolChangedEvent();
this.publishChangeCellSymbolEvent(squareIndex);
if (this.isTie()) {
setTimeout(function(){alert("Game is a tie.");}, 1);
gameEnded = true;
}
if (currentPlayer.isWinner()) {
setTimeout(function(){alert("Player " + currentPlayer.playerSymbol + " has won!");}, 1);
gameEnded = true;
}
else {
if (currentPlayer == playerX) {
currentPlayer = playerO;
} else if (currentPlayer == playerO) {
currentPlayer = playerX;
}
}
}
}
};
The 1st line defines the name of the function.
The 2nd ine says, "do this if the game hasn't ended." Simple enough.
The 3rd line says "if the current square that has been clicked on is empty." We have this check because we don't want to overwrite a cell that has already been written in. That would be cheating.
In the 4th line, we mark the appropriate cell as marked.
In the 5th line, we update the score of the current player by the value of the square. Remember the powers of two values we assigned to each of the squares? It's that.
In the 6th line, we call a publishSymbolChangedEvent:
this.publishSymbolChangedEvent = function() {
var symbol = currentPlayer.playerSymbol;
$.publish('symbolChangedEvent', [symbol]);
};
All this does is take the symbol of the current player, either X or O, and publish it to the symbolChangedEvent as a list with one element in it. I didn't need to make it into a list, as you will see soon.
In the 7th line, we call a publishChangeCellSymbolEvent with the parameter squareIndex. This is the same parameter we used to mark the appropriate cell as marked in the 4th line.
this.publishChangeCellSymbolEvent = function(index) {
$.publish('changeCellSymbolEvent', index);
};
Honestly, I didn't even need to make this into a method, since it has one line of code in it. I did it for clarity's sake. What this does is it takes in an index variable, in our case the value of the index of the square that has been clicked on, and publishes the value of that index to a changeCellSymbolEvent.
The remainder of the lines deal with tied/winning situations and switching the player from X to O to X to O and so on. I won't explain them in detail. You can figure it out.
Here's the Gui! In our program, the Gui also acts as a driver, so it starts with $(document).ready(function Gui() instead of function (Gui). This means that this script will start running as soon as the document has been loaded and is ready. Other scripts, which start with just function, are not actually executed. They are just loaded into memory, ready to be used.
The first thing you noticed is probably the prevalence of the phrase GRID_NAMESPACE in this piece of code. This is the namespacing we talked about earlier. Note that this is completely unnecessary. I could take out all the GRID_NAMESPACE's in this code and everything will work just fine. I only wanted to show you another design pattern in JavaScript that can be useful in more complex programs.
Unlike the Game, where there was one super-convoluted method, the Gui is divided into many less convoluted methods. Let's look at them one by one, not necessarily in vertical order.
data specifies the indexes in the grid, from 0 to 8. This will be very handy later on.
addHandlers adds handlers. Pretty tautological, right?
More specifically, it makes the handleClickedOn method called when anything in the html with the id "td", or the cells in the table, is called. In other words, it calls the handleClickedOn method when any of the cells are clicked on by the player.
Additionally, it makes the handleRestartButtonClicked method called when anything in the html with the id "button", or the restart button, is called, In other words, it calls the handleRestartbuttonClicked method when the restart button is clicked.
handleClickedOn is called when a cell has been clicked on. It invokes the Game's takeaTurn method, passing in as the parameter the id of the cell that has been clicked on.
handleRestartButtonClicked is called when the restart button has been clicked on. It restarts the game by refreshing it.
The IIFE builds the grid in the html. I won't go on into detail about this.
subscribe subscribes the Gui to all three events we discussed, and for each one assigns a callback method: handleSymbolChanged, handleClickedOn, handleChangeCellSymbol.
handleSymbolChanged is a method that handles the symbolChangedEvent. In English, that means this method is called when the symbolChangedEvent is published from the publisher(in our case, the Game object). This method is called in the takeaTurn method of the Game object.
Let's examine this line of code:
var values = [].slice.call(arguments, 1);
arguments is the arguments in the function expressed as a list. Because we are using jQuery, the first argument is going to be the jQuery object. Which is why it is necessary to slice the list, losing the first argument.
But why must we call it this weird way instead of arguments.slice(1)? Because JavaScript is weakly typed, we do not know if arguments is a list or not. So, we cannot invoke the slice method on it directly. Instead, we have to go on a roundabout, starting with a list [] and calling slice and using the method call to actually call the slice method on arguments.
Now, values will contain what the Game sent over - the symbol. It is contained in form of a list, because we did so for no particular reason, like ["X"] or ["O"]. The next line extracts this value and puts it in the object variable theSymbol.
handleChangeCellSymbol is a method that handles the changeCellEvent. In English, this means this method is called when the changeCellEvent is published from the publisher(in our case, the Game object). This method is called in the takeaTurn method of the Game object.
In many ways, it does exactly what handleSymbolChanged is doing. One difference is the third line:
$("#" + theID).text(theSymbol);
The pound sign(#) specifies an element in the html document. So what this line of code does is take the element with id theID and sets its text as theSymbol. In English, it puts the "X" or the "O" in the cell so it can be seen.
Finally, we execute the addHandlers() and subscribe() method, and create a new Game. The new Game immediately starts a new game with the declaration startNewGame() that is at the end of the script. Everything goes from there.
We're done covering the JavaScript. Now, here's the html code for you to look at. Since this is a JavaScript tutorial, I won't be explaining the html. I'm sure you can figure out how it connects to the JavaScript.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<!-- Always force latest IE rendering engine (even in intranet) & Chrome Frame -->
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Grid</title>
<link rel="stylesheet" type="text/css" href="styles/gridStyles.css">
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script src="http://cloud.github.com/downloads/wycats/handlebars.js/handlebars-1.0.rc.1.min.js"></script>
<!-- Handlebars template; needs CSS for style -->
<!-- a grid, with an id in each cell. -->
<script id="grid-template" type="text/x-handlebars-template">
<table align="center">
<tbody>
{{#each grid}}
<tr>
{{#each this}}
<td id="{{this}}">
</td>
{{/each}}
</tr>
{{/each}}
</tbody>
</table>
</script>
<script src="src/model/Square.js"></script>
<script src="src/model/Player.js"></script>
<script src="src/model/Board.js"></script>
<script src="src/controller/Game.js"></script>
<script src="src/controller/publishSubscribe.js"></script>
<script src="src/view/Gui.js"></script>
</head>
<body>
<div>
<header>
TicTacToe
</header>
<div id="theTable"> </div>
<br>
<div id="restartButton">
<button style="font:14px Georgia">Restart Game</button>
</div>
<footer>
<p>
© CS3212 - Spring, 2014 Jongmin Jerome Baek
</p>
</footer>
</div>
</body>
</html>

Here's the lesson you should be taking: don't make a TicTacToe game like this.
No comments:
Post a Comment