3. Tic-tac-toe for Mono

In this tutorial I will teach you how to program Mono’s display and touch device by creating a tiny game.

Anatomy of a Mono application

Mono apps can be written inside the Arduino IDE, but if you really want be a pro, you can write Mono apps directly in C++. For that you will need to implement an AppController with at least three functions. So I will start there, with my app_controller.h header file:

#include <mono.h>

class AppController
:
    public mono::IApplication
{
public:
    AppController ();
    void monoWakeFromReset ();
    void monoWillGotoSleep ();
    void monoWakeFromSleep ();
};

My matching app_controller.cpp implementation file will start out as this:

#include "app_controller.h"

AppController::AppController ()
{
}

void AppController::monoWakeFromReset ()
{
}

void AppController::monoWakeFromSleep ()
{
}

void AppController::monoWillGotoSleep ()
{
}

Now I have a fully functional Mono application! It does not do much, but hey, there it is.

Screen and Touch

Tic Tac Toe is played on a 3-by-3 board, so let me sketch out the layout something like this:

   Tic Tac Toe
+---+ +---+ +---+
|   | |   | |   |
+---+ +---+ +---+
+---+ +---+ +---+
|   | |   | |   |
+---+ +---+ +---+
+---+ +---+ +---+
|   | |   | |   |
+---+ +---+ +---+

I will make the AppController hold the board as an array of arrays holding the tokens X and O, and also a token _ for an empty field:

class AppController
    ...
{
    ...
    enum Token { _, X, O };
    Token board[3][3];
};

For simplicity, I do not want Mono to make any moves by itself (yet); I just want two players to take turns by touching the board. So I need to show the board on the screen, and I want each field of the board to respond to touch.

This kind of input and output can in Mono be controlled by the ResponderView. It is a class that offers a lot of functionality out of the box, and in my case I only need to override two methods, repaint for generating the output and touchBegin for receiving input:

class TouchField
:
    public mono::ui::ResponderView
{
    void touchBegin (mono::TouchEvent &);
    void repaint ();
};

class AppController
    ...
{
    ...
    TouchField fields[3][3];
};

Above I have given AppController nine touch fields, one for each coordinate on the board. To make a TouchField able to paint itself, it needs to know how to get hold of the token it has to draw:

class TouchField
    ...
{
    ...
public:
    AppController * app;
    uint8_t boardX, boardY;
};

With the above information, I can make a TouchField draw a circle or a cross on the screen using the geometric classes Point, Rect, and the underlying functionality it inherits from ResponderView. The ResponderView is a subclass of View, and all Views have a DisplayPainter named painter that takes care of actually putting pixels on the screen:

using mono::geo::Point;
using mono::geo::Rect;

void TouchField::repaint ()
{
    // Clear background.
    painter.drawFillRect(viewRect,true);
    // Show box around touch area.
    painter.drawRect(viewRect);
    // Draw the game piece.
    switch (app->board[boardY][boardX])
    {
        case AppController::X:
        {
            painter.drawLine(Position(),Point(viewRect.X2(),viewRect.Y2()));
            painter.drawLine(Point(viewRect.X2(),viewRect.Y()),Point(viewRect.X(),viewRect.Y2()));
            break;
        }
        case AppController::O:
        {
            uint16_t radius = viewRect.Size().Width() / 2;
            painter.drawCircle(viewRect.X()+radius,viewRect.Y()+radius,radius);
            break;
        }
        default:
            // Draw nothing.
            break;
    }
}

Above, I use the View’s viewRect to figure out where to draw. The viewRect defines the View’s position and size on the screen, and its methods X(), Y(), X2(), and Y2() give me the screen coordinates of the View. The method Position() is just a shortcut to get X() and Y() as a Point.

With respect to the board, I index multidimensional arrays by row-major order to please you old-school C coders out there. So it is board[y][x], thank you very much.

Well, now that each field can draw itself, we need the AppController to setup the board and actually initialise each field when a game is started:

using mono::ui::View;

void AppController::startNewGame ()
{
    // Clear the board.
    for (uint8_t x = 0; x < 3; ++x)
        for (uint8_t y = 0; y < 3; ++y)
            board[y][x] = _;
    // Setup touch fields.
    const uint8_t width = View::DisplayWidth();
    const uint8_t height = View::DisplayHeight();
    const uint8_t fieldSize = 50;
    const uint8_t fieldSeparation = 8;
    const uint8_t screenMargin = (width-(3*fieldSize+2*fieldSeparation))/2;
    uint8_t yOffset = height-width-(fieldSeparation-screenMargin);
    for (uint8_t y = 0; y < 3; ++y)
    {
        yOffset += fieldSeparation;
        uint8_t xOffset = screenMargin;
        for (uint8_t x = 0; x < 3; ++x)
        {
            // Give each touch field enough info to paint itself.
            TouchField & field = fields[y][x];
            field.app = this;
            field.boardX = x;
            field.boardY = y;
            // Tell the view & touch system where the field is on the screen.
            field.setRect(Rect(xOffset,yOffset,fieldSize,fieldSize));
            // Next x position.
            xOffset += fieldSize + fieldSeparation;
        }
        // Next y position.
        yOffset += fieldSize;
    }
    continueGame();
}

Above I space out the fields evenly on the bottom part of the screen, using the DisplayWidth() and DisplayHeight() to get the full size of the screen, and while telling each field where it should draw itself, I also tell the field which board coordinate it represents.

Before we talk about the game control and implement the function continueGame, let us hook up each field so that it responds to touch events:

using mono::TouchEvent;

void TouchField::touchBegin (TouchEvent & event)
{
    app->humanMoved(boardX,boardY);
}

Above the touch event is implicitly translated to a board coordinate (because each field knows its own board coordinate) and passed to the AppController that holds the board and controls the game play.

Game status display

To inform the players what is going on, I want the top of the display to show a status message. And I also want to keep track of which player is next:

class AppController
    ...
{
    ...
    mono::ui::TextLabelView topLabel;
    Token nextToMove;
};

using mono::ui::TextLabelView;

AppController::AppController ()
:
    topLabel(Rect(0,10,View::DisplayWidth(),20),"Tic Tac Toe")
{
    topLabel.setAlignment(TextLabelView::ALIGN_CENTER);
}

A TextLabelView is a View that holds a piece of text and displays this text in inside its viewRect. I can now change the label at the top of the screen depending on the state of the game after each move by using setText(), followed by a call to show() to force the TextLabelView to repaint:

void AppController::continueGame ()
{
    updateView();
    whosMove();
    if (hasWinner())
    {
        if (winner() == X) topLabel.setText("X wins!");
        else topLabel.setText("O wins!");
    }
    else if (nextToMove == _) topLabel.setText("Tie!");
    else if (nextToMove == X) topLabel.setText("X to move");
    else topLabel.setText("O to move");
    topLabel.show();
}

The updateView() function simply forces all the fields to repaint:

void AppController::updateView ()
{
    for (uint8_t y = 0; y < 3; ++y)
        for (uint8_t x = 0; x < 3; ++x)
            fields[y][x].show();
}

Game control

I now need to implement functionality that decides which player should move next and whether there is a winner. First, I can figure out who’s turn it is by counting the number of game pieces for both players, and placing the result in nextToMove. If nextToMove gets the value _, then it means that the board is full:

void AppController::whosMove ()
{
    uint8_t xPieces = 0;
    uint8_t oPieces = 0;
    for (uint8_t y = 0; y < 3; ++y)
        for (uint8_t x = 0; x < 3; ++x)
            if (board[y][x] == X) ++xPieces;
            else if (board[y][x] == O) ++oPieces;
    if (xPieces + oPieces >= 9) nextToMove = _;
    else if (xPieces <= oPieces) nextToMove = X;
    else nextToMove = O;
}

Finding out whether there is a winner is just plain grunt work, checking the board for three-in-a-row:

bool AppController::hasThreeInRow (Token token)
{
    // Check columns.
    for (uint8_t x = 0; x < 3; ++x)
        if (board[0][x] == token &&
            board[1][x] == token &&
            board[2][x] == token
        ) return true;
    // Check rows.
    for (uint8_t y = 0; y < 3; ++y)
        if (board[y][0] == token &&
            board[y][1] == token &&
            board[y][2] == token
        ) return true;
    // Check diagonal.
    if (board[0][0] == token &&
        board[1][1] == token &&
        board[2][2] == token
    ) return true;
    // Check other diagonal.
    if (board[0][2] == token &&
        board[1][1] == token &&
        board[2][0] == token
    ) return true;
    return false;
}

AppController::Token AppController::winner ()
{
    if (hasThreeInRow(X)) return X;
    if (hasThreeInRow(O)) return O;
    return _;
}

bool AppController::hasWinner ()
{
    return winner() != _;
}

Lastly, I need to figure out what to do when a player touches a field. If the game has ended, one way or the other, then I want to start a new game, no matter which field is touched; If the player touches a field that is already occupied, then I ignore the touch; Otherwise, I place the proper piece on the board:

void AppController::humanMoved (uint8_t x, uint8_t y)
{
    if (nextToMove == _ || hasWinner()) return startNewGame();
    else if (board[y][x] != _) return;
    else board[y][x] = nextToMove;
    continueGame();
}

Fallen asleep?

To wrap things up, I want Mono to start a new game whenever it comes out of a reset or sleep:

void AppController::monoWakeFromReset ()
{
    startNewGame();
}

void AppController::monoWakeFromSleep ()
{
    startNewGame();
}

Well there you have it: An astonishing, revolutionary, new game has been born! Now your job is to type it all in.