The example program on this page may be used, distributed and modified without limitation. C0664 510077 qtdoc-1.2-a4-2.ps

Tic Tac Toe

This is an implementation of the Tic-tac-toe game. We didn't put much effort in making a clever algorithm so it's not a challenge to play against the computer. Instead, study the source code to see how you can make reusable components such as the TicTacGameBoard widget. Here is the definition of the tic-tac-toe classes:
//
// Qt Example Application: Tic-Tac-Toe
//

#ifndef TICTAC_H
#define TICTAC_H

#include <qpushbt.h>
#include <qvector.h>

class QComboBox;
class QLabel;

// --------------------------------------------------------------------------
// TicTacButton implements a single tic-tac-toe button
//

class TicTacButton : public QButton
{
    Q_OBJECT
public:
    TicTacButton( QWidget *parent=0 );
    enum Type { Blank, Circle, Cross };
    Type        type() const            { return t; }
    void        setType( Type type )    { t = type; paintEvent(0); }
protected:
    void        drawButton( QPainter * );
private:
    Type t;
};

// Using template vector to make vector-class of TicTacButton.
// This vector is used by the TicTacGameBoard class defined below.

typedef QVector<TicTacButton>   TicTacButtons;
typedef QArray<int>             TicTacArray;

// --------------------------------------------------------------------------
// TicTacGameBoard implements the tic-tac-toe game board.
// TicTacGameBoard is a composite widget that contains N x N TicTacButtons.
// N is specified in the constructor.
//

class TicTacGameBoard : public QWidget
{
    Q_OBJECT
public:
    TicTacGameBoard( int n, QWidget *parent=0, const char *name=0 );
   ~TicTacGameBoard();
    enum        State { Init, HumansTurn, HumanWon, ComputerWon, NobodyWon };
    State       state() const           { return st; }
    void        computerStarts( bool v );
    void        newGame();
signals:
    void        finished();                     // game finished
private slots:
    void        buttonClicked();
protected:
    void        resizeEvent( QResizeEvent * );
private:
    void        setState( State state ) { st = state; }
    void        updateButtons();
    int         checkBoard( TicTacArray * );
    void        computerMove();
    State       st;
    int         nBoard;
    bool        comp_starts;
    TicTacArray *btArray;
    TicTacButtons *buttons;
};

// --------------------------------------------------------------------------
// TicTacToe implements the complete game.
// TicTacToe is a composite widget that contains a TicTacGameBoard and
// two push buttons for starting the game and quitting.
//

class TicTacToe : public QWidget
{
    Q_OBJECT
public:
    TicTacToe( int boardSize=3, QWidget *parent=0, const char *name=0 );
private slots:
    void        newGameClicked();
    void        gameOver();
private:
    void        newState();
    QComboBox   *whoStarts;
    QPushButton *newGame;
    QPushButton *quit;
    QLabel      *message;
    TicTacGameBoard *board;
};

#endif // TICTAC_H

Here is the implementation:
//
// Qt Example Application: Tic-Tac-Toe
//

#include "tictac.h"
#include <qapp.h>
#include <qpainter.h>
#include <qdrawutl.h>
#include <qcombo.h>
#include <qchkbox.h>
#include <qlabel.h>
#include <stdlib.h>                             // rand() function
#include <qdatetm.h>                            // seed for rand()

//***************************************************************************
//* TicTacButton member functions
//***************************************************************************

// --------------------------------------------------------------------------
// Creates a TicTacButton
//

TicTacButton::TicTacButton( QWidget *parent ) : QButton( parent )
{
    initMetaObject();                           // initialize meta object
    setBackgroundColor( blue );                 // special background color
    t = Blank;                                  // initial type
}

// --------------------------------------------------------------------------
// Paints TicTacButton
//

void TicTacButton::drawButton( QPainter *p )
{
    QRect r = rect();                           // get rectangle
    static QColorGroup g( white, blue, white, darkBlue, blue, black, black );
    QBrush fill( blue );
    qDrawShadePanel( p, r, g, isDown(), 1, &fill );
    p->setPen( QPen(white,2) );                 // set fat pen
    if ( t == Circle )                          // draw circle
        p->drawEllipse( r.left()+4, r.top()+4, r.width()-8, r.height()-8 );
    else if ( t == Cross ) {                    // draw cross
        p->drawLine( r.topLeft()   +QPoint(4,4), r.bottomRight()-QPoint(4,4));
        p->drawLine( r.bottomLeft()+QPoint(4,-4),r.topRight()   -QPoint(4,-4));
    }
}

//***************************************************************************
//* TicTacGameBoard member functions
//***************************************************************************

// --------------------------------------------------------------------------
// Creates a game board with N x N buttons and connects the "clicked()"
// signal of all buttons to the "buttonClicked()" slot.
//

TicTacGameBoard::TicTacGameBoard( int n, QWidget *parent, const char *name )
    : QWidget( parent, name )
{
    initMetaObject();                           // initialize meta object
    setBackgroundColor( lightGray );            // set background color
    st = Init;                                  // initial state
    nBoard = n;
    n *= n;                                     // make square
    comp_starts = FALSE;                        // human starts
    buttons = new TicTacButtons(n);             // create real buttons
    btArray = new TicTacArray(n);               // create button model
    for ( int i=0; i<n; i++ ) {                 // create and connect buttons
        TicTacButton *p = new TicTacButton( this );
        connect( p, SIGNAL(clicked()), SLOT(buttonClicked()) );
        buttons->insert( i, p );
        btArray->at(i) = TicTacButton::Blank;   // initial button type
    }
    QTime t = QTime::currentTime();             // set random seed
    srand( t.hour()*12+t.minute()*60+t.second()*60 );
}

TicTacGameBoard::~TicTacGameBoard()
{
    delete buttons;
    delete btArray;
}

// --------------------------------------------------------------------------
// TicTacGameBoard::computerStarts( bool v )
//
// Computer starts if v=TRUE. The human starts by default.
//

void TicTacGameBoard::computerStarts( bool v )
{
    comp_starts = v;
}

// --------------------------------------------------------------------------
// TicTacGameBoard::newGame()
//
// Clears the game board and prepares for a new game
//

void TicTacGameBoard::newGame()
{
    st = HumansTurn;
    for ( int i=0; i<nBoard*nBoard; i++ )
        btArray->at(i) = TicTacButton::Blank;
    if ( comp_starts )
        computerMove();
    else
        updateButtons();
}

// --------------------------------------------------------------------------
// TicTacGameBoard::buttonClicked()             - SLOT
//
// This slot is activated when a TicTacButton emits the signal "clicked()",
// i.e. the user has clicked on a TicTacButton.
//

void TicTacGameBoard::buttonClicked()
{
    if ( st != HumansTurn )                     // not ready
        return;
    int i = buttons->findRef( (TicTacButton*)sender() );
    TicTacButton *b = buttons->at(i);           // get piece that was pressed
    if ( b->type() == TicTacButton::Blank ) {   // empty piece?
        btArray->at(i) = TicTacButton::Circle;
        updateButtons();
        if ( checkBoard( btArray ) == 0 )       // not a winning move?
            computerMove();
        int s = checkBoard( btArray );
        if ( s ) {                              // any winners yet?
            st = s == TicTacButton::Circle ? HumanWon : ComputerWon;
            emit finished();
        }
    }
}

// --------------------------------------------------------------------------
// TicTacGameBoard::updateButtons()
//
// Updates all buttons that have changed state
//

void TicTacGameBoard::updateButtons()
{
    for ( int i=0; i<nBoard*nBoard; i++ ) {
        if ( buttons->at(i)->type() != btArray->at(i) )
            buttons->at(i)->setType( (TicTacButton::Type)btArray->at(i) );
    }
}

// --------------------------------------------------------------------------
// TicTacGameBoard::checkBoard()
//
// Checks if one of the players won the game, works for any board size.
//
// Returns:
//  - TicTacButton::Cross  if the player with X buttons won
//  - TicTacButton::Circle if the player with O buttons won
//  - Zero (0) if there is no winner yet
//

int TicTacGameBoard::checkBoard( TicTacArray *a )
{
    int  t = 0;
    int  row, col;
    bool won = FALSE;
    for ( row=0; row<nBoard && !won; row++ ) {  // check horizontal
        t = a->at(row*nBoard);
        if ( t == TicTacButton::Blank )
            continue;
        col = 1;
        while ( col<nBoard && a->at(row*nBoard+col) == t )
            col++;
        if ( col == nBoard )
            won = TRUE;
    }
    for ( col=0; col<nBoard && !won; col++ ) {  // check vertical
        t = a->at(col);
        if ( t == TicTacButton::Blank )
            continue;
        row = 1;
        while ( row<nBoard && a->at(row*nBoard+col) == t )
            row++;
        if ( row == nBoard )
            won = TRUE;
    }
    if ( !won ) {                               // check diagonal top left
        t = a->at(0);                           //   to bottom right
        if ( t != TicTacButton::Blank ) {
            int i = 1;
            while ( i<nBoard && a->at(i*nBoard+i) == t )
                i++;
            if ( i == nBoard )
                won = TRUE;
        }
    }
    if ( !won ) {                               // check diagonal bottom left
        int j = nBoard-1;                       //   to top right
        int i = 0;
        t = a->at(i+j*nBoard);
        if ( t != TicTacButton::Blank ) {
            i++; j--;
            while ( i<nBoard && a->at(i+j*nBoard) == t ) {
                i++; j--;
            }
            if ( i == nBoard )
                won = TRUE;
        }
    }
    if ( !won )                                 // no winner
        t = 0;
    return t;
}

// --------------------------------------------------------------------------
// TicTacGameBoard::computerMove()
//
// Puts a piece on the game board. Very, very simple.
//

void TicTacGameBoard::computerMove()
{
    int numButtons = nBoard*nBoard;
    int *altv = new int[numButtons];            // buttons alternatives
    int altc = 0;
    int stopHuman = -1;
    TicTacArray a = btArray->copy();
    int i;
    for ( i=0; i<numButtons; i++ ) {            // try all positions
        if ( a[i] != TicTacButton::Blank )      // already a piece there
            continue;
        a[i] = TicTacButton::Cross;             // test if computer wins
        if ( checkBoard(&a) == a[i] ) {         // computer will win
            st = ComputerWon;
            stopHuman = -1;
            break;
        }
        a[i] = TicTacButton::Circle;            // test if human wins
        if ( checkBoard(&a) == a[i] ) {         // oops...
            stopHuman = i;                      // remember position
            a[i] = TicTacButton::Blank;         // restore button
            continue;                           // computer still might win
        }
        a[i] = TicTacButton::Blank;             // restore button
        altv[altc++] = i;                       // remember alternative
    }
    if ( stopHuman >= 0 )                       // must stop human from winning
        a[stopHuman] = TicTacButton::Cross;
    else if ( i == numButtons ) {               // tried all alternatives
        if ( altc > 0 )                         // set random piece
            a[altv[rand()%(altc--)]] = TicTacButton::Cross;
        if ( altc == 0 ) {                      // no more blanks
            st = NobodyWon;
            emit finished();
        }
    }
    *btArray = a;                               // update model
    updateButtons();                            // update buttons
    delete altv;
}

// --------------------------------------------------------------------------
// Handle board resize events
// We resize the matrix of tic-tac buttons to fit into the new rectangle.
//

void TicTacGameBoard::resizeEvent( QResizeEvent * )
{
    float w = width()/nBoard;
    float h = height()/nBoard;
    QSize ps( (int)(0.9*w), (int)(0.9*h) );     // size of every piece
    int i = 0;
    for ( int x=0; x<nBoard; x++ ) {
        for ( int y=0; y<nBoard; y++ ) {
            TicTacButton *p = buttons->at(i++); // get piece #i
            QRect pr( QPoint(0,0), ps );        // piece rectangle
            pr.moveCenter( QPoint((int)(w*x+w/2), (int)(h*y+h/2)) );
            p->setGeometry( pr );               // set pos and size of piece
        }
    }
}

//***************************************************************************
//* TicTacToe member functions
//***************************************************************************

// --------------------------------------------------------------------------
// Creates a game widget with a game board and two push buttons, and connects
// signals of child widgets to slots.
//

TicTacToe::TicTacToe( int boardSize, QWidget *parent, const char *name )
    : QWidget( parent, name )
{
    initMetaObject();                           // initialize meta object
    setBackgroundColor( lightGray );            // set background color
    resize( 200, 300 );                         // resize this widget

// Create the game board and connect the signal finished() to this
// gameOver() slot

    board= new TicTacGameBoard(boardSize,this); // create and connect widgets
    board->setGeometry( 30, 50, 140, 140 );     // resize the game board
    connect( board, SIGNAL(finished()), SLOT(gameOver()) );

// Create the combo box for deciding who should start, and
// connect its clicked() signals to the buttonClicked() slot

    whoStarts = new QComboBox( this );
    whoStarts->insertItem( "Computer starts" );
    whoStarts->insertItem( "Human starts" );
    whoStarts->move( 0,0 );
    whoStarts->adjustSize();
    whoStarts->move( 15, 220 );

// Create the push buttons and connect their clicked() signals
// to this buttonClicked() slot

    newGame = new QPushButton( "Play!", this );
    newGame->setGeometry( 15, 260, 70, 25 );
    connect( newGame, SIGNAL(clicked()), SLOT(newGameClicked()) );
    quit = new QPushButton( "Quit", this );
    quit->setGeometry( 110, 260, 70, 25 );
    connect( quit, SIGNAL(clicked()), qApp, SLOT(quit()) );

// Create a message label

    message = new QLabel( this );
    message->setGeometry( 20, 10, 160, 20 );
    message->setFrameStyle( QFrame::WinPanel | QFrame::Sunken );
    message->setBackgroundColor( message->colorGroup().base() );
    message->setAlignment( AlignCenter );

// Create a horizontal frame line

    QFrame *line = new QFrame( this );
    line->setGeometry( 10, 200, 180, 10 );
    line->setFrameStyle( QFrame::HLine | QFrame::Sunken );

    newState();
}

// --------------------------------------------------------------------------
// TicTacToe::newGameClicked()                  - SLOT
//
// This slot is activated when the new game button is clicked.
//

void TicTacToe::newGameClicked()
{
    board->computerStarts( whoStarts->currentItem() == 0 );
    board->newGame();
    newState();
}

// --------------------------------------------------------------------------
// TicTacToe::gameOver()                        - SLOT
//
// This slot is activated when the TicTacGameBoard emits the signal
// "finished()", i.e. when a player has won or when it is a draw.
//

void TicTacToe::gameOver()
{
    newState();                                 // update text box
}

// --------------------------------------------------------------------------
// Updates the message to reflect a new state.
//

void TicTacToe::newState()
{
    static char *msg[] = {                      // TicTacGameBoard::State texts
        "Wanna play?", "Make your move",
        "You won!", "Computer won!", "It's a draw" };
    message->setText( msg[board->state()] );
    return;
}

Generated at 17:29, 1997/04/07 for Qt version 1.2 by the webmaster at Troll Tech