From bd0f3345a938b35ce6a12f6150373b0955b8dd12 Mon Sep 17 00:00:00 2001 From: Timothy Pearson Date: Sun, 10 Jul 2011 15:24:15 -0500 Subject: Add Qt3 development HEAD version --- doc/html/tutorial1-13.html | 396 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 396 insertions(+) create mode 100644 doc/html/tutorial1-13.html (limited to 'doc/html/tutorial1-13.html') diff --git a/doc/html/tutorial1-13.html b/doc/html/tutorial1-13.html new file mode 100644 index 0000000..d2d9da3 --- /dev/null +++ b/doc/html/tutorial1-13.html @@ -0,0 +1,396 @@ + + + + + +Qt Tutorial - Chapter 13: Game Over + + + + + + + +
+ +Home + | +All Classes + | +Main Classes + | +Annotated + | +Grouped Classes + | +Functions +

Qt Tutorial - Chapter 13: Game Over

+ + +

Screenshot of tutorial thirteen
+

In this example we start to approach a real playable game with a +score. We give MyWidget a new name (GameBoard) and add some slots. +

We put the definition in gamebrd.h and the implementation in gamebrd.cpp. +

The CannonField now has a game over state. +

The layout problems in LCDRange are fixed. +

+

Line-by-line Walkthrough +

+

t13/lcdrange.h +

+

+ +

    #include <qwidget.h>
+
+    class QSlider;
+    class QLabel;
+
+    class LCDRange : public QWidget
+
+

We inherit QWidget rather than QVBox. QVBox is very easy to use, but +again it showed its limitations so we switch to the more powerful and +slightly harder to use QVBoxLayout. (As you remember, QVBoxLayout is +not a widget, it manages one.) +

t13/lcdrange.cpp +

+

+ +

    #include <qlayout.h>
+
+

We need to include qlayout.h now to get the other layout management +API. +

    LCDRange::LCDRange( QWidget *parent, const char *name )
+            : QWidget( parent, name )
+
+

We inherit QWidget in the usual way. +

The other constructor has the same change. init() is unchanged, +except that we've added some lines at the end: +

        QVBoxLayout * l = new QVBoxLayout( this );
+
+

We create a QVBoxLayout with all the default values, managing this +widget's children. +

        l->addWidget( lcd, 1 );
+
+

At the top we add the QLCDNumber with a non-zero stretch. +

        l->addWidget( slider );
+        l->addWidget( label );
+
+

Then we add the other two, both with the default zero stretch. +

This stretch control is something QVBoxLayout (and QHBoxLayout, and +QGridLayout) offers but classes like QVBox do not. In this case +we're saying that the QLCDNumber should stretch and the others should +not. +

t13/cannon.h +

+

The CannonField now has a game over state and a few new functions. +

+ +

        bool  gameOver() const { return gameEnded; }
+
+

This function returns TRUE if the game is over or FALSE if a game +is going on. +

        void  setGameOver();
+        void  restartGame();
+
+

Here are two new slots: setGameOver() and restartGame(). +

        void  canShoot( bool );
+
+

This new signal indicates that the CannonField is in a state where the +shoot() slot makes sense. We'll use it below to enable/disable the +Shoot button. +

        bool gameEnded;
+
+

This private variable contains the game state. TRUE means that the +game is over, and FALSE means that a game is going on. +

t13/cannon.cpp +

+

+ +

        gameEnded = FALSE;
+
+

This line has been added to the constructor. Initially, the game is not +over (luckily for the player :-). +

    void CannonField::shoot()
+    {
+        if ( isShooting() )
+            return;
+        timerCount = 0;
+        shoot_ang = ang;
+        shoot_f = f;
+        autoShootTimer->start( 50 );
+        emit canShoot( FALSE );
+    }
+
+

We added a new isShooting() function, so shoot() uses it instead of +testing directly. Also, shoot tells the world that the CannonField +cannot shoot now. +

    void CannonField::setGameOver()
+    {
+        if ( gameEnded )
+            return;
+        if ( isShooting() )
+            autoShootTimer->stop();
+        gameEnded = TRUE;
+        repaint();
+    }
+
+

This slot ends the game. It must be called from outside CannonField, +because this widget does not know when to end the game. This is an +important design principle in component programming. We choose to +make the component as flexible as possible to make it usable with +different rules (for example, a multi-player version of this in which the +first player to hit ten times wins could use the CannonField unchanged). +

If the game has already been ended we return immediately. If a game is +going on we stop the shot, set the game over flag, and repaint the entire +widget. +

    void CannonField::restartGame()
+    {
+        if ( isShooting() )
+            autoShootTimer->stop();
+        gameEnded = FALSE;
+        repaint();
+        emit canShoot( TRUE );
+    }
+
+

This slot starts a new game. If a shot is in the air, we stop shooting. +We then reset the gameEnded variable and repaint the widget. +

moveShot() too emits the new canShoot(TRUE) signal at the same time as +either hit() or miss(). +

Modifications in CannonField::paintEvent(): +

    void CannonField::paintEvent( QPaintEvent *e )
+    {
+        QRect updateR = e->rect();
+        QPainter p( this );
+
+        if ( gameEnded ) {
+            p.setPen( black );
+            p.setFont( QFont( "Courier", 48, QFont::Bold ) );
+            p.drawText( rect(), AlignCenter, "Game Over" );
+        }
+
+

The paint event has been enhanced to display the text "Game Over" if +the game is over, i.e., gameEnded is TRUE. We don't bother to +check the update rectangle here because speed is not critical when +the game is over. +

To draw the text we first set a black pen; the pen color is used +when drawing text. Next we choose a 48 point bold font from the +Courier family. Finally we draw the text centered in the widget's +rectangle. Unfortunately, on some systems (especially X servers with +Unicode fonts) it can take a while to load such a large font. Because +Qt caches fonts, you will notice this only the first time the font is +used. +

        if ( updateR.intersects( cannonRect() ) )
+            paintCannon( &p );
+        if ( isShooting() && updateR.intersects( shotRect() ) )
+            paintShot( &p );
+        if ( !gameEnded && updateR.intersects( targetRect() ) )
+            paintTarget( &p );
+    }
+
+

We draw the shot only when shooting and the target only when playing +(that is, when the game is not ended). +

t13/gamebrd.h +

+

This file is new. It contains the definition of the GameBoard class, +which was last seen as MyWidget. +

+ +

    class QPushButton;
+    class LCDRange;
+    class QLCDNumber;
+    class CannonField;
+
+    #include "lcdrange.h"
+    #include "cannon.h"
+
+    class GameBoard : public QWidget
+    {
+        Q_OBJECT
+    public:
+        GameBoard( QWidget *parent=0, const char *name=0 );
+
+    protected slots:
+        void  fire();
+        void  hit();
+        void  missed();
+        void  newGame();
+
+    private:
+        QLCDNumber  *hits;
+        QLCDNumber  *shotsLeft;
+        CannonField *cannonField;
+    };
+
+

We have now added four slots. These are protected and are used internally. +We have also added two QLCDNumbers (hits and shotsLeft) which display +the game status. +

t13/gamebrd.cpp +

+

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( this, "cannonField" );
+
+

cannonField is now a member variable, so we carefully change the +constructor to use it. (The good programmers at Trolltech never +forget this, but I do. Caveat programmor - if "programmor" is Latin, +at least. Anyway, back to the code.) +

        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()), 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( "&New Game", this, "newgame" );
+        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, this, "hits" );
+        shotsLeft = new QLCDNumber( 2, this, "shotsleft" );
+        QLabel *hitsL = new QLabel( "HITS", this, "hitsLabel" );
+        QLabel *shotsLeftL
+            = new QLabel( "SHOTS LEFT", this, "shotsleftLabel" );
+
+

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 *topBox = new QHBoxLayout;
+        grid->addLayout( topBox, 0, 1 );
+        topBox->addWidget( shoot );
+        topBox->addWidget( hits );
+        topBox->addWidget( hitsL );
+        topBox->addWidget( shotsLeft );
+        topBox->addWidget( shotsLeftL );
+        topBox->addStretch( 1 );
+        topBox->addWidget( restart );
+
+

The number of widgets in the top-right cell is getting large. Once it +was empty; now it's full enough that we group together the layout +setting for better overview. +

Notice that we let all the widgets have their preferred sizes, instead +putting the stretch just to the left of the New Game button. +

        newGame();
+    }
+
+

We're all done constructing the GameBoard, so we start it all using +newGame(). (NewGame() is a slot, but as we said, slots can be used as +ordinary functions, too.) +

    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 Restart 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. +

t13/main.cpp +

+

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. +

Behavior +

+

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. +

(See Compiling for how to create a +makefile and build the application.) +

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. +

You're now ready for Chapter 14. +

[Previous tutorial] +[Next tutorial] +[Main tutorial page] +

+ +


+ +
Copyright © 2007 +TrolltechTrademarks +
Qt 3.3.8
+
+ -- cgit v1.2.3