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