Space invaders with a nRF2401A wireless joypad
Dependencies: Gameduino mbed nRF2401A
Fork of Gameduino_Invaders_game by
Gameduino and an nRF2401A hooked up to an mbed on an mbeduino:
Diff: game.cpp
- Revision:
- 0:8a7c58553b44
- Child:
- 1:f44175dd69fd
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/game.cpp Thu Jun 21 19:13:34 2012 +0000 @@ -0,0 +1,1095 @@ +#include "game.h" + + +SPI spigame(ARD_MOSI, ARD_MISO, ARD_SCK); // mosi, miso, sclk +/*--------------------------------------------- + Trivia: There is NO random number generator + anywhere in Space Invaders.... +---------------------------------------------*/ + + +/*--------------------------------------------- + Global definitions +---------------------------------------------*/ +#define invaderRows 5 +#define invadersPerRow 11 +#define numInvaders (invaderRows*invadersPerRow) + +// Positions of things on screen +// nb. Space Invaders screen is 256x224 pixels +#define screenTop 24 +#define screenLeft 88 +#define screenWidth 224 +#define screenHeight 256 +// Player +#define playerMinLeft 18 +#define playerMaxRight 188 +#define playerYpos 216 +#define playerSpeed 1 +// Bullet +#define bulletHeight 4 +#define bulletSpeed 4 +#define bulletTop 35 +// Invaders +#define invaderAppearX 26 +#define invaderAppearY 64 +#define invaderXspacing 16 +#define invaderYspacing 16 +#define invaderXstep 2 +#define invaderYstep 8 +#define invaderXmin 10 +#define invaderXmax 202 +// Saucer +#define saucerYpos 42 +#define saucerSpeed 1 +#define saucerXmin 0 +#define saucerXmax (screenWidth-16) +#define saucerSkip 3 +#define saucerFrequency (25*72) +// Shields +#define numShields 4 +#define shieldXpos 32 +#define shieldYpos 192 +#define shieldXstep 45 +// Bombs +#define bombSpeed 1 +#define bombYmax 230 + +/*------------------------------------------------------- + Sprite allocation list + + nb. Sprite order is important for collision detection +-------------------------------------------------------*/ +enum sprite_id { + SP_PLAYER, + SP_FIRST_SHIELD, + SP_LAST_SHIELD = SP_FIRST_SHIELD+(2*numShields)-1, + // Invader bombs (can hit shields and player) + SP_BOMB1, // nb. There's only three bombs in Space invaders... + SP_BOMB2, + SP_BOMB3, + // Invaders (can't be hit by their own bombs) + SP_FIRST_INVADER, + SP_LAST_INVADER = SP_FIRST_INVADER+numInvaders-1, + // Flying saucer (needs two sprites because it's very wide...) + SP_SAUCER1, + SP_SAUCER2, + // Bullet (last ... because it can hit anything) + SP_BULLET +}; + + +/*--------------------------------------------- + Global vars +---------------------------------------------*/ +// Joystick object +Joystick joystick; + +// This increments once per frame +static unsigned int frameCounter; + +// The current wave of invaders [0..n] +static unsigned int invaderWave; + +// Number of lives the player has left... +static byte numLives; + +// Player's score... +static unsigned int playerScore; + +// High score +static unsigned int highScore; + +// Number of living space invaders +static unsigned int remainingInvaders; + +// Timer for the background heartbeat sound +static int beatCounter; + +/*--------------------------------------------- + General functions +---------------------------------------------*/ +static PROGMEM prog_char scoreMsg[] = "Score"; +static PROGMEM prog_char hiScoreMsg[] = "Hi-Score"; +static unsigned int previousPlayerScore, previousHighScore; +void redrawScores() +{ + previousPlayerScore = previousHighScore = 0xffff; +} + +unsigned int putDigit(unsigned int s, unsigned int d) +{ + byte c = '0'; + while (s >= d) { + ++c; + s -= d; + } + spigame.write(c); + return s; +} +void printScore(int8 x, const prog_char *m, unsigned int s, int8 xoff) +{ + x += screenLeft/8; + int y = screenTop/8; + unsigned int addr = (y*64)+x; + GD.__wstart(addr); + char c = *m; + while (c != 0) { + spigame.write(c); + c = *m++; + } + GD.__end(); + addr += (2*64)+xoff; + GD.__wstart(addr); + s = putDigit(s,10000); + s = putDigit(s,1000); + s = putDigit(s,100); + s = putDigit(s,10); + spigame.write(s+'0'); + GD.__end(); +} +void updateScore() +{ + if (playerScore != previousPlayerScore) { + printScore(0,scoreMsg,playerScore,0); + previousPlayerScore = playerScore; + if (playerScore > highScore) { + highScore = playerScore; + } + } + if (highScore != previousHighScore) { + printScore(20,hiScoreMsg,highScore,3); + previousHighScore = highScore; + } +} + +static unsigned short int prevLives; +static void redrawBases() +{ + prevLives = 0xffff; +} +void updateRemainingBases() +{ + if (numLives != prevLives) { + prevLives = numLives; + GD.__wstart((64*((screenTop+240)>>3))+(screenLeft>>3)); + spigame.write(numLives+'0'); + spigame.write(0); + for (byte i=1; i<numLives; ++i) { + spigame.write(CH_PLAYERL); + spigame.write(CH_PLAYERR); + } + spigame.write(0); + spigame.write(0); + GD.__end(); + } +} + +/*--------------------------------------------- + A generic object in the game +---------------------------------------------*/ +enum object_status { + S_WAITING, + S_ALIVE, + S_DYING, + S_DEAD +}; + +struct GameObject { + byte sprite; // Which sprite to use for my graphic (see "sprite_id") + byte status; // What I'm doing at the moment + int xpos,ypos; // Position on screen + // State of objects in the game + void initialize(byte s, object_status t=S_WAITING, int x=400, int y=0) { + sprite = s; + status = t; + xpos = x; + ypos = y; + updateSprite(0,0); + } + void updateSprite(byte img, byte frame) { + GD.sprite(sprite,xpos+screenLeft,ypos+screenTop,img,8+(frame<<1),0,0); + } + void doubleSprite(byte img1, byte frame1, byte img2, byte frame2, int8 xoff) { + int x = xpos+screenLeft+xoff; + int y = ypos+screenTop; + GD.sprite(sprite, x, y,img1,8+(frame1<<1),0,0); + GD.sprite(sprite+1,x+16,y,img2,8+(frame2<<1),0,0); + } + byte collision() { + return GD.rd(0x2900+sprite); + } +}; + +/*--------------------------------------------- + Player's bullet +---------------------------------------------*/ +// Forward references to functions +bool killInvader(byte spriteNumber); +void shootShield(byte spriteNumber, int bulletX); +void shootSaucer(); +void shootBomb(byte spriteNumber); +void incSaucerCounter(); + +class BulletObject : GameObject { + byte timer; + bool visibleDeath; + void die(bool v) { + visibleDeath = v; + status = S_DYING; + timer = 12; + } +public: + void reset() { + initialize(SP_BULLET); + updateSprite(GR_BULLET,3); + timer = 0; + } + void fire(GameObject& p) { + if (status == S_WAITING){ + status = S_ALIVE; + xpos = p.xpos; + ypos = p.ypos+bulletSpeed-bulletHeight; + playerShootSound = true; + } + } + void update() { + int frame = 3; + switch (status) { + case S_ALIVE: ypos -= bulletSpeed; + if (ypos <= bulletTop) { + ypos = bulletTop; + die(true); + frame = 1; + } + else { + frame = 0; + } + break; + case S_DYING: if (!--timer) { + status = S_WAITING; + incSaucerCounter(); + } + else if (visibleDeath) { + frame = 1; + } + break; + } + updateSprite(GR_BULLET,frame); + } + void setY(int y) { + if (status == S_DYING) { + ypos = y; + updateSprite(GR_BULLET,1); + //GD.wr16(SCROLL_Y,GD.rd16(SCROLL_Y)+1); + } + } + // See if the bullet hit anything + void collide() { + if (status == S_ALIVE) { + byte b = collision(); + if (b != 0xff) { + if ((b >= SP_FIRST_INVADER) and (b <= SP_LAST_INVADER)) { + if (killInvader(b)) { + die(false); + } + } + if ((b >= SP_FIRST_SHIELD) and (b <= SP_LAST_SHIELD)) { + shootShield(b,xpos); + die(true); + } + if ((b >= SP_SAUCER1) and (b <= SP_SAUCER2)) { + shootSaucer(); + die(false); + } + if ((b >= SP_BOMB1) and (b <= SP_BOMB3)) { + shootBomb(b); + die(false); + } + } + } + } +} bullet; + +/*--------------------------------------------- + The player +---------------------------------------------*/ +class Player : public GameObject { + byte timer; +public: + void reset() { + timer = 2*numInvaders; + initialize(SP_PLAYER,S_WAITING,playerMinLeft,playerYpos); + updateSprite(GR_PLAYER,3); + } + + void update() { + int frame = 3; + switch (status) { + case S_WAITING: xpos = playerMinLeft; + ypos = playerYpos; + if (!--timer) { + status = S_ALIVE; + } + break; + case S_ALIVE: if (joystick.left()) { + xpos -= playerSpeed; + if (xpos < playerMinLeft) { + xpos = playerMinLeft; + } + } + if (joystick.right()) { + xpos += playerSpeed; + if (xpos > playerMaxRight) { + xpos = playerMaxRight; + } + } + { byte n = Joystick::buttonA|Joystick::buttonB; + if (joystick.isPressed(n) and joystick.changed(n)) { + bullet.fire(*this); + } + } + frame = 0; + break; + case S_DYING: if (!--timer) { + timer = 3*remainingInvaders; + status = (--numLives>0)? S_WAITING: S_DEAD; + } + else { + frame = ((frameCounter&4)==0)? 1:2; + } + break; + } + updateSprite(GR_PLAYER,frame); + } + void kill() { + if (status == S_ALIVE) { + status = S_DYING; + timer = 50; + playerDeathSound = true; + } + } + bool isAlive() { + return (status==S_ALIVE); + } + bool isDying() { + return (status==S_DYING); + } + bool isDead() { + return (status==S_DEAD); + } + void wakeUp() { + } +} player; + +/*--------------------------------------------- + "Shields" for the player to hide behind +---------------------------------------------*/ +class Shields { + struct BlastInfo { + byte sprite; + int xpos; + void reset() { + sprite = 255; + } + bool hasBlast() const { + return (sprite!=255); + } + void blast(byte s, int x) { + sprite = s; + xpos = x; + } + }; + BlastInfo bulletBlast, bombBlast[3]; + void blastShield(BlastInfo& n, bool asBullet) { + if (n.hasBlast()) { + byte s = (n.sprite-SP_FIRST_SHIELD)>>1; + int8 x = int8(n.xpos-(shieldXpos+(s*shieldXstep))); + //int8 y = zapShield(s,x,asBullet); + if (asBullet) { + //bullet.setY(shieldYpos+y); + } + n.reset(); + } + } +public: + void reset() { + remakeShields(); + int x = shieldXpos; + byte s = SP_FIRST_SHIELD; + for (int i=0; i<numShields; ++i) { + GD.sprite(s, x+screenLeft, shieldYpos+screenTop,GR_SHIELD1+i,8+0,0,0); + GD.sprite(s+1,x+screenLeft+16,shieldYpos+screenTop,GR_SHIELD1+i,8+2,0,0); + x += shieldXstep; + s += 2; + } + bulletBlast.reset(); + for (int8 i=0; i<3; ++i) { + bombBlast[i].reset(); + } + } + void update() { + blastShield(bulletBlast,true); + for (int8 i=0; i<3; ++i) { + blastShield(bombBlast[i],false); + } + } + // Zap them in various ways + // nb. We defer the action because updating the sprites + // might be slow and we want to make sure all collision + // detection happens in the vertical blank + void shoot(byte s, int x) { + bulletBlast.blast(s,x); + } + void bomb(byte s, int x) { + for (int8 i=0; i<3; ++i) { + BlastInfo& b = bombBlast[i]; + if (!b.hasBlast()) { + b.blast(s,x); + break; + } + } + } +} shields; +void shootShield(byte sprite, int bulletX) +{ + shields.shoot(sprite,bulletX); +} + +/*--------------------------------------------- + Flying saucer + + The score for the saucer depends on how + many bullets you've fired. If you want + a good score hit it with bullet 22 and + every 15th bullet after that. + + The direction of the saucer also depends + on the bullet count. If you're counting + bullets then note the the saucer will + appear on alternate sides and you can + be ready for it. + + Repeat after me: There are NO random + numbers in Space Invaders. +---------------------------------------------*/ +static PROGMEM prog_uchar saucerScores[15] = { + // nb. There's only one '300' here... + 10,5,10,15,10,10,5,30,10,10,10,5,15,10,5 +}; +class Saucer : GameObject { + byte timer, scoreTimer; + byte score; + byte bulletCounter; + unsigned int timeUntilNextSaucer; + bool leftRight,goingRight,showingScore; + void startWaiting() { + status = S_WAITING; + timeUntilNextSaucer = saucerFrequency; + } +public: + void reset() { + initialize(SP_SAUCER1); + timer = 1; + ypos = saucerYpos; + showingScore = false; + bulletCounter = 0; + leftRight = true; + timeUntilNextSaucer = saucerFrequency; + } + void update() { + int xoff=0; + byte gr1=GR_SAUCER, gr2=gr1; + byte fr1=3, fr2=fr1; // Blank sprite + switch (status) { + case S_WAITING: if ((remainingInvaders>7) and !--timeUntilNextSaucer) { + status = S_ALIVE; + timer = saucerSkip; + goingRight = leftRight; + if (goingRight) { + xpos = saucerXmin-saucerSpeed; + } + else { + xpos = saucerXmax+saucerSpeed; + } + saucerSound = true; + } + else { + stopSaucerSnd = true; + } + break; + case S_ALIVE: if (!--timer) { + // The player has to go faster then the saucer so we skip frames... + timer = saucerSkip; + } + else { + if (goingRight) { + xpos += saucerSpeed; + if (xpos > saucerXmax) { + startWaiting(); + } + } + else { + xpos -= saucerSpeed; + if (xpos < saucerXmin) { + startWaiting(); + } + } + } + fr1 = 0; // Normal saucer + break; + case S_DYING: if (!--timer) { + if (showingScore) { + startWaiting(); + } + else { + timer = 60; + showingScore = true; + playerScore += score*10; + } + } + else { + if (showingScore) { + xoff = -5; + gr1 = GR_SAUCER_SCORE; + gr2 = GR_BULLET; fr2 = 2; + if (score == 5) { fr1=0; xoff-=4;} + else if (score == 10) { fr1 = 1; } + else if (score == 15) { fr1 = 2; } + else if (score == 30) { fr1 = 3; } + } + else { + fr1 = 1; // Explosion left + fr2 = 2; // Explosion right + xoff = -5; // Move it a bit to the left + } + } + break; + } + // Saucer sometimes needs two sprites... + doubleSprite(gr1,fr1,gr2,fr2,xoff); + } + void incCounter() { + if (++bulletCounter == 15) { + bulletCounter = 0; + } + leftRight = !leftRight; + } + void kill() { + status = S_DYING; + timer = 36; + saucerDieSound = true; + showingScore = false; + score = *saucerScores+bulletCounter; + } +} saucer; + +void incSaucerCounter() +{ + saucer.incCounter(); +} +void shootSaucer() +{ + saucer.kill(); +} +/*--------------------------------------------- + A space invader... +---------------------------------------------*/ +enum invader_type { + INVADER_T, // Top-row invader + INVADER_M, // Middle-row invader + INVADER_B, // Bottom-row invader + NUM_INVADER_TYPES +}; +static PROGMEM prog_uchar invaderGraphic[NUM_INVADER_TYPES] = { + GR_INVADER_T, GR_INVADER_M, GR_INVADER_B +}; + +static PROGMEM prog_uchar invaderScore[NUM_INVADER_TYPES] = { + 30, 20, 10 +}; + +class Invader : public GameObject { + // Bitmasks for my vars + enum var_bits { + TYPEMASK = 0x0003, // Type of invader, 0=top row, 1=middle row, 2=bottom row + ANIM = 0x0010, // Flip-flop for animation frame + GO_RIGHT = 0x0020, // Horizontal direction + GO_DOWN = 0x0040, // If I should go downwards next time + }; + byte vars; // All my vars, packed together + + byte readTable(const prog_uchar *t) { + return (*t + (vars&TYPEMASK)); + } + void updateTheSprite() { + byte img = readTable(invaderGraphic); + byte fr = 3; // Invisible... + switch (status) { + case S_ALIVE: fr = (vars&ANIM)? 0:1; // Two frame animation + break; + case S_DYING: fr = 2; // Explosion graphic + break; + } + updateSprite(img,fr); + } +public: + + bool isAlive() const { + return ((status==S_WAITING) or (status==S_ALIVE)); + } + void goDown() { + vars |= GO_DOWN; + } + + // Put me on screen at (x,y), set my type and sprite number. + // I will be invisible and appear next frame (ie. when you call "update()") + void reset(byte sp, int x, int y, invader_type t) { + initialize(sp,S_WAITING,x,y); + vars = t|GO_RIGHT; + updateTheSprite(); + } + + // Update me, return "true" if I reach the edge of the screen + bool update() { + bool hitTheEdge = false; + switch (status) { + case S_WAITING: status = S_ALIVE; + break; + case S_ALIVE: if (vars&GO_DOWN) { + ypos += invaderYstep; + vars &= ~GO_DOWN; + vars ^= GO_RIGHT; + } + else { + if (vars&GO_RIGHT) { + xpos += invaderXstep; + hitTheEdge = (xpos >= invaderXmax); + } + else { + xpos -= invaderXstep; + hitTheEdge = (xpos <= invaderXmin); + } + } + vars = vars^ANIM; // Animation flipflop + break; + } + updateTheSprite(); + return hitTheEdge; + } + bool die() { + bool result = (status==S_ALIVE); + if (result) { + status = S_DYING; + updateTheSprite(); + playerScore += readTable(invaderScore); + alienDeathSound = true; + } + return result; + } + void kill() { + status = S_DEAD; + updateTheSprite(); + --remainingInvaders; + } +}; + +/*--------------------------------------------- + The array of invaders +---------------------------------------------*/ +// Table for starting height of invaders on each level +static PROGMEM prog_char invaderHeightTable[] = { + 1,2,3,3,3,4,4,4 +}; + +class InvaderList { + byte nextInvader; // The invader to update on the next frame + int dyingInvader; // Which invader is currently dying + int8 deathTimer; // COuntdown during death phase + bool anInvaderHitTheEdge; // When "true" the invaders should go down a line and change direction + bool anInvaderReachedTheBottom;// When "true" an invader has landed... Game Over! + Invader invader[numInvaders]; // The invaders + + bool findNextLivingInvader() { + // Find next living invader in the array + bool foundOne = false; + for (int8 i=0; i<numInvaders; ++i) { + if (++nextInvader == numInvaders) { + // Actions taken after all the invaders have moved + nextInvader = 0; + if (anInvaderHitTheEdge) { + for (int8 j=0; j<numInvaders; ++j) { + invader[j].goDown(); + } + anInvaderHitTheEdge = false; + } + } + if (invader[nextInvader].isAlive()) { + foundOne = true; + break; + } + } + return foundOne; + } +public: + void reset(int8 level) { + int y = invaderAppearY+(invaderRows*invaderYspacing); + if (invaderWave > 0) { + char w = (*invaderHeightTable+((invaderWave-1)&7)); + y += w*invaderYstep; + } + for (int8 row=0; row<invaderRows; ++row) { + int x = invaderAppearX; + for (int8 col=0; col<invadersPerRow; ++col) { + const int8 index = (row*invadersPerRow)+col; + Invader& n = invader[index]; + invader_type t = INVADER_B; + if (row > 1) { t = INVADER_M; } + if (row > 3) { t = INVADER_T; } + n.reset(SP_FIRST_INVADER+index,x,y,t); + x += invaderXspacing; + } + y -= invaderYspacing; + } + remainingInvaders = numInvaders; + nextInvader = 0; // Start updating them here... + dyingInvader = -1; + deathTimer = 0; + anInvaderHitTheEdge = false; + anInvaderReachedTheBottom = false; + } + void update() { + if (dyingInvader != -1) { + // We stop marching when an invader dies + if (!--deathTimer) { + invader[dyingInvader].kill(); + dyingInvader = -1; + } + } + else if (!player.isDying() and (remainingInvaders>0)) { + // Update an invader + Invader& n = invader[nextInvader]; + if (n.isAlive()) { + // Move the invader + if (n.update()) { + anInvaderHitTheEdge = true; + } + if ((n.ypos+8) > player.ypos) { + anInvaderReachedTheBottom = true; + } + } + findNextLivingInvader(); + } + } + // Kill the invader with sprite 'n' + bool kill(byte n) { + n -= SP_FIRST_INVADER; + bool result = invader[n].die(); + if (result) { + if (dyingInvader != -1) { + invader[dyingInvader].kill(); + } + dyingInvader = n; + deathTimer = 16; + } + return result; + } + int nearestColumnToPlayer() { + Invader& n = invader[nextInvader]; // We know this invader is alive so use it as a reference + int r = nextInvader%invadersPerRow; // The column this invader is in + int left = n.xpos-(r*invaderXspacing); + int c = (((player.xpos-left)+(invaderXspacing/2))/invaderXspacing); + if ((c>=0) and (c<invadersPerRow)) { + return c; + } + return -1; + } + const Invader *getColumn(int c) { + while ((c>=0) and (c<numInvaders)) { + const Invader *v = invader+c; + if (v->isAlive()) { + return v; + } + c += invadersPerRow; + } + return 0; + } + bool haveLanded() { + return anInvaderReachedTheBottom; + } +} invaders; + +bool killInvader(byte n) +{ + return invaders.kill(n); +} + +/*--------------------------------------------------------- + Space invader bombs + + There's three bombs in Space Invaders. Two of them + follow a pattern of columns, the other one always + appears right above the player (to stop you getting + bored...!) + + Mantra: There are NO random numbers in Space Invaders... + + nb. Column 1 is the most dangerous and column 5 + isn't in either table... :-) +---------------------------------------------------------*/ +// Column table for the 'zigzag' bomb +static prog_char zigzagBombColumns[] = { + 11,1,6,3,1,1,11,9,2,8,2,11,4,7,10,-1 +}; +// Column table for the bomb with horizontal bars across it +static prog_char barBombColumns[] = { + 1,7,1,1,1,4,11,1,6,3,1,1,11,9,2,8,-1 +}; +byte bombTimer; // Countdown until next bomb can be dropped +void resetBombTimer() +{ + if (!player.isAlive()) { + bombTimer = 60; // We don't drop for this long after you reanimate + } + else { + // You get more bombs as the game progresses :-) + if (playerScore < 200) { bombTimer = 48; } + else if (playerScore < 1000) { bombTimer = 16; } + else if (playerScore < 2000) { bombTimer = 11; } + else if (playerScore < 3000) { bombTimer = 8; } + else { bombTimer = 7; } + } +} +class Bomb : public GameObject { + byte graphic; + byte timer; + byte cycle; + prog_char *columnTable, *tablePtr; + bool readyToDrop() { + return (bombTimer==0); + } + int8 getNextColumn() { + int c = *tablePtr; + if (c == -1) { + tablePtr = columnTable; + c = *tablePtr; + } + else { + ++tablePtr; + } + return c-1; + } +public: + Bomb() { + tablePtr = 0; + } + bool isAlive() { + return (status!=S_WAITING); + } + void die() { + status = S_DYING; + timer = 12; + } + void reset(byte sprite, byte gr, prog_char *ct) { + initialize(sprite); + graphic = gr; + columnTable = ct; + if (!tablePtr) { + tablePtr = ct; // Only set this the first time... + } + cycle = timer = 0; + updateSprite(GR_BOMB_OTHER,3); + } + void update() { + byte gr = GR_BOMB_OTHER; + byte frame = 3; + switch (status) { + case S_WAITING: if (bombTimer == 0) { + int c = -1; + if (columnTable) { + // Follow sequence of columns + c = getNextColumn(); + } + else { + // Drop me above the player + c = invaders.nearestColumnToPlayer(); + } + const Invader *v = invaders.getColumn(c); + if (v) { + status = S_ALIVE; + xpos = v->xpos; + ypos = v->ypos+8; + resetBombTimer(); + } + } + break; + case S_ALIVE: ypos += bombSpeed; + if (ypos > bombYmax) { + ypos = bombYmax; + die(); + } + gr = graphic; + if (++timer==2) { + ++cycle; + timer = 0; + } + frame = cycle&3; + break; + case S_DYING: if (!--timer) { + status = S_WAITING; + } + else { + frame = 0; // Bomb blast graphic + } + break; + } + updateSprite(gr,frame); + } + void collide() { + if (status==S_ALIVE) { + byte b = collision(); + if (b == SP_PLAYER) { + player.kill(); + status = S_DYING; + } + if ((b>=SP_FIRST_SHIELD) and (b<=SP_LAST_SHIELD)) { + shields.bomb(b,xpos); + die(); + } + } + } +}; + +class Bombs { + Bomb zigzag,bar,diag; +public: + void reset() { + resetBombTimer(); + prog_char* bombptr = zigzagBombColumns; + zigzag.reset(SP_BOMB1, GR_BOMB_ZIGZAG, bombptr); + bombptr = barBombColumns; + bar .reset(SP_BOMB2, GR_BOMB_BARS, bombptr); + diag .reset(SP_BOMB3, GR_BOMB_DIAG, 0); + } + void update() { + if (player.isAlive()) { + if (bombTimer > 0) { + --bombTimer; + } + zigzag.update(); + bar .update(); + diag .update(); + } + } + void collide() { + zigzag.collide(); + bar .collide(); + diag .collide(); + } + void shoot(byte s) { + if (zigzag.sprite==s) zigzag.die(); + if (bar.sprite ==s) bar.die(); + if (diag.sprite ==s) diag.die(); + } +} bombs; + +void shootBomb(byte s) +{ + bombs.shoot(s); +} +/*--------------------------------------------- + Start next wave of invaders +---------------------------------------------*/ +void startNextWave() +{ + beatCounter = 0; + player.reset(); + bullet.reset(); + saucer.reset(); + bombs.reset(); + shields.reset(); + invaders.reset(invaderWave); + if (++invaderWave == 0) { + invaderWave = 1; + } +} + +/*--------------------------------------------- + Reset the game +---------------------------------------------*/ +void resetGame() +{ + numLives = 3; + playerScore = 0; + invaderWave = 0; + startNextWave(); + redrawScores(); + redrawBases(); + GD.fill((64*((screenTop+239)>>3))+(screenLeft>>3),CH_FLOOR,screenWidth>>3); +} + +/*--------------------------------------------- + Update the game - called from "loop()" +---------------------------------------------*/ +void updateGame() +{ + ++frameCounter; + // Collision detection first (we have to do it all during vertical blanking!) + bullet.collide(); + bombs.collide(); + // The rest of the game logic + joystick.read(); + player.update(); + bullet.update(); + saucer.update(); + bombs.update(); + shields.update(); + invaders.update(); + if (!remainingInvaders) { + startNextWave(); + } + if (player.isDying()) { + bombs.reset(); + bullet.reset(); + } + if (player.isDead()) { + resetGame(); + } + if (invaders.haveLanded()) { + numLives = 1; + player.kill(); + } + updateScore(); + updateRemainingBases(); + if (--beatCounter < 0) { + alienBeatSound = true; + beatCounter = remainingInvaders+4; + } +} + +/*--------------------------------------------- + This is called once from "setup()" +---------------------------------------------*/ +void initGame() +{ + joystick.recalibrate(); + // Use a copperlist to simulate the colored plastic + // screen overlay... + CopperlistBuilder cp; + cp.begin(0x3700); + // White at the top + cp.write16(PALETTE4A+2,0x7fff); + // Red for the saucer + cp.wait(screenTop+bulletTop); + cp.write16(PALETTE4A+2,0x7c00); + // Back to white again + cp.wait(screenTop+invaderAppearY); + cp.write16(PALETTE4A+2,0x7fff); + // Green for the shields/player + cp.wait(screenTop+shieldYpos); + cp.write16(PALETTE4A+2,0x03e0); + cp.end(); + highScore = 0; + resetGame(); +} +