The BLOCKS SDK
Tic Tac Toe

Introduction

Implement the classic game of Tic Tac Toe on the Lightpad Block.

Launch the BLOCKS CODE application and open the script called TicTacToe.littlefoot. You can find the script in the littlefoot/scripts/Example Scripts folder. If you don't have the BLOCKS CODE IDE installed on your system, please refer to the section Getting started with BLOCKS CODE for help.

Drawing the Grid

Let's start by defining global variables to save the current state of the game. To define global variables in LittleFoot simply place your variables outside any function declaration and at the top of the file for convenience. We first have 9 variables for the state of each cell in the grid, a currentTurn variable for the current player's turn and a winner variable to determine which player is the winner.

int tl, t, tr;
int ml, m, mr;
int bl, b, br;
int currentTurn;
int winner;

The initialise() function in the littlefoot language is useful to initialise variables and the state of your script as the function is called once when the program is loaded onto the device. Here we set all the cells to 0, a number chosen to define the blank state of a cell, the current turn to player 1 and the winner to 0, again a number chosen to define the ongoing state of the game.

void initialise()
{
tl = 0;
t = 0;
tr = 0;
ml = 0;
m = 0;
mr = 0;
bl = 0;
b = 0;
br = 0;
currentTurn = 1;
winner = 0;
}
void initialise()
Called when a program is loaded onto the block and is about to start.

In the repaint() function, we first clear the display as usual and check if a winner has been selected. If not, this means the game is still ongoing and we proceed to draw a grid and signs if there are any. Otherwise we draw the winner screen.

void repaint()
{
if (winner == 0)
{
drawGrid();
drawSigns();
}
else
{
drawWinner();
}
}
void repaint()
Use this method to draw the display.
void clearDisplay()
Clears the display and sets all the LEDs to black.

In order to draw the default grid, we have created a helper function called drawGrid() which is called in the previously defined repaint() function. We first define a variable to the colour white and proceed to draw a rectangle if the cell is still unoccupied.

void drawGrid()
{
int white = makeARGB (255, 200, 200, 200);
if (tl == 0) drawRect (white, 0, 0, 5, 5);
if (t == 0) drawRect (white, 5, 0, 5, 5);
if (tr == 0) drawRect (white, 10, 0, 5, 5);
if (ml == 0) drawRect (white, 0, 5, 5, 5);
if (m == 0) drawRect (white, 5, 5, 5, 5);
if (mr == 0) drawRect (white, 10, 5, 5, 5);
if (bl == 0) drawRect (white, 0, 10, 5, 5);
if (b == 0) drawRect (white, 5, 10, 5, 5);
if (br == 0) drawRect (white, 10, 10, 5, 5);
}
int makeARGB(int alpha, int red, int green, int blue)
Combines a set of 8-bit ARGB values into a 32-bit colour and returns the result.

This is performed on each cell using the helper function drawRect() defined below which takes as argument a colour, the coordinates and the size of the rectangle.

void drawRect (int colour, int x, int y, int w, int h)
{
fillRect (colour, x, y, 1, h);
fillRect (colour, x, y, w, 1);
fillRect (colour, x + w - 1, y, 1, h);
fillRect (colour, x, y + h - 1, w, 1);
}
void fillRect(int rgb, int x, int y, int width, int height)
Fills a rectangle on the display with a specified colour.

Notice here we use the built-in fillRect() function with either a width or height of 1 to draw lines for the four sides of the rectangle because we don't want the rectangle to be filled.

The Tic Tac Toe grid

Drawing the Signs

Now let's see how we can draw the player signs if any of the cells are occupied. The drawSigns() function called in the repaint() loop checks every cell using the evaluate() helper function defined after. It passes the cell variables along with the coordinates in case we need to draw the signs.

void drawSigns()
{
evaluate (tl, 0, 0);
evaluate (t , 5, 0);
evaluate (tr, 10, 0);
evaluate (ml, 0, 5);
evaluate (m, 5, 5);
evaluate (mr, 10, 5);
evaluate (bl, 0, 10);
evaluate (b, 5, 10);
evaluate (br, 10, 10);
}

The evaluate() function below checks if the cell variable has been switched to a player index of 1 or 2 in which case we need to draw the corresponding player sign. If the cell variable is still set to 0 this means that the cell is still blank and we keep the white default square.

void evaluate (int value, int x, int y)
{
if (value == 1)
drawX (x, y);
else if (value == 2)
drawO (x, y);
}

To draw player 1's "X" sign, we first define the player colour and draw each point in the two diagonals as follows:

void drawX (int x, int y)
{
int xColour = makeARGB (255, 0, 255, 0);
fillRect (xColour, x, y, 1, 1);
fillRect (xColour, x + 1, y + 1, 1, 1);
fillRect (xColour, x + 2, y + 2, 1, 1);
fillRect (xColour, x + 3, y + 3, 1, 1);
fillRect (xColour, x + 4, y + 4, 1, 1);
fillRect (xColour, x + 4, y, 1, 1);
fillRect (xColour, x + 3, y + 1, 1, 1);
fillRect (xColour, x + 2, y + 2, 1, 1);
fillRect (xColour, x + 1, y + 3, 1, 1);
fillRect (xColour, x, y + 4, 1, 1);
}

To draw player 2's "O" sign, we first define the player colour and draw the four sides like so:

void drawO (int x, int y)
{
int oColour = makeARGB (255, 0, 0, 255);
fillRect (oColour, x, y, 5, 1);
fillRect (oColour, x, y + 4, 5, 1);
fillRect (oColour, x, y, 1, 5);
fillRect (oColour, x + 4, y, 1, 5);
}

Handling Touch Events

In its current implementation state, our script won't receive any touch events and therefore the game will stay in the blank state with a white grid showing. Let's implement the touchStart() callback to receive player moves.

void touchStart (int index, float x, float y, float z, float vz)
{
int xPos = int (x * 7);
int yPos = int (y * 7);
int index = getIndex (xPos, yPos);
if (setValueForIndex (index, currentTurn))
{
currentTurn = currentTurn == 1 ? 2 : 1;
}
if (xHasWon()) winner = 1;
else if (oHasWon()) winner = 2;
}
void touchStart(int index, float x, float y, float z, float vz)
Called when a touch event starts.

In the above function, we first convert the device coordinates into LED grid coordinates by multiplying both x and y variables by 7. Device coordinates are defined using the number of DNA connectors on the side of the device so for example in the case of a Lightpad Block, the device has a size of 2x2 and therefore the device coordinates will range from 0.0 to 2.0 on each x and y dimensions. Multiplying this range by 7 gives us the LED grid coordinates ranging from 0 to 14 inclusive.

Now using these grid coordinates, we use the getIndex() helper function defined below to retrieve the cell index ranging from 0 to 8 from our 3x3 game grid.

int getIndex (int x, int y)
{
int xInd = x / 5;
int yInd = y / 5;
return yInd * 3 + xInd;
}

When the index is retrieved we attempt to set a cell variable value only if the touched cell is unoccupied. If a value has been successfully set for the current player, the function returns true and the currentTurn variable is switched to the other player.

bool setValueForIndex (int index, int value)
{
if (index == 0) { tl = tl == 0 ? value : tl; return true; }
if (index == 1) { t = t == 0 ? value : t ; return true; }
if (index == 2) { tr = tr == 0 ? value : tr; return true; }
if (index == 3) { ml = ml == 0 ? value : ml; return true; }
if (index == 4) { m = m == 0 ? value : m ; return true; }
if (index == 5) { mr = mr == 0 ? value : mr; return true; }
if (index == 6) { bl = bl == 0 ? value : bl; return true; }
if (index == 7) { b = b == 0 ? value : b ; return true; }
if (index == 8) { br = br == 0 ? value : br; return true; }
return false;
}
The Tic Tac Toe signs

Determining a Winner

We were able to implement the basic gameplay but we don't have a mechanism to select a winner yet. As a last step to the touchStart() callback, we check if a winner has been selected by calling respectively the xHasWon() and oHasWon() helper functions defined as follows:

bool xHasWon() { return hasWon (1); }
bool oHasWon() { return hasWon (2); }

These in turn call the hasWon() function with the player number which checks all 8 possible combination of winning lines (3 horizontal, 3 vertical and 2 diagonal).

bool hasWon (int player)
{
return (tl == player && t == player && tr == player)
|| (ml == player && m == player && mr == player)
|| (bl == player && b == player && br == player)
|| (tl == player && ml == player && bl == player)
|| (t == player && m == player && b == player)
|| (tr == player && mr == player && br == player)
|| (tl == player && m == player && br == player)
|| (tr == player && m == player && bl == player);
}

If a winner is selected the repaint() function will call the drawWinner() function defined below and fill the screen with the appropriate winner's colour:

void drawWinner()
{
if (winner == 1)
{
int xColour = makeARGB (255, 0, 255, 0);
fillRect (xColour, 0, 0, 15, 15);
}
else if (winner == 2)
{
int oColour = makeARGB (255, 0, 0, 255);
fillRect (oColour, 0, 0, 15, 15);
}
}

As a final step we can implement a simple way to reset the game back to the beginning by calling the initialise() function ourselves when the side button of the device is pressed. This is achieved by implementing the handleButtonDown() callback function like this:

void handleButtonDown (int index)
{
}
void handleButtonDown(int index)
Called when a button is pushed.

Summary

In this example, we learnt how to recreate the classic game of Tic Tac Toe on the Lightpad Block.

See also