Invaders game for the Gameduino

Dependencies:   Gameduino mbed

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers game.cpp Source File

game.cpp

00001 #include "game.h"
00002 
00003 extern GDClass GD;
00004 SPI spigame(ARD_MOSI, ARD_MISO, ARD_SCK); // mosi, miso, sclk
00005 /*---------------------------------------------
00006   Trivia: There is NO random number generator
00007   anywhere in Space Invaders....
00008 ---------------------------------------------*/
00009 
00010 
00011 /*---------------------------------------------
00012   Global definitions
00013 ---------------------------------------------*/
00014 #define invaderRows 5
00015 #define invadersPerRow 11
00016 #define numInvaders (invaderRows*invadersPerRow)
00017 
00018 // Positions of things on screen
00019 // nb. Space Invaders screen is 256x224 pixels
00020 #define screenTop 24
00021 #define screenLeft 88
00022 #define screenWidth 224
00023 #define screenHeight 256
00024 // Player
00025 #define playerMinLeft 18
00026 #define playerMaxRight 188
00027 #define playerYpos 216
00028 #define playerSpeed 1
00029 // Bullet
00030 #define bulletHeight 4
00031 #define bulletSpeed 4
00032 #define bulletTop 35
00033 // Invaders
00034 #define invaderAppearX 26
00035 #define invaderAppearY 64
00036 #define invaderXspacing 16
00037 #define invaderYspacing 16
00038 #define invaderXstep 2
00039 #define invaderYstep 8
00040 #define invaderXmin 10
00041 #define invaderXmax 202
00042 // Saucer
00043 #define saucerYpos 42
00044 #define saucerSpeed 1
00045 #define saucerXmin 0
00046 #define saucerXmax (screenWidth-16)
00047 #define saucerSkip 3
00048 #define saucerFrequency (25*72)
00049 // Shields
00050 #define numShields 4
00051 #define shieldXpos 32
00052 #define shieldYpos 192 
00053 #define shieldXstep 45
00054 // Bombs
00055 #define bombSpeed 1
00056 #define bombYmax 230
00057 
00058 /*-------------------------------------------------------
00059   Sprite allocation list
00060 
00061   nb. Sprite order is important for collision detection
00062 -------------------------------------------------------*/
00063 enum sprite_id {
00064   SP_PLAYER,
00065   SP_FIRST_SHIELD,
00066   SP_LAST_SHIELD    = SP_FIRST_SHIELD+(2*numShields)-1,
00067   // Invader bombs (can hit shields and player)
00068   SP_BOMB1,  // nb. There's only three bombs in Space invaders...
00069   SP_BOMB2,
00070   SP_BOMB3,
00071   // Invaders (can't be hit by their own bombs)
00072   SP_FIRST_INVADER,
00073   SP_LAST_INVADER   = SP_FIRST_INVADER+numInvaders-1,
00074   // Flying saucer (needs two sprites because it's very wide...)
00075   SP_SAUCER1,
00076   SP_SAUCER2,
00077   // Bullet (last ... because it can hit anything)
00078   SP_BULLET
00079 };
00080 
00081 
00082 /*---------------------------------------------
00083   Global vars
00084 ---------------------------------------------*/
00085 // Joystick object
00086 Joystick joystick;
00087 
00088 // This increments once per frame
00089 static unsigned int frameCounter;
00090 
00091 // The current wave of invaders [0..n]
00092 static unsigned int invaderWave;
00093 
00094 // Number of lives the player has left...
00095 static byte numLives;
00096 
00097 // Player's score...
00098 static unsigned int playerScore;
00099 
00100 // High score
00101 static unsigned int highScore;
00102 
00103 // Number of living space invaders
00104 static unsigned int remainingInvaders;
00105 
00106 // Timer for the background heartbeat sound
00107 static int beatCounter;
00108 
00109 /*---------------------------------------------
00110   General functions
00111 ---------------------------------------------*/
00112 static PROGMEM prog_char scoreMsg[] = "Score";
00113 static PROGMEM prog_char hiScoreMsg[] = "Hi-Score";
00114 static unsigned int previousPlayerScore, previousHighScore;
00115 void redrawScores()
00116 {
00117   previousPlayerScore = previousHighScore = 0xffff;
00118 }
00119 
00120 unsigned int putDigit(unsigned int s, unsigned int d)
00121 {
00122   byte c = '0';
00123   while (s >= d) {
00124     ++c;
00125     s -= d;
00126   }
00127   spigame.write(c);
00128   return s;
00129 }
00130 void printScore(int8 x, const prog_char *m, unsigned int s, int8 xoff)
00131 {
00132   x += screenLeft/8;
00133   int y = screenTop/8;
00134   unsigned int addr = (y*64)+x;
00135   GD.__wstart(addr);
00136   char c = *m;
00137   c = *m++;
00138   while (c != 0) {
00139     spigame.write(c);
00140     c = *m++;
00141   }
00142   GD.__end();
00143   addr += (2*64)+xoff;
00144   GD.__wstart(addr);
00145   s = putDigit(s,10000);
00146   s = putDigit(s,1000);
00147   s = putDigit(s,100);
00148   s = putDigit(s,10);
00149   spigame.write(s+'0');
00150   GD.__end();
00151 }
00152 void updateScore()
00153 {
00154   if (playerScore != previousPlayerScore) {
00155     printScore(0,scoreMsg,playerScore,0);
00156     previousPlayerScore = playerScore;
00157     if (playerScore > highScore) {
00158       highScore = playerScore;
00159     }
00160   }
00161   if (highScore != previousHighScore) {
00162     printScore(20,hiScoreMsg,highScore,3);
00163     previousHighScore = highScore;
00164   }
00165 }
00166 
00167 static unsigned short int prevLives;
00168 static void redrawBases()
00169 {
00170   prevLives = 0xffff;
00171 }
00172 void updateRemainingBases()
00173 {
00174   if (numLives != prevLives) {
00175     prevLives = numLives;
00176     GD.__wstart((64*((screenTop+240)>>3))+(screenLeft>>3));
00177     spigame.write(numLives+'0');
00178     spigame.write(0);
00179     for (byte i=1; i<numLives; ++i) {
00180       spigame.write(CH_PLAYERL);
00181       spigame.write(CH_PLAYERR);
00182     }
00183     spigame.write(0);
00184     spigame.write(0);
00185     GD.__end();
00186   }
00187 }
00188 
00189 /*---------------------------------------------
00190   A generic object in the game
00191 ---------------------------------------------*/
00192 enum object_status {
00193   S_WAITING,
00194   S_ALIVE,
00195   S_DYING,
00196   S_DEAD
00197 };
00198 
00199 struct GameObject {
00200   byte sprite;     // Which sprite to use for my graphic (see "sprite_id")
00201   byte status;     // What I'm doing at the moment
00202   int xpos,ypos;   // Position on screen
00203   // State of objects in the game
00204   void initialize(byte s, object_status t=S_WAITING, int x=400, int y=0) {
00205     sprite = s;
00206     status = t;
00207     xpos = x;
00208     ypos = y;
00209     updateSprite(0,0);
00210   }
00211   void updateSprite(byte img, byte frame) {
00212     GD.sprite(sprite,xpos+screenLeft,ypos+screenTop,img,8+(frame<<1),0,0);
00213   }
00214   void doubleSprite(byte img1, byte frame1, byte img2, byte frame2, int8 xoff) {
00215     int x = xpos+screenLeft+xoff;
00216     int y = ypos+screenTop;
00217     GD.sprite(sprite,  x,   y,img1,8+(frame1<<1),0,0);
00218     GD.sprite(sprite+1,x+16,y,img2,8+(frame2<<1),0,0);
00219   }
00220   byte collision() {
00221     return GD.rd(0x2900+sprite);
00222   }
00223 };
00224 
00225 /*---------------------------------------------
00226  Player's bullet
00227 ---------------------------------------------*/
00228 // Forward references to functions
00229 bool killInvader(byte spriteNumber);
00230 void shootShield(byte spriteNumber, int bulletX);
00231 void shootSaucer();
00232 void shootBomb(byte spriteNumber);
00233 void incSaucerCounter();
00234 
00235 class BulletObject : GameObject {
00236   byte timer;
00237   bool visibleDeath;
00238   void die(bool v) {
00239     visibleDeath = v;
00240     status = S_DYING;
00241     timer = 12;
00242   }
00243 public:
00244   void reset() {
00245     initialize(SP_BULLET);
00246     updateSprite(GR_BULLET,3);
00247     timer = 0;
00248   }
00249   void fire(GameObject& p) {
00250     if (status == S_WAITING){
00251       status = S_ALIVE;
00252       xpos = p.xpos;
00253       ypos = p.ypos+bulletSpeed-bulletHeight;
00254       playerShootSound = true;
00255     }
00256   }
00257   void update() {
00258     int frame = 3;
00259     switch (status) {
00260       case S_ALIVE:  ypos -= bulletSpeed;
00261                      if (ypos <= bulletTop) {
00262                        ypos = bulletTop;
00263                        die(true);
00264                        frame = 1;
00265                      }
00266                      else {
00267                        frame = 0;
00268                      }
00269                      break;
00270       case S_DYING:  if (!--timer) {
00271                        status = S_WAITING;
00272                        incSaucerCounter();
00273                      }
00274                      else if (visibleDeath) {
00275                        frame = 1;
00276                      }
00277                      break;
00278     }
00279     updateSprite(GR_BULLET,frame);
00280   }
00281   void setY(int y) {
00282     if (status == S_DYING) {
00283       ypos = y;
00284       updateSprite(GR_BULLET,1);
00285       //GD.wr16(SCROLL_Y,GD.rd16(SCROLL_Y)+1);
00286     }
00287   }
00288   // See if the bullet hit anything
00289   void collide() {
00290     if (status == S_ALIVE) {
00291       byte b = collision();
00292       if (b != 0xff) {
00293         if ((b >= SP_FIRST_INVADER) and (b <= SP_LAST_INVADER)) {
00294           if (killInvader(b)) {
00295             die(false);
00296           }
00297         }
00298         if ((b >= SP_FIRST_SHIELD) and (b <= SP_LAST_SHIELD)) {
00299           shootShield(b,xpos);
00300           die(true);
00301         }
00302         if ((b >= SP_SAUCER1) and (b <= SP_SAUCER2)) {
00303           shootSaucer();
00304           die(false);
00305         }
00306         if ((b >= SP_BOMB1) and (b <= SP_BOMB3)) {
00307           shootBomb(b);
00308           die(false);
00309         }
00310       }
00311     }
00312   }
00313 } bullet;
00314 
00315 /*---------------------------------------------
00316  The player
00317 ---------------------------------------------*/
00318 class Player : public GameObject {
00319   byte timer;
00320 public:
00321   void reset() {
00322     timer = 2*numInvaders;
00323     initialize(SP_PLAYER,S_WAITING,playerMinLeft,playerYpos);
00324     updateSprite(GR_PLAYER,3);
00325   }
00326 
00327   void update() {
00328     int frame = 3;
00329     switch (status) {
00330       case S_WAITING: xpos = playerMinLeft;
00331                       ypos = playerYpos;
00332                       if (!--timer) {
00333                         status = S_ALIVE;
00334                       }
00335                       break;
00336       case S_ALIVE:   if (joystick.left()) {
00337                         xpos -= playerSpeed;
00338                         if (xpos < playerMinLeft) {
00339                           xpos = playerMinLeft;
00340                         }
00341                       }
00342                       if (joystick.right()) {
00343                         xpos += playerSpeed;
00344                         if (xpos > playerMaxRight) {
00345                           xpos = playerMaxRight;
00346                         }
00347                       }
00348                       { byte n = Joystick::buttonA|Joystick::buttonB;
00349                         if (joystick.isPressed(n) and joystick.changed(n)) {
00350                           bullet.fire(*this);
00351                         }
00352                       }
00353                       frame = 0;
00354                       break;
00355       case S_DYING:   if (!--timer) {
00356                         timer = 3*remainingInvaders;
00357                         status = (--numLives>0)? S_WAITING: S_DEAD;
00358                       }
00359                       else {
00360                         frame = ((frameCounter&4)==0)? 1:2;
00361                       }
00362                       break;
00363     }
00364     updateSprite(GR_PLAYER,frame);
00365   }
00366   void kill() {
00367     if (status == S_ALIVE) {
00368       status = S_DYING;
00369       timer = 50;
00370       playerDeathSound = true;
00371     }
00372   }
00373   bool isAlive() {
00374     return (status==S_ALIVE);
00375   }
00376   bool isDying() {
00377     return (status==S_DYING);
00378   }
00379   bool isDead() {
00380     return (status==S_DEAD);
00381   }
00382   void wakeUp() {
00383   }
00384 } player;
00385 
00386 /*---------------------------------------------
00387   "Shields" for the player to hide behind
00388 ---------------------------------------------*/
00389 class Shields {
00390   struct BlastInfo {
00391     byte sprite;
00392     int xpos;
00393     void reset() {
00394       sprite = 255;
00395     }
00396     bool hasBlast() const {
00397       return (sprite!=255);
00398     }
00399     void blast(byte s, int x) {
00400       sprite = s;
00401       xpos = x;
00402     }
00403   };
00404   BlastInfo bulletBlast, bombBlast[3];
00405   void blastShield(BlastInfo& n, bool asBullet) {
00406     if (n.hasBlast()) {
00407       int8_t s = (n.sprite-SP_FIRST_SHIELD)>>1;
00408       int8_t x = int8(n.xpos-(shieldXpos+(s*shieldXstep)));
00409       int8_t y = zapShield(s,x,asBullet);
00410       if (asBullet) {
00411         bullet.setY(shieldYpos+y);
00412       }
00413       n.reset();
00414     }
00415   }
00416 public:
00417   void reset() {
00418     remakeShields();
00419     int x = shieldXpos;
00420     byte s = SP_FIRST_SHIELD;
00421     for (int i=0; i<numShields; ++i) {
00422       GD.sprite(s,  x+screenLeft,   shieldYpos+screenTop,GR_SHIELD1+i,8+0,0,0);
00423       GD.sprite(s+1,x+screenLeft+16,shieldYpos+screenTop,GR_SHIELD1+i,8+2,0,0);
00424       x += shieldXstep;
00425       s += 2;
00426     }
00427     bulletBlast.reset();
00428     for (int8 i=0; i<3; ++i) {
00429       bombBlast[i].reset();
00430     }
00431   }
00432   void update() {
00433     blastShield(bulletBlast,true);
00434     for (int8 i=0; i<3; ++i) {
00435       blastShield(bombBlast[i],false);
00436     }    
00437   }
00438   // Zap them in various ways
00439   // nb. We defer the action because updating the sprites
00440   // might be slow and we want to make sure all collision
00441   // detection happens in the vertical blank
00442   void shoot(byte s, int x) {
00443     bulletBlast.blast(s,x);
00444   }
00445   void bomb(byte s, int x) {
00446     for (int8 i=0; i<3; ++i) {
00447       BlastInfo& b = bombBlast[i];
00448       if (!b.hasBlast()) {
00449         b.blast(s,x);
00450         break;
00451       }
00452     }
00453   }
00454 } shields;
00455 void shootShield(byte sprite, int bulletX)
00456 {
00457   shields.shoot(sprite,bulletX);
00458 }
00459 
00460 /*---------------------------------------------
00461   Flying saucer
00462   
00463   The score for the saucer depends on how
00464   many bullets you've fired. If you want
00465   a good score hit it with bullet 22 and
00466   every 15th bullet after that.
00467   
00468   The direction of the saucer also depends
00469   on the bullet count. If you're counting
00470   bullets then note the the saucer will
00471   appear on alternate sides and you can
00472   be ready for it.
00473  
00474   Repeat after me: There are NO random
00475   numbers in Space Invaders.
00476 ---------------------------------------------*/
00477 static PROGMEM prog_uchar saucerScores[15] = {
00478   // nb. There's only one '300' here...
00479   10,5,10,15,10,10,5,30,10,10,10,5,15,10,5
00480 };
00481 class Saucer : GameObject {
00482   byte timer, scoreTimer;
00483   byte score;
00484   byte bulletCounter;
00485   unsigned int timeUntilNextSaucer;
00486   bool leftRight,goingRight,showingScore;
00487   void startWaiting() {
00488     status = S_WAITING;
00489     timeUntilNextSaucer = saucerFrequency;
00490   }
00491 public:
00492   void reset() {
00493     initialize(SP_SAUCER1);
00494     timer = 1;
00495     ypos = saucerYpos;
00496     showingScore = false;
00497     bulletCounter = 0;
00498     leftRight = true;
00499     timeUntilNextSaucer = saucerFrequency;
00500   }
00501   void update() {
00502     int xoff=0;
00503     byte gr1=GR_SAUCER, gr2=gr1;
00504     byte fr1=3, fr2=fr1;  // Blank sprite
00505     switch (status) {
00506       case S_WAITING: if ((remainingInvaders>7) and !--timeUntilNextSaucer) {
00507                         status = S_ALIVE;
00508                         timer = saucerSkip;
00509                         goingRight = leftRight;
00510                         if (goingRight) {
00511                           xpos = saucerXmin-saucerSpeed;
00512                         }
00513                         else {
00514                           xpos = saucerXmax+saucerSpeed;
00515                         }
00516                         saucerSound = true;
00517                       }
00518                       else {
00519                         stopSaucerSnd = true;
00520                       }
00521                       break;
00522       case S_ALIVE:   if (!--timer) {
00523                         // The player has to go faster then the saucer so we skip frames...
00524                         timer = saucerSkip;
00525                       }
00526                       else {
00527                         if (goingRight) {
00528                           xpos += saucerSpeed;
00529                           if (xpos > saucerXmax) {
00530                             startWaiting();
00531                           }
00532                         }
00533                         else {
00534                           xpos -= saucerSpeed;
00535                           if (xpos < saucerXmin) {
00536                             startWaiting();
00537                           }
00538                         }
00539                       }
00540                       fr1 = 0;    // Normal saucer
00541                       break;
00542       case S_DYING:   if (!--timer) {
00543                         if (showingScore) {
00544                           startWaiting();
00545                         }
00546                         else {
00547                           timer = 60;
00548                           showingScore = true;
00549                           playerScore += score*10;
00550                         }
00551                       }
00552                       else {
00553                         if (showingScore) {
00554                           xoff = -5;
00555                           gr1 = GR_SAUCER_SCORE;
00556                           gr2 = GR_BULLET;    fr2 = 2;
00557                           if (score == 5) { fr1=0; xoff-=4;}
00558                           else if (score == 10) { fr1 = 1; }
00559                           else if (score == 15) { fr1 = 2; }
00560                           else if (score == 30) { fr1 = 3; }
00561                         }
00562                         else {
00563                           fr1 = 1;    // Explosion left
00564                           fr2 = 2;    // Explosion right
00565                           xoff = -5;  // Move it a bit to the left
00566                         }
00567                       }
00568                       break;
00569     }
00570     // Saucer sometimes needs two sprites...
00571     doubleSprite(gr1,fr1,gr2,fr2,xoff);
00572   }
00573   void incCounter() {
00574     if (++bulletCounter == 15) {
00575       bulletCounter = 0;
00576     }
00577     leftRight = !leftRight;
00578   }
00579   void kill() {
00580     status = S_DYING;
00581     timer = 36;
00582     saucerDieSound = true;
00583     showingScore = false;
00584     score = *saucerScores+bulletCounter;
00585   }
00586 } saucer;
00587 
00588 void incSaucerCounter()
00589 {
00590   saucer.incCounter();
00591 }
00592 void shootSaucer()
00593 {
00594   saucer.kill();
00595 }
00596 /*---------------------------------------------
00597   A space invader...
00598 ---------------------------------------------*/
00599 enum invader_type {
00600   INVADER_T,    // Top-row invader
00601   INVADER_M,    // Middle-row invader
00602   INVADER_B,    // Bottom-row invader
00603   NUM_INVADER_TYPES
00604 };
00605 static PROGMEM prog_uchar invaderGraphic[NUM_INVADER_TYPES] = {
00606   GR_INVADER_T, GR_INVADER_M, GR_INVADER_B
00607 };
00608 
00609 static PROGMEM prog_uchar invaderScore[NUM_INVADER_TYPES] = {
00610   30, 20, 10
00611 };
00612 
00613 class Invader : public GameObject {
00614   // Bitmasks for my vars
00615   enum var_bits {
00616     TYPEMASK = 0x0003,    // Type of invader, 0=top row, 1=middle row, 2=bottom row
00617     ANIM     = 0x0010,    // Flip-flop for animation frame
00618     GO_RIGHT = 0x0020,    // Horizontal direction
00619     GO_DOWN  = 0x0040,    // If I should go downwards next time
00620   };
00621   byte vars;      // All my vars, packed together
00622 
00623   byte readTable(const prog_uchar *t) {
00624     return (*t + (vars&TYPEMASK));
00625   }
00626   void updateTheSprite() {
00627     byte img = readTable(invaderGraphic);
00628     byte fr = 3;    // Invisible...
00629     switch (status) {
00630       case S_ALIVE:   fr = (vars&ANIM)? 0:1;  // Two frame animation
00631                       break;
00632       case S_DYING:   fr = 2;                 // Explosion graphic
00633                       break;
00634     }
00635     updateSprite(img,fr);
00636   }
00637 public:
00638   
00639   bool isAlive() const {
00640     return ((status==S_WAITING) or (status==S_ALIVE));
00641   }
00642   void goDown() {
00643     vars |= GO_DOWN;
00644   }
00645 
00646   // Put me on screen at (x,y), set my type and sprite number.
00647   // I will be invisible and appear next frame (ie. when you call "update()")
00648   void reset(byte sp, int x, int y, invader_type t) {
00649     initialize(sp,S_WAITING,x,y);
00650     vars = t|GO_RIGHT;
00651     updateTheSprite();
00652   }
00653 
00654   // Update me, return "true" if I reach the edge of the screen
00655   bool update() {
00656     bool hitTheEdge = false;
00657     switch (status) {
00658       case S_WAITING: status = S_ALIVE;
00659                       break;
00660       case S_ALIVE:   if (vars&GO_DOWN) {
00661                         ypos += invaderYstep;
00662                         vars &= ~GO_DOWN;
00663                         vars ^= GO_RIGHT;
00664                       }
00665                       else {
00666                         if (vars&GO_RIGHT) {
00667                           xpos += invaderXstep;
00668                           hitTheEdge = (xpos >= invaderXmax);
00669                         }
00670                         else {
00671                           xpos -= invaderXstep;
00672                           hitTheEdge = (xpos <= invaderXmin);
00673                         }
00674                       }
00675                       vars = vars^ANIM;  // Animation flipflop
00676                       break;
00677     }
00678     updateTheSprite();
00679     return hitTheEdge;
00680   }
00681   bool die() {
00682     bool result = (status==S_ALIVE);
00683     if (result) {
00684       status = S_DYING;
00685       updateTheSprite();
00686       playerScore += readTable(invaderScore);
00687       alienDeathSound = true;
00688     }
00689     return result;
00690   }
00691   void kill() {
00692     status = S_DEAD;
00693     updateTheSprite();
00694     --remainingInvaders;
00695   }
00696 };
00697 
00698 /*---------------------------------------------
00699   The array of invaders
00700 ---------------------------------------------*/
00701 // Table for starting height of invaders on each level
00702 static PROGMEM prog_char invaderHeightTable[] = {
00703   1,2,3,3,3,4,4,4
00704 };
00705 
00706 class InvaderList {
00707   byte nextInvader;              // The invader to update on the next frame
00708   int dyingInvader;             // Which invader is currently dying
00709   int8 deathTimer;               // COuntdown during death phase
00710   bool anInvaderHitTheEdge;      // When "true" the invaders should go down a line and change direction
00711   bool anInvaderReachedTheBottom;// When "true" an invader has landed... Game Over!
00712   Invader invader[numInvaders];  // The invaders
00713   
00714   bool findNextLivingInvader() {
00715     // Find next living invader in the array
00716     bool foundOne = false;
00717     for (int8 i=0; i<numInvaders; ++i) {
00718       if (++nextInvader == numInvaders) {
00719         // Actions taken after all the invaders have moved
00720         nextInvader = 0;
00721         if (anInvaderHitTheEdge) {
00722           for (int8 j=0; j<numInvaders; ++j) {
00723             invader[j].goDown();
00724           }
00725           anInvaderHitTheEdge = false;
00726         }
00727       }
00728       if (invader[nextInvader].isAlive()) {
00729         foundOne = true;
00730         break;
00731       }
00732     }
00733     return foundOne;
00734   }
00735 public:
00736   void reset(int8 level) {
00737     int y = invaderAppearY+(invaderRows*invaderYspacing);
00738     if (invaderWave > 0) {
00739       char w = (*invaderHeightTable+((invaderWave-1)&7));
00740       y += w*invaderYstep;
00741     }
00742     for (int8 row=0; row<invaderRows; ++row) {
00743       int x = invaderAppearX;
00744       for (int8 col=0; col<invadersPerRow; ++col) {
00745         const int8 index = (row*invadersPerRow)+col;
00746         Invader& n = invader[index];
00747         invader_type t = INVADER_B;
00748         if (row > 1) {  t = INVADER_M;   }
00749         if (row > 3) {  t = INVADER_T;   }
00750         n.reset(SP_FIRST_INVADER+index,x,y,t);
00751         x += invaderXspacing;
00752       }
00753       y -= invaderYspacing;
00754     }
00755     remainingInvaders = numInvaders;
00756     nextInvader = 0;    // Start updating them here...
00757     dyingInvader = -1;
00758     deathTimer = 0;
00759     anInvaderHitTheEdge = false;
00760     anInvaderReachedTheBottom = false;
00761   }
00762   void update() {
00763     if (dyingInvader != -1) {
00764       // We stop marching when an invader dies
00765       if (!--deathTimer) {
00766         invader[dyingInvader].kill();
00767         dyingInvader = -1;
00768       }
00769     }
00770     else if (!player.isDying() and (remainingInvaders>0)) {
00771       // Update an invader
00772       Invader& n = invader[nextInvader];
00773       if (n.isAlive()) {
00774         // Move the invader
00775         if (n.update()) {
00776           anInvaderHitTheEdge = true;
00777         }
00778         if ((n.ypos+8) > player.ypos) {
00779           anInvaderReachedTheBottom = true;
00780         }
00781       }
00782       findNextLivingInvader();
00783     }
00784   }
00785   // Kill the invader with sprite 'n'
00786   bool kill(byte n) {
00787     n -= SP_FIRST_INVADER;
00788     bool result = invader[n].die();
00789     if (result) {
00790       if (dyingInvader != -1) {
00791         invader[dyingInvader].kill();
00792       }
00793       dyingInvader = n;
00794       deathTimer = 16;
00795     }
00796     return result;
00797   }
00798   int nearestColumnToPlayer() {
00799     Invader& n = invader[nextInvader];  // We know this invader is alive so use it as a reference
00800     int r = nextInvader%invadersPerRow; // The column this invader is in
00801     int left = n.xpos-(r*invaderXspacing);
00802     int c = (((player.xpos-left)+(invaderXspacing/2))/invaderXspacing);
00803     if ((c>=0) and (c<invadersPerRow)) {
00804       return c;
00805     }
00806     return -1;
00807   }
00808   const Invader *getColumn(int c) {
00809     while ((c>=0) and (c<numInvaders)) {
00810       const Invader *v = invader+c;
00811       if (v->isAlive()) {
00812         return v;
00813       }
00814       c += invadersPerRow;
00815     }
00816     return 0;
00817   }
00818   bool haveLanded() {
00819     return anInvaderReachedTheBottom;
00820   }
00821 } invaders;
00822 
00823 bool killInvader(byte n)
00824 {
00825   return invaders.kill(n);
00826 }
00827 
00828 /*---------------------------------------------------------
00829   Space invader bombs
00830   
00831   There's three bombs in Space Invaders. Two of them
00832   follow a pattern of columns, the other one always
00833   appears right above the player (to stop you getting
00834   bored...!)
00835   
00836   Mantra: There are NO random numbers in Space Invaders...
00837 
00838   nb. Column 1 is the most dangerous and column 5
00839       isn't in either table... :-)
00840 ---------------------------------------------------------*/
00841 // Column table for the 'zigzag' bomb
00842 static prog_char zigzagBombColumns[] = {
00843   11,1,6,3,1,1,11,9,2,8,2,11,4,7,10,-1
00844 };
00845 // Column table for the bomb with horizontal bars across it
00846 static prog_char barBombColumns[] = {
00847   1,7,1,1,1,4,11,1,6,3,1,1,11,9,2,8,-1
00848 };
00849 byte bombTimer;    // Countdown until next bomb can be dropped
00850 void resetBombTimer()
00851 {
00852   if (!player.isAlive()) {
00853     bombTimer = 60;    // We don't drop for this long after you reanimate
00854   }
00855   else {
00856     // You get more bombs as the game progresses :-)
00857     if (playerScore < 200)       { bombTimer = 48;  }
00858     else if (playerScore < 1000) { bombTimer = 16;  }
00859     else if (playerScore < 2000) { bombTimer = 11;  }
00860     else if (playerScore < 3000) { bombTimer = 8;   }
00861     else                         { bombTimer = 7;   }
00862   }
00863 }
00864 class Bomb : public GameObject {
00865   byte graphic;
00866   byte timer;
00867   byte cycle;
00868   prog_char *columnTable, *tablePtr;
00869   bool readyToDrop() {
00870     return (bombTimer==0);
00871   }
00872   int8 getNextColumn() {
00873     int c = *tablePtr;
00874     if (c == -1) {
00875       tablePtr = columnTable;
00876       c = *tablePtr;
00877     }
00878     else {
00879       ++tablePtr;
00880     }
00881     return c-1;
00882   }
00883 public:
00884   Bomb() {
00885     tablePtr = 0;
00886   }
00887   bool isAlive() {
00888     return (status!=S_WAITING);
00889   }
00890   void die() {
00891     status = S_DYING;
00892     timer = 12;
00893   }
00894   void reset(byte sprite, byte gr, prog_char *ct) {
00895     initialize(sprite);
00896     graphic = gr;
00897     columnTable = ct;
00898     if (!tablePtr) {
00899       tablePtr = ct;  // Only set this the first time...
00900     }
00901     cycle = timer = 0;
00902     updateSprite(GR_BOMB_OTHER,3);
00903   }
00904   void update() {
00905     byte gr = GR_BOMB_OTHER;
00906     byte frame = 3;
00907     switch (status) {
00908       case S_WAITING: if (bombTimer == 0) {
00909                         int c = -1;
00910                         if (columnTable) {
00911                           // Follow sequence of columns
00912                           c = getNextColumn();
00913                         }
00914                         else {
00915                           // Drop me above the player
00916                           c = invaders.nearestColumnToPlayer();
00917                         }
00918                         const Invader *v = invaders.getColumn(c);
00919                         if (v) {
00920                           status = S_ALIVE;
00921                           xpos = v->xpos;
00922                           ypos = v->ypos+8;
00923                           resetBombTimer();
00924                         }
00925                       }
00926                       break;
00927       case S_ALIVE:   ypos += bombSpeed;
00928                       if (ypos > bombYmax) {
00929                         ypos = bombYmax;
00930                         die();
00931                       }
00932                       gr = graphic;
00933                       if (++timer==2) {
00934                         ++cycle;
00935                         timer = 0;
00936                       }
00937                       frame = cycle&3;
00938                       break;
00939       case S_DYING:   if (!--timer) {
00940                         status = S_WAITING;
00941                       }
00942                       else {
00943                         frame = 0;  // Bomb blast graphic
00944                       }
00945                       break;
00946     }
00947     updateSprite(gr,frame);
00948   }
00949   void collide() {
00950     if (status==S_ALIVE) {
00951       byte b = collision();
00952       if (b == SP_PLAYER) {
00953         player.kill();
00954         status = S_DYING;
00955       }
00956       if ((b>=SP_FIRST_SHIELD) and (b<=SP_LAST_SHIELD)) {
00957         shields.bomb(b,xpos);
00958         die();
00959       }
00960     }
00961   }
00962 };
00963 
00964 class Bombs {
00965   Bomb zigzag,bar,diag;
00966 public:
00967   void reset() {
00968     resetBombTimer();
00969     prog_char* bombptr = zigzagBombColumns;
00970     zigzag.reset(SP_BOMB1, GR_BOMB_ZIGZAG, bombptr);
00971     bombptr = barBombColumns;
00972     bar   .reset(SP_BOMB2, GR_BOMB_BARS,   bombptr);
00973     diag  .reset(SP_BOMB3, GR_BOMB_DIAG,  0);
00974   }
00975   void update() {
00976     if (player.isAlive()) {
00977       if (bombTimer > 0) {
00978         --bombTimer;
00979       }
00980       zigzag.update();
00981       bar   .update();
00982       diag  .update();
00983     }
00984   }
00985   void collide() {
00986     zigzag.collide();
00987     bar   .collide();
00988     diag  .collide();
00989   }
00990   void shoot(byte s) {
00991     if (zigzag.sprite==s) zigzag.die();
00992     if (bar.sprite   ==s) bar.die();
00993     if (diag.sprite  ==s) diag.die();
00994   }
00995 } bombs;
00996 
00997 void shootBomb(byte s)
00998 {
00999   bombs.shoot(s);
01000 }
01001 /*---------------------------------------------
01002   Start next wave of invaders
01003 ---------------------------------------------*/
01004 void startNextWave()
01005 {
01006   beatCounter = 0;
01007   player.reset();
01008   bullet.reset();
01009   saucer.reset();
01010   bombs.reset();
01011   shields.reset();
01012   invaders.reset(invaderWave);
01013   if (++invaderWave == 0) {
01014     invaderWave = 1;
01015   }
01016 }
01017 
01018 /*---------------------------------------------
01019   Reset the game
01020 ---------------------------------------------*/
01021 void resetGame()
01022 {
01023   numLives = 3;
01024   playerScore = 0;
01025   invaderWave = 0;
01026   startNextWave();
01027   redrawScores();
01028   redrawBases();
01029   GD.fill((64*((screenTop+239)>>3))+(screenLeft>>3),CH_FLOOR,screenWidth>>3);
01030 }
01031 
01032 /*---------------------------------------------
01033   Update the game - called from "loop()"
01034 ---------------------------------------------*/
01035 void updateGame()
01036 {
01037   ++frameCounter;
01038   // Collision detection first (we have to do it all during vertical blanking!)
01039   bullet.collide();
01040   bombs.collide();
01041   // The rest of the game logic
01042   joystick.read();
01043   player.update();
01044   bullet.update();
01045   saucer.update();
01046   bombs.update();
01047   shields.update();
01048   invaders.update();
01049   if (!remainingInvaders) {
01050     startNextWave();
01051   }
01052   if (player.isDying()) {
01053     bombs.reset();
01054     bullet.reset();
01055   }
01056   if (player.isDead()) {
01057     resetGame();
01058   }
01059   if (invaders.haveLanded()) {
01060     numLives = 1;
01061     player.kill();
01062   }
01063   updateScore();
01064   updateRemainingBases();
01065   if (--beatCounter < 0) {
01066     alienBeatSound = true;
01067     beatCounter = remainingInvaders+4;
01068   }
01069 }
01070 
01071 /*---------------------------------------------
01072   This is called once from "setup()"
01073 ---------------------------------------------*/
01074 void initGame()
01075 {
01076   joystick.recalibrate();
01077   // Use a copperlist to simulate the colored plastic
01078   // screen overlay...
01079   CopperlistBuilder cp;
01080   cp.begin(0x3700);
01081   // White at the top
01082   cp.write16(PALETTE4A+2,0x7fff);
01083   // Red for the saucer
01084   cp.wait(screenTop+bulletTop);
01085   cp.write16(PALETTE4A+2,0x7c00);
01086   // Back to white again
01087   cp.wait(screenTop+invaderAppearY);
01088   cp.write16(PALETTE4A+2,0x7fff);
01089   // Green for the shields/player
01090   cp.wait(screenTop+shieldYpos);
01091   cp.write16(PALETTE4A+2,0x03e0);
01092   cp.end();
01093   highScore = 0;
01094   resetGame();
01095 }