1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<!-- /home/espenr/tmp/qt-3.3.8-espenr-2499/qt-x11-free-3.3.8/doc/tutorial.doc:2032 -->
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Qt Tutorial - Chapter 13: Game Over</title>
<style type="text/css"><!--
fn { margin-left: 1cm; text-indent: -1cm; }
a:link { color: #004faf; text-decoration: none }
a:visited { color: #672967; text-decoration: none }
body { background: #ffffff; color: black; }
--></style>
</head>
<body>
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr bgcolor="#E5E5E5">
<td valign=center>
<a href="index.html">
<font color="#004faf">Home</font></a>
| <a href="classes.html">
<font color="#004faf">All Classes</font></a>
| <a href="mainclasses.html">
<font color="#004faf">Main Classes</font></a>
| <a href="annotated.html">
<font color="#004faf">Annotated</font></a>
| <a href="groups.html">
<font color="#004faf">Grouped Classes</font></a>
| <a href="functions.html">
<font color="#004faf">Functions</font></a>
</td>
<td align="right" valign="center"><img src="logo32.png" align="right" width="64" height="32" border="0"></td></tr></table><h1 align=center>Qt Tutorial - Chapter 13: Game Over</h1>
<p> <center><img src="t13.png" alt="Screenshot of tutorial thirteen"></center>
<p> 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.
<p> We put the definition in gamebrd.h and the implementation in gamebrd.cpp.
<p> The CannonField now has a game over state.
<p> The layout problems in LCDRange are fixed.
<p> <ul>
<li> <a href="t13-lcdrange-h.html">t13/lcdrange.h</a> contains the LCDRange
class definition.
<li> <a href="t13-lcdrange-cpp.html">t13/lcdrange.cpp</a> contains the LCDRange
implementation.
<li> <a href="t13-cannon-h.html">t13/cannon.h</a> contains the CannonField class
definition
<li> <a href="t13-cannon-cpp.html">t13/cannon.cpp</a> contains the CannonField
implementation.
<li> <a href="t13-gamebrd-h.html">t13/gamebrd.h</a> contains the GameBoard
class definition.
<li> <a href="t13-gamebrd-cpp.html">t13/gamebrd.cpp</a> contains the GameBoard
implementation.
<li> <a href="t13-main-cpp.html">t13/main.cpp</a> contains MyWidget and main.
</ul>
<p> <h2> Line-by-line Walkthrough
</h2>
<a name="1"></a><p> <h3> <a href="t13-lcdrange-h.html">t13/lcdrange.h</a>
</h3>
<a name="1-1"></a><p>
<p> <pre> #include <<a href="qwidget-h.html">qwidget.h</a>>
class QSlider;
class QLabel;
class LCDRange : public <a href="qwidget.html">QWidget</a>
</pre>
<p> We inherit <a href="qwidget.html">QWidget</a> rather than <a href="qvbox.html">QVBox</a>. QVBox is very easy to use, but
again it showed its limitations so we switch to the more powerful and
slightly harder to use <a href="qvboxlayout.html">QVBoxLayout</a>. (As you remember, QVBoxLayout is
not a widget, it manages one.)
<p> <h3> <a href="t13-lcdrange-cpp.html">t13/lcdrange.cpp</a>
</h3>
<a name="1-2"></a><p>
<p> <pre> #include <<a href="qlayout-h.html">qlayout.h</a>>
</pre>
<p> We need to include qlayout.h now to get the other layout management
API.
<p> <pre> LCDRange::LCDRange( <a href="qwidget.html">QWidget</a> *parent, const char *name )
: <a href="qwidget.html">QWidget</a>( parent, name )
</pre>
<p> We inherit QWidget in the usual way.
<p> The other constructor has the same change. init() is unchanged,
except that we've added some lines at the end:
<p> <pre> <a href="qvboxlayout.html">QVBoxLayout</a> * l = new <a href="qvboxlayout.html">QVBoxLayout</a>( this );
</pre>
<p> We create a QVBoxLayout with all the default values, managing this
widget's children.
<p> <pre> <a name="x2401"></a> l-><a href="qboxlayout.html#addWidget">addWidget</a>( lcd, 1 );
</pre>
<p> At the top we add the <a href="qlcdnumber.html">QLCDNumber</a> with a non-zero stretch.
<p> <pre> l-><a href="qboxlayout.html#addWidget">addWidget</a>( slider );
l-><a href="qboxlayout.html#addWidget">addWidget</a>( label );
</pre>
<p> Then we add the other two, both with the default zero stretch.
<p> This stretch control is something <a href="qvboxlayout.html">QVBoxLayout</a> (and <a href="qhboxlayout.html">QHBoxLayout</a>, and
<a href="qgridlayout.html">QGridLayout</a>) offers but classes like <a href="qvbox.html">QVBox</a> do not. In this case
we're saying that the QLCDNumber should stretch and the others should
not.
<p> <h3> <a href="t13-cannon-h.html">t13/cannon.h</a>
</h3>
<a name="1-3"></a><p> The CannonField now has a game over state and a few new functions.
<p>
<p> <pre> bool gameOver() const { return gameEnded; }
</pre>
<p> This function returns TRUE if the game is over or FALSE if a game
is going on.
<p> <pre> void setGameOver();
void restartGame();
</pre>
<p> Here are two new slots: setGameOver() and restartGame().
<p> <pre> void canShoot( bool );
</pre>
<p> 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.
<p> <pre> bool gameEnded;
</pre>
<p> This private variable contains the game state. TRUE means that the
game is over, and FALSE means that a game is going on.
<p> <h3> <a href="t13-cannon-cpp.html">t13/cannon.cpp</a>
</h3>
<a name="1-4"></a><p>
<p> <pre> gameEnded = FALSE;
</pre>
<p> This line has been added to the constructor. Initially, the game is not
over (luckily for the player :-).
<p> <pre> void CannonField::shoot()
{
if ( isShooting() )
return;
timerCount = 0;
shoot_ang = ang;
shoot_f = f;
<a name="x2407"></a> autoShootTimer-><a href="qtimer.html#start">start</a>( 50 );
emit canShoot( FALSE );
}
</pre>
<p> 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.
<p> <pre> void CannonField::setGameOver()
{
if ( gameEnded )
return;
if ( isShooting() )
autoShootTimer-><a href="qtimer.html#stop">stop</a>();
gameEnded = TRUE;
<a href="qwidget.html#repaint">repaint</a>();
}
</pre>
<p> 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).
<p> 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.
<p> <pre> void CannonField::restartGame()
{
if ( isShooting() )
<a name="x2408"></a> autoShootTimer-><a href="qtimer.html#stop">stop</a>();
gameEnded = FALSE;
<a href="qwidget.html#repaint">repaint</a>();
emit canShoot( TRUE );
}
</pre>
<p> This slot starts a new game. If a shot is in the air, we stop shooting.
We then reset the <tt>gameEnded</tt> variable and repaint the widget.
<p> moveShot() too emits the new canShoot(TRUE) signal at the same time as
either hit() or miss().
<p> Modifications in CannonField::paintEvent():
<p> <pre> void CannonField::<a href="qwidget.html#paintEvent">paintEvent</a>( <a href="qpaintevent.html">QPaintEvent</a> *e )
{
<a name="x2405"></a> <a href="qrect.html">QRect</a> updateR = e-><a href="qpaintevent.html#rect">rect</a>();
<a href="qpainter.html">QPainter</a> p( this );
if ( gameEnded ) {
p.<a href="qpainter.html#setPen">setPen</a>( black );
<a name="x2403"></a> p.<a href="qpainter.html#setFont">setFont</a>( QFont( "Courier", 48, QFont::Bold ) );
p.<a href="qpainter.html#drawText">drawText</a>( <a href="qwidget.html#rect">rect</a>(), AlignCenter, "Game Over" );
}
</pre>
<p> The paint event has been enhanced to display the text "Game Over" if
the game is over, i.e., <tt>gameEnded</tt> is TRUE. We don't bother to
check the update rectangle here because speed is not critical when
the game is over.
<p> 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.
<p> <pre> <a name="x2406"></a> if ( updateR.<a href="qrect.html#intersects">intersects</a>( cannonRect() ) )
paintCannon( &p );
if ( isShooting() && updateR.<a href="qrect.html#intersects">intersects</a>( shotRect() ) )
paintShot( &p );
if ( !gameEnded && updateR.<a href="qrect.html#intersects">intersects</a>( targetRect() ) )
paintTarget( &p );
}
</pre>
<p> We draw the shot only when shooting and the target only when playing
(that is, when the game is not ended).
<p> <h3> <a href="t13-gamebrd-h.html">t13/gamebrd.h</a>
</h3>
<a name="1-5"></a><p> This file is new. It contains the definition of the GameBoard class,
which was last seen as MyWidget.
<p>
<p> <pre> class QPushButton;
class LCDRange;
class QLCDNumber;
class CannonField;
#include "lcdrange.h"
#include "cannon.h"
class GameBoard : public <a href="qwidget.html">QWidget</a>
{
<a href="metaobjects.html#Q_OBJECT">Q_OBJECT</a>
public:
GameBoard( <a href="qwidget.html">QWidget</a> *parent=0, const char *name=0 );
protected slots:
void fire();
void hit();
void missed();
void newGame();
private:
<a href="qlcdnumber.html">QLCDNumber</a> *hits;
<a href="qlcdnumber.html">QLCDNumber</a> *shotsLeft;
CannonField *cannonField;
};
</pre>
<p> We have now added four slots. These are protected and are used internally.
We have also added two QLCDNumbers (<tt>hits</tt> and <tt>shotsLeft</tt>) which display
the game status.
<p> <h3> <a href="t13-gamebrd-cpp.html">t13/gamebrd.cpp</a>
</h3>
<a name="1-6"></a><p> This file is new. It contains the implementation of the GameBoard
class, which was last seen as MyWidget.
<p>
<p> We have made some changes in the GameBoard constructor.
<p> <pre> cannonField = new CannonField( this, "cannonField" );
</pre>
<p> <tt>cannonField</tt> is now a member variable, so we carefully change the
constructor to use it. (The <em>good</em> programmers at Trolltech never
forget this, but I do. Caveat programmor - if "programmor" is Latin,
at least. Anyway, back to the code.)
<p> <pre> <a href="qobject.html#connect">connect</a>( cannonField, SIGNAL(hit()),
this, SLOT(hit()) );
<a href="qobject.html#connect">connect</a>( cannonField, SIGNAL(missed()),
this, SLOT(missed()) );
</pre>
<p> 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.
<p> <pre> <a href="qobject.html#connect">connect</a>( shoot, SIGNAL(<a href="qbutton.html#clicked">clicked</a>()), SLOT(fire()) );
</pre>
<p> 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.
<p> Notice how easy it is to change the behavior of a program when you are
working with self-contained components.
<p> <pre> <a href="qobject.html#connect">connect</a>( cannonField, SIGNAL(canShoot(bool)),
<a name="x2416"></a> shoot, SLOT(<a href="qwidget.html#setEnabled">setEnabled</a>(bool)) );
</pre>
<p> We also use the cannonField's canShoot() signal to enable or disable
the Shoot button appropriately.
<p> <pre> QPushButton *restart
= new <a href="qpushbutton.html">QPushButton</a>( "&New Game", this, "newgame" );
restart->setFont( QFont( "Times", 18, QFont::Bold ) );
<a href="qobject.html#connect">connect</a>( restart, SIGNAL(clicked()), this, SLOT(newGame()) );
</pre>
<p> 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.
<p> <pre> hits = new <a href="qlcdnumber.html">QLCDNumber</a>( 2, this, "hits" );
shotsLeft = new <a href="qlcdnumber.html">QLCDNumber</a>( 2, this, "shotsleft" );
<a href="qlabel.html">QLabel</a> *hitsL = new <a href="qlabel.html">QLabel</a>( "HITS", this, "hitsLabel" );
QLabel *shotsLeftL
= new <a href="qlabel.html">QLabel</a>( "SHOTS LEFT", this, "shotsleftLabel" );
</pre>
<p> We create four new widgets. Note that we don't bother to keep the
pointers to the <a href="qlabel.html">QLabel</a> 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.
<p> <pre> <a href="qhboxlayout.html">QHBoxLayout</a> *topBox = new <a href="qhboxlayout.html">QHBoxLayout</a>;
<a name="x2413"></a> grid-><a href="qgridlayout.html#addLayout">addLayout</a>( topBox, 0, 1 );
topBox-><a href="qboxlayout.html#addWidget">addWidget</a>( shoot );
topBox-><a href="qboxlayout.html#addWidget">addWidget</a>( hits );
topBox-><a href="qboxlayout.html#addWidget">addWidget</a>( hitsL );
topBox-><a href="qboxlayout.html#addWidget">addWidget</a>( shotsLeft );
topBox-><a href="qboxlayout.html#addWidget">addWidget</a>( shotsLeftL );
<a name="x2410"></a> topBox-><a href="qboxlayout.html#addStretch">addStretch</a>( 1 );
<a name="x2411"></a> topBox-><a href="qboxlayout.html#addWidget">addWidget</a>( restart );
</pre>
<p> 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.
<p> Notice that we let all the widgets have their preferred sizes, instead
putting the stretch just to the left of the New Game button.
<p> <pre> newGame();
}
</pre>
<p> 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.)
<p> <pre> void GameBoard::fire()
{
if ( cannonField->gameOver() || cannonField->isShooting() )
return;
shotsLeft-><a href="qlcdnumber.html#display">display</a>( shotsLeft-><a href="qlcdnumber.html#intValue">intValue</a>() - 1 );
cannonField->shoot();
}
</pre>
<p> 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.
<p> <pre> void GameBoard::hit()
{
hits-><a href="qlcdnumber.html#display">display</a>( hits-><a href="qlcdnumber.html#intValue">intValue</a>() + 1 );
if ( shotsLeft-><a href="qlcdnumber.html#intValue">intValue</a>() == 0 )
cannonField->setGameOver();
else
cannonField->newTarget();
}
</pre>
<p> 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.
<p> <pre> void GameBoard::missed()
{
<a name="x2415"></a> if ( shotsLeft-><a href="qlcdnumber.html#intValue">intValue</a>() == 0 )
cannonField->setGameOver();
}
</pre>
<p> This slot is activated when a shot has missed the target. If there are no
shots left, the game is over.
<p> <pre> void GameBoard::newGame()
{
<a name="x2414"></a> shotsLeft-><a href="qlcdnumber.html#display">display</a>( 15 );
hits-><a href="qlcdnumber.html#display">display</a>( 0 );
cannonField->restartGame();
cannonField->newTarget();
}
</pre>
<p> 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.
<p> <h3> <a href="t13-main-cpp.html">t13/main.cpp</a>
</h3>
<a name="1-7"></a><p> 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.
<p> <h2> Behavior
</h2>
<a name="2"></a><p> The cannon can shoot at a target; a new target is automatically created
when one has been hit.
<p> 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.
<p> (See <a href="tutorial1-07.html#compiling">Compiling</a> for how to create a
makefile and build the application.)
<p> <h2> Exercises
</h2>
<a name="3"></a><p> Add a random wind factor and show it to the user.
<p> Make some splatter effects when the shot hits the target.
<p> Implement multiple targets.
<p> You're now ready for <a href="tutorial1-14.html">Chapter 14.</a>
<p> [<a href="tutorial1-12.html">Previous tutorial</a>]
[<a href="tutorial1-14.html">Next tutorial</a>]
[<a href="tutorial.html">Main tutorial page</a>]
<p>
<!-- eof -->
<p><address><hr><div align=center>
<table width=100% cellspacing=0 border=0><tr>
<td>Copyright © 2007
<a href="troll.html">Trolltech</a><td align=center><a href="trademarks.html">Trademarks</a>
<td align=right><div align=right>Qt 3.3.8</div>
</table></div></address></body>
</html>
|