Chris Dick
/
Gameduino_Invaders_game
Invaders game for the Gameduino
Embed:
(wiki syntax)
Show/hide line numbers
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 }
Generated on Thu Jul 14 2022 11:20:53 by 1.7.2