1 /*  $Id: CosmoSmashEngine.cpp,v 1.70 2017/12/09 18:20:43 sarrazip Exp $
2 
3     cosmosmash - A space rock shooting video game.
4     Copyright (C) 2000-2011 Pierre Sarrazin <http://sarrazip.com/>
5 
6     This program is free software; you can redistribute it and/or
7     modify it under the terms of the GNU General Public License
8     as published by the Free Software Foundation; either version 2
9     of the License, or (at your option) any later version.
10 
11     This program is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15 
16     You should have received a copy of the GNU General Public
17     License along with this program; if not, write to the Free
18     Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19     Boston, MA  02110-1301, USA.
20 */
21 
22 #include "CosmoSmashEngine.h"
23 
24 #include "Controller.h"
25 
26 #include <flatzebra/PixmapLoadError.h>
27 
28 #include <assert.h>
29 #include <stdlib.h>
30 #include <stdio.h>
31 #include <string.h>
32 #include <ctype.h>
33 #include <math.h>
34 #include <stdint.h>
35 
36 #include <map>
37 #include <vector>
38 #include <string>
39 #include <algorithm>
40 #include <iomanip>
41 #include <sstream>
42 
43 #include "base.xpm"
44 #include "boosted-base.xpm"
45 #include "basebullet.xpm"
46 #include "bigrock0.xpm"
47 #include "bigrock1.xpm"
48 #include "bigrock2.xpm"
49 #include "bigrock3.xpm"
50 #include "bigrock4.xpm"
51 #include "bigrock5.xpm"
52 #include "smallrock0.xpm"
53 #include "smallrock1.xpm"
54 #include "smallrock2.xpm"
55 #include "smallrock3.xpm"
56 #include "smallrock4.xpm"
57 #include "smallrock5.xpm"
58 #include "explosion0.xpm"
59 #include "explosion1.xpm"
60 #include "bigspinner_backslash.xpm"
61 #include "bigspinner_horizontal.xpm"
62 #include "bigspinner_slash.xpm"
63 #include "bigspinner_vertical.xpm"
64 #include "smallspinner_backslash.xpm"
65 #include "smallspinner_horizontal.xpm"
66 #include "smallspinner_slash.xpm"
67 #include "smallspinner_vertical.xpm"
68 #include "pulsar_empty.xpm"
69 #include "pulsar_mid.xpm"
70 #include "pulsar_full.xpm"
71 #include "saucer0.xpm"
72 #include "saucer1.xpm"
73 #include "saucerbullet_empty.xpm"
74 #include "saucerbullet_mid.xpm"
75 #include "saucerbullet_full.xpm"
76 #include "question0.xpm"
77 #include "question1.xpm"
78 
79 using namespace std;
80 using namespace flatzebra;
81 
82 #ifdef _MSC_VER
83     #define snprintf _snprintf
84 #endif
85 
86 
87 /*  Various constants:
88 */
89 
90 static const size_t
91 FRAMES_PER_SECOND = 20;
92 
93 static const Couple theDrawingPixmapSize(WINDOW_WIDTH, WINDOW_HEIGHT);
94 
95 
96 static const int
97 PLAYER_SPEED = 15,
98 PLAYER_BULLET_SPEED = 36,
99 PULSAR_SPEED = 20,
100 SAUCER_SPEED = 8,
101 SAUCER_BULLET_SPEED = 20,
102 QUESTION_SPEED = 16;
103 
104 static const int
105 SAUCER_Y_POS = 75,
106 NUM_INIT_LIVES = 5;
107 
108 static const double TWO_PI = 8.0 * atan(1.0);
109 
110 static const long
111 SCORE_LOST_BASE = -100,
112 SCORE_BIG_ROCK_LANDING = -5,
113 SCORE_SMALL_ROCK_LANDING = -10,
114 SCORE_BIG_ROCK = 10,
115 SCORE_SMALL_ROCK = 20,
116 SCORE_BIG_SPINNER = 40,
117 SCORE_SMALL_SPINNER = 80,
118 SCORE_PULSAR = 50,
119 SCORE_SAUCER_BULLET = 50,
120 SCORE_SAUCER = 100,
121 SCORE_QUESTION = 50;
122 
123 
124 // Defined as constants for efficiency:
125 static const size_t
126 NUM_SPINNER_IMAGES = 4,
127 NUM_PULSAR_IMAGES = 3,
128 NUM_ROCK_IMAGES = 6,
129 NUM_SAUCER_IMAGES = 2,
130 NUM_SAUCER_BULLET_IMAGES = 3,
131 NUM_STARS = 40,
132 NUM_MOUNTAIN_RANGE_POINTS = 10;
133 
134 
135 inline int
getLevelNum(long score)136 getLevelNum(long score)
137 {
138     if (score >= 100000)
139         return 6;
140     if (score >= 50000)
141         return 5;
142     if (score >= 20000)
143         return 4;
144     if (score >= 5000)
145         return 3;
146     if (score >= 1000)
147         return 2;
148     return 1;
149 }
150 
151 
152 inline int
discreteUniformRand(const Couple & limits)153 discreteUniformRand(const Couple &limits)
154 /*
155     Returns a random integer between limits.x and limits.y inclusively.
156     limits.x must be <= limits.y.
157 */
158 {
159     return rand() % (limits.y - limits.x + 1) + limits.x;
160 }
161 
162 
163 inline int
rnd(int lowerLimit,int upperLimit)164 rnd(int lowerLimit, int upperLimit)
165 {
166     return rand() % (upperLimit - lowerLimit + 1) + lowerLimit;
167 }
168 
169 
170 inline int
mulRound(int n,double x)171 mulRound(int n, double x)
172 /*  Multiply the arguments, and round the result to the nearest integer
173     according to its sign.
174 */
175 {
176     double product = n * x;
177     if (product >= 0.0)
178         return (int) (product + 0.5);
179     return (int) (product - 0.5);
180 }
181 
182 inline int
183 (min)(int a, int b)
184 {
185         return (a < b ? a : b);
186 }
187 
188 
189 inline int
190 (max)(int a, int b)
191 {
192         return (a > b ? a : b);
193 }
194 
195 
196 inline int
divideAmplitude(int n,int divisor)197 divideAmplitude(int n, int divisor)
198 {
199     if (n == 0)
200         return 0;
201     if (n < 0)
202         return min(n / divisor, -1);
203     return max(n / divisor, +1);
204 }
205 
206 
207 inline Couple
divideAmplitude(const Couple & c,int divisor)208 divideAmplitude(const Couple &c, int divisor)
209 {
210     return Couple(divideAmplitude(c.x, divisor), divideAmplitude(c.y, divisor));
211 }
212 
213 
214 inline int
decrementAmplitude(int n)215 decrementAmplitude(int n)
216 {
217     if (n > 0)
218         return n - 1;
219     if (n < 0)
220         return n + 1;
221     return 0;
222 }
223 
224 
225 inline Couple
decrementAmplitude(const Couple & c)226 decrementAmplitude(const Couple &c)
227 {
228     return Couple(decrementAmplitude(c.x), decrementAmplitude(c.y));
229 }
230 
231 
232 inline void
removeNullElementsFromSpriteList(SpriteList & slist)233 removeNullElementsFromSpriteList(SpriteList &slist)
234 {
235     SpriteList::iterator it =
236                     remove(slist.begin(), slist.end(), (Sprite *) NULL);
237         /*  remove() has "packed" the remaining elements at the beginning
238             of the sequence, but has not shortened the list.  This must
239             be done by a call to the erase() method.  Doesn't this seem
240             unintuitive?  I thought remove() removed stuff.
241             @sarrazip 20010501
242         */
243     slist.erase(it, slist.end());
244 }
245 
246 
247 ///////////////////////////////////////////////////////////////////////////////
248 
249 
CosmoSmashEngine(long initialScore,bool _mirrorHyperspace,bool _useSound,bool fullScreen,Controller * _controller,ostream * _gameScriptStream,bool processActiveEvent)250 CosmoSmashEngine::CosmoSmashEngine(
251                     long initialScore,
252                     bool _mirrorHyperspace, bool _useSound,
253                     bool fullScreen, Controller *_controller,
254                     ostream *_gameScriptStream,
255                     bool processActiveEvent)
256   : GameEngine(theDrawingPixmapSize, "Cosmosmash", fullScreen, processActiveEvent),
257                   // may throw string exception
258 
259     pauseState(PLAYING),
260     tickCount(0),
261     useSound(_useSound),
262 
263     levelColors(),
264     whiteColor(SDL_MapRGB(theSDLScreen->format, 0xff, 0xff, 0xff)),
265     greenColor(SDL_MapRGB(theSDLScreen->format, 0x00, 0xff, 0x00)),
266     explosionBGColor(SDL_MapRGB(theSDLScreen->format, 0xff, 0x80, 0x00)),
267 
268     mirrorHyperspace(_mirrorHyperspace),
269     useGameExtensions(false),
270     playerBoostTicks(0),
271     playerBoostType(NO_BOOST),
272 
273     playerPA(2),
274     playerSprite(NULL),
275 
276     baseBulletPA(1),
277     baseBulletSprites(),
278 
279     bigRockPA(NUM_ROCK_IMAGES),
280     smallRockPA(NUM_ROCK_IMAGES),
281     rockSprites(),
282 
283     bigSpinnerPA(NUM_SPINNER_IMAGES),
284     smallSpinnerPA(NUM_SPINNER_IMAGES),
285     spinnerSprites(),
286 
287     pulsarPA(NUM_PULSAR_IMAGES),
288     pulsarSprites(),
289 
290     saucerPA(NUM_SAUCER_IMAGES),
291     saucerSprites(),
292 
293     saucerBulletPA(NUM_SAUCER_BULLET_IMAGES),
294     saucerBulletSprites(),
295 
296     explosionPA(2),
297     explosionSprites(),
298 
299     questionPA(2),
300     questionSprites(),
301 
302     groundPos(0),
303     groundHeight(0),
304     mountainTopPos(0),
305 
306     controller(_controller),
307     quitKS(SDLK_ESCAPE),
308 
309     initScore(initialScore),
310     theScore(0),
311     thePeakScore(0),
312     theLevel(0),
313     scoreAreaPos(),
314     peakScoreAreaPos(),
315     levelNumAreaPos(),
316 
317     numLives(0),
318     updateNumLives(true),
319     numLivesAreaPos(),
320 
321     timeBeforeNextPulsar(0),
322     timeBeforeNextSaucer(0),
323     timeBeforeNextQuestion(0),
324 
325     starPositions(),
326     mountainRangePositions(),
327 
328     theSoundMixer(NULL),
329     playerHitSound(),
330     pulsarBeepSound(),
331     saucerShootingSound(),
332     hyperspaceSound(),
333     timeBeforeNextCadenceSound(0),
334 
335     gameScriptStream(_gameScriptStream)
336 {
337     assert(controller != NULL);
338 
339     levelColors[0] = 0;  // unused
340     levelColors[1] = SDL_MapRGB(theSDLScreen->format, 0x00, 0x00, 0x00);
341     levelColors[2] = SDL_MapRGB(theSDLScreen->format, 0x00, 0x00, 0xff);
342     levelColors[3] = SDL_MapRGB(theSDLScreen->format, 0x80, 0x00, 0x80);
343     levelColors[4] = SDL_MapRGB(theSDLScreen->format, 0x00, 0x7f, 0x7f);
344     levelColors[5] = SDL_MapRGB(theSDLScreen->format, 0x7f, 0x7f, 0x7f);
345     levelColors[6] = SDL_MapRGB(theSDLScreen->format, 0x00, 0x00, 0x00);
346 
347     assert(initScore >= 0);
348     assert(theSDLScreen != NULL);
349     assert(theSDLScreen->format != NULL);
350 
351     try
352     {
353         initializeSprites();
354     }
355     catch (PixmapLoadError &e)
356     {
357         string msg = "Could not load pixmap " + e.getFilename();
358         throw msg;
359     }
360 
361     initializeMisc(useSound);  // may throw a 'string'
362 }
363 
364 
~CosmoSmashEngine()365 CosmoSmashEngine::~CosmoSmashEngine()
366 {
367     // Sprites must be killed to avoid memory leaks.
368     killAllAutomaticCharacters();
369     killSpritesInList(baseBulletSprites);
370 
371     roundbeetle::Engine::destroy();
372     theSoundMixer = NULL;
373 
374     delete controller;
375     delete playerSprite;
376 }
377 
378 
379 void
initializeSprites()380 CosmoSmashEngine::initializeSprites()
381 {
382     groundPos = theDrawingPixmapSize.y - 50;
383     groundHeight = 10;
384 
385     // Load some pixmaps:
386     loadPixmap(base_xpm, playerPA, 0);
387     loadPixmap(boosted_base_xpm, playerPA, 1);
388 
389     // Create and position the player sprite:
390     playerSprite = new Sprite(playerPA,
391             Couple((theDrawingPixmapSize.x - playerPA.getImageSize().x) / 2,
392                     groundPos - playerPA.getImageSize().y),
393             Couple(0, 0), Couple(0, 0),
394             Couple(6, 6), playerPA.getImageSize() - Couple(12, 12));
395         // this Sprite object does not own playerPA
396 
397 
398     // Base bullets:
399     loadPixmap(basebullet_xpm, baseBulletPA, 0);
400 
401     // Rocks:
402     loadPixmap(bigrock0_xpm, bigRockPA, 0);
403     loadPixmap(bigrock1_xpm, bigRockPA, 1);
404     loadPixmap(bigrock2_xpm, bigRockPA, 2);
405     loadPixmap(bigrock3_xpm, bigRockPA, 3);
406     loadPixmap(bigrock4_xpm, bigRockPA, 4);
407     loadPixmap(bigrock5_xpm, bigRockPA, 5);
408     loadPixmap(smallrock0_xpm, smallRockPA, 0);
409     loadPixmap(smallrock1_xpm, smallRockPA, 1);
410     loadPixmap(smallrock2_xpm, smallRockPA, 2);
411     loadPixmap(smallrock3_xpm, smallRockPA, 3);
412     loadPixmap(smallrock4_xpm, smallRockPA, 4);
413     loadPixmap(smallrock5_xpm, smallRockPA, 5);
414 
415     // Big spinner:
416     loadPixmap(bigspinner_horizontal_xpm, bigSpinnerPA, 0);
417     loadPixmap(bigspinner_slash_xpm,      bigSpinnerPA, 1);
418     loadPixmap(bigspinner_vertical_xpm,   bigSpinnerPA, 2);
419     loadPixmap(bigspinner_backslash_xpm,  bigSpinnerPA, 3);
420 
421     // Small spinner:
422     loadPixmap(smallspinner_horizontal_xpm, smallSpinnerPA, 0);
423     loadPixmap(smallspinner_slash_xpm,      smallSpinnerPA, 1);
424     loadPixmap(smallspinner_vertical_xpm,   smallSpinnerPA, 2);
425     loadPixmap(smallspinner_backslash_xpm,  smallSpinnerPA, 3);
426 
427     // Pulsar:
428     loadPixmap(pulsar_empty_xpm, pulsarPA, 0);
429     loadPixmap(pulsar_mid_xpm,   pulsarPA, 1);
430     loadPixmap(pulsar_full_xpm,  pulsarPA, 2);
431 
432     // Saucer:
433     loadPixmap(saucer0_xpm, saucerPA, 0);
434     loadPixmap(saucer1_xpm, saucerPA, 1);
435 
436     // Saucer bullets:
437     loadPixmap(saucerbullet_empty_xpm, saucerBulletPA, 0);
438     loadPixmap(saucerbullet_mid_xpm,   saucerBulletPA, 1);
439     loadPixmap(saucerbullet_full_xpm,  saucerBulletPA, 2);
440 
441     // Explosion:
442     loadPixmap(explosion0_xpm, explosionPA, 0);
443     loadPixmap(explosion1_xpm, explosionPA, 1);
444 
445     // Question (surprise) sprites:
446     loadPixmap(question0_xpm, questionPA, 0);
447     loadPixmap(question1_xpm, questionPA, 1);
448 }
449 
450 
451 static
452 string
getDir(const char * defaultValue,const char * envVarName)453 getDir(const char *defaultValue, const char *envVarName)
454 {
455     string dir;
456     const char *s = getenv(envVarName);
457     if (s != NULL)
458         dir = s;
459     else
460         dir = defaultValue;
461 
462     if (!dir.empty() && dir[dir.length() - 1] != '/')
463         dir += '/';
464 
465     return dir;
466 }
467 
468 
469 void
initializeMisc(bool useSound)470 CosmoSmashEngine::initializeMisc(bool useSound)
471 /*
472     Throws an error message in a 'string' if an error occurs.
473 */
474 {
475     theScore = initScore;
476     thePeakScore = initScore;
477     theLevel = getLevelNum(theScore);
478     scoreAreaPos = Couple(4, theDrawingPixmapSize.y - 31);
479     peakScoreAreaPos = scoreAreaPos + Couple(0, 15);
480     numLives = 0;
481     updateNumLives = true;
482     numLivesAreaPos = Couple(theDrawingPixmapSize.x - 79, peakScoreAreaPos.y);
483     levelNumAreaPos = Couple(numLivesAreaPos.x, scoreAreaPos.y);
484     playerBoostTicks = 0;
485     playerBoostType = NO_BOOST;
486 
487 
488     setTimeBeforeNextPulsar();
489     setTimeBeforeNextSaucer();
490     setTimeBeforeNextQuestion();
491 
492 
493     /*  Initialize mountain range positions:
494     */
495     int y0 = groundPos - 100;
496     int y1 = groundPos -  70;
497     int y2 = groundPos -  50;
498 
499     mountainTopPos = y0;
500 
501     size_t i;
502     for (i = 0; i < NUM_MOUNTAIN_RANGE_POINTS; i++)
503     {
504         int x = i * theDrawingPixmapSize.x / (NUM_MOUNTAIN_RANGE_POINTS - 1);
505         int y;
506         if (i & 1)
507             y = rnd(y0, y1);
508         else
509             y = rnd(y1, y2);
510         mountainRangePositions.push_back(Couple(x, y));
511     }
512 
513 
514     /*  Initialize star positions:
515     */
516     const int padding = 8;
517     for (i = 0; i < NUM_STARS; i++)
518     {
519         int x = rnd(padding, theDrawingPixmapSize.x - padding);
520         int y = rnd(padding, mountainTopPos - 10);
521         starPositions.push_back(Couple(x, y));
522     }
523 
524 
525     /*  Sound effects:
526     */
527     if (useSound)
528     {
529         string pkgsounddir = getDir(PKGSOUNDDIR, "PKGSOUNDDIR");
530 
531         // A high sample rate is necessary for the sound renderer,
532         // to make sure the spinner square wave's frequency
533         // decreases smoothly to the human ear.
534         //
535         int status = roundbeetle::Engine::create(44100, 0, NULL);
536         if (status != 0)
537         {
538             stringstream ss;
539             ss << "RoundBeetle sound engine initialization error #" << status;
540             throw ss.str();
541         }
542 
543         theSoundMixer = &roundbeetle::Engine::instance();
544         assert(theSoundMixer != NULL);
545         theSoundMixer->postBusInit();
546 
547         try
548         {
549             for (size_t i = 0; i < NUM_ROCK_HIT_SOUNDS; i++)
550             {
551                 string filename = pkgsounddir + "rock-hit-" + char('0' + i) + ".wav";
552                 rockHitSounds[i].init(filename);
553             }
554 
555             playerHitSound.init(pkgsounddir + "player-hit.wav");
556             pulsarBeepSound.init(pkgsounddir + "pulsar-beep.wav");
557             saucerShootingSound.init(pkgsounddir + "saucer-shooting.wav");
558             hyperspaceSound.init(pkgsounddir + "hyperspace.wav");
559 
560             timeBeforeNextCadenceSound = 0;
561         }
562         catch (const string &e)
563         {
564             throw e;
565         }
566     }
567 }
568 
569 
570 void
setTimeBeforeNextPulsar()571 CosmoSmashEngine::setTimeBeforeNextPulsar()
572 {
573     int min = 13 - 2 * theLevel;
574     int max = 53 - 8 * theLevel;
575     timeBeforeNextPulsar = rnd(20 * min, 20 * max);
576 }
577 
578 
579 void
setTimeBeforeNextSaucer()580 CosmoSmashEngine::setTimeBeforeNextSaucer()
581 {
582     int lev = theLevel - 4;
583     int min = 5 - lev * 2;
584     int max = 30 - lev * 10;
585     timeBeforeNextSaucer = rnd(20 * min, 20 * max);
586 }
587 
588 
589 void
setTimeBeforeNextQuestion()590 CosmoSmashEngine::setTimeBeforeNextQuestion()
591 {
592     static const char *q = getenv("Q");
593     timeBeforeNextQuestion = (q != NULL ? 100 : rnd(20 * 10, 20 * 30));
594 }
595 
596 
597 ///////////////////////////////////////////////////////////////////////////////
598 
599 
600 /*virtual*/
601 void
processKey(SDLKey keysym,bool pressed)602 CosmoSmashEngine::processKey(SDLKey keysym, bool pressed)
603 {
604     controller->processKey(keysym, pressed);
605 
606     quitKS.check(keysym, pressed);
607 }
608 
609 
610 /*virtual*/
611 void
processActivation(bool appActive)612 CosmoSmashEngine::processActivation(bool appActive)
613 {
614     if (!appActive)  // if loss of focus:
615     {
616         assert(pauseState == PLAYING || pauseState == AWAITING_RESUME);
617 
618         if (pauseState == PLAYING)
619         {
620             pause();
621 
622             if (numLives > 0)
623             {
624                 // Display the pause message here because tick() will not be called
625                 // until this method is called again to reactivate the application.
626                 //
627                 displayPauseMessage();
628             }
629         }
630 
631         pauseState = LOST_FOCUS;
632     }
633     else  // gaining focus:
634     {
635         assert(pauseState == LOST_FOCUS);
636 
637         // No resume here. Game waits for user to press resume key.
638         //
639         pauseState = AWAITING_RESUME;
640     }
641 
642     registerTickEnd();
643 }
644 
645 
646 // CAUTION: does not change 'pauseState', which must be managed
647 // by the caller.
648 //
649 void
pause()650 CosmoSmashEngine::pause()
651 {
652     registerCommand(COMMAND_PAUSE);
653     if (theSoundMixer != NULL)
654         (void) theSoundMixer->pauseEngine();
655 }
656 
657 
658 void
resume()659 CosmoSmashEngine::resume()
660 {
661     registerCommand(COMMAND_RESUME);
662     if (theSoundMixer != NULL)
663         (void) theSoundMixer->resumeEngine();
664 }
665 
666 
667 /*virtual*/
668 bool
tick()669 CosmoSmashEngine::tick()
670 {
671     if (quitKS.isPressed())
672         return false;
673 
674     controller->startOfTick();
675 
676     bool ret = true;
677 
678     if (controller->isFullScreenToggleRequested())
679         setVideoMode(theScreenSizeInPixels, !inFullScreenMode());  // ignore failure
680 
681     if (pauseState == AWAITING_RESUME)
682     {
683         if (controller->isResumeRequested())
684         {
685             resume();
686             pauseState = PLAYING;
687         }
688         else
689             displayPauseMessage();
690     }
691     else
692     {
693         tickCount++;
694 
695         detectCollisions();
696 
697         if (!animatePlayer())
698             ret = false;
699         else
700         {
701             animateAutomaticCharacters();
702             restoreBackground();
703             drawSprites();
704 
705 
706             /*  If the game is in demo mode, the Space bar starts a new game.
707             */
708             if (numLives == 0)
709             {
710                 if (controller->isStartRequested(useGameExtensions))
711                 {
712                     registerCommand(COMMAND_START);
713 
714                     displayStartMessage(false);
715 
716                     killAllAutomaticCharacters();
717                     centerPlayerSprite();
718                     theScore = initScore;
719                     thePeakScore = initScore;
720                     numLives = NUM_INIT_LIVES;
721 
722                     addToScore(0);
723                     addToNumLives(0);
724                 }
725                 else
726                 {
727                     /*  At the end of a game, wait for the last base to have
728                         finished exploding before displaying the start
729                         message again.
730                     */
731                     bool baseExploding = (playerSprite->getTimeToLive() != 0);
732                     if (! baseExploding)
733                         displayStartMessage(true);
734                 }
735             }
736             else
737                 playCadenceSound();
738         }
739     }
740 
741     controller->endOfTick();
742 
743     registerTickEnd();
744 
745     return ret;
746 }
747 
748 
749 void
fireBullet(Couple pos)750 CosmoSmashEngine::fireBullet(Couple pos)
751 {
752     Couple bbSize = baseBulletPA.getImageSize();
753     Sprite *s = new Sprite(baseBulletPA,
754                 pos, Couple(0, -PLAYER_BULLET_SPEED), Couple(0, 0),
755                 Couple(-4, 0), bbSize + Couple(4, 0));
756     baseBulletSprites.push_back(s);
757 
758     if (theLevel >= 5)  // double bullet in higher levels
759     {
760         int n = (theLevel == 5 ? bbSize.y / 2 : bbSize.y);
761         Couple pos2 = pos;
762         pos2.y -= n;
763         Sprite *s = new Sprite(baseBulletPA,
764                 pos2, Couple(0, -PLAYER_BULLET_SPEED), Couple(0, 0),
765                 Couple(0, 0), bbSize);
766         baseBulletSprites.push_back(s);
767     }
768 
769     if (playerBoostTicks > 0 && playerBoostType == TRIPLE_BULLETS)
770     {
771         int vx = 3;
772         Couple pos2 = pos;
773         pos2.x -= bbSize.x + 1;
774         Sprite *s = new Sprite(baseBulletPA,
775                     pos2, Couple(-vx, -PLAYER_BULLET_SPEED), Couple(0, 0),
776                     Couple(0, 0), bbSize);
777         baseBulletSprites.push_back(s);
778 
779         pos2 = pos;
780         pos2.x += bbSize.x + 1;
781         s = new Sprite(baseBulletPA,
782                     pos2, Couple(+vx, -PLAYER_BULLET_SPEED), Couple(0, 0),
783                     Couple(0, 0), bbSize);
784         baseBulletSprites.push_back(s);
785     }
786 }
787 
788 
789 bool
animatePlayer()790 CosmoSmashEngine::animatePlayer()
791 /*
792     Returns true if the game must continue, or false to have it stop.
793 */
794 {
795     // Inform the controller of the current state of the game:
796     {
797         controller->beginGameState();
798 
799         controller->setPlayerPos(playerSprite->getCenterPos());
800 
801         SpriteList::iterator its;
802         for (its = rockSprites.begin(); its != rockSprites.end(); its++)
803             controller->addRockPos((*its)->getCenterPos());
804         for (its = spinnerSprites.begin(); its != spinnerSprites.end(); its++)
805             controller->addSpinnerPos((*its)->getCenterPos());
806         for (its = pulsarSprites.begin(); its != pulsarSprites.end(); its++)
807             controller->addPulsarPos((*its)->getCenterPos());
808         for (its = saucerBulletSprites.begin(); its != saucerBulletSprites.end(); its++)
809             controller->addPulsarPos((*its)->getCenterPos());
810 
811         controller->endGameState();
812     }
813 
814     if (numLives > 0 && controller->isPauseRequested())
815     {
816         pause();
817         pauseState = AWAITING_RESUME;
818         return true;
819     }
820 
821     if (playerSprite->decTimeToLive() != 0)  // if player base exploding
822         return true;
823 
824     if (controller->isEndRequested())
825         numLives = 0;
826 
827     if (! playerIsActive())
828         return true;
829 
830 
831     Couple &playerPos = playerSprite->getPos();
832     const Couple &playerSize = playerSprite->getSize();
833 
834 
835     /*  Fire a bullet once in a while:
836     */
837     int ticksBetweenBullets =
838            (playerBoostTicks > 0 && playerBoostType == CLOSER_BULLETS ? 2 : 4);
839     if (tickCount % ticksBetweenBullets == 0 && controller->isShootingActive())
840     {
841         registerCommand(COMMAND_SHOOT);
842 
843         Couple bbSize = baseBulletPA.getImageSize();
844         Couple pos = playerPos + Couple((playerSize.x - bbSize.x) / 2, 0);
845 
846         fireBullet(pos);
847     }
848     if (playerBoostTicks > 0)
849         playerBoostTicks--;
850 
851 
852     Couple &playerSpeed = playerSprite->getSpeed();
853 
854     /*  Get and process user commands:
855     */
856 
857     playerSpeed.zero();
858 
859     if (controller->isHyperspaceRequested())
860     {
861         registerCommand(COMMAND_HYPERSPACE);
862 
863         Couple size = playerSprite->getSize();
864         int newX;
865         if (mirrorHyperspace)
866         {
867             // Mirror the player's position WRT the center:
868             Couple center = playerSprite->getCenterPos();
869             int newXCenter = theScreenSizeInPixels.x - center.x;
870             newX = newXCenter - size.x / 2;
871             if (newX < 0)
872                 newX = 0;  // just to be sure...
873         }
874         else
875             newX = rnd(0, theScreenSizeInPixels.x - size.x);
876 
877         Couple newPos(newX, groundPos - playerPA.getImageSize().y);
878         repositionPlayerSprite(newPos);
879 
880         playSoundEffect(hyperspaceSound);
881 
882         return true;
883     }
884 
885     if (controller->isLeftMoveRequested())
886     {
887         registerCommand(COMMAND_MOVE_LEFT);
888         playerSpeed.x = -PLAYER_SPEED;
889     }
890     if (controller->isRightMoveRequested())
891     {
892         registerCommand(COMMAND_MOVE_RIGHT);
893         playerSpeed.x = +PLAYER_SPEED;
894     }
895 
896     if (playerSpeed.isZero())
897         return true;
898 
899     Couple newPos = playerPos + playerSpeed;
900     if (newPos.x < 0)
901         newPos.x = 0;
902     else if (newPos.x + playerSize.x > theDrawingPixmapSize.x)
903         newPos.x = theDrawingPixmapSize.x - playerSize.x;
904 
905     playerPos = newPos;
906 
907     return true;
908 }
909 
910 
911 void
killSpritesInList(SpriteList & sl)912 CosmoSmashEngine::killSpritesInList(SpriteList &sl)
913 {
914     for (SpriteList::iterator its = sl.begin(); its != sl.end(); its++)
915         delete *its;
916 
917     sl.clear();
918     assert(sl.size() == 0);
919 }
920 
921 
922 void
centerPlayerSprite()923 CosmoSmashEngine::centerPlayerSprite()
924 {
925     Couple newPos(
926             (theDrawingPixmapSize.x - playerPA.getImageSize().x) / 2,
927             groundPos - playerPA.getImageSize().y);
928     repositionPlayerSprite(newPos);
929 }
930 
931 
932 void
repositionPlayerSprite(Couple newPos)933 CosmoSmashEngine::repositionPlayerSprite(Couple newPos)
934 {
935     playerSprite->setPos(newPos);
936 }
937 
938 
939 void
killAllAutomaticCharacters()940 CosmoSmashEngine::killAllAutomaticCharacters()
941 {
942     killSpritesInList(rockSprites);
943     killSpritesInList(spinnerSprites);
944     killSpritesInList(pulsarSprites);
945     killSpritesInList(saucerSprites);
946     killSpritesInList(saucerBulletSprites);
947     killSpritesInList(questionSprites);
948 }
949 
950 
951 void
killPlayerBase()952 CosmoSmashEngine::killPlayerBase()
953 {
954     static const char *noKillEnvVar = getenv("NOKILL");
955 
956     if (noKillEnvVar != NULL)
957             return;
958 
959     playSoundEffect(playerHitSound);
960 
961     addToScore(SCORE_LOST_BASE);
962 
963     Couple pos = playerSprite->getCenterPos();
964     pos.y = playerSprite->getLowerRightPos().y - 1;
965     static const Couple directions[] =
966     {
967         Couple(  10,   0 ),
968         Couple(   7,  -7 ),
969         Couple(   0, -10 ),
970         Couple(  -7,  -7 ),
971         Couple( -10,   0 ),
972         Couple(   0,   0 )  // zero marks the end
973     };
974 
975     const int explosionTime = 40;  // in ticks
976 
977     for (size_t i = 0; directions[i].isNonZero(); i++)
978     {
979         Sprite *explosion = createExplosionSprite(pos, explosionTime);
980         explosion->setSpeed(directions[i]);
981     }
982 
983     playerSprite->setTimeToLive(explosionTime);
984             /*  The player's "time to live" value is used to mark
985                 the period during which it is exploding. The player's
986                 sprite is not displayed during that time. It is
987                 repositioned for when it starts being displayed again.
988             */
989     centerPlayerSprite();
990     addToNumLives(-1);
991 
992     playerBoostTicks = 0;
993     playerBoostType = NO_BOOST;
994 
995     killAllAutomaticCharacters();
996             /*  We kill falling sprites to avoid situations where e.g.,
997                 spinners keep landing while the player is exploding.
998                 Such situations could make the player lose many lives in a row.
999             */
1000 
1001     killSpritesInList(baseBulletSprites);
1002 }
1003 
1004 
1005 inline bool
playerIsActive() const1006 CosmoSmashEngine::playerIsActive() const
1007 /*
1008     Returns true iff the player still has lives and not currently exploding...
1009 */
1010 {
1011     return (numLives > 0 && playerSprite->getTimeToLive() == 0);
1012 }
1013 
1014 
1015 pair<bool, size_t>
animateFallingObjects(SpriteList & sprites)1016 CosmoSmashEngine::animateFallingObjects(SpriteList &sprites)
1017 /*
1018     Moves the sprites in the designated sprite list.
1019 
1020     If one of the sprites collides with the player, the player dies
1021     and the sprite is eliminated.
1022     Sprites that land are eliminated.
1023     Sprites that go off to the left or right of the screen are
1024     silently eliminated.
1025 
1026     Returns a boolean value that indicates if the player dies, and the
1027     number of falling objects that landed.
1028 
1029     When this method exits, pointers to eliminated sprites have been
1030     removed from 'sprites'.
1031 */
1032 {
1033     bool playerDies = false;
1034     size_t numLandings = 0;  // number of objects that touch the ground
1035 
1036     for (SpriteList::iterator it = sprites.begin(); it != sprites.end(); it++)
1037     {
1038         Sprite *s = *it;
1039         assert(s != NULL);
1040         s->addSpeedToPos();
1041 
1042         if (playerIsActive() && s->collidesWithSprite(*playerSprite))
1043         {
1044             playerDies = true;
1045 
1046             delete s;
1047             *it = NULL;
1048             continue;
1049         }
1050 
1051         // Get y coordinate of bottom of sprite's collision box:
1052         int collBoxBottom = (s->getPos() + s->getCollBoxPos() + s->getCollBoxSize()).y;
1053         int height = groundPos - collBoxBottom;
1054 
1055         if (height <= - groundHeight)  // if sprite low enough, it dies
1056         {
1057             numLandings++;
1058 
1059             delete s;
1060             *it = NULL;
1061             continue;
1062         }
1063 
1064         const Couple newPos = s->getPos();
1065         if (newPos.x >= theScreenSizeInPixels.x
1066                         || newPos.x + s->getSize().x <= 0)
1067         {
1068             // object disappears without consequence
1069             delete s;
1070             *it = NULL;
1071             continue;
1072         }
1073     }
1074 
1075     removeNullElementsFromSpriteList(sprites);
1076 
1077     return pair<bool, size_t>(playerDies, numLandings);
1078 }
1079 
1080 
1081 void
animateSaucers()1082 CosmoSmashEngine::animateSaucers()
1083 {
1084     bool playerDies = false;
1085 
1086     for (SpriteList::iterator it = saucerSprites.begin();
1087                                 it != saucerSprites.end(); it++)
1088     {
1089         Sprite *saucer = *it;
1090         Couple &pos = saucer->getPos();
1091         const Couple &size = saucer->getSize();
1092 
1093         pos += saucer->getSpeed();
1094 
1095         // if the saucer is now off the screen:
1096         if (pos.x >= theScreenSizeInPixels.x || pos.x + size.x <= 0)
1097         {
1098             delete saucer;
1099             *it = NULL;
1100             continue;
1101         }
1102 
1103         // make saucer shoot periodically:
1104         if (tickCount % 8 == 0 && playerSprite->getTimeToLive() == 0)
1105         {
1106             const Couple size = saucerBulletPA.getImageSize();
1107             Couple bulletPos = pos;
1108 
1109             // Choose speed in direction of player base:
1110             Couple diff = playerSprite->getPos() - bulletPos;
1111             double k = SAUCER_BULLET_SPEED / hypot(diff.x, diff.y);
1112             Couple speed(mulRound(diff.x, k), mulRound(diff.y, k));
1113             speed = accelerate(speed);
1114             if (speed.isZero())
1115                 speed = Couple(0, SAUCER_BULLET_SPEED);
1116             Sprite *s = new Sprite(saucerBulletPA,
1117                                     pos, speed, Couple(0, 0),
1118                                     Couple(2, 2), size - Couple(2, 2));
1119             saucerBulletSprites.push_back(s);
1120 
1121             playSoundEffect(saucerShootingSound);
1122         }
1123     }
1124     removeNullElementsFromSpriteList(saucerSprites);
1125 
1126     if (playerDies)
1127         killPlayerBase();
1128 }
1129 
1130 
1131 void
startSpinnerSound(SpinnerSprite & s)1132 CosmoSmashEngine::startSpinnerSound(SpinnerSprite &s)
1133 {
1134     if (theSoundMixer == NULL)
1135         return;
1136     roundbeetle::ADSR adsr(.0f, .1f, 0, 0, 3600, 0);  // longer than needed
1137     int req = theSoundMixer->requestSquareWave(s.getLinearMovingFreq(),
1138                                                adsr,
1139                                                1,
1140                                                theSoundMixer->getMainBus());
1141     s.setSoundReq(req);
1142 }
1143 
1144 
1145 void
animateAutomaticCharacters()1146 CosmoSmashEngine::animateAutomaticCharacters()
1147 {
1148     /*  If there are not enough ordinary falling sprites, add some:
1149     */
1150     size_t numFallingObjects = rockSprites.size() + spinnerSprites.size();
1151     size_t min = (theLevel < 5 ? 2 : 3);
1152     if (numFallingObjects < min && playerSprite->getTimeToLive() == 0)
1153     {
1154         int rockFreq = 8;
1155         if (theLevel >= 2)
1156             --rockFreq;
1157         if (theLevel >= 3)
1158             --rockFreq;
1159         if (rand() % rockFreq == 0)
1160         {
1161             // New spinner (more small spinners starting at level 2):
1162             int n = (theLevel < 2 ? 3 : 2);
1163             bool small = (rand() % n == 0);
1164             const PixmapArray *pa = (small ? &smallSpinnerPA : &bigSpinnerPA);
1165             const Couple size = pa->getImageSize();
1166             Couple pos(rnd(0, theScreenSizeInPixels.x - size.x), - size.y);
1167             Couple speed(rnd(-2, +2), rnd(3, 9));
1168             speed = accelerate(speed);
1169 
1170             // Specify square wave sound:
1171             float startFreq = 400 + (small ? 40 : 0) + rand() % 40;
1172             float endFreq = 80 + (small ? 20 : 0) + rand() % 20;
1173             float durationInS = float(WINDOW_HEIGHT - groundHeight)
1174                                 / speed.y / FRAMES_PER_SECOND;
1175 
1176             SpinnerSprite *s = new SpinnerSprite(*pa, pos, speed,
1177                                                  size / 3, size / 3,
1178                                                  theSoundMixer,
1179                                                  startFreq,
1180                                                  endFreq,
1181                                                  durationInS);
1182             if (playerIsActive())
1183                 startSpinnerSound(*s);
1184             spinnerSprites.push_back(s);
1185         }
1186         else
1187         {
1188             // New rock:
1189             const PixmapArray *pa =
1190                         (rand() % 3 == 0 ? &smallRockPA : &bigRockPA);
1191             const Couple size = pa->getImageSize();
1192             Couple pos(rnd(0, theScreenSizeInPixels.x - size.x), - size.y);
1193             Couple speed(rnd(-2, +2), rnd(3 + theLevel, 20 + theLevel));
1194             speed = accelerate(speed);
1195             Sprite *s = new Sprite(*pa, pos, speed, Couple(0, 0),
1196                                     Couple(4, 4), size - Couple(4, 4));
1197             s->currentPixmapIndex = rnd(0, NUM_ROCK_IMAGES - 1);
1198             rockSprites.push_back(s);
1199         }
1200     }
1201 
1202 
1203     /*  Send a new pulsar once in a while.
1204     */
1205     if (playerIsActive()
1206                 && playerSprite->getTimeToLive() == 0
1207                 && --timeBeforeNextPulsar == 0)
1208     {
1209         setTimeBeforeNextPulsar();
1210         const Couple size = pulsarPA.getImageSize();
1211         Couple pos(rnd(0, theScreenSizeInPixels.x - size.x), - size.y);
1212         Couple speed(0, PULSAR_SPEED + 3 * (theLevel >= 4));
1213         speed = accelerate(speed);
1214         Sprite *s = new Sprite(pulsarPA,
1215                                 pos, speed, Couple(0, 0),
1216                                 size / 4, size / 2);
1217         s->setTimeToLive(4 * 20);
1218         pulsarSprites.push_back(s);
1219     }
1220 
1221 
1222     /*  Send a new saucer once in a while.
1223     */
1224     if (playerIsActive()
1225                 && theLevel >= 4
1226                 && playerSprite->getTimeToLive() == 0
1227                 && --timeBeforeNextSaucer == 0)
1228     {
1229         setTimeBeforeNextSaucer();
1230         const Couple size = saucerPA.getImageSize();
1231 
1232         Couple pos, speed;
1233         int v = SAUCER_SPEED + 2 * (theLevel >= 5);
1234         if (rnd(0, 1) == 0)
1235         {
1236             pos = Couple(- size.x, SAUCER_Y_POS);
1237             speed = Couple(v, 0);
1238         }
1239         else
1240         {
1241             pos = Couple(theScreenSizeInPixels.x, SAUCER_Y_POS);
1242             speed = Couple(- v, 0);
1243         }
1244 
1245         Sprite *s = new Sprite(saucerPA,
1246                                 pos, speed, Couple(0, 0),
1247                                 Couple(2, 2), size - Couple(2, 2));
1248         s->currentPixmapIndex = 0;
1249         saucerSprites.push_back(s);
1250     }
1251 
1252 
1253     /*  Send a new question mark once in a while.
1254     */
1255     if (useGameExtensions
1256                 && playerIsActive()
1257                 && theLevel >= 0
1258                 && playerSprite->getTimeToLive() == 0
1259                 && --timeBeforeNextQuestion == 0)
1260     {
1261         setTimeBeforeNextQuestion();
1262         const Couple size = questionPA.getImageSize();
1263 
1264         Couple pos, speed;
1265         pos = Couple(
1266                 rnd(size.x * 4, theScreenSizeInPixels.x - size.x * 4),
1267                 - size.y);
1268         speed = Couple(rnd(-2, +2), QUESTION_SPEED + 2 * (theLevel >= 5));
1269 
1270         Sprite *s = new Sprite(questionPA,
1271                                 pos, speed, Couple(0, 0),
1272                                 Couple(2, 2), size - Couple(2, 2));
1273         s->currentPixmapIndex = 0;
1274         questionSprites.push_back(s);
1275     }
1276 
1277 
1278     /*  Let the rocks fall. If they hit the ground or go off the screen
1279         on the left or right, kill them. If they hit the player (while it
1280         is not currently exploding), then make the player explode.
1281     */
1282     pair<bool, size_t> p = animateFallingObjects(rockSprites);
1283     if (p.second > 0 && numLives > 0)  // if rocks landed and player has lives
1284         addToScore(SCORE_BIG_ROCK_LANDING * p.second);
1285                 // NOTE: landing of small rock still scored as big rock
1286     if (p.first)  // if collision with player
1287         killPlayerBase();
1288 
1289 
1290     /*  Let the question marks fall...
1291     */
1292     if (useGameExtensions)
1293     {
1294         pair<bool, size_t> p = animateFallingObjects(questionSprites);
1295         // We don't care when the question marks land.
1296         if (p.first)  // if collision with player
1297             killPlayerBase();
1298     }
1299 
1300 
1301     /*  Let the spinners fall. If they hit the ground, the player dies.
1302         If they go off the screen on the left or right, kill them.
1303         If they hit the player (while it is not currently exploding),
1304         then the player dies.
1305     */
1306     p = animateFallingObjects(spinnerSprites);
1307     if (p.second > 0 && playerIsActive())
1308         killPlayerBase();
1309     else if (p.first)
1310         killPlayerBase();
1311 
1312 
1313     /*  Let the saucer bullets fall. If they hit the ground, kill them.
1314         If they go off the screen on the left or right, kill them.
1315         If they hit the player (while it is not currently exploding),
1316         then the player dies.
1317     */
1318     p = animateFallingObjects(saucerBulletSprites);
1319     if (p.first)
1320         killPlayerBase();
1321 
1322 
1323     /*  Let the pulsars fall. If they hit the ground, kill them.
1324         If they go off the screen on the left or right, kill them.
1325         If they hit the player (while it is not currently exploding),
1326         then the player dies.
1327         Pulsars disappear after some time.
1328         Once in a while, redirect them towards the player.
1329     */
1330     p = animateFallingObjects(pulsarSprites);
1331     if (p.first)
1332         killPlayerBase();
1333 
1334 
1335     int n = (theLevel < 3 ? 8 : 6);  // ticks between direction changes
1336     SpriteList::iterator it;
1337     for (it = pulsarSprites.begin(); it != pulsarSprites.end(); it++)
1338     {
1339         Sprite *pulsar = *it;
1340         assert(pulsar != NULL);
1341         if (pulsar->decTimeToLive() == 0)
1342         {
1343             delete pulsar;
1344             *it = NULL;
1345         }
1346         else if (tickCount % n == 0)
1347         {
1348             Couple diff = playerSprite->getPos() - pulsar->getPos();
1349             // NOTE: use projection instead of trig; see animateSaucers()
1350             double angle = atan2((double) diff.y, (double) diff.x);
1351             Couple newSpeed(int(PULSAR_SPEED * cos(angle)),
1352                                         int(PULSAR_SPEED * sin(angle)));
1353             if (newSpeed.isZero())
1354                 newSpeed = Couple(0, PULSAR_SPEED);
1355             pulsar->setSpeed(newSpeed);
1356         }
1357     }
1358     removeNullElementsFromSpriteList(pulsarSprites);
1359 
1360 
1361     /*  Move the saucers.
1362     */
1363     animateSaucers();
1364 
1365 
1366     /*  Move the player's bullets:
1367     */
1368     for (it = baseBulletSprites.begin(); it != baseBulletSprites.end(); it++)
1369     {
1370         Sprite *s = *it;
1371         assert(s != NULL);
1372         s->addSpeedToPos();
1373         if (s->getPos().y + s->getSize().y <= 0)  // if completely above screen
1374         {
1375             delete s;
1376             *it = NULL;
1377         }
1378     }
1379     removeNullElementsFromSpriteList(baseBulletSprites);
1380 
1381 
1382     /*  Animate explosions:
1383     */
1384     for (it = explosionSprites.begin(); it != explosionSprites.end(); it++)
1385     {
1386         Sprite *s = *it;
1387         assert(s != NULL);
1388 
1389         if (s->currentPixmapIndex > 1)
1390         {
1391             fprintf(stderr, "s->currentPixmapIndex = %lu\n", (unsigned long) s->currentPixmapIndex);
1392             assert(false);
1393         }
1394         if (s->decTimeToLive() == 0)
1395         {
1396             delete s;
1397             *it = NULL;
1398             continue;
1399         }
1400 
1401         s->addSpeedToPos();  // some explosions move
1402 
1403         s->currentPixmapIndex ^= 1;  // switch between 0 and 1
1404         assert(s->currentPixmapIndex <= 1);
1405     }
1406     removeNullElementsFromSpriteList(explosionSprites);
1407 }
1408 
1409 
1410 Sprite *
createExplosionSprite(Couple posOfCenterOfExplosion,int timeToLive)1411 CosmoSmashEngine::createExplosionSprite(Couple posOfCenterOfExplosion,
1412                                                 int timeToLive)
1413 /*
1414     Creates an explosion sprite centered at 'posOfCenterOfExplosion'.
1415     Sets the "time to live" value for the new sprite to 'timeToLive',
1416     which must be expressed in ticks.
1417     Inserts the address of this sprite in the list 'explosionSprites'.
1418     The sprite has no speed.
1419     Returns a pointer to the created sprite.
1420 */
1421 {
1422     Couple size = explosionPA.getImageSize();
1423     Couple pos = posOfCenterOfExplosion - size / 2;
1424     Sprite *s = new Sprite(explosionPA, pos,
1425                     Couple(0, 0), Couple(0, 0),
1426                     size / 4, size / 2);
1427     s->setTimeToLive(timeToLive);
1428     explosionSprites.push_back(s);
1429     return s;
1430 }
1431 
1432 
1433 pair<Sprite *, Sprite *>
splitBigRock(const Sprite & bigRock) const1434 CosmoSmashEngine::splitBigRock(const Sprite &bigRock) const
1435 /*
1436     Returns two new rock sprites which have not been inserted in
1437     any sprite list.
1438 */
1439 {
1440     const Couple bigRockPos = bigRock.getPos();
1441     const Couple bigRockSize = bigRock.getSize();
1442 
1443     const Couple smallRockSize = smallRockPA.getImageSize();
1444     int smallRockY = bigRockPos.y + bigRockSize.y / 2 - smallRockSize.y / 2;
1445     Couple pos1(bigRockPos.x + bigRockSize.x * 1 / 4, smallRockY);
1446     Couple pos2(bigRockPos.x + bigRockSize.x * 3 / 4, smallRockY);
1447     pos2.x -= smallRockSize.x;
1448 
1449     Couple speed1(rnd(-4, -2), bigRock.getSpeed().y);  // must go to the left
1450     Couple speed2(- speed1.x, speed1.y);  // must go to the right
1451 
1452     // Rotate the speeds of the small rocks some times:
1453     int dir = rand() % 3;
1454     for (int times = rand() % 2 + 1; times > 0; times--)
1455         switch (dir)
1456         {
1457             case 0:
1458                 speed1.x--;
1459                 speed2.y++;
1460                 break;
1461             case 1:
1462                 speed2.x++;
1463                 speed1.y++;
1464                 break;
1465             default:
1466                 ;
1467         }
1468 
1469     speed1 = accelerate(speed1);
1470     speed2 = accelerate(speed2);
1471     Sprite *smallRock1 = new Sprite(smallRockPA,
1472                             pos1, speed1, Couple(0, 0), Couple(2, 2),
1473                             smallRockSize - Couple(2, 2));
1474     smallRock1->currentPixmapIndex = bigRock.currentPixmapIndex;
1475     Sprite *smallRock2 = new Sprite(smallRockPA,
1476                             pos2, speed2, Couple(0, 0), Couple(2, 2),
1477                             smallRockSize - Couple(2, 2));
1478     smallRock2->currentPixmapIndex = bigRock.currentPixmapIndex;
1479 
1480     //rockSprites.push_back(smallRock1);
1481     //rockSprites.push_back(smallRock2);
1482         /*  We do not insert the two new sprites in the rockSprites list
1483             right now because this method is called during an iteration
1484             over that list.  Insertions in the list at this point could
1485             reallocate the internal data structure and thus disrupt
1486             the iteration. That is why we return a pair of newly
1487             created sprites which the caller can insert in the list
1488             when appropriate.
1489         */
1490     return pair<Sprite *, Sprite *>(smallRock1, smallRock2);
1491 }
1492 
1493 
1494 void
makeSpriteExplode(const Sprite * target,const Sprite * bullet,long points)1495 CosmoSmashEngine::makeSpriteExplode(const Sprite *target,
1496                                     const Sprite *bullet,
1497                                     long points)
1498 /*
1499     Makes the 'target' sprite explode.
1500     'bullet' must be the sprite that triggers the explosion; it is erased
1501     from the screen.
1502     The target is erased from the screen and an explosion is started
1503     at its position.
1504     'points' is the number of points to add to the player's score.
1505     The object designated by 'target' is not destroyed.
1506     'target' and 'bullet' must not be null.
1507 */
1508 {
1509     assert(target != NULL);
1510     assert(bullet != NULL);
1511 
1512     playShortExplosionSound();
1513 
1514     Couple targetPos = target->getPos();
1515 
1516     Couple expPos = target->getCenterPos();
1517     createExplosionSprite(expPos, 6);
1518 
1519     addToScore(points);
1520 }
1521 
1522 
1523 bool
detectCollisionsWithBullet(const Sprite & bullet,SpriteList & slist,int scoreOnCollision)1524 CosmoSmashEngine::detectCollisionsWithBullet(const Sprite &bullet,
1525                                             SpriteList &slist,
1526                                             int scoreOnCollision)
1527 /*
1528     Detects fatal collisions between a bullet and sprites in 'slist'.
1529 
1530     'slist' is allowed to contain null pointers; they are ignored.
1531 
1532     If a collision is detected between 'bullet' and an element
1533     of 'slist', this element is made null in 'slist' and the
1534     corresponding Sprite object is destroyed with operator delete;
1535     also, an explosion is created at the point of collision and the
1536     method returns true.
1537 
1538     The method returns false if no collision is detected.
1539 */
1540 {
1541     bool collisionDetected = false;
1542 
1543     for (SpriteList::iterator its = slist.begin(); its != slist.end(); its++)
1544     {
1545         Sprite *aSprite = *its;
1546         if (aSprite == NULL)  // if already dead
1547             continue;
1548 
1549         if (bullet.collidesWithSprite(*aSprite))
1550         {
1551             makeSpriteExplode(aSprite, &bullet, scoreOnCollision);
1552 
1553             delete aSprite;
1554             *its = NULL;
1555             collisionDetected = true;
1556         }
1557     }
1558 
1559     return collisionDetected;
1560 }
1561 
1562 
1563 bool
detectCollisionsWithExplosions()1564 CosmoSmashEngine::detectCollisionsWithExplosions()
1565 /*
1566     Kills certain sprites if they touch an explosion sprite.
1567     Explosion sprites do not disappear because of a collision.
1568     Sprite lists containing killed sprites will contain null
1569     elements after this method finishes.  The caller is
1570     responsible for cleaning up those null elements.
1571 
1572     Returns true if the player dies, or false otherwise.
1573 */
1574 {
1575     if (!playerIsActive())
1576         return false;
1577 
1578     /*  Make a local copy of the list of explosion sprites, and iterate
1579         on the copy.
1580         We cannot iterate over 'explosionSprites' because the calls
1581         to detectCollisionsWithBullet() will add elements to it;
1582         this could invalidate an iterator that would point into the list.
1583 
1584         This technique is not efficient in principle, but the number of
1585         explosions is normally low, so no visible performance hit should
1586         be observed.
1587     */
1588     SpriteList tempList(explosionSprites.size());
1589     copy(explosionSprites.begin(), explosionSprites.end(), tempList.begin());
1590 
1591     assert(tempList.size() == explosionSprites.size());
1592 
1593     SpriteList::const_iterator it;
1594     for (it = tempList.begin(); it != tempList.end(); it++)
1595     {
1596         const Sprite *anExplosion = *it;
1597         if (anExplosion == NULL)
1598             continue;
1599 
1600         // Consider the explosion sprite as a bullet:
1601         (void) detectCollisionsWithBullet(*anExplosion, rockSprites, 0);
1602         (void) detectCollisionsWithBullet(*anExplosion, spinnerSprites, 0);
1603         (void) detectCollisionsWithBullet(*anExplosion, pulsarSprites, 0);
1604         (void) detectCollisionsWithBullet(*anExplosion, saucerSprites, 0);
1605         (void) detectCollisionsWithBullet(*anExplosion, saucerBulletSprites, 0);
1606 
1607         #if 0  // this makes the explosions kill the base also.
1608         if (anExplosion->collidesWithSprite(*playerSprite))
1609         {
1610             killPlayerBase();  // this kills all meanies
1611             return true;       // no need to continue
1612         }
1613         #endif
1614     }
1615 
1616     return false;
1617 }
1618 
1619 
detectCollisionsBetweenFallingObjects(SpriteList & slist)1620 void CosmoSmashEngine::detectCollisionsBetweenFallingObjects(SpriteList &slist)
1621 /*
1622     Sprite lists containing killed sprites will contain null
1623     elements after this method finishes.  The caller is
1624     responsible for cleaning up those null elements.
1625 */
1626 {
1627     SpriteList::iterator it;
1628     for (it = slist.begin(); it != slist.end(); it++)
1629     {
1630         const Sprite *s = *it;
1631         if (s == NULL)
1632             continue;
1633 
1634         bool coll = false;
1635         if (!coll)
1636             coll = detectCollisionsWithBullet(*s, rockSprites, 0);
1637         if (!coll)
1638             coll = detectCollisionsWithBullet(*s, saucerSprites, 0);
1639         if (!coll)
1640             coll = detectCollisionsWithBullet(*s, saucerBulletSprites, 0);
1641         if (coll)
1642         {
1643             *it = NULL;
1644             delete s;
1645         }
1646     }
1647 }
1648 
1649 
1650 void
detectCollisions()1651 CosmoSmashEngine::detectCollisions()
1652 /*
1653     NOTE: Many collisions are detected here, but there are some others
1654     that are detected elsewhere, as in animateFallingObjects(), for example.
1655 */
1656 {
1657     SpriteList newRocks;
1658     bool playerBoosted = false;
1659 
1660     /*  Detect collisions involving the player's bullets:
1661     */
1662     for (SpriteList::iterator itb = baseBulletSprites.begin();
1663                                     itb != baseBulletSprites.end(); itb++)
1664     {
1665         Sprite *bullet = *itb;
1666         assert(bullet != NULL);
1667 
1668         for (SpriteList::iterator itr = rockSprites.begin();
1669                                         itr != rockSprites.end(); itr++)
1670         {
1671             Sprite *rock = *itr;
1672             if (rock == NULL)  // if already dead
1673                 continue;  // next rock
1674 
1675             assert(bullet != NULL);
1676 
1677             if (bullet->collidesWithSprite(*rock))
1678             {
1679                 long points;
1680                 bool explode = true;
1681                 if (rock->getSize() == bigRockPA.getImageSize())
1682                 {
1683                     points = SCORE_BIG_ROCK;
1684 
1685                     /*  Some big rocks break into two small rocks.
1686                     */
1687                     if (rand() % 3 == 0)
1688                     {
1689                         playShortExplosionSound();
1690 
1691                         pair<Sprite *, Sprite *> p = splitBigRock(*rock);
1692                         newRocks.push_back(p.first);
1693                         newRocks.push_back(p.second);
1694 
1695 
1696                         /*  We don't create an explosion when a big rock splits
1697                             because an explosion would instantly destroy the
1698                             two new small rocks.  @sarrazip 20010530
1699                         */
1700                         explode = false;
1701                     }
1702                 }
1703                 else
1704                     points = SCORE_SMALL_ROCK;
1705 
1706                 if (explode)
1707                     makeSpriteExplode(rock, bullet, points);
1708 
1709 
1710                 // don't destroy 'bullet' -- only mark it for deletion:
1711                 *itb = NULL;
1712                 delete rock;
1713                 *itr = NULL;
1714                 break;
1715             }
1716         }
1717 
1718 
1719         assert(bullet != NULL);  // must still be alive for next for()
1720 
1721 
1722         SpriteList::iterator its;
1723         for (its = spinnerSprites.begin(); its != spinnerSprites.end(); its++)
1724         {
1725             Sprite *spinner = *its;
1726             if (spinner == NULL)  // if already dead
1727                 continue;  // next spinner
1728 
1729             assert(bullet != NULL);
1730             if (bullet->collidesWithSprite(*spinner))
1731             {
1732                 long points;
1733                 if (spinner->getSize() == bigSpinnerPA.getImageSize())
1734                     points = SCORE_BIG_SPINNER;
1735                 else
1736                     points = SCORE_SMALL_SPINNER;
1737 
1738                 makeSpriteExplode(spinner, bullet, points);
1739 
1740                 // don't destroy 'bullet' -- only mark it for deletion:
1741                 *itb = NULL;
1742                 delete spinner;
1743                 *its = NULL;
1744                 break;
1745             }
1746         }
1747 
1748 
1749         assert(bullet != NULL);  // must still be alive for next for()
1750 
1751 
1752         if (detectCollisionsWithBullet(
1753                                 *bullet, pulsarSprites, SCORE_PULSAR))
1754             *itb = NULL;  // this marks that the bullet hit something
1755 
1756         if (detectCollisionsWithBullet(
1757                                 *bullet, saucerSprites, SCORE_SAUCER))
1758             *itb = NULL;
1759 
1760         if (detectCollisionsWithBullet(
1761                             *bullet, saucerBulletSprites, SCORE_SAUCER_BULLET))
1762             *itb = NULL;
1763 
1764         if (detectCollisionsWithBullet(
1765                             *bullet, questionSprites, SCORE_QUESTION))
1766         {
1767             *itb = NULL;
1768             playerBoosted = true;
1769         }
1770 
1771         if (*itb == NULL)  // if bullet hit something
1772             delete bullet;
1773 
1774     }   // for each bullet
1775 
1776 
1777     detectCollisionsBetweenFallingObjects(pulsarSprites);
1778     detectCollisionsBetweenFallingObjects(questionSprites);
1779 
1780     (void) detectCollisionsWithExplosions();
1781 
1782     removeNullElementsFromSpriteList(baseBulletSprites);
1783     removeNullElementsFromSpriteList(rockSprites);
1784     removeNullElementsFromSpriteList(spinnerSprites);
1785     removeNullElementsFromSpriteList(pulsarSprites);
1786     removeNullElementsFromSpriteList(saucerSprites);
1787     removeNullElementsFromSpriteList(saucerBulletSprites);
1788     removeNullElementsFromSpriteList(questionSprites);
1789 
1790     rockSprites.insert(rockSprites.end(), newRocks.begin(), newRocks.end());
1791 
1792     if (playerBoosted)
1793         boostPlayer();
1794 }
1795 
1796 
1797 void
boostPlayer()1798 CosmoSmashEngine::boostPlayer()
1799 {
1800     playerBoostTicks = rnd(100, 150);
1801 
1802     switch (rnd(0, 1))
1803     {
1804         case 0: playerBoostType = TRIPLE_BULLETS; break;
1805         case 1: playerBoostType = CLOSER_BULLETS; break;
1806     }
1807 }
1808 
1809 
1810 ///////////////////////////////////////////////////////////////////////////////
1811 
1812 
1813 void
restoreBackground()1814 CosmoSmashEngine::restoreBackground()
1815 {
1816     bool baseExploding = (playerSprite->getTimeToLive() != 0);
1817 
1818     // Background:
1819     assert(theLevel >= 1 && theLevel <= NUM_LEVELS);
1820     Uint32 bgColor = (baseExploding ? explosionBGColor : levelColors[theLevel]);
1821     fillRect(0, 0, theDrawingPixmapSize.x, theDrawingPixmapSize.y, bgColor);
1822 
1823     // Ground:
1824     fillRect(0, groundPos, theDrawingPixmapSize.x, groundHeight, greenColor);
1825 
1826     // Stars:
1827     bool moveStars = (numLives > 0 && ! baseExploding && tickCount % 4 == 0);
1828     CoupleList::iterator it;
1829     for (it = starPositions.begin(); it != starPositions.end(); it++)
1830     {
1831         Couple &pos = *it;
1832         drawPixel(pos.x, pos.y, whiteColor);
1833 
1834         if (moveStars)
1835         {
1836             pos.y--;
1837             if (pos.y < 0)
1838                 pos.y = mountainTopPos - 1;
1839         }
1840     }
1841 
1842     // Mountain range:
1843     Couple previous(-1, -1);
1844     for (it = mountainRangePositions.begin(); it != mountainRangePositions.end(); it++)
1845     {
1846         if (previous.x != -1)  // do nothing the first time
1847             drawLine(previous.x, previous.y, (*it).x, (*it).y, greenColor);
1848         previous = Couple((*it).x, (*it).y);
1849     }
1850 }
1851 
1852 
1853 void
drawSprites()1854 CosmoSmashEngine::drawSprites()
1855 {
1856     if (playerIsActive())
1857     {
1858         int pixmapIndex = (playerBoostTicks > 0 ? ((tickCount & 4) != 0) : 0);
1859         copySpritePixmap(*playerSprite, pixmapIndex, playerSprite->getPos());
1860     }
1861 
1862     SpriteList::const_iterator it;
1863     for (it = rockSprites.begin(); it != rockSprites.end(); it++)
1864         copySpritePixmap(**it, (**it).currentPixmapIndex, (**it).getPos());
1865 
1866     if (useGameExtensions)
1867         for (it = questionSprites.begin(); it != questionSprites.end(); it++)
1868         {
1869             size_t &index = (**it).currentPixmapIndex;
1870             index = (tickCount / 8) % 2;
1871             copySpritePixmap(**it, index, (**it).getPos());
1872         }
1873 
1874     for (it = spinnerSprites.begin(); it != spinnerSprites.end(); it++)
1875     {
1876         size_t &index = (**it).currentPixmapIndex;
1877         index = (tickCount / 4) % NUM_SPINNER_IMAGES;
1878         copySpritePixmap(**it, index, (**it).getPos());
1879     }
1880 
1881     if (!pulsarSprites.empty() && tickCount % (NUM_PULSAR_IMAGES * 4) == 0)
1882         playSoundEffect(pulsarBeepSound);
1883     for (it = pulsarSprites.begin(); it != pulsarSprites.end(); it++)
1884     {
1885         size_t &index = (**it).currentPixmapIndex;
1886         index = (tickCount / 2) % NUM_PULSAR_IMAGES;
1887         copySpritePixmap(**it, index, (**it).getPos());
1888     }
1889 
1890     for (it = saucerSprites.begin(); it != saucerSprites.end(); it++)
1891     {
1892         size_t &index = (**it).currentPixmapIndex;
1893         index = (tickCount / 4) % NUM_SAUCER_IMAGES;
1894         copySpritePixmap(**it, index, (**it).getPos());
1895     }
1896 
1897     for (it = saucerBulletSprites.begin(); it != saucerBulletSprites.end(); it++)
1898     {
1899         size_t &index = (**it).currentPixmapIndex;
1900         index = (tickCount / 2) % NUM_SAUCER_BULLET_IMAGES;
1901         copySpritePixmap(**it, index, (**it).getPos());
1902     }
1903 
1904     for (it = baseBulletSprites.begin(); it != baseBulletSprites.end(); it++)
1905         copySpritePixmap(**it, 0, (**it).getPos());
1906 
1907     for (it = explosionSprites.begin(); it != explosionSprites.end(); it++)
1908     {
1909         assert(*it != NULL);
1910         assert((**it).currentPixmapIndex <= 1);
1911         copySpritePixmap(**it, (**it).currentPixmapIndex, (**it).getPos());
1912     }
1913 
1914     {
1915         int ext = (useGameExtensions ? '*' : ' ');
1916         char s[64];
1917         snprintf(s, sizeof(s), " Score      :%8ld%c", theScore, ext);
1918         writeString(s, scoreAreaPos);
1919         snprintf(s, sizeof(s), " Peak Score :%8ld%c", thePeakScore, ext);
1920         writeString(s, peakScoreAreaPos);
1921     }
1922 
1923     {
1924         char s[64];
1925         snprintf(s, sizeof(s), " Level:%3d ", theLevel);
1926         writeString(s, levelNumAreaPos);
1927         snprintf(s, sizeof(s), " Lives:%3d ", numLives);
1928         writeString(s, numLivesAreaPos);
1929         updateNumLives = false;
1930     }
1931 }
1932 
1933 
1934 void
addToScore(long n)1935 CosmoSmashEngine::addToScore(long n)
1936 {
1937     theScore += n * theLevel;
1938     if (theScore > thePeakScore)
1939     {
1940         /*  Maintain the peak score. Give the player a new life each time
1941             the peak score crosses some boundary.
1942         */
1943         const long newLifeScoreInterval =
1944                 (useGameExtensions ? 10000 : 1000);
1945         long before = thePeakScore / newLifeScoreInterval;
1946         thePeakScore = theScore;
1947         long after = thePeakScore / newLifeScoreInterval;
1948         if (before != after)
1949             addToNumLives(+1);
1950     }
1951     theLevel = getLevelNum(theScore);
1952 }
1953 
1954 
1955 void
addToNumLives(int n)1956 CosmoSmashEngine::addToNumLives(int n)
1957 {
1958     numLives += n;
1959     updateNumLives = true;
1960 }
1961 
1962 
1963 Couple
accelerate(Couple speed) const1964 CosmoSmashEngine::accelerate(Couple speed) const
1965 {
1966     int coef = (theScore >= 100000 ? theScore - 100000 : 0) / 25000 + 1;
1967     if (speed.x < 0)
1968         speed.x -= coef;
1969     else if (speed.x > 0)
1970         speed.x += coef;
1971     if (speed.y < 0)
1972         speed.y -= coef;
1973     else if (speed.y > 0)
1974         speed.y += coef;
1975     return speed;
1976 }
1977 
1978 
1979 void
displayPauseMessage()1980 CosmoSmashEngine::displayPauseMessage()
1981 {
1982     displayMessage(10, "PAUSED -- press P to resume");
1983 }
1984 
1985 
1986 void
displayStartMessage(bool display)1987 CosmoSmashEngine::displayStartMessage(bool display)
1988 /*
1989     Displays the start message if 'display' is true, or erases the
1990     corresponding region if 'display' is false.
1991 */
1992 {
1993     if (display)
1994     {
1995         int lineNum = 0;
1996         displayMessage(lineNum++, "Cosmosmash " VERSION " - by Pierre Sarrazin  ");
1997         displayMessage(lineNum++, "                                       ");
1998         displayMessage(lineNum++, "Press SPACE to start a standard game   ");
1999         displayMessage(lineNum++, "Press E to start a game with extensions");
2000         displayMessage(lineNum++, "                                       ");
2001         displayMessage(lineNum++, "Move:               left/right arrows  ");
2002         displayMessage(lineNum++, "Hyperspace:         down arrow         ");
2003         displayMessage(lineNum++, "Pause:              P                  ");
2004         displayMessage(lineNum++, "Toggle Full Screen: F11                ");
2005 
2006         if (useSound && theSoundMixer == NULL)
2007         {
2008             lineNum++;
2009             lineNum++;
2010             displayMessage(lineNum++, "*** Sound device not available ***     ");
2011         }
2012     }
2013 }
2014 
2015 
2016 void
displayMessage(int row,const char * msg)2017 CosmoSmashEngine::displayMessage(int row, const char *msg)
2018 {
2019     const Couple fontdim = getFontDimensions();
2020 
2021     const size_t len = strlen(msg);
2022     int x = (theScreenSizeInPixels.x - len * fontdim.x) / 2;
2023     int y = theScreenSizeInPixels.y + (row - 26) * fontdim.y;
2024 
2025     writeString(msg, Couple(x, y));
2026 }
2027 
2028 
2029 void
playShortExplosionSound()2030 CosmoSmashEngine::playShortExplosionSound()
2031 {
2032     int soundNo = rand() % NUM_ROCK_HIT_SOUNDS;
2033     playSoundEffect(rockHitSounds[soundNo]);
2034 }
2035 
2036 
2037 void
playSoundEffect(const roundbeetle::WaveFileBuffer & wfb)2038 CosmoSmashEngine::playSoundEffect(const roundbeetle::WaveFileBuffer &wfb)
2039 {
2040     if (! playerIsActive() || theSoundMixer == NULL)
2041         return;
2042 
2043     roundbeetle::Bus &mainBus = theSoundMixer->getMainBus();
2044     int reqHandle = theSoundMixer->requestWaveFileSound(wfb, mainBus);
2045     if (reqHandle == -1)
2046         cout << PACKAGE ": failed to play sound " << &wfb << endl;
2047 }
2048 
2049 
2050 void
registerCommand(Command command)2051 CosmoSmashEngine::registerCommand(Command command)
2052 {
2053     if (gameScriptStream == NULL)
2054         return;
2055 
2056     *gameScriptStream << static_cast<int>(command) << ' ';
2057 }
2058 
2059 
2060 void
registerTickEnd()2061 CosmoSmashEngine::registerTickEnd()
2062 {
2063     if (gameScriptStream == NULL)
2064         return;
2065 
2066     *gameScriptStream << '\n';
2067 }
2068 
2069 
2070 void
playCadenceSound()2071 CosmoSmashEngine::playCadenceSound()
2072 {
2073     if (! playerIsActive() || theSoundMixer == NULL)
2074         return;
2075 
2076     if (timeBeforeNextCadenceSound > 0)
2077     {
2078         --timeBeforeNextCadenceSound;
2079         return;
2080     }
2081 
2082     // Compute a period in seconds between cadence sounds.
2083     // The cadence accelerates with the score.
2084     // The cadence period has a minimum.
2085     //
2086     const double startPeriod = 0.750;  // period in seconds when score is 0
2087     const double endPeriod   = 0.300;  // period in seconds when score is scoreOfFastestPeriod
2088     const long scoreOfFastestPeriod = 50000;
2089     double t = double(theScore) / scoreOfFastestPeriod;
2090     t = std::max(t, 0.0);
2091     t = std::min(t, 1.0);
2092     double cadenceInSeconds = startPeriod + t * (endPeriod - startPeriod);
2093 
2094     timeBeforeNextCadenceSound = (unsigned long) (cadenceInSeconds * 20);
2095     /*cout << theScore << " -> " << cadenceInSeconds << " s, "
2096          << timeBeforeNextCadenceSound << " ticks" << endl;*/
2097 
2098     // Play a white noise with decay and release time that fit
2099     // the computed cadence period.
2100     //
2101     float decayTime = cadenceInSeconds * .267f;
2102     float releaseTime = cadenceInSeconds * .4f;
2103     roundbeetle::ADSR adsr(.25f, .15f, .0f, decayTime, .0f, releaseTime);
2104     /*int req =*/ theSoundMixer->requestWhiteNoise(adsr, 1, theSoundMixer->getMainBus());
2105     //if (req == -1)
2106     //    cout << PACKAGE ": failed to play cadence sound" << endl;
2107 }
2108