-
Notifications
You must be signed in to change notification settings - Fork 11
/
chapter20.html
930 lines (702 loc) · 95 KB
/
chapter20.html
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
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
<head>
<script type="text/javascript">
var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
</script>
<script type="text/javascript">
try {
var pageTracker = _gat._getTracker("UA-5459430-3");
pageTracker._trackPageview();
} catch(err) {}</script>
<meta http-equiv="Content-Type" content="text/html;charset=us-ascii" />
<title>IYOCGwP, Chapter 20 - Dodger</title>
<link rel="stylesheet" href="inventbook.css" type="text/css" media="all" />
</head>
<body class='chapter20body'>
<table border='0' width='100%'><tr><td><a href='chapter19.html'>Go to Chapter 19 - Sound and Images</a></td><td align='right'></td></tr></table>
<div style='height: 310px;'><a href='http://www.amazon.com/Invent-Your-Computer-Games-Python/dp/0982106017/'><img src='images/buyad.png' align='right'></a></div>
<div style='height: 350px;'><img src='images/chap20.png'></div>
<div class='inthischapter'><h3 id="TopicsCoveredInThisChapter">Topics Covered In This Chapter:</h3>
<ul>
<li>The <span class='m'>pygame.FULLSCREEN</span> flag</li>
<li>Pygame Constant Variables for Keyboard Keys</li>
<li>The <span class='m'>move_ip()</span> Method for <span class='m'>Rect</span> objects</li>
<li>The <span class='m'>pygame.mouse.set_pos()</span> Function</li>
<li>Implementing Cheat Codes in Your Games</li>
<li>Modifying the Dodger Game</li>
</ul></div>
<p>The last three chapters have gone over the Pygame software library and demonstrated how to use its many features. (You don't need to read those chapters before reading this chapter, though it may make this chapter easier to understand.) In this chapter, we will use that knowledge to create a graphical game with sound that receives input from the keyboard and mouse.</p>
<p>The Dodger game has the player control a small man (which we call the player's character) who must dodge a whole bunch of baddies that fall from the top of the screen. The longer the player can keep dodging the baddies, the higher the score they will get.</p>
<p>Just for fun, we will also add some cheat modes to the game. If the player holds down the "x" key, every baddie's speed will be reduced to a super slow rate. If the player holds down the "z" key, the baddies will reverse their direction and travel up the screen instead of downwards.</p>
<h2 class='pagebreaker' id="ReviewoftheBasicPygameDataTypes">Review of the Basic Pygame Data Types</h2>
<p>Let's review some of the basic data types used in the Pygame library:</p>
<ul>
<li><span class='m'>pygame.Rect</span> - <span class='m'>Rect</span> objects represent a rectangular space's location and size. The location can be determined by the <span class='m'>Rect</span> object's <span class='m'>topleft</span> attribute (or the <span class='m'>topright</span>, <span class='m'>bottomleft</span>, and <span class='m'>bottomright</span> attributes). These corner attributes are a tuple of integers for the X- and Y-coordinates. The size can be determined by the width and height attributes, which are integers of how many pixels long or high the rectangle area is. <span class='m'>Rect</span> objects have a <span class='m'>colliderect()</span> method to check if they are intersecting with another <span class='m'>Rect</span> object.</li>
<li><span class='m'>pygame.Surface</span> - <span class='m'>Surface</span> objects are areas of colored pixels. <span class='m'>Surface</span> objects represent a rectangular image, while <span class='m'>Rect</span> objects only represent a rectangular space and location. <span class='m'>Surface</span> objects have a <span class='m'>blit()</span> method that is used to draw the image on one <span class='m'>Surface</span> object onto another <span class='m'>Surface</span> object. The <span class='m'>Surface</span> object returned by the <span class='m'>pygame.display.set_mode()</span> function is special because anything drawn on that Surface object will be displayed on the user's screen.</li>
<li>Remember that <span class='m'>Surface</span> have things drawn on them, but we cannot see this because it only exists in the computer's memory. We can only see a <span class='m'>Surface</span> object when it is "blitted" (that is, drawn) on the screen. This is just the same as it is with any other piece of data. If you think about it, you cannot see the string that is stored in a variable until the variable is printed to the screen.</li>
<li><span class='m'>pygame.event.Event</span> - The <span class='m'>Event</span> data type in the <span class='m'>pygame.event</span> module generates <span class='m'>Event</span> objects whenever the user provides keyboard, mouse, or another kind of input. The <span class='m'>pygame.event.get()</span> function returns a list of <span class='m'>Event</span> objects. You can check what type of event the <span class='m'>Event</span> object is by checking its type attribute. <span class='m'>QUIT</span>, <span class='m'>KEYDOWN</span>, and <span class='m'>MOUSEBUTTONUP</span> are examples of some event types.</li>
<li><span class='m'>pygame.font.Font</span> - The <span class='m'>pygame.font module</span> has the <span class='m'>Font</span> data type which represent the typeface used for text in Pygame. You can create a <span class='m'>Font</span> object by calling the <span class='m'>pygame.font.SysFont()</span> constructor function. The arguments to pass are a string of the font name and an integer of the font size, however it is common to pass <span class='m'>None</span> for the font name to get the default system font. For example, the common function call to create a <span class='m'>Font</span> object is <span class='m'>pygame.font.SysFont(None, 48)</span>.</li>
<li><span class='m'>pygame.time.Clock</span> - The <span class='m'>Clock</span> object in the <span class='m'>pygame.time</span> module are very helpful for keeping our games from running as fast as possible. (This is often too fast for the player to keep up with the computer, and makes the games not fun.) The <span class='m'>Clock</span> object has a <span class='m'>tick()</span> method, which we pass how many frames per second (fps) we want the game to run at. The higher the fps, the faster the game runs. Normally we use 40 fps. Notice that the <span class='m'>pygame.time</span> module is a different module than the time module which contains the <span class='m'>sleep()</span> function.</li>
</ul>
<p>Type in the following code and save it to a file named <i>dodger.py</i>. This game also requires some other image and sound files which you can download from the URL <a href='http://inventwithpython.com/resources'>http://inventwithpython.com/resources</a>.</p>
<h2 id="DodgersSourceCode">Dodger's Source Code</h2>
<p>You can download this code from the URL <a href='http://inventwithpython.com/chapter20'>http://inventwithpython.com/chapter20</a>.</p>
<div class='sourcecode'><span class='sourcecodeHeader'>dodger.py</span><br /><span class='sourcecodeSubHeader'>This code can be downloaded from <a href='http://inventwithpython.com/dodger.py'>http://inventwithpython.com/dodger.py</a><br />If you get errors after typing this code in, compare it to the book's code with the online diff tool at <a href='http://inventwithpython.com/diff'>http://inventwithpython.com/diff</a> or email the author at <a href="mailto:[email protected]">[email protected]</a></span><br /><ol start='1'>
<li>import pygame, random, sys</li>
<li>from pygame.locals import *</li>
<li></li>
<li>WINDOWWIDTH = 600</li>
<li>WINDOWHEIGHT = 600</li>
<li>TEXTCOLOR = (255, 255, 255)</li>
<li>BACKGROUNDCOLOR = (0, 0, 0)</li>
<li>FPS = 40</li>
<li>BADDIEMINSIZE = 10</li>
<li>BADDIEMAXSIZE = 40</li>
<li>BADDIEMINSPEED = 1</li>
<li>BADDIEMAXSPEED = 8</li>
<li>ADDNEWBADDIERATE = 6</li>
<li>PLAYERMOVERATE = 5</li>
<li></li>
<li>def terminate():</li>
<li> pygame.quit()</li>
<li> sys.exit()</li>
<li></li>
<li>def waitForPlayerToPressKey():</li>
<li> while True:</li>
<li> for event in pygame.event.get():</li>
<li> if event.type == QUIT:</li>
<li> terminate()</li>
<li> if event.type == KEYDOWN:</li>
<li> if event.key == K_ESCAPE: # pressing escape quits</li>
<li> terminate()</li>
<li> return</li>
<li></li>
<li>def playerHasHitBaddie(playerRect, baddies):</li>
<li> for b in baddies:</li>
<li> if playerRect.colliderect(b['rect']):</li>
<li> return True</li>
<li> return False</li>
<li></li>
<li>def drawText(text, font, surface, x, y):</li>
<li> textobj = font.render(text, 1, TEXTCOLOR)</li>
<li> textrect = textobj.get_rect()</li>
<li> textrect.topleft = (x, y)</li>
<li> surface.blit(textobj, textrect)</li>
<li></li>
<li># set up pygame, the window, and the mouse cursor</li>
<li>pygame.init()</li>
<li>mainClock = pygame.time.Clock()</li>
<li>windowSurface = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))</li>
<li>pygame.display.set_caption('Dodger')</li>
<li>pygame.mouse.set_visible(False)</li>
<li></li>
<li># set up fonts</li>
<li>font = pygame.font.SysFont(None, 48)</li>
<li></li>
<li># set up sounds</li>
<li>gameOverSound = pygame.mixer.Sound('gameover.wav')</li>
<li>pygame.mixer.music.load('background.mid')</li>
<li></li>
<li># set up images</li>
<li>playerImage = pygame.image.load('player.png')</li>
<li>playerRect = playerImage.get_rect()</li>
<li>baddieImage = pygame.image.load('baddie.png')</li>
<li></li>
<li># show the "Start" screen</li>
<li>drawText('Dodger', font, windowSurface, (WINDOWWIDTH / 3), (WINDOWHEIGHT / 3))</li>
<li>drawText('Press a key to start.', font, windowSurface, (WINDOWWIDTH / 3) - 30, (WINDOWHEIGHT / 3) + 50)</li>
<li>pygame.display.update()</li>
<li>waitForPlayerToPressKey()</li>
<li></li>
<li></li>
<li>topScore = 0</li>
<li>while True:</li>
<li> # set up the start of the game</li>
<li> baddies = []</li>
<li> score = 0</li>
<li> playerRect.topleft = (WINDOWWIDTH / 2, WINDOWHEIGHT - 50)</li>
<li> moveLeft = moveRight = moveUp = moveDown = False</li>
<li> reverseCheat = slowCheat = False</li>
<li> baddieAddCounter = 0</li>
<li> pygame.mixer.music.play(-1, 0.0)</li>
<li></li>
<li> while True: # the game loop runs while the game part is playing</li>
<li> score += 1 # increase score</li>
<li></li>
<li> for event in pygame.event.get():</li>
<li> if event.type == QUIT:</li>
<li> terminate()</li>
<li></li>
<li> if event.type == KEYDOWN:</li>
<li> if event.key == ord('z'):</li>
<li> reverseCheat = True</li>
<li> if event.key == ord('x'):</li>
<li> slowCheat = True</li>
<li> if event.key == K_LEFT or event.key == ord('a'):</li>
<li> moveRight = False</li>
<li> moveLeft = True</li>
<li> if event.key == K_RIGHT or event.key == ord('d'):</li>
<li> moveLeft = False</li>
<li> moveRight = True</li>
<li> if event.key == K_UP or event.key == ord('w'):</li>
<li> moveDown = False</li>
<li> moveUp = True</li>
<li> if event.key == K_DOWN or event.key == ord('s'):</li>
<li> moveUp = False</li>
<li> moveDown = True</li>
<li></li>
<li> if event.type == KEYUP:</li>
<li> if event.key == ord('z'):</li>
<li> reverseCheat = False</li>
<li> score = 0</li>
<li> if event.key == ord('x'):</li>
<li> slowCheat = False</li>
<li> score = 0</li>
<li> if event.key == K_ESCAPE:</li>
<li> terminate()</li>
<li></li>
<li> if event.key == K_LEFT or event.key == ord('a'):</li>
<li> moveLeft = False</li>
<li> if event.key == K_RIGHT or event.key == ord('d'):</li>
<li> moveRight = False</li>
<li> if event.key == K_UP or event.key == ord('w'):</li>
<li> moveUp = False</li>
<li> if event.key == K_DOWN or event.key == ord('s'):</li>
<li> moveDown = False</li>
<li></li>
<li> if event.type == MOUSEMOTION:</li>
<li> # If the mouse moves, move the player where the cursor is.</li>
<li> playerRect.move_ip(event.pos[0] - playerRect.centerx, event.pos[1] - playerRect.centery)</li>
<li></li>
<li> # Add new baddies at the top of the screen, if needed.</li>
<li> if not reverseCheat and not slowCheat:</li>
<li> baddieAddCounter += 1</li>
<li> if baddieAddCounter == ADDNEWBADDIERATE:</li>
<li> baddieAddCounter = 0</li>
<li> baddieSize = random.randint(BADDIEMINSIZE, BADDIEMAXSIZE)</li>
<li> newBaddie = {'rect': pygame.Rect(random.randint(0, WINDOWWIDTH-baddieSize), 0 - baddieSize, baddieSize, baddieSize),</li>
<li> 'speed': random.randint(BADDIEMINSPEED, BADDIEMAXSPEED),</li>
<li> 'surface':pygame.transform.scale(baddieImage, (baddieSize, baddieSize)),</li>
<li> }</li>
<li></li>
<li> baddies.append(newBaddie)</li>
<li></li>
<li> # Move the player around.</li>
<li> if moveLeft and playerRect.left > 0:</li>
<li> playerRect.move_ip(-1 * PLAYERMOVERATE, 0)</li>
<li> if moveRight and playerRect.right < WINDOWWIDTH:</li>
<li> playerRect.move_ip(PLAYERMOVERATE, 0)</li>
<li> if moveUp and playerRect.top > 0:</li>
<li> playerRect.move_ip(0, -1 * PLAYERMOVERATE)</li>
<li> if moveDown and playerRect.bottom < WINDOWHEIGHT:</li>
<li> playerRect.move_ip(0, PLAYERMOVERATE)</li>
<li></li>
<li> # Move the mouse cursor to match the player.</li>
<li> pygame.mouse.set_pos(playerRect.centerx, playerRect.centery)</li>
<li></li>
<li> # Move the baddies down.</li>
<li> for b in baddies:</li>
<li> if not reverseCheat and not slowCheat:</li>
<li> b['rect'].move_ip(0, b['speed'])</li>
<li> elif reverseCheat:</li>
<li> b['rect'].move_ip(0, -5)</li>
<li> elif slowCheat:</li>
<li> b['rect'].move_ip(0, 1)</li>
<li></li>
<li> # Delete baddies that have fallen past the bottom.</li>
<li> for b in baddies[:]:</li>
<li> if b['rect'].top > WINDOWHEIGHT:</li>
<li> baddies.remove(b)</li>
<li></li>
<li> # Draw the game world on the window.</li>
<li> windowSurface.fill(BACKGROUNDCOLOR)</li>
<li></li>
<li> # Draw the score and top score.</li>
<li> drawText('Score: %s' % (score), font, windowSurface, 10, 0)</li>
<li> drawText('Top Score: %s' % (topScore), font, windowSurface, 10, 40)</li>
<li></li>
<li> # Draw the player's rectangle</li>
<li> windowSurface.blit(playerImage, playerRect)</li>
<li></li>
<li> # Draw each baddie</li>
<li> for b in baddies:</li>
<li> windowSurface.blit(b['surface'], b['rect'])</li>
<li></li>
<li> pygame.display.update()</li>
<li></li>
<li> # Check if any of the baddies have hit the player.</li>
<li> if playerHasHitBaddie(playerRect, baddies):</li>
<li> if score > topScore:</li>
<li> topScore = score # set new top score</li>
<li> break</li>
<li></li>
<li> mainClock.tick(FPS)</li>
<li></li>
<li> # Stop the game and show the "Game Over" screen.</li>
<li> pygame.mixer.music.stop()</li>
<li> gameOverSound.play()</li>
<li></li>
<li> drawText('GAME OVER', font, windowSurface, (WINDOWWIDTH / 3), (WINDOWHEIGHT / 3))</li>
<li> drawText('Press a key to play again.', font, windowSurface, (WINDOWWIDTH / 3) - 80, (WINDOWHEIGHT / 3) + 50)</li>
<li> pygame.display.update()</li>
<li> waitForPlayerToPressKey()</li>
<li></li>
<li> gameOverSound.stop()</li>
</ol></div>
<p>When you run this program, the game will look like this:</p>
<p class='centeredImageP'><img src='images/20-1.png' alt='' class='centeredImage' /><br />Figure 20-1: A screenshot of the Dodger game in action.
</p>
<h3 id="ImportingtheModules">Importing the Modules</h3>
<div class='sourcecode'><ol start='1'>
<li>import pygame, random, sys</li>
<li>from pygame.locals import *</li>
</ol></div>
<p>The Dodger game will import the same modules that our previous Pygame games have: <span class='m'>pygame</span>, <span class='m'>random</span>, <span class='m'>sys</span>, and <span class='m'>pygame.locals</span>. The <span class='m'>pygame.locals</span> module contains several constant variables that the Pygame library uses such as the event types (<span class='m'>QUIT</span>, <span class='m'>KEYDOWN</span>, etc.) and keyboard keys (<span class='m'>K_ESCAPE</span>, <span class='m'>K_LEFT</span>, etc.). By using the <span class='m'>from pygame.locals import *</span> syntax, we can just type <span class='m'>QUIT</span> instead of <span class='m'>pygame.locals.QUIT</span>.</p>
<h3 id="SettingUptheConstantVariables">Setting Up the Constant Variables</h3>
<p>There are several constant variables in this game. We use constant variables because the variable name is much more descriptive than a number. For example, from the line <span class='m'>windowSurface.fill(BACKGROUNDCOLOR)</span> we know that the argument being sent is a color for the background. However, the line <span class='m'>windowSurface.fill(BACKGROUNDCOLOR)</span> is not as clear what the argument being passed means.</p>
<p>We can also easily change some simple aspects about our game without having the change much of the code by changing the values stored in these constant variables. By changing <span class='m'>WINDOWWIDTH</span> on line 4, we automatically change the code everywhere <span class='m'>WINDOWWIDTH</span> is used. If we had used the value <span class='m'>600</span> instead, then we would have to change each occurrence of <span class='m'>600</span> in the code. This would be especially confusing because <span class='m'>600</span> would also be used for the height of the window as well, and we would not want to change those values.</p>
<div class='sourcecode'><ol start='4'>
<li>WINDOWWIDTH = 600</li>
<li>WINDOWHEIGHT = 600</li>
<li>TEXTCOLOR = (255, 255, 255)</li>
<li>BACKGROUNDCOLOR = (0, 0, 0)</li>
</ol></div>
<p>Here we set the height and width of the main window. Since the rest of our code works off of these constant variables, changing the value here will change it everywhere in our program.</p>
<p>Instead of storing color tuples into a variable named <span class='m'>WHITE</span> or <span class='m'>BLACK</span>, we will use constant variables for the color of the text and background. Remember that the three integers in the color tuples range from <span class='m'>0</span> to <span class='m'>255</span> and stand for red, green, and blue.</p>
<div class='sourcecode'><ol start='8'>
<li>FPS = 40</li>
</ol></div>
<p>Just so the computer does not run the game too fast for the user to handle, we will call <span class='m'>mainClock.tick()</span> on each iteration of the game loop to slow it down. We need to pass an integer to <span class='m'>mainClock.tick()</span> so that the function knows how long to pause the program. This integer will be the number of frames per second we want the game to run. A "frame" is the drawing of graphics on the screen for a single iteration through the game loop. We will set up a constant variable <span class='m'>FPS</span> to <span class='m'>40</span>, and always call <span class='m'>mainClock.tick(FPS)</span>. You can change <span class='m'>FPS</span> to a higher value to have the game run faster or a lower value to slow the game down.</p>
<div class='sourcecode'><ol start='9'>
<li>BADDIEMINSIZE = 10</li>
<li>BADDIEMAXSIZE = 40</li>
<li>BADDIEMINSPEED = 1</li>
<li>BADDIEMAXSPEED = 8</li>
<li>ADDNEWBADDIERATE = 6</li>
</ol></div>
<p>Here we set some more constant variables that will describe the falling baddies. The width and height of the baddies will be between <span class='m'>BADDIEMINSIZE</span> and <span class='m'>BADDIEMAXSIZE</span>. The rate at which the baddies fall down the screen will be between <span class='m'>BADDIEMINSPEED</span> and <span class='m'>BADDIEMAXSPEED</span> pixels per iteration through the game loop. And a new baddie will be added to the top of the window every <span class='m'>ADDNEWBADDIERATE</span> iterations through the game loop.</p>
<div class='sourcecode'><ol start='14'>
<li>PLAYERMOVERATE = 5</li>
</ol></div>
<p>The <span class='m'>PLAYERMOVERATE</span> will store the number of pixels the player's character moves in the window on each iteration through the game loop (if the character is moving). By increasing this number, you can increase the speed the character moves. If you set <span class='m'>PLAYERMOVERATE</span> to <span class='m'>0</span>, then the player's character won't be able to move at all (the player would move 0 pixels per iteration). This wouldn't be a very fun game.</p>
<h3 id="DefiningFunctions">Defining Functions</h3>
<p>We will create several functions for our game. By putting code into functions, we can avoid having to type the same code several times in our program. And because the code is in one place, if we find a bug the code only needs to be fixed in one place.</p>
<div class='sourcecode'><ol start='16'>
<li>def terminate():</li>
<li> pygame.quit()</li>
<li> sys.exit()</li>
</ol></div>
<p>There are several places in our game that we want to terminate the program. In our other programs, this just required a single call to <span class='m'>sys.exit()</span>. But since Pygame requires that we call both <span class='m'>pygame.quit()</span> and <span class='m'>sys.exit()</span>, we will put them into a function called <span class='m'>terminate()</span> and just call the function. This keeps us from repeating the same code over and over again. And remember, the more we type, the more likely we will make a mistake and create a bug in our program.</p>
<div class='sourcecode'><ol start='20'>
<li>def waitForPlayerToPressKey():</li>
<li> while True:</li>
<li> for event in pygame.event.get():</li>
</ol></div>
<p>There are also a couple places where we want the game to pause and wait for the player to press a key. We will create a new function called <span class='m'>waitForPlayerToPressKey()</span> to do this. Inside this function, we have an infinite loop that only breaks when a <span class='m'>KEYDOWN</span> or <span class='m'>QUIT</span> event is received. At the start of the loop, we call <span class='m'>pygame.event.get()</span> to return a list of <span class='m'>Event</span> objects to check out.</p>
<div class='sourcecode'><ol start='23'>
<li> if event.type == QUIT:</li>
<li> terminate()</li>
</ol></div>
<p>If the player has closed the window while the program is waiting for the player to press a key, Pygame will generate a <span class='m'>QUIT</span> event and we should terminate the program. We will call our <span class='m'>terminate()</span> function here, rather than call <span class='m'>pygame.quit()</span> and <span class='m nw'>sys.exit()</span> themselves.</p>
<div class='sourcecode'><ol start='25'>
<li> if event.type == KEYDOWN:</li>
<li> if event.key == K_ESCAPE: # pressing escape quits</li>
<li> terminate()</li>
<li> return</li>
</ol></div>
<p>If we receive a <span class='m'>KEYDOWN</span> event, then we should first check if it is the Esc key that was pressed. If we are waiting for the player to press a key, and the player presses the Esc key, we want to terminate the program. If that wasn't the case, then execution will skip the if-block on line 27 and go straight to the <span class='m'>return</span> statement, which exits the <span class='m'>waitForPlayerToPressKey()</span> function.</p>
<p>If a <span class='m'>QUIT</span> or <span class='m'>KEYDOWN</span> event is not generated, then this loop will keep looping until it is. This will freeze the game until the player presses a key or closes the window.</p>
<div class='sourcecode'><ol start='30'>
<li>def playerHasHitBaddie(playerRect, baddies):</li>
<li> for b in baddies:</li>
<li> if playerRect.colliderect(b['rect']):</li>
<li> return True</li>
<li> return False</li>
</ol></div>
<p>We will also define a function named <span class='m'>playerHasHitBaddie()</span> which will return <span class='m'>True</span> if the player's character has collided with one of the baddies. The <span class='m'>baddies</span> parameter is a list of baddie data structures. These data structures are just dictionaries, so it is accurate to say that <span class='m'>baddies</span> is a list of dictionary objects. Each of these dictionaries has a <span class='m'>'rect'</span> key, and the value for that key is a <span class='m'>Rect</span> object that represents the baddie's size and location.</p>
<p><span class='m'>playerRect</span> is also a <span class='m'>Rect</span> object. Remember that <span class='m'>Rect</span> objects have a method named <span class='m'>colliderect()</span> that returns <span class='m'>True</span> if the <span class='m'>Rect</span> object has collided with the Rect object that is passed to the method. Otherwise, <span class='m'>colliderect()</span> will return <span class='m'>False</span>.</p>
<p>We can use this method in our <span class='m'>playerHasHitBaddie()</span> function. First we iterate through each baddie data structure in the <span class='m'>baddies</span> list. If any of these baddies collide with the player's character, then <span class='m'>playerHasHitBaddie()</span> will return <span class='m'>True</span>. If the code manages to iterate through all the baddies in the <span class='m'>baddies</span> list without colliding with any of them, we will return <span class='m'>False</span>.</p>
<div class='sourcecode'><ol start='36'>
<li>def drawText(text, font, surface, x, y):</li>
<li> textobj = font.render(text, 1, TEXTCOLOR)</li>
<li> textrect = textobj.get_rect()</li>
<li> textrect.topleft = (x, y)</li>
<li> surface.blit(textobj, textrect)</li>
</ol></div>
<p>Drawing text on the window involves many different steps. First, we must create a <span class='m'>Surface</span> object that has the string rendered in a specific font on it. The <span class='m'>render()</span> method does this. Next, we need to know the size and location of the <span class='m'>Surface</span> object we just made. We can get a <span class='m'>Rect</span> object with this information with the <span class='m'>get_rect()</span> method for <span class='m'>Surface</span> objects.</p>
<p>This Rect object has no special connection to the <span class='m'>Surface</span> object with the text drawn on it, other than the fact that it has a copy of the width and height information from the <span class='m'>Surface</span> object. We can change the location of the <span class='m'>Rect</span> object by setting a new tuple value for its <span class='m'>topleft</span> attribute.</p>
<p>Finally, we blit the <span class='m'>Surface</span> object of the rendered text onto the <span class='m'>Surface</span> object that was passed to our <span class='m'>drawText()</span> function. Displaying text in Pygame take a few more steps than simply calling the <span class='m'>print()</span> function, but if we put this code into a single function (<span class='m'>drawText()</span>), then we only need to call the function instead of typing out all the code every time we want to display text on the screen.</p>
<h3 id="InitializingPygameandSettingUptheWindow">Initializing Pygame and Setting Up the Window</h3>
<p>Now that the constant variables and functions are finished, we can start calling the Pygame functions that will set up Pygame for use in our code. Many of these function calls are to set up the GUI window and create objects that we will use in the game.</p>
<div class='sourcecode'><ol start='42'>
<li># set up pygame, the window, and the mouse cursor</li>
<li>pygame.init()</li>
<li>mainClock = pygame.time.Clock()</li>
<li>windowSurface = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))</li>
<li>pygame.display.set_caption('Dodger')</li>
<li>pygame.mouse.set_visible(False)</li>
</ol></div>
<p>Line 43 sets up the Pygame library. Remember, the <span class='m'>pygame.init()</span> function must be called before we can use any of Pygame's functions or data types. Line 44 creates a <span class='m'>pygame.time.Clock()</span> object and stores it in the <span class='m'>mainClock</span> variable. This object will help us keep the program from running too fast.</p>
<p>Line 45 creates a new <span class='m'>Surface</span> object which will be used for the window displayed on the screen. We will specify the width and height of this <span class='m'>Surface</span> object (and the window) by passing a tuple with the <span class='m'>WINDOWWIDTH</span> and <span class='m'>WINDOWHEIGHT</span> constant variables. Notice that there is only one argument passed to <span class='m'>pygame.display.set_mode()</span>: a tuple. The arguments for <span class='m'>pygame.display.set_mode()</span> are not two integers but a tuple of two integers.</p>
<p>On line 46, the caption of the window is set to the string <span class='m'>'Dodger'</span>. This caption will appear in the title bar at the top of the window.</p>
<p>In our game, we do not want the mouse cursor (the mouse cursor is the arrow that moves around the screen when we move the mouse) to be visible. This is because we want the mouse to be able to move the player's character around the screen, and the arrow cursor would get in the way of the character's image on the screen. We pass <span class='m'>False</span> to tell Pygame to make the cursor invisible. If we wanted to make the cursor visible again at some point in the program, we could call <span class='m'>pygame.mouse.set_visible(True)</span>.</p>
<h3 id="FullscreenMode">Fullscreen Mode</h3>
<p>The <span class='m'>pygame.display.set_mode()</span> function has a second, optional parameter that you can pass to it. The value you can pass for this parameter is <span class='m'>pygame.FULLSCREEN</span>, like this modification to line 45 in our Dodger program:</p>
<div class='sourcecode'><ol start='45'>
<li>windowSurface = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT), pygame.FULLSCREEN)</li>
</ol></div>
<p>Passing <span class='m'>pygame.FULLSCREEN</span> will make the program take up the entire space of the screen. It will still be <span class='m'>WINDOWWIDTH</span> and <span class='m'>WINDOWHEIGHT</span> in size for the windows width and height, but the image will be stretched larger to fit the screen. There may be wasted space along the top and bottom (or the left and right) sides of the screen if you did not set the window size in proportion with the screen's resolution.) To avoid the wasted space, you should set the size of the window to a 4:3 ratio (for every 4 pixels of width, have 3 pixels for height).</p>
<p>If you do not use the fullscreen mode, then you do not need to worry about using a 4:3 ratio for the width and height. Just use whatever width and height works best for your game.</p>
<div class='sourcecode'><ol start='49'>
<li># set up fonts</li>
<li>font = pygame.font.SysFont(None, 48)</li>
</ol></div>
<p>We need to create a <span class='m'>Font</span> object to use when we create a <span class='m'>Surface</span> object with the image of text drawn on it. (This process is called "rendering".) We want to create a generic font, so we will use the default <span class='m'>Font</span> object that the <span class='m'>pygame.font.SysFont()</span> constructor function returns. We pass <span class='m'>None</span> so that the default font is used, and we pass <span class='m'>48</span> so that the font has a size of 48 points.</p>
<div class='sourcecode'><ol start='52'>
<li># set up sounds</li>
<li>gameOverSound = pygame.mixer.Sound('gameover.wav')</li>
<li>pygame.mixer.music.load('background.mid')</li>
</ol></div>
<p>Next we want to create the <span class='m'>Sound</span> objects and also set up the background music. The background music will constantly be playing during the game, but <span class='m'>Sound</span> objects will only be played when we specifically want them to. In this case, the <span class='m'>Sound</span> object will be played when the player loses the game.</p>
<p>You can use any .wav or .mid file for this game. You can download these sound files from this book's website at the URL <a href='http://inventwithpython.com/resources'>http://inventwithpython.com/resources</a>. Or you can use your own sound files for this game, as long as they have the filenames of <i>gameover.wav</i> and <i>background.mid</i>. (Or you can change the strings used on lines 53 and 54 to match the filenames.)</p>
<p>The <span class='m'>pygame.mixer.Sound()</span> constructor function creates a new <span class='m'>Sound</span> object and stores a reference to this object in the <span class='m'>gameOverSound</span> variable. In your own games, you can create as many <span class='m'>Sound</span> objects as you like, each with a different sound file that it will play.</p>
<p>The <span class='m'>pygame.mixer.music.load()</span> function loads a sound file to play for the background music. This function does not create any objects, and only one sound file can be loaded at a time.</p>
<div class='sourcecode'><ol start='56'>
<li># set up images</li>
<li>playerImage = pygame.image.load('player.png')</li>
<li>playerRect = playerImage.get_rect()</li>
<li>baddieImage = pygame.image.load('baddie.png')</li>
</ol></div>
<p>Next we will load the image files that used for the player's character and the baddies on the screen. The image for the character is stored in <i>player.png</i> and the image for the baddies is stored in <i>baddie.png</i>. All the baddies look the same, so we only need one image file for them. You can download these images from the book's website at the URL <a href='http://inventwithpython.com/resources'>http://inventwithpython.com/resources</a>.</p>
<h3 id="DisplaytheStartScreen">Display the Start Screen</h3>
<p>When the game first starts, we want to display the name of the game on the screen. We also want to instruct the player that they can start the game by pushing any key. This screen appears so that the player has time to get ready to start playing after running the program. Also, before each game starts, we want to reset the value of the top score back to <span class='m'>0</span>.</p>
<div class='sourcecode'><ol start='61'>
<li># show the "Start" screen</li>
<li>drawText('Dodger', font, windowSurface, (WINDOWWIDTH / 3), (WINDOWHEIGHT / 3))</li>
<li>drawText('Press a key to start.', font, windowSurface, (WINDOWWIDTH / 3) - 30, (WINDOWHEIGHT / 3) + 50)</li>
<li>pygame.display.update()</li>
<li>waitForPlayerToPressKey()</li>
</ol></div>
<p>On lines 62 and 63, we call our <span class='m'>drawText()</span> function and pass it five arguments: 1) the string of the text we want to appear, 2) the font that we want the string to appear in, 3) the <span class='m'>Surface</span> object onto which to render the text, and 4) and 5) the X and Y coordinate on the <span class='m'>Surface</span> object to draw the text at.</p>
<p>This may seem like many arguments to pass for a function call, but keep in mind that this function call replaces five lines of code each time we call it. This shortens our program and makes it easier to find bugs since there is less code to check.</p>
<p>The <span class='m'>waitForPlayerToPressKey()</span> function will pause the game by entering into a loop that checks for any <span class='m'>KEYDOWN</span> events. Once a <span class='m'>KEYDOWN</span> event is generated, the execution breaks out of the loop and the program continues to run.</p>
<h3 id="StartoftheMainGameCode">Start of the Main Game Code</h3>
<div class='sourcecode'><ol start='68'>
<li>topScore = 0</li>
<li>while True:</li>
</ol></div>
<p>We have finished defining the helper functions and variables that we need for this game. Line 68 is the start of the main game code. The value in the <span class='m'>topScore</span> variable starts at <span class='m'>0</span> only when the program first runs. Whenever the player loses and has a score larger than the current top score, the top score is replaced with the player's score.</p>
<p>The infinite loop started on line 69 is technically not the "game loop". (The main game loop handles events and drawing the window while the game is running.) Instead, this <span class='m'>while</span> loop will iterate each time the player starts a new game. We will set up the code so that when the player loses and we need to reset the game, the program's execution will go back to the start of this loop.</p>
<div class='sourcecode'><ol start='70'>
<li> # set up the start of the game</li>
<li> baddies = []</li>
<li> score = 0</li>
</ol></div>
<p>At the very beginning, we want to set the <span class='m'>baddies</span> list to an empty list. The <span class='m'>baddies</span> list is a list of dictionary objects with the following keys:</p>
<ul>
<li><span class='m'>'rect'</span> - The <span class='m'>Rect</span> object that describes where and what size the baddie is.</li>
<li><span class='m'>'speed'</span> - How fast the baddie falls down the screen. This integer represents pixels per iteration through the game loop.</li>
<li><span class='m'>'surface'</span> - The <span class='m'>Surface</span> object that has the scaled image of the baddie image drawn on it. This is the <span class='m'>Surface</span> object that will be blitted to the <span class='m'>Surface</span> object returned by <span class='m'>pygame.display.set_mode()</span> and drawn on the screen.</li>
</ul>
<p>Next, we want to reset the player's score to <span class='m'>0</span>.</p>
<div class='sourcecode'><ol start='73'>
<li> playerRect.topleft = (WINDOWWIDTH / 2, WINDOWHEIGHT - 50)</li>
</ol></div>
<p>The starting location of the player will be in the center of the screen and 50 pixels up from the bottom. The tuple that we set the <span class='m'>topleft</span> attribute to will change the location of the <span class='m'>playerRect</span> object. The first item in the tuple is the X-coordinate of the left edge. The second item in the tuple is the Y-coordinate of the top edge.</p>
<div class='sourcecode'><ol start='74'>
<li> moveLeft = moveRight = moveUp = moveDown = False</li>
<li> reverseCheat = slowCheat = False</li>
<li> baddieAddCounter = 0</li>
</ol></div>
<p>Also at the start of the game, we want to have the movement variables <span class='m'>moveLeft</span>, <span class='m'>moveRight</span>, <span class='m'>moveUp</span>, and <span class='m'>moveDown</span> set to <span class='m'>False</span>. The <span class='m'>reverseCheat</span> and <span class='m'>slowCheat</span> variables will be set to <span class='m'>True</span> only when the player enables these cheats by holding down the "z" and "x" keys, respectively.</p>
<p>The <span class='m'>baddieAddCounter</span> variable is used for a counter to tell the program when to add a new baddie at the top of the screen. The value in <span class='m'>baddieAddCounter</span> will be incremented by one each time the game loop iterates. When the <span class='m'>baddieAddCounter</span> counter is equal to the value in <span class='m'>ADDNEWBADDIERATE</span>, then the <span class='m'>baddieAddCounter</span> counter is reset back to 0 and a new baddie is added to the top of the screen.</p>
<div class='sourcecode'><ol start='77'>
<li> pygame.mixer.music.play(-1, 0.0)</li>
</ol></div>
<p>At the start of the game, we want the background music to begin playing. We can do this with a call to <span class='m'>pygame.mixer.music.play()</span>. The first argument is the number of times the music should repeat itself. <span class='m'>-1</span> is a special value that tells Pygame we want the music to repeat endlessly. The second argument is a float that says how many seconds into the music we want it to start playing. Passing <span class='m'>0.0</span> means we want to play the music starting from the beginning of the music file. (Passing <span class='m'>2.0</span>, for example, would have started the music two seconds into the music file.)</p>
<h3 id="TheGameLoop">The Game Loop</h3>
<p>The game loop contains the code that is executed while the game is being played. The game loop constantly updates the state of the game world by changing the position of the player and baddies, handling events generated by Pygame, and drawing the state of the game world on the screen. All of this happens several dozen times a second, which makes it seem that the game is happening in real time to the player.</p>
<div class='sourcecode'><ol start='79'>
<li> while True: # the game loop runs while the game part is playing</li>
<li> score += 1 # increase score</li>
</ol></div>
<p>Line 79 is the start of the main game loop. In the main game loop, we will increase the player's score, handle any events that were generated, add any baddies to the top of the screen if needed, move the baddies down a little, and then draw everything on the screen. This code will be executed over and over again as the program execution iterates through the game loop. The loop will only exit when the player either loses the game or quits the program.</p>
<p>First, we will increment the player's score. The longer the player can go without losing, the higher their score will be.</p>
<h3 id="EventHandling">Event Handling</h3>
<p>There are four different types of events we will handle in our game: <span class='m'>QUIT</span>, <span class='m'>KEYDOWN</span>, <span class='m'>KEYUP</span>, and <span class='m'>MOUSEMOTION</span>. The <span class='m'>QUIT</span> event is generated by Pygame if the player closes the program's window or shuts down the computer. In that case, we want the program to close itself. The <span class='m'>KEYDOWN</span> and <span class='m'>KEYUP</span> events are generated when the player pushes down and releases the keyboard keys, respectively. These events will be how we can tell which direction the player wants to move the character. The player could also have pressed the Esc key to signal that they want to shut down the program. Each time the player moves the mouse, Pygame will generate a <span class='m'>MOUSEMOTION</span> event which will tell us the X- and Y-coordinates of the mouse cursor over the window.</p>
<div class='sourcecode'><ol start='82'>
<li> for event in pygame.event.get():</li>
<li> if event.type == QUIT:</li>
<li> terminate()</li>
</ol></div>
<p>Line 82 is the start of the event-handling code. First we call <span class='m'>pygame.event.get()</span>, which returns a list of <span class='m'>Event</span> objects. Each <span class='m'>Event</span> object represents an event that has been created since the last call to <span class='m'>pygame.event.get()</span>. We will check the <span class='m'>type</span> attribute of the event object to see what type of event it is, and handle the event accordingly.</p>
<p>If the <span class='m'>type</span> attribute of the <span class='m'>Event</span> object is equal to <span class='m'>QUIT</span>, then this tells us that the user has closed the program somehow. The <span class='m'>QUIT</span> constant variable was imported from the <span class='m'>pygame.locals</span> module, but since we imported that module with the line <span class='m'>from pygame.locals import *</span> instead of simply <span class='m'>import pygame.locals</span>, we only need to type <span class='m'>QUIT</span> and not <span class='m'>pygame.locals.QUIT</span>.</p>
<div class='sourcecode'><ol start='86'>
<li> if event.type == KEYDOWN:</li>
<li> if event.key == ord('z'):</li>
<li> reverseCheat = True</li>
<li> if event.key == ord('x'):</li>
<li> slowCheat = True</li>
</ol></div>
<p>If the event's type is <span class='m'>KEYDOWN</span>, then we know that the player has pressed down a key. The <span class='m'>Event</span> object for keyboard events will also have a <span class='m'>key</span> attribute that is set to the numeric ASCII value of the key pressed. The <span class='m'>ord()</span> function will return the ASCII value of the letter passed to it.</p>
<p>For example, on line 87, we can check if the event describes the "z" key being pressed down by checking if <span class='m'>event.key == ord('z')</span>. If this condition is <span class='m'>True</span>, then we want to set the <span class='m'>reverseCheat</span> variable to <span class='m'>True</span> to indicate that the reverse cheat has been activated. We will also check if the "x" key has been pressed to activate the slow cheat in a similar way.</p>
<p>Pygame's keyboard events always use the ASCII values of lowercase letters, not uppercase. What this means for your code is that you should always use <span class='m'>event.key == ord('z')</span> instead of <span class='m'>event.key == ord('Z')</span>. Otherwise, your program may act as though the key hasn't been pressed at all.</p>
<div class='sourcecode'><ol start='91'>
<li> if event.key == K_LEFT or event.key == ord('a'):</li>
<li> moveRight = False</li>
<li> moveLeft = True</li>
<li> if event.key == K_RIGHT or event.key == ord('d'):</li>
<li> moveLeft = False</li>
<li> moveRight = True</li>
<li> if event.key == K_UP or event.key == ord('w'):</li>
<li> moveDown = False</li>
<li> moveUp = True</li>
<li> if event.key == K_DOWN or event.key == ord('s'):</li>
<li> moveUp = False</li>
<li> moveDown = True</li>
</ol></div>
<p>We also want to check if the event was generated by the player pressing one of the arrow keys. There is not an ASCII value for every key on the keyboard, such as the arrow keys or the Esc key. Instead, Pygame provides some constant variables to use instead.</p>
<p>We can check if the player has pressed the left arrow key with the condition: <span class='m'>event.key == K_LEFT</span>. Again, the reason we can use <span class='m'>K_LEFT</span> instead of <span class='m'>pygame.locals.K_LEFT</span> is because we imported <span class='m'>pygame.locals</span> with the line <span class='m'>from pygame.locals import *</span> instead of <span class='m'>import pygame.locals</span>.</p>
<p>Noticed that pressing down on one of the arrow keys not only sets one of the movement variables to <span class='m'>True</span>, but it also sets the movement variable in the opposite direction to <span class='m'>False</span>. For example, if the left arrow key is pushed down, then the code on line 93 sets <span class='m'>moveLeft</span> to <span class='m'>True</span>, but it also sets <span class='m'>moveRight</span> to <span class='m'>False</span>. This prevents the player from confusing the program into thinking that the player's character should move in two opposite directions at the same time.</p>
<p>Here is a list of commonly-used constant variables for the key attribute of keyboard-related <span class='m'>Event</span> objects:</p>
<span class='createspace'><br /></span>
<table class='simpletable centertable'>
<caption>Table 20-1: Constant Variables for Keyboard Keys</caption>
<tr><th class='simpletd'>Pygame Constant Variable</th><th class='simpletd'>Keyboard Key</th><th class='simpletd'>Pygame Constant Variable</th><th class='simpletd'>Keyboard Key</th></tr>
<tr><td class='simpletd'>K_LEFT</td><td class='simpletd'>Left arrow</td> <td class='simpletd'>K_HOME</td><td class='simpletd'>Home</td></tr>
<tr><td class='simpletd'>K_RIGHT</td><td class='simpletd'>Right arrow</td> <td class='simpletd'>K_END</td><td class='simpletd'>End</td></tr>
<tr><td class='simpletd'>K_UP</td><td class='simpletd'>Up arrow</td> <td class='simpletd'>K_PAGEUP</td><td class='simpletd'>PgUp</td></tr>
<tr><td class='simpletd'>K_DOWN</td><td class='simpletd'>Down arrow</td> <td class='simpletd'>K_PAGEDOWN</td><td class='simpletd'>PgDn</td></tr>
<tr><td class='simpletd'>K_ESCAPE</td><td class='simpletd'>Esc</td> <td class='simpletd'>K_F1</td><td class='simpletd'>F1</td></tr>
<tr><td class='simpletd'>K_BACKSPACE</td><td class='simpletd'>Backspace</td> <td class='simpletd'>K_F2</td><td class='simpletd'>F2</td></tr>
<tr><td class='simpletd'>K_TAB</td><td class='simpletd'>Tab</td> <td class='simpletd'>K_F3</td><td class='simpletd'>F3</td></tr>
<tr><td class='simpletd'>K_RETURN</td><td class='simpletd'>Return or Enter</td> <td class='simpletd'>K_F4</td><td class='simpletd'>F4</td></tr>
<tr><td class='simpletd'>K_SPACE</td><td class='simpletd'>Space bar</td> <td class='simpletd'>K_F5</td><td class='simpletd'>F5</td></tr>
<tr><td class='simpletd'>K_DELETE</td><td class='simpletd'>Del</td> <td class='simpletd'>K_F6</td><td class='simpletd'>F6</td></tr>
<tr><td class='simpletd'>K_LSHIFT</td><td class='simpletd'>Left Shift</td> <td class='simpletd'>K_F7</td><td class='simpletd'>F7</td></tr>
<tr><td class='simpletd'>K_RSHIFT</td><td class='simpletd'>Right Shift</td> <td class='simpletd'>K_F8</td><td class='simpletd'>F8</td></tr>
<tr><td class='simpletd'>K_LCTRL</td><td class='simpletd'>Left Ctrl</td> <td class='simpletd'>K_F9</td><td class='simpletd'>F9</td></tr>
<tr><td class='simpletd'>K_RCTRL</td><td class='simpletd'>Right Ctrl</td> <td class='simpletd'>K_F10</td><td class='simpletd'>F10</td></tr>
<tr><td class='simpletd'>K_LALT</td><td class='simpletd'>Left Alt</td> <td class='simpletd'>K_F11</td><td class='simpletd'>F11</td></tr>
<tr><td class='simpletd'>K_RALT</td><td class='simpletd'>Right Alt</td> <td class='simpletd'>K_F12</td><td class='simpletd'>F12</td></tr>
</table>
<div class='sourcecode'><ol start='104'>
<li> if event.type == KEYUP:</li>
<li> if event.key == ord('z'):</li>
<li> reverseCheat = False</li>
<li> score = 0</li>
<li> if event.key == ord('x'):</li>
<li> slowCheat = False</li>
<li> score = 0</li>
</ol></div>
<p>The <span class='m'>KEYUP</span> event is created whenever the player stops pressing down on a keyboard key and it returns to its normal, up position. <span class='m'>KEYUP</span> objects with a type of <span class='m'>KEYUP</span> also have a <span class='m'>key</span> attribute just like <span class='m'>KEYDOWN</span> events.</p>
<p>On line 105, we check if the player has released the "z" key, which will deactivate the reverse cheat. In that case, we set <span class='m'>reverseCheat</span> to <span class='m'>False</span> and reset the score to <span class='m'>0</span>. The score reset is to discourage the player for using the cheats.</p>
<p>Lines 108 to 110 do the same thing for the "x" key and the slow cheat. When the "x" key is released, <span class='m'>slowCheat</span> is set to <span class='m'>False</span> and the player's score is reset to <span class='m'>0</span>.</p>
<div class='sourcecode'><ol start='111'>
<li> if event.key == K_ESCAPE:</li>
<li> terminate()</li>
</ol></div>
<p>At any time during the game, the player can press the Esc key on the keyboard to quit the game. Here we check if the key that was released was the Esc key by checking <span class='m'>event.key == K_ESCAPE</span>. If so, we call our <span class='m'>terminate()</span> function which will exit the program.</p>
<div class='sourcecode'><ol start='114'>
<li> if event.key == K_LEFT or event.key == ord('a'):</li>
<li> moveLeft = False</li>
<li> if event.key == K_RIGHT or event.key == ord('d'):</li>
<li> moveRight = False</li>
<li> if event.key == K_UP or event.key == ord('w'):</li>
<li> moveUp = False</li>
<li> if event.key == K_DOWN or event.key == ord('s'):</li>
<li> moveDown = False</li>
</ol></div>
<p>Lines 114 to 121 check if the player has stopped holding down one of the arrow keys (or the corresponding WASD key). In that event, we will set the corresponding movement variable to <span class='m'>False</span>. For example, if the player was holding down the left arrow key, then the <span class='m'>moveLeft</span> would have been set to <span class='m'>True</span> on line 93. When they release it, the condition on line 114 will evaluate to <span class='m'>True</span>, and the <span class='m'>moveLeft</span> variable will be set to <span class='m'>False</span>.</p>
<h3 id="ThemoveipMethodforRectobjects">The <span class='m'>move_ip()</span> Method for <span class='m'>Rect</span> objects</h3>
<div class='sourcecode'><ol start='123'>
<li> if event.type == MOUSEMOTION:</li>
<li> # If the mouse moves, move the player where the cursor is.</li>
<li> playerRect.move_ip(event.pos[0] - playerRect.centerx, event.pos[1] - playerRect.centery)</li>
</ol></div>
<p>Now that we have handled the keyboard events, let's handle any mouse events that may have been generated. In the Dodger game we don't do anything if the player has clicked a mouse button, but the game does respond when the player moves the mouse. This gives the player two ways of controlling the player character in the game: the keyboard and the mouse.</p>
<p>If the event's type is <span class='m'>MOUSEMOTION</span>, then we want to move the player's character to the location of the mouse cursor. The <span class='m'>MOUSEMOTION</span> event is generated whenever the mouse is moved. <span class='m'>Event</span> objects with a <span class='m'>type</span> of <span class='m'>MOUSEMOTION</span> also have an attribute named <span class='m'>pos</span>. The <span class='m'>pos</span> attribute stores a tuple of the X- and Y-coordinates of where the mouse cursor moved in the window.</p>
<p>The <span class='m'>move_ip()</span> method for <span class='m'>Rect</span> objects will move the location of the <span class='m'>Rect</span> object horizontally or vertically by a number of pixels. For example, <span class='m'>playerRect.move_ip(10, 20)</span> would move the <span class='m'>Rect</span> object 10 pixels to the right and 20 pixels down. To move the <span class='m'>Rect</span> object left or up, pass negative values. For example, <span class='m'>playerRect.move_ip(-5, -15)</span> will move the <span class='m'>Rect</span> object left by 5 pixels and up 15 pixels.</p>
<p>The "ip" at the end of <span class='m'>move_ip()</span> stands for "in place". This is because the method changes the <span class='m'>Rect</span> object itself, in its own place. There is also a <span class='m'>move()</span> method which does not change the <span class='m'>Rect</span> object, but instead creates a new <span class='m'>Rect</span> object that has the new location. This is useful if you want to keep the original <span class='m'>Rect</span> object's location the same but also have a <span class='m'>Rect</span> object with the new location.</p>
<h3 id="AddingNewBaddies">Adding New Baddies</h3>
<div class='sourcecode'><ol start='127'>
<li> # Add new baddies at the top of the screen, if needed.</li>
<li> if not reverseCheat and not slowCheat:</li>
<li> baddieAddCounter += 1</li>
</ol></div>
<p>On each iteration of the game loop, we want to increment the <span class='m'>baddieAddCounter</span> variable by one. However, we only want to do this if the cheats are not enabled. Remember that <span class='m'>reverseCheat</span> and <span class='m'>slowCheat</span>: are only set to <span class='m'>True</span> as long as the "z" and "x" keys are being held down, respectively. And while those keys are being held down, <span class='m'>baddieAddCounter</span> is not incremented. This means that no new baddies will appear at the top of the screen.</p>
<div class='sourcecode'><ol start='130'>
<li> if baddieAddCounter == ADDNEWBADDIERATE:</li>
<li> baddieAddCounter = 0</li>
<li> baddieSize = random.randint(BADDIEMINSIZE, BADDIEMAXSIZE)</li>
<li> newBaddie = {'rect': pygame.Rect(random.randint(0, WINDOWWIDTH-baddieSize), 0 - baddieSize, baddieSize, baddieSize),</li>
<li> 'speed': random.randint(BADDIEMINSPEED, BADDIEMAXSPEED),</li>
<li> 'surface':pygame.transform.scale(baddieImage, (baddieSize, baddieSize)),</li>
<li> }</li>
</ol></div>
<p>When the <span class='m'>baddieAddCounter</span> reaches the value in <span class='m'>ADDNEWBADDIERATE</span>, then the condition on line 130 is <span class='m'>True</span> and it is time to add a new baddie to the top of the screen. First, the <span class='m'>baddieAddCounter</span> counter is reset back to <span class='m'>0</span> (otherwise, when it keeps incrementing it will always be greater than <span class='m'>ADDNEWBADDIERATE</span> and never equal to it. This will cause baddies to stop appearing at the top of the screen.)</p>
<p>Line 132 generates a size for the baddie in pixels. The size will be between <span class='m'>BADDIEMINSIZE</span> and <span class='m'>BADDIEMAXSIZE</span>, which we have set to <span class='m'>10</span> and <span class='m'>40</span> in this program.</p>
<p>Line 133 is where a new baddie data structure is created. Remember, the data structure for baddies is simply a dictionary with keys <span class='m'>'rect'</span>, <span class='m'>'speed'</span>, and <span class='m'>'surface'</span>. The <span class='m'>'rect'</span> key holds a reference to a <span class='m'>Rect</span> object which stores the location and size of the baddie. The call to the <span class='m'>pygame.Rect()</span> constructor function has four parameters: the X-coordinate of the top edge of the area, the Y-coordinate of the left edge of the area, the width in pixels, and the height in pixels.</p>
<p>We want the baddie to appear randomly across the top of the window, so we pass <span class='m'>random.randint(0, WINDOWWIDTH-baddieSize)</span> for the X-coordinate of the left edge. This will evaluate to a random place across the top of the window. The reason we pass <span class='m'>WINDOWWIDTH-baddieSize</span> instead of <span class='m'>WINDOWWIDTH</span> is because this value is for the left edge of the baddie. If the left edge of the baddie is too far on the right side of the screen, then part of the baddie will be off the edge of the window and not visible.</p>
<p>We want the bottom edge of the baddie to be just above the top edge of the window. The Y-coordinate of the top edge of the window is 0, so to put the baddie's bottom edge there, we want to set the top edge to <span class='m'>0 - baddieSize</span>.</p>
<p>The baddie's width and height should be the same (the image is a square), so we will pass <span class='m'>baddieSize</span> for the third and fourth argument.</p>
<p>The rate of speed that the baddie moves down the screen will be set in the <span class='m'>'speed'</span> key, and is set to a random integer between <span class='m'>BADDIEMINSPEED</span> and <span class='m'>BADDIEMAXSPEED</span>.</p>
<div class='sourcecode'><ol start='138'>
<li> baddies.append(newBaddie)</li>
</ol></div>
<p>Line 138 will add the newly created baddie data structure to the list of baddie data structures. Our program will use this list to check if the player has collided with any of the baddies and to know where to draw baddies on the window.</p>
<h3 id="MovingthePlayersCharacter">Moving the Player's Character</h3>
<div class='sourcecode'><ol start='140'>
<li> # Move the player around.</li>
<li> if moveLeft and playerRect.left > 0:</li>
<li> playerRect.move_ip(-1 * PLAYERMOVERATE, 0)</li>
</ol></div>
<p>The four movement variables <span class='m'>moveLeft</span>, <span class='m'>moveRight</span>, <span class='m'>moveUp</span> and <span class='m'>moveDown</span> are set to <span class='m'>True</span> and <span class='m'>False</span> when Pygame generates the <span class='m'>KEYDOWN</span> and <span class='m'>KEYUP</span> events, respectively. (This code is from line 86 to line 121.)</p>
<p>If the player's character is moving left and the left edge of the player's character is greater than <span class='m'>0</span> (which is the left edge of the window), then we want to move the character's <span class='m'>Rect</span> object (stored in <span class='m'>playerRect</span>).</p>
<p>We will always move the <span class='m'>playerRect</span> object by the number of pixels in <span class='m'>PLAYERMOVERATE</span>. To get the negative form of an integer, you can simply multiple it by <span class='m'>-1</span>. So on line 142, since <span class='m'>5</span> is stored in <span class='m'>PLAYERMOVERATE</span>, the expression <span class='m'>-1 * PLAYERMOVERATE</span> evaluates to <span class='m'>-5</span>.</p>
<p>This means that calling <span class='m'>playerRect.move_ip(-1 * PLAYERMOVERATE, 0)</span> will change the location of playerRect by 5 pixels to the left of its current location.</p>
<div class='sourcecode'><ol start='143'>
<li> if moveRight and playerRect.right < WINDOWWIDTH:</li>
<li> playerRect.move_ip(PLAYERMOVERATE, 0)</li>
<li> if moveUp and playerRect.top > 0:</li>
<li> playerRect.move_ip(0, -1 * PLAYERMOVERATE)</li>
<li> if moveDown and playerRect.bottom < WINDOWHEIGHT:</li>
<li> playerRect.move_ip(0, PLAYERMOVERATE)</li>
</ol></div>
<p>We want to do the same thing for the other three directions: right, up, and down. Each of the three <span class='m'>if</span> statements in lines 143 to 148 checks that their movement variable is set to <span class='m'>True</span> and that the edge of the <span class='m'>Rect</span> object of the player is inside the window before calling the <span class='m'>move_ip()</span> method to move the <span class='m'>Rect</span> object.</p>
<h3 id="ThepygamemousesetposFunction">The <span class='m'>pygame.mouse.set_pos()</span> Function</h3>
<div class='sourcecode'><ol start='150'>
<li> # Move the mouse cursor to match the player.</li>
<li> pygame.mouse.set_pos(playerRect.centerx, playerRect.centery)</li>
</ol></div>
<p>Line 151 moves the mouse cursor to the same position as the player's character. The <span class='m'>pygame.mouse.set_pos()</span> function moves the mouse cursor to the X- and Y-coordinates that you pass it. Specifically, the cursor will be right in the middle of the character's <span class='m'>Rect</span> object because we pass the <span class='m'>centerx</span> and <span class='m'>centery</span> attributes of <span class='m'>playerRect</span> for the coordinates. The mouse cursor still exists and can be moved, even though it is invisible because we called <span class='m'>pygame.mouse.set_visible(False)</span> on line 47.</p>
<p>The reason we want the mouse cursor to match the location of the player's character is to avoid sudden jumps. Imagine that the mouse cursor and the player's character are at the same location on the left side of the window. When the player holds down the right arrow key, the character moves to the right edge of the window but the mouse cursor would stay at the left edge of the screen. If the player then moves the mouse just a little bit, the player's character would immediately jump to the location of the mouse cursor on the left edge of the screen. By moving the mouse cursor along with the player's character, any mouse movements would not result in a sudden jump across the window.</p>
<div class='sourcecode'><ol start='153'>
<li> # Move the baddies down.</li>
<li> for b in baddies:</li>
</ol></div>
<p>Now we want to loop through each baddie data structure in the <span class='m'>baddies</span> list to move them down a little.</p>
<div class='sourcecode'><ol start='155'>
<li> if not reverseCheat and not slowCheat:</li>
<li> b['rect'].move_ip(0, b['speed'])</li>
</ol></div>
<p>If neither of the cheats have been activated (by the player pushing the "z" or "x" keys which sets <span class='m'>reverseCheat</span> or <span class='m'>slowCheat</span> to <span class='m'>True</span>, respectively), then move the baddie's location down a number of pixels equal to its speed, which is stored in the <span class='m'>'speed'</span> key.</p>
<h2 id="ImplementingtheCheatCodes">Implementing the Cheat Codes</h2>
<div class='sourcecode'><ol start='157'>
<li> elif reverseCheat:</li>
<li> b['rect'].move_ip(0, -5)</li>
</ol></div>
<p>If the reverse cheat has been activated, then the baddie should actually be moved up by five pixels. Passing <span class='m'>-5</span> for the second argument to <span class='m'>move_ip()</span> will move the <span class='m'>Rect</span> object upwards by five pixels.</p>
<div class='sourcecode'><ol start='159'>
<li> elif slowCheat:</li>
<li> b['rect'].move_ip(0, 1)</li>
</ol></div>
<p>If the slow cheat has been activated, then the baddie should move downwards, but only by the slow speed of one pixel per iteration through the game loop. The baddie's normal speed (which is stored in the <span class='m'>'speed'</span> key of the baddie's data structure) will be ignored while the slow cheat is activated.</p>
<h3 id="RemovingtheBaddies">Removing the Baddies</h3>
<div class='sourcecode'><ol start='162'>
<li> # Delete baddies that have fallen past the bottom.</li>
<li> for b in baddies[:]:</li>
</ol></div>
<p>After moving the baddies down the window, we want to remove any baddies that fell below the bottom edge of the window from the <span class='m'>baddies</span> list. Remember that we while we are iterating through a list, we should not modify the contents of the list by adding or removing items. So instead of iterating through the <span class='m'>baddies</span> list with our <span class='m'>baddies</span> loop, we will iterate through a copy of the <span class='m'>baddies</span> list.</p>
<p>Remember that a list slice will evaluate a copy of a list's items. For example, <span class='m'>spam[2:4]</span> will return a new list with the items from index <span class='m'>2</span> up to (but not including) index <span class='m'>4</span>. Leaving the first index blank will indicate that index <span class='m'>0</span> should be used. For example, <span class='m'>spam[:4]</span> will return a list with items from the start of the list up to (but not including) the item at index <span class='m'>4</span>. Leaving the second index blank will indicate that up to (and including) the last index should be used. For example, <span class='m'>spam[2:]</span> will return a list with items from index <span class='m'>2</span> all the way to (and including) the last item in the list.</p>
<p>But leaving both indexes in the slice blank is a way to represent the entire list. The <span class='m'>baddies[:]</span> expression is a list slice of the whole list, so it evaluates to a copy of the entire list. This is useful because while we are iterating on the copy of the list, we can modify the original list and remove any baddie data structures that have fallen past the bottom edge of the window.</p>
<p>Our <span class='m'>for</span> loop on line 163 uses a variable <span class='m'>b</span> for the current item in the iteration through <span class='m'>baddies[:]</span>.</p>
<div class='sourcecode'><ol start='164'>
<li> if b['rect'].top > WINDOWHEIGHT:</li>
<li> baddies.remove(b)</li>
</ol></div>
<p>Let's evaluate the expression <span class='m'>b['rect'].top</span>. <span class='m'>b</span> is the current baddie data structure from the <span class='m'>baddies[:]</span> list. Each baddie data structure in the list is a dictionary with a <span class='m'>'rect'</span> key, which stores a <span class='m'>Rect</span> object. So <span class='m'>b['rect']</span> is the <span class='m'>Rect</span> object for the baddie. Finally, the top is the Y-coordinate of the top edge of the rectangular area. Remember that in the coordinate system, the Y-coordinates increase going down. So <span class='m'>b['rect'].top > WINDOWHEIGHT</span> will check if the top edge of the baddie is below the bottom of the window.</p>
<p>If this condition is <span class='m'>True</span>, then the we will remove the baddie data structure from the baddies list.</p>
<h3 id="DrawingtheWindow">Drawing the Window</h3>
<p>It isn't enough that our game updates the state of the game world in its memory. Our program will also have to display the game world to the player. We can do this by drawing the graphics of the baddies and player's character on the screen. Because the game loop is executed several times a second, drawing the baddies and player in new positions makes their movement look smooth and natural. But every element on the screen must be drawn one at a time by calling the appropriate Pygame function.</p>
<div class='sourcecode'><ol start='167'>
<li> # Draw the game world on the window.</li>
<li> windowSurface.fill(BACKGROUNDCOLOR)</li>
</ol></div>
<p>Now that we have updated all the data structures for the baddies and the player's character, let's draw everything on the screen. First, before we draw anything else on the <span class='m'>Surface</span> object referred to by <span class='m'>windowSurface</span>, we want to black out the entire screen to erase anything drawn on it in a previous iteration through the game loop.</p>
<p>Remember that the <span class='m'>Surface</span> object in <span class='m'>windowSurface</span> is the special <span class='m'>Surface</span> object because it was the one returned by <span class='m'>pygame.display.set_mode()</span>. This means that anything drawn on that <span class='m'>Surface</span> object will appear on the screen, but only after the <span class='m'>pygame.display.update()</span> function is called.</p>
<h3 id="DrawingthePlayersScore">Drawing the Player's Score</h3>
<div class='sourcecode'><ol start='170'>
<li> # Draw the score and top score.</li>
<li> drawText('Score: %s' % (score), font, windowSurface, 10, 0)</li>
<li> drawText('Top Score: %s' % (topScore), font, windowSurface, 10, 40)</li>
</ol></div>
<p>Next we will render the text for score and top score to the top left corner of the window. The <span class='m'>'Score: %s' % (score)</span> uses string interpolation to insert the value in the score variable into the string. This is the same thing as <span class='m'>'Score: ' + str(score)</span>. We pass this string, the <span class='m'>Font</span> object stored in the <span class='m'>font</span> variable, the <span class='m'>Surface</span> object on which to draw the text on, and the X- and Y-coordinates of where the text should be placed. Remember that our <span class='m'>drawText()</span> will handle the call to the <span class='m'>render()</span> and <span class='m'>blit()</span> methods.</p>
<p>For the top score, we do the exact same thing. We pass <span class='m'>40</span> for the Y-coordinate instead of <span class='m'>0</span> (like we do for the score) so that the top score text appears beneath the score text.</p>
<h3 id="DrawingthePlayersCharacter">Drawing the Player's Character</h3>
<div class='sourcecode'><ol start='174'>
<li> # Draw the player's rectangle</li>
<li> windowSurface.blit(playerImage, playerRect)</li>
</ol></div>
<p>Remember that the information about the player is kept in two different variables. <span class='m'>playerImage</span> is a <span class='m'>Surface</span> object that contains all the colored pixels that make up the player's character's image. <span class='m'>playerRect</span> is a <span class='m'>Rect</span> object that stores the information about the size and location of the player's character.</p>
<p>We call the <span class='m'>blit()</span> method on <span class='m'>windowSurface</span> and pass <span class='m'>playerImage</span> and <span class='m'>playerRect</span>. This draws the player character's image on <span class='m'>windowSurface</span> at the appropriate location.</p>
<div class='sourcecode'><ol start='177'>
<li> # Draw each baddie</li>
<li> for b in baddies:</li>
<li> windowSurface.blit(b['surface'], b['rect'])</li>
</ol></div>
<p>We use a <span class='m'>for</span> loop here to draw every baddie on the <span class='m'>windowSurface</span> object. Remember that each item in the <span class='m'>baddies</span> list is a dictionary with <span class='m'>'surface'</span> and <span class='m'>'rect'</span> keys containing the <span class='m'>Surface</span> object with the baddie image and the <span class='m'>Rect</span> object with the position and size information, respectively.</p>
<div class='sourcecode'><ol start='181'>
<li> pygame.display.update()</li>
</ol></div>
<p>Now that we have finished drawing everything to the <span class='m'>windowSurface</span> object, we should draw this surface to the screen with a call to <span class='m'>pygame.display.update()</span>.</p>
<h3 id="CollisionDetection">Collision Detection</h3>
<div class='sourcecode'><ol start='183'>
<li> # Check if any of the baddies have hit the player.</li>
<li> if playerHasHitBaddie(playerRect, baddies):</li>
<li> if score > topScore:</li>
<li> topScore = score # set new top score</li>
<li> break</li>
</ol></div>
<p>Now let's check if the player has collided with any of the baddies. We already wrote a function to check for this: <span class='m'>playerHasHitBaddie()</span>. This function will return <span class='m'>True</span> if the player's character has collided with any of the baddies in the <span class='m'>baddies</span> list. Otherwise, the function will return <span class='m'>False</span>.</p>
<p>If the player's character has hit a baddie, then we check if the player's current score is greater than the top score. If it is, we set the new top score to be the player's current score. Either way, we break out of the game loop. The program's execution will jump down to line 191.</p>
<div class='sourcecode'><ol start='189'>
<li> mainClock.tick(FPS)</li>
</ol></div>
<p>To keep the computer from running through the game loop as fast as possible (which would be much too fast for the player to keep up with), we call <span class='m'>mainClock.tick()</span> to pause for a brief amount of time. The pause will be long enough to ensure that about <span class='m'>40</span> (the value we stored inside the <span class='m'>FPS</span> variable) iterations through the game loop occur each second.</p>
<h3 id="TheGameOverScreen">The Game Over Screen</h3>
<div class='sourcecode'><ol start='191'>
<li> # Stop the game and show the "Game Over" screen.</li>
<li> pygame.mixer.music.stop()</li>
<li> gameOverSound.play()</li>
</ol></div>
<p>When the player loses, we want to stop playing the background music and play the "game over" sound effect. We call the <span class='m'>stop()</span> function in the <span class='m'>pygame.mixer.music</span> module to stop the background music. Then we call the <span class='m'>play()</span> method on the <span class='m'>Sound</span> object stored in <span class='m'>gameOverSound</span>.</p>
<div class='sourcecode'><ol start='195'>
<li> drawText('GAME OVER', font, windowSurface, (WINDOWWIDTH / 3), (WINDOWHEIGHT / 3))</li>
<li> drawText('Press a key to play again.', font, windowSurface, (WINDOWWIDTH / 3) - 80, (WINDOWHEIGHT / 3) + 50)</li>
<li> pygame.display.update()</li>
<li> waitForPlayerToPressKey()</li>
</ol></div>
<p>Now we want to display text on the window to tell the player that the game is over, and they should press a key to start playing a new game. The two calls to our <span class='m'>drawText()</span> function will draw this text to the <span class='m'>windowSurface</span> object, and the call to <span class='m'>pygame.display.update()</span> will draw this <span class='m'>Surface</span> object to the screen.</p>
<p>After displaying this text, we want the game to stop until the player presses a key, so we call our <span class='m'>waitForPlayerToPressKey()</span> function.</p>
<div class='sourcecode'><ol start='200'>
<li> gameOverSound.stop()</li>
</ol></div>
<p>After the player presses a key, the program execution will return from the <span class='m'>waitForPlayerToPressKey()</span> call on line 198. Depending on how long the player takes to press a key, the "game over" sound effect may or may not still be playing. We want to stop this sound effect before this loop ends and we start a new game, so we have a call to <span class='m'>gameOverSound.stop()</span> here.</p>
<h2 id="ModifyingtheDodgerGame">Modifying the Dodger Game</h2>
<p>That's it for our graphical game. You may find that the game is too easy or too hard. But the game is very easy to modify because we took the time to use constant variables instead of typing in the values directly. Now all we need to do to change the game is modify the value set in the constant variables.</p>
<p>For example, if you want the game to run slower in general, change the <span class='m'>FPS</span> variable on line 8 to a smaller value such as <span class='m'>20</span>. This will make both the baddies and the player's character move slower since the game loop will only be executed 20 times a second instead of 40.</p>
<p>If you just want to slow down the baddies and not the player, then change <span class='m'>BADDIEMAXSPEED</span> to a smaller value such as <span class='m'>4</span>. This will make all the baddies move between 1 (the value in <span class='m'>BADDIEMINSPEED</span>) and 4 pixels per iteration through the game loop instead of 1 and 8.</p>
<p>If you want the game to have fewer but larger baddies instead of many fast baddies, then increase <span class='m'>ADDNEWBADDIERATE</span> to <span class='m'>12</span>, <span class='m'>BADDIEMINSIZE</span> to <span class='m'>40</span>, and <span class='m'>BADDIEMAXSIZE</span> to <span class='m'>80</span>. Now that baddies are being added every 12 iterations through the game loop instead of every 6 iterations, there will be half as many baddies as before. But to keep the game interesting, the baddies are now much larger than before.</p>
<p>While the basic game remains the same, you can modify any of the constant variables to drastically affect the behavior of the game. Keep trying out new values for the constant variables until you find a set of values you like the best.</p>
<h2 id="SummaryCreatingYourOwnGames">Summary: Creating Your Own Games</h2>
<p>Unlike our previous text-based games, Dodger really looks like the kind of modern computer game we usually play. It has graphics and music and uses the mouse. While Pygame provides functions and data types as building blocks, it is you the programmer who puts them together to create fun, interactive games.</p>
<p>And it is all because you know exactly how to instruct the computer to do it, step by step, line by line. You can speak the computer's language, and get it to do large amounts of number crunching and drawing for you. This is a very useful skill, and I hope you will continue to learn more about Python programming. (And there is still more to learn!)</p>
<p>Here are several websites that can teach you more about programming Python:</p>
<ul>
<li><a href='http://www.python.org/doc/'>http://www.python.org/doc/</a> - More Python tutorials and the documentation of all the Python modules and functions.</li>
<li><a href='http://www.pygame.org/docs/'>http://www.pygame.org/docs/</a> - Complete documentation on the modules and functions for Pygame.</li>
<li><a href='http://inventwithpython.com'>http://inventwithpython.com</a> - This book's website, which includes all the source code for these programs and additional information. This site also has the image and sound files used in the Pygame programs.</li>
<li><a href='http://inventwithpython.com/traces'>http://inventwithpython.com/traces</a> - A web application that helps you trace through the execution of the programs in this book, step by step.</li>
<li><a href='http://inventwithpython.com/videos'>http://inventwithpython.com/videos</a> - Videos that accompany the programs in this book.</li>
<li><a href='http://gamedevlessons.com'>http://gamedevlessons.com</a> - A helpful website about how to design and program video games.</li>
<li><a href='<a href="mailto:[email protected]">[email protected]</a>'><a href="mailto:[email protected]">[email protected]</a></a> - The author's email address. Feel free to email Al your questions about this book or about Python programming.</li>
</ul>
<p>Or you can find out more about Python by searching the World Wide Web. Go to the search engine website <!--fakelink-->http://google.com<!--/fakelink--> and search for "Python programming" or "Python tutorials" to find web sites that can teach you more about Python programming.</p>
<p>Now get going and invent your own games. And good luck!</p>
<table border='0' width='100%'><tr><td><a href='chapter19.html'>Go to Chapter 19 - Sound and Images</a></td><td align='right'></td></tr></table>
<div style='height: 310px;'><a href='http://www.amazon.com/Invent-Your-Computer-Games-Python/dp/0982106017/'><img src='images/buyad.png' align='right'></a></div>
</body>
</html>