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
game.cpp
- Committer:
- TheChrisyd
- Date:
- 2012-12-20
- Revision:
- 2:20a89dc286d5
- Parent:
- 1:f44175dd69fd
- Child:
- 4:bb78bedae411
File content as of revision 2:20a89dc286d5:
#include "game.h"
extern GDClass GD;
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();
}
