Aloha!
Today, we will be doing something mind-numbingly simple... apparently. It's a
Noughts And Crosses game on the web, also known as "Tic-tac-toe" in local parlance.
Rules of the game, for everyone who hasn't played this as a kid (which is absolutely nobody, but let's do it anyway, just to spell out the requirements):
1. There is a 3 by 3 square board, totalling nine squares forming a larger square.
2. Each of the opponents take turns placing their mark (either "x" or "o") on the board.
3. Once either opponent manages to get three squares in a row with "x" or "o" (vertically, horizontally or diagonally), the game is won.
4. You can't place an "x" or "o" in any occupied square.
5. If there are no more vacant squares and no one has won the game, it ends in a draw.
Clear? Let's begin.
Set up some basic HTML structure. We'll just do the
easy part first.
<!DOCTYPE html>
<html>
<head>
<title>Tic-tac-toe</title>
<style>
</style>
<script>
</script>
</head>
<body>
</body>
</html>
Add a header tag and two buttons. Their ids are
btnPlay and
btnQuit respectively, and they're styled with the CSS class
btn. One says "PLAY" and the other says "QUIT".
<!DOCTYPE html>
<html>
<head>
<title>Tic-tac-toe</title>
<style>
</style>
<script>
</script>
</head>
<body>
<h1>Let's play Tic-tac-toe!</h1>
<button id="btnPlay" class="btn">PLAY</button>
<button id="btnQuit" class="btn">QUIT</button>
</body>
</html>
So far so good. This is just the beginning.
Now for some CSS! It's a kiddy game, so let's use a kiddy font for the game... the hideously cheerful Comic Sans MS. I'm going to include some styling for the header and buttons, but honestly, they don't matter much in the grand scheme of things.
<style>
body
{
font-family: "Comic Sans MS";
}
h1
{
width: 30em;
text-align: center;
margin: 0 auto 0 auto;
}
.btn
{
font-family: "Comic Sans MS";
font-size: 2em;
display: block;
width: 5em;
height: 1.5em;
border-radius: 0.5em;
border: 0px solid;
background-color: #FF4400;
color: #FFFFFF;
margin: 1em auto 0 auto;
}
.btn:hover
{
background-color: #FFFF00;
color: #FF4400;
}
</style>
Bueno!
Add a div. The id is
game_container. Within it, add three other divs. They are
user_x_container,
board_container and
user_o_container. Style
user_x_container and
user_o_container using the
player CSS class.
<body>
<h1>Let's play Tic-tac-toe!</h1>
<button id="btnPlay" class="btn">PLAY</button>
<button id="btnQuit" class="btn">QUIT</button>
<div id="game_container">
<div id="user_x_container" class="player">
</div>
<div id="board_container">
</div>
<div id="user_o_container" class="player">
</div>
</div>
</body>
Within user_
x_container and
user_o_container, add divs styled using
token and
text. The divs styled using
text should have ids of
user_x_text and
user_o_text, respectively. Also add smiley face symbols in the divs styled with token.
<div id="game_container">
<div id="user_x_container" class="player">
<div class="token">☺</div>
<div id="user_x_text" class="text"></div>
</div>
<div id="board_container">
</div>
<div id="user_o_container" class="player">
<div class="token">☺</div>
<div id="user_o_text" class="text"></div>
</div>
</div>
That's all you will see right now - two tiny smiley faces on the left.
Use different colors for
user_x_container and
user_o_container. I'm going with
orange and
green.
<style>
.btn:hover
{
background-color: #FFFF00;
color: #FF4400;
}
#user_x_container
{
color: #FF4400;
}
#user_o_container
{
color: #44FF00;
}
</style>
For
player, set the width and height, and use the
margin property to align the divs to the middle of the screen. For
token, this is mostly cosmetic. Set the font size to something large, limit the width, and float left. Since we want the smiley faces to align nicely, set the
text-align property to
center.
text aligns left as well, and takes up 70% width. I've made some margin adjustments so that any text we put in there won't be hideously misaligned.
<style>
#user_o_container
{
color: #44FF00;
}
.player
{
width: 600px;
height: 150px;
margin: 0 auto 0 auto;
}
.token
{
width: 20%;
font-size: 5em;
float: left;
text-align: center;
padding: 10px;
}
.text
{
width: 70%;
font-size: 2em;
float: left;
text-align: left;
padding: 10px;
margin-top: 1.5em;
}
</style>
And it's taking shape...
Now let's set the board. Add nine divs within
board_container, all with a class of
square. Set the ids from 0 to 8.
<div id="board_container">
<div id="square0" class="square"></div>
<div id="square1" class="square"></div>
<div id="square2" class="square"></div>
<div id="square3" class="square"></div>
<div id="square4" class="square"></div>
<div id="square5" class="square"></div>
<div id="square6" class="square"></div>
<div id="square7" class="square"></div>
<div id="square8" class="square"></div>
</div>
board_container will be 330 by 330 pixels, and set in the middle of the screen. We give it a
light grey background, and some padding to the left and top.
square is a 100 by 100 pixel square, with a right and bottom margin of 10 pixels to separate it from the other divs, so we get some nice spacing between squares. Give it a
deep grey outline and a
white background. And finally, set the
cursor property to
pointer.
And... this
really should go without saying, but, adjust colors to personal taste.
.btn:hover
{
background-color: #FFFF00;
color: #FF4400;
} body
#board_container
{
width: 330px;
height: 330px;
margin: 0 auto 0 auto;
background-color: #AAAAAA;
padding-top: 10px;
padding-left: 10px;
}
.square
{
width: 100px;
height: 100px;
margin-right: 10px;
margin-bottom: 10px;
float: left;
outline: 1px solid #444444;
background-color: #FFFFFF;
cursor: pointer;
}
#user_x_container
{
color: #FF4400;
}
OK! You have a board.
Time to JavaScript the hell out of this guy...
We still start with two objects -
game and
user.
<script>
var game =
{
};
var user =
{
};
</script>
game has four properties (a string, a boolean and two arrays) and seven methods (use these names for now; I'll explain as we go through them.
user has two methods -
takeTurn() and
myTurn().
<script>
var game =
{
turn: "x",
started: true,
board:
[
],
winPatterns:
[
],
init: function()
{
},
play: function()
{
},
quit: function()
{
},
getRandomNumber: function(min, max)
{
},
getRandomBlankSquare: function()
{
},
nextTurn: function()
{
},
checkWin: function(mark)
{
}
};
var user =
{
takeTurn: function(square, mark)
{
},
myTurn: function()
{
}
};
</script>
For the
game object,
turn is a string used to determine whether player "x" or "o" gets to make their turn. Kind of like a traffic controller, if you will, and set to "x" by default.
started is a flag that determines if the game has begun. By default, it's
true.
board is a series of objects - each with
square and
value properties. The
square property corresponds to the layout of the board.
value is meant to determine if the square is currently occupied by "x" or "o".
var game =
{
turn: "x",
started: true,
board:
[
{square: "a", value: ""},
{square: "b", value: ""},
{square: "c", value: ""},
{square: "d", value: ""},
{square: "e", value: ""},
{square: "f", value: ""},
{square: "g", value: ""},
{square: "h", value: ""},
{square: "i", value: ""}
],
winPatterns:
[
],
init: function()
{
},
play: function()
{
},
quit: function()
{
},
getRandomNumber: function(min, max)
{
},
getRandomBlankSquare: function()
{
},
nextTurn: function()
{
},
checkWin: function(mark)
{
}
};
winPatterns is an array of an array of objects that help you determine if victory has been achieved. Take, for example, the first array that has been filled in for you. "abc" is a straight line right at the top of the board.
var game =
{
turn: "x",
started: true,
board:
[
{square: "a", value: ""},
{square: "b", value: ""},
{square: "c", value: ""},
{square: "d", value: ""},
{square: "e", value: ""},
{square: "f", value: ""},
{square: "g", value: ""},
{square: "h", value: ""},
{square: "i", value: ""}
],
winPatterns:
[
[
{square: "a", value: ""},
{square: "b", value: ""},
{square: "c", value: ""}
]
],
init: function()
{
},
play: function()
{
},
quit: function()
{
},
getRandomNumber: function(min, max)
{
},
getRandomBlankSquare: function()
{
},
nextTurn: function()
{
},
checkWin: function(mark)
{
}
};
And the rest, you can figure out yourself. There are eight possible winning combinations.
winPatterns:
[
[
{square: "a", value: ""},
{square: "b", value: ""},
{square: "c", value: ""}
],
[
{square: "a", value: ""},
{square: "d", value: ""},
{square: "g", value: ""}
],
[
{square: "a", value: ""},
{square: "e", value: ""},
{square: "i", value: ""}
],
[
{square: "b", value: ""},
{square: "e", value: ""},
{square: "h", value: ""}
],
[
{square: "c", value: ""},
{square: "f", value: ""},
{square: "i", value: ""}
],
[
{square: "c", value: ""},
{square: "e", value: ""},
{square: "g", value: ""}
],
[
{square: "d", value: ""},
{square: "e", value: ""},
{square: "f", value: ""}
],
[
{square: "g", value: ""},
{square: "h", value: ""},
{square: "i", value: ""}
]
]
Now let's set create the
init() method for the
game object. This initializes the properties of the
game object. We start with setting
turn to "x" and
started to
true.
init: function()
{
this.started = true;
this.turn = "x";
},
And then we iterate through the
board and
winPatterns arrays, and reset all values to an empty string.
init: function()
{
this.started = true;
this.turn = "x";
for (var i = 0; i < this.board.length; i++)
{
this.board[i].value = "";
}
for (var i = 0; i < this.winPatterns.length; i++)
{
for (var j = 0; j < this.winPatterns[i].length; j++)
{
this.winPatterns[i][j].value = "";
}
}
},
After that, go through all the squares in the board and reset the class to
square.
init: function()
{
this.started = true;
this.turn = "x";
for (var i = 0; i < this.board.length; i++)
{
this.board[i].value = "";
}
for (var i = 0; i < this.winPatterns.length; i++)
{
for (var j = 0; j < this.winPatterns[i].length; j++)
{
this.winPatterns[i][j].value = "";
}
}
for (var i = 0; i < 9; i++)
{
document.getElementById("square" + i).className = "square";
}
},
Then clear the
innerHTML property of all elements styled using the
text CSS class.
init: function()
{
this.started = true;
this.turn = "x";
for (var i = 0; i < this.board.length; i++)
{
this.board[i].value = "";
}
for (var i = 0; i < this.winPatterns.length; i++)
{
for (var j = 0; j < this.winPatterns[i].length; j++)
{
this.winPatterns[i][j].value = "";
}
}
for (var i = 0; i < 9; i++)
{
document.getElementById("square" + i).className = "square";
}
var texts = document.getElementsByClassName("text");
for (var i = 0; i < texts.length; i++)
{
texts.innerHTML = "";
}
},
For the
play() method, hide the "PLAY" button and show the "QUIT" button. Then show
game_container and run the
nextTurn() method.
For
quit(), you do almost the exact opposite - show the "PLAY" button and hide the "QUIT" button. Then
hide game_container. But before all that, run the
init() method.
play: function()
{
document.getElementById("btnPlay").style.display = "none";
document.getElementById("btnQuit").style.display = "block";
document.getElementById("game_container").style.display = "block";
this.nextTurn();
},
quit: function()
{
this.init();
document.getElementById("btnQuit").style.display = "none";
document.getElementById("btnPlay").style.display = "block";
document.getElementById("game_container").style.display = "none";
},
Now make sure the buttons run those.
<button id="btnPlay" class="btn" onclick="game.play()">PLAY</button>
<button id="btnQuit" class="btn" onclick="game.quit()">QUIT</button>
For there to be anything noticeable when you click the buttons, you first need to set this CSS to hide the "QUIT" button and
game_container. Now, refresh. Click the "PLAY" button. It should disappear, only to be replaced by the "QUIT" button. And then the board should appear as well.
.btn:hover
{
background-color: #FFFF00;
color: #FF4400;
}
#btnQuit, #game_container
{
display: none;
}
#board_container
{
width: 330px;
height: 330px;
margin: 0 auto 0 auto;
background-color: #AAAAAA;
padding-top: 10px;
padding-left: 10px;
}
The
nextTurn() method will set the
turn property to "x" if it's currently "o", and vice versa. Then it will run the
myTurn() method of the
user object.
nextTurn: function()
{
this.turn = (this.turn == "x" ? "o" : "x");
user.myTurn();
},
Let's create
myTurn(), then. This will manipulate the DOM to display whose turn it is on the board. First we clear the
user_x_text and
user_o_text divs, then fill the appropriate div with the string "My turn!".
myTurn: function()
{
document.getElementById("user_x_text").innerHTML = "";
document.getElementById("user_o_text").innerHTML = "";
document.getElementById("user_" + game.turn + "_text").innerHTML = "My turn!";
}
If is the computer's turn, i.e. "x", then we do more stuff. Let's just leave the
If block here for now.
myTurn: function()
{
document.getElementById("user_x_text").innerHTML = "";
document.getElementById("user_o_text").innerHTML = "";
document.getElementById("user_" + game.turn + "_text").innerHTML = "My turn!";
if (game.turn == "x")
{
}
}
Now test this. When you refresh, you should now only see the "PLAY" button.
When you click the "PLAY" button, does "My turn!" come on for player "o"?
Next
The groundwork has been laid. The next codes we write will handle the meat of the game.