Tip:
Highlight text to annotate it
X
Hi, in this video I try to code live the game Ultimate Tic Tac Toe
It is an evolution of the classic tic tac toe game
that uses nine grids at the same time
the game rule is, every time you pick a case in an inner grid
the cell position determine the next extern grid to play
so if I choose this cell, the next player will have to play in that grid next
So it brings cool perspectives in terms of strategy
I'll code in Javascript and HTML so that everyone could play with just their web browser
and to do this, I'll use the IDE Webstorm
best Javascript IDE on the market
and the data-binding framework Angular JS by Google
and also my favorite CSS preprocessor, lessCSS
useful to quickly write CSS rules
I start from a very basic setup
an empty index.html file
I prepared some images, very bad visually but no matter
these 8 sticks are the 8 conditions of victory, 3 columns, 3 lines and two diagonals
Let's start by loading AngularJS library
I load it from a content delivery network so I don't have to deal with it locally
I create my stylesheet file, main.less
You can see two files already in the styles folder
that I use in almost all my projects
the first one is a normalization stylesheet based on normalize.css 2.1
and the second one is a collection of less mixins, which are like functions in CSS
useful for dealing with vendor prefixes as you see
I import both in my main file
and Webstorm will automatically compile them in classis CSS everytime I edit my lessCSS file
All I have to do now is import the compiled main.css file in my HTML view
let's test
I have my project opened here in Chrome, using Webstorm local server
If I changed the less file, for example the page background color
it works
let's write a basic DOM for my page. First, a title
then a section with ID board, which will contains the game board
the board will be a table element
I make just one tic tac toe grid to start
a 3x3 grid, 3 with 3 in each
styling the page, centering everything...
for my table, each cell has its dimensions fixed. I declare a less variable for this
that I apply to each of my cells, width and height
table element will have a width and height of 3 cells
a border on my cells to better see them
a bigger one on the table element
do not forget to center my table with margin: 0 auto
and here we are
now let's add circles and crosses to cells
I start by setting a class on some cells for testing
cell-1 for player 1 for example
and tds with this class will have a background image
let's say player 1 has the circles and player 2 the crosses
it starts looking good, now let's bind everything with Angular
I start by declaring the application as an Angular app with ng-app
then I bind this section to a controller
named Game for example
then I create a Javascript file Game.js
which contains a function with specific Angular arguments passed
the interesting one is $scope, which let us share variables between model and view layers
for example, if I declare a test variable in scope
do not forget to import the js file
I call this variable test in my template
so you can see data binding is working well
I remove testing code and start writing my data model
I declare a Grid object
if we have 9 of them, better write this object-oriented right now
a grid contains rows and columns, so let's use a bidimensional array
it will contains numeral values representing state of each cell
at the beginning, I put zero for empty cells
and I put 1 or 2 to identify circles and crosses
Now I translate this DOM as an Angular template
the tag will be repeated for each row using ng-repeat instruction
as much as there are rows in my grid
remember, rows is bound to "this", and "this" is bound to $scope.grid
same stuff for , repeated for each column in each row
I can erase the rest, it will be generated with ng-repeat
so we still have our 3x3 grid
If i put cell value in my template, I retrieve the states zero
If I change the model, the view is updated consequently
so instead of numbers let's draw circles and crosses
to do this, I declare a specific class for s
depending on the cell state
and as I already have cell-1 and cell-2 classes, it just works right now
now adding an action to the click event with ng-click
and a check function in the grid object
with row number and column numbers as arguments
column number is current loop index, accessible with $index
and row number is parent loop index, accessible with $parent.$index
I declare the function in my controller, receiving these two arguments
just adding a log to check if everything is okay
1, 2 in logs for line 1 column 2, everything is OK
so we can access to the correspondant cell using this.rows[indexRow][indexCol]
and then set the cell state to any value
for example, if I set to 1
I can now check cells with player 1 (circles)
If I want to switch between circles and crosses
I have to declare in the scope which one is the current player
let's say rounds begins
I set clicked cell value to the current player
and just after, I switched current player variable
to toggle between 1 and 2 is as simple as n modulo 2 + 1
that's how you switch between players at each pick
I add a condition to only accept picks on empty cells
so that player can't pick a cell previously picked
just checking if initial value is zero
cleaning test code...
now, victory tests
I check if grid is won with a testEnd function
next, I list all the victory moves in an array I call winMoves
and I describe each victory move as an array of 3 cells
these three cells will have the same value, 1 or 2 according to the winning player
cell coordonates are groups of two indexes
for example 0,0 for top left cell
so for winmove number one, the vertical line on the left
it should be 0,0 - 1,0 - 2,0
we have 8 like this
2: column 2
3: column 3
4: line 1
5: line 2
6: line 3
last two, diagonals
7: diagonal top-left to bottom-right
and last, 8: diagonal top-right to bottom-left
Oops, this is not how you declare a function in Javascript
So we test each of these 8 moves
I write this loop that way so it ends when move is undefined, ie when all moves have been tested
not a very rigorous notation, but it does the job here
Inside this loop, I have to get cell values at corresponding coordinates
so I declare another helper function to simplify things
call it cellAt, taking coordinates as arguments and returning cell value
coords[0] is row index, coords[1] is column index
now I can call cellAt for each of the 3 cells in each move
if cell 1 value is equals to cell 2 value and cell 2 value is equals to cell 3 value...
and to not take in account empty cells, checking that this value is not zero
then we are in a victory condition
so I can inform grid object that this grid is won
setting grid.win to the winning player number, 1 or 2
I can also keep in memory the win move
by its number in my winmoves array for example
plus one to have numbers between 1 and 8
I do that in order to use these images (win1.png - win8.png)
I call this victory test each time a cell is picked
I also return a boolean in my test function to know if grid is won
so if grid is won, I can print to screen a random message
let's say, Player 1 has won
test !
Yep, player 1 won
of course we gonna use less intrusive notifications than plain old alert() messages
what we gonna do is stocking a message variable in game controller
I can add it to the HTML template
I can also use this message to inform players who has to play next turn
if grid is not over, I switch to next player and refresh the message
let's say, "Player n turn"
I also add a message at the beginning
At the same time, I create an initialization function
so I can put all this code in my init function
I immediately call this init function
test !
so we don't have the win stroke yet
I bind the winmove to a class in the DOM
let's say this table has class grid, and then a class depending on the win move
then, in CSS, set the different background images for each win move
I will have to do this 8 times, so let's write a mixin
with the win move number as argument
I can call it with this notation to insert in background image URL
I call this mixin 8 times
Testing !
Winmove seems to not have been bound...
because I forgot to precise winMove is in the grid object
Voila
testing some other cases...
next step, before I forgot, I have to check if the grid ends with a draw
this will not pass in one of these victory tests
but grid will be over, because there is not any empty cells left
I check this with a double for loop
if a cell has a zero value, grid is not over, else it is a draw game
in this case I set grid.win to zero
Here I can add a specific message whether its a draw game or not
Testing
Okay, we have already done the classic Tic Tac Toe game
Now, dealing with nine grids !
the grid declaration is about to change
now we have nine grids to declare
using another double for loop
instead of declaring one grid, we declare a 2-dimensions array of grids
each element will be a new Grid object
each grid will have its own win state and win move
now I have to adapt my template
this is an array in another array
it will be close to this other template
with two ng-repeat
but here we repeat grid rows in grid array
and here we repeat grids in grid rows
each 3x3 grid will be bound to these elements
some CSS corrections
main board will have a height and width of 9 cells
and centered
just a test to check if everything is OK with the model
I also check if we can pick everywhere
victory tests still work, perfect
now we have to implement the most important rule of Ultimate Tic Tac Toe
when picking a cell, cell position in inner grid determines the next outer grid to play
so let's add a nextGrid variable to the scope of our controller
that will be the center grid at the beginning
ie grids[1][1]
and then each time I'll pick a cell
the next grid will be grid at coordinates row, col
this rule is as simple as that to implement
then I have to check that next picked grid is scope.nextGrid
to do this, I declare a function isPickable
which returns true if this grid can be used for cell picking
another rule of Tic Tac Toe is
if nextGrid has already been won, next player can play in any grid
so this is another condition
that means is next grid has been won,
ie win is different from undefined
then I set nextGrid to null to indicate next player can play wherever he wants
so in isPickable, I can add the condition
if nextGrid is null, all grids are pickable
just calling this isPickable condition in my check function
I declare isPickable as a function so I can call it from my template
and then set a custom class for styling purposes
with pickable-false and pickable-true classes
so I can differentiate visually pickable grids from unpickable ones
white background if pickable
grey if unpickable
I also add a special color for grids which have been won
with win-1 and win-2 classes for example
red background if player 1 has won
blue for player 2
in this game, we can pick a cell in a grid that has been already won
provided that grid has still empty cells
I set a brighter color for pickable and won grids
for tied game grids, there are no empty cells so we do not have to manage pickable-state styles
just setting a dark gray background
adjusting colors...
Test !
next Grid mecanism seems to work well
well, is it a blue grid ? not really...
because I use win-1 class but I forgot to declare it in my template
fixed it
nope, still not working
okay, the color css rule is overwritten
replacing background by background-image will probably do the trick
here, we have a light red background meaning this grid is won but I can still pick a cell in
Well, I have a last one thing to do
it is the final victory condition, when all grids have been won
first, this old win message needs another condition
it is not bound to a grid anymore, but to the game controller
I will test this with a testEndGame function
this function will simply traverse each grid and check that they are all won
either by a player or a tie game
If a grid.win is undefined, the game is not over so I return false
instead, game is over
and I can show the win message here
I also keep in scope the player who has won the entire game
winning player is the one who has won the most grids (VARIATION OF ORIGINAL RULES HERE)
I add scores variable to count won grids by each player
each time a grid is won
I will update the scores
incrementing by one
so, in testEndGame the winner will be the one with the highest score
if scores are equals, it will be a draw game
set to zero for draw games
if player 1 score higher than player 2 score
player 1 won, else player 2
i call testEndGame here after an && condition
&& because testEndGame needs to be call only if testEnd() returns true (ie when a grid is won)
refactoring a bit so we go to next turn only if game is not over
I can also add scores to my template so players can see them in real time
Test!
Oops, player 1 has won but score 2 has been incremented
just an index mistake
perfect, let's continue to see if victory test works
scores 3-3, everything good
Player 1 won 5 grids to 4
finally, working in the last details...
prettier messages
adjusting colors...
and it will be enough for today :)
just adding a special border color for next grid depending on player turn
still dealing with this by using css classes
Okay, here is the final result !
There is probably tons of bugs left
classic in live coding sessions
Please report any mistake you have seen
screencasts do not work in a one way direction ;)
I will upload a debugged and finalized version on my website, and post the link below this video
Thanks for watching, see you ! SPV