Tic Tac Toe


This tutorial will show you how to make Tic Tac Toe with the iio Engine with just 9 code statements. It should serve as an introduction to iio Application development.

This tutorial is intended for anyone who is new to the iio Engine, and is hopefully accessible even to those who have never programmed before. I will explain most lines of code, but I will sometimes leave easily inferable portions up to you to figure out.

To follow this tutorial, you'll need the latest iio Engine JS file, a text editor, and a web browser.

Download: iioEngine.min.js

 

Prepping the Application Environment

The first thing to do when developing an iio App is to create an HTML page to hold a canvas element. Application scripts can be written directly into this HTML page, but it is good practice to put self-contained App code into its own file.

If you need help with these steps refer to the quick started guide, or to the HTML & CSS Tutorial.

You can set up your app environment any way you want, but this tutorial is based on the following HTML page:

<!doctype html>
  <body>
    <canvas id="ttt" width="450px" height="450px"/>
    <script type="text/javascript" src="iioEngine.min.js"></script>
    <script type="text/javascript" src="TicTacToe.io.js"></script>
    <script type="text/javascript"> iio.start(TicTacToe,'ttt') </script>
  </body>
</html>

This is a simple page with a canvas that loads the iio Engine and our application script. The last line starts our application and binds it to the canvas element.

You should already have the iio Engine file, so now create a new JavaScript file called TicTacToe.io.js, and put it in the same directory as the HTML and Engine files. We will be working in this file from now on.

 

The Main Application Function

We need to create a main application function to define our app and give it access to an AppManager. Put the following into your TicTacToe application script.

TicTacToe = function(io){

};

This is the basic setup for all iio apps - declare a function with the same name as our app that receives an AppManager as a parameter (which I always denote as io for convenience).

All of our application code will be contained within this function. This structure hides your application data from the global scope, and also allows you to safely deploy your app to any canvas on any webpage without worrying about the surrounding content.

From now on in this tutorial, put the provided sample code into the TicTacToe function. Lets start by adding a grid.

 

Adding the Grid

The iio Engine provides a class for grids called Grid. We'll need a 3x3 grid for Tic Tac Toe, and to fill the entire canvas, each cell should be 150x150 pixels. To create this grid with iio, simply write the following:

var grid = new iio.Grid(0,0,3,3,150);

The first two parameters (0,0) define the grids starting position. Positions are relative to the top-left corner of an element in HTML and in Grid, so this grid will fit our canvas perfectly.

The grid now exists, but we can't see it because it hasn't been drawn. The easiest way to draw the grid is to give it to our AppManager:

io.addObj(grid);

By giving the AppManager a reference to our grid, we are letting it take control of the grid's rendering - so it will draw, clear, and redraw the grid whenever necessary.

The default draw color is black. You can change this color and the width of the draw stroke with these functions:

//change the stroke style to red
grid.setStrokeStyle('red');

//make the draw stroke thicker
grid.setLineWidth(4);

//alter both styles with the same function
grid.setStrokeStyle('red',4);

Note though that the grid will not automatically redraw if you change its style, so you'll either need to tell your AppManager to re-render the canvas:

io.draw();

Or you can just set the styles before you add the grid - here's the full app:

TicTacToe = function(io){

   var grid = new iio.Grid(0,0,3,3,150);
   grid.setStrokeStyle('red',4);
   io.addObj(grid);

   //or...
   //with cascading code structure
   var grid = io.addObj((new iio.Grid(0,0,3,3,150))
                  .setStrokeStyle('red',4));
};

Learn more about cascading code structures here.

What you should be seeing

 

Detecting Input

To listen for user input in JavaScript, you add an 'EventListener' to the element of interest and specify which type of input to listen for. For us, the element of interest is our canvas, and the input we want to catch is a mouse click:

io.canvas.addEventListener('mousedown', function(event){
  
});

This code adds an event listener and defines a callback function that will get called each time the mousedown event is triggered.

Before moving forward with the callback code, we should first check to see if its actually working. Put this line in the callback function:

//create an alert box and display input coordinates
alert(io.getEventPosition(event).toString());

If you save the JS file and refresh your html page, you should now see an alert dialogue displaying the input coordinates pop up each time you click the canvas. Pretty simple right? Maybe not though, that line does have a lot of parentheses in it - lets break it down:

The event parameter in our callback function is an HTML DOM Event object. While this object contains lots of information about the mouse click event that triggered our callback, all we really want to know is what the coordinates of the click were, relative to our canvas element. To get that information, we convert the event object to an Vec using our AppManager's helpful getEventPosition() method. After conversion, we can easily get the relative coordinates through Vec's x and y properties, and convert it to a readable string with its toString() function.

Now that we know our input callback works, we need a way to figure out which cell a user has selected when they click the grid. Grid provides a function for this, so all you need to do is change the callback function to this:

//get the coordinates of the selected cell
var cell = grid.getCellAt(io.getEventPosition(event));
//create an alert box and display the cell coordinates
alert(cell.toString());

When you click the grid now, the alert pops up with the coordinates of the cell you selected. The next step is to get the center position of the selected cell (relative to the canvas) so that we can place an object there. Again, Grid provides us with a function for that. The getCellCenter() function works on cell coordinates or pixel coordinates, so either of the following lines will give us what we want:

var cell = grid.getCellAt(io.getEventPosition(event));
var cellCenter = grid.getCellCenter(cell);
alert(cellCenter.toString());

//or

var cellCenter = grid.getCellCenter(io.getEventPosition(event),true);
alert(cellCenter.toString());

A minimalist implementation at this point would look something like this:

TicTacToe = function(io){

  var grid = io.addObj(new iio.Grid(0,0,3,3,150));

  io.canvas.addEventListener('mousedown', function(event){
    alert(grid.getCellCenter(io.getEventPosition(event),true).toString());
  });
};

Click this canvas to see the cell center

 

Adding Shapes

Lets now replace the alert box with an XShape. The iio framework provides a class for X shapes called XShape. Unlike the grid and canvas element, the origin of an XShape is at its center. This makes adding one to a selected cell very simple:

TicTacToe = function(io){

  var grid = io.addObj(new iio.Grid(0,0,3,3,150));

  io.canvas.addEventListener('mousedown', function(event){
    var cellCenter = grid.getCellCenter(io.getEventPosition(event),true);
    io.addObj(new iio.XShape(cellCenter, 100));
  });
};

Click this canvas to add some X's

The same setStrokeStyle and setLineWidth functions that we used on the Grid exist for XShape. If you wanted to make the Xs stroke size larger and red for instance, replace the code with this:

var xShape = new iio.XShape(cellCenter, 100);
xShape.setStrokeStyle('red',4);
io.addObj(xShape);

//or...
//with cascading code structure
io.addObj((new iio.XShape(cellCenter,100))
    .setStrokeStyle('red',4));

Adding Circles is straightforward too, since iio provides us with the Circle class.

One important thing to note though is that unlike XShape's, Circle's do not have a default draw style. So in our minimalist implementation, if we wanted to add circles instead of X's, we would replace the XShape creation line with this:

//create and add a new black stroked circle shape with a 50px radius
io.addObj((new iio.Circle(cellCenter, 50))
    .setStrokeStyle('black'));

To alternate between drawing X's and O's, we need to create a local Boolean (true/false) variable in the app script to act as a switch for an if, else structure:

TicTacToe = function(io){

  var grid = io.addObj(new iio.Grid(0,0,3,3,150));
  var xTurn = true;

  io.canvas.addEventListener('mousedown', function(event){

    var cellCenter = grid.getCellCenter(io.getEventPosition(event),true);

    if (xTurn)
      io.addObj(new iio.XShape(cellCenter, 100));
    else
      io.addObj((new iio.Circle(cellCenter, 50))
          .setStrokeStyle('black'));

    xTurn=!xTurn;
  });
};

The last line sets the value of xTurn to the opposite of whatever it is currently, thereby switching which shape gets drawn on every input event (! means 'not').

The app with both shapes

 

Adding New Object Properties

The game is mostly done, but it would be nice if we could prevent players from picking a cell multiple times.

Because JavaScript allows us to add new properties and functions to objects at any time, this can be easily accomplished by adding a new property called 'taken' (or whatever you want) to a cell the first time it is selected, and always checking for that property whenever any cell is selected.

Grid already has a matrix of objects representing cells, and you can access it directly through Grid's cells property.

As an example, if we wanted to get a reference to the center cell in our 3x3 grid, we could use this code:

var centerCell = grid.cells[1][1];

Almost all counting starts with 0 in Computer Science, so the cells in our grid are labeled (0,0) to (2,2), with (1,1) being the center.

To give that cell a new property, use the following code:

centerCell.newPropertyName = someValue;

If we need to test whether or not an object has a property, use this code:

if (typeof centerCell.newPropertyName == 'undefined')
  //the property is defined
else
  //the property is not defined

Try implementing a fix to the multi selection issue yourself as an exercise.

Afterwards, take a look at this one - the entire game is written in just 9 lines of internal app code!

 
TicTacToe = function(io){

  var grid = io.addObj(new iio.Grid(0,0,3,3,150));
  var xTurn=true;

  io.canvas.addEventListener('mousedown', function(event){
  
    var cell = grid.getCellAt(io.getEventPosition(event),true);

    if (typeof grid.cells[cell.x][cell.y].taken == 'undefined'){

      if (xTurn) io.addObj(new iio.XShape(grid.getCellCenter(cell),100));
      else io.addObj(new iio.Circle(grid.getCellCenter(cell),50)
               .setStrokeStyle('black'));
      
      grid.cells[cell.x][cell.y].taken=true;
      xTurn=!xTurn;
    }
   });
};

The completed Tic Tac Toe game

You now know the basics on how to create an input driven HTML5 Canvas application using the iio Framework.

If you would like to learn more basics, like how to attach images and animations, or use kinematics, you should read through the iio Basics documentation page, or just check out the demos for some quick code samples.

If you would like to move on to more advanced concepts and learn how to make a frame rate driven application, check out the Scroll Shooter Tutorial.