This file is new. It contains the definition of the GameBoard class, which was last seen as MyWidget.
We have now added four slots. These are protected and are used internally. We have also added two QLCDNumbers (hits and shotsLeft) that display the game status.
This file is new. It contains the implementation of the GameBoard class, which was last seen as MyWidget.
We have made some changes in the GameBoard constructor.
cannonField = new CannonField;
cannonField is now a member variable, so we carefully change the constructor to use it.
connect(cannonField, SIGNAL(hit()),
this, SLOT(hit()));
connect(cannonField, SIGNAL(missed()),
this, SLOT(missed()));
This time we want to do something when the shot has hit or missed the target. Thus we connect the hit() and missed() signals of the CannonField to two protected slots with the same names in this class.
connect(shoot, SIGNAL(clicked()),
this, SLOT(fire()));
Previously we connected the Shoot button's clicked() signal directly to the CannonField's shoot() slot. This time we want to keep track of the number of shots fired, so we connect it to a protected slot in this class instead.
Notice how easy it is to change the behavior of a program when you are working with self-contained components.
connect(cannonField, SIGNAL(canShoot(bool)),
shoot, SLOT(setEnabled(bool)));
We also use the cannonField's canShoot() signal to enable or disable the Shoot button appropriately.
QPushButton *restart = new QPushButton(tr("&New Game"));
restart->setFont(QFont("Times", 18, QFont::Bold));
connect(restart, SIGNAL(clicked()), this, SLOT(newGame()));
We create, set up, and connect the New Game button as we have done with the other buttons. Clicking this button will activate the newGame() slot in this widget.
hits = new QLCDNumber(2);
hits->setSegmentStyle(QLCDNumber::Filled);
shotsLeft = new QLCDNumber(2);
shotsLeft->setSegmentStyle(QLCDNumber::Filled);
QLabel *hitsLabel = new QLabel(tr("HITS"));
QLabel *shotsLeftLabel = new QLabel(tr("SHOTS LEFT"));
We create four new widgets. Note that we don't bother to keep the pointers to the QLabel widgets in the GameBoard class because there's nothing much we want to do with them. Qt will delete them when the GameBoard widget is destroyed, and the layout classes will resize them appropriately.
QHBoxLayout *topLayout = new QHBoxLayout;
topLayout->addWidget(shoot);
topLayout->addWidget(hits);
topLayout->addWidget(hitsLabel);
topLayout->addWidget(shotsLeft);
topLayout->addWidget(shotsLeftLabel);
topLayout->addStretch(1);
topLayout->addWidget(restart);
The top-right cell of the QGridLayout is starting to get crowded. We put a stretch just to the left of the New Game button to ensure that this button will always appear on the right side of the window.
newGame();
We're all done constructing the GameBoard, so we start it all using newGame(). Although newGame() is a slot, it can also be used as an ordinary function.
void GameBoard::fire()
{
if (cannonField->gameOver() || cannonField->isShooting())
return;
shotsLeft->display(shotsLeft->intValue() - 1);
cannonField->shoot();
}
This function fires a shot. If the game is over or if there is a shot in the air, we return immediately. We decrement the number of shots left and tell the cannon to shoot.
void GameBoard::hit()
{
hits->display(hits->intValue() + 1);
if (shotsLeft->intValue() == 0)
cannonField->setGameOver();
else
cannonField->newTarget();
}
This slot is activated when a shot has hit the target. We increment the number of hits. If there are no shots left, the game is over. Otherwise, we make the CannonField generate a new target.
void GameBoard::missed()
{
if (shotsLeft->intValue() == 0)
cannonField->setGameOver();
}
This slot is activated when a shot has missed the target. If there are no shots left, the game is over.
void GameBoard::newGame()
{
shotsLeft->display(15);
hits->display(0);
cannonField->restartGame();
cannonField->newTarget();
}
This slot is activated when the user clicks the New Game button. It is also called from the constructor. First it sets the number of shots to 15. Note that this is the only place in the program where we set the number of shots. Change it to whatever you like to change the game rules. Next we reset the number of hits, restart the game, and generate a new target.
This file has just been on a diet. MyWidget is gone, and the only thing left is the main() function, unchanged except for the name change.
Running the Application
The cannon can shoot at a target; a new target is automatically created when one has been hit.
Hits and shots left are displayed and the program keeps track of them. The game can end, and there's a button to start a new game.
Exercises
Add a random wind factor and show it to the user.
Make some splatter effects when the shot hits the target.
Implement multiple targets.
[Previous: Chapter 12]
[Qt Tutorial]
[Next: Chapter 14]