Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Dependencies: Gameduino mbed nRF2401A
Fork of Gameduino_Invaders_game by
Diff: game.cpp
- Revision:
- 0:8a7c58553b44
- Child:
- 1:f44175dd69fd
diff -r 000000000000 -r 8a7c58553b44 game.cpp
--- /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();
+}
+
