Last active
July 18, 2022 16:47
-
-
Save seanboe/3b5eddb7c466c64d1a83809813f12887 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <SPI.h> | |
#include <Wire.h> | |
#include <Adafruit_GFX.h> | |
// #include <Adafruit_SSD1306.h> | |
#include <Adafruit_ST7735.h> | |
#include "PinChangeInterrupt.h" | |
#include <StopWatch.h> | |
#define TFT_CS 10 | |
#define TFT_RST 8 // Or set to -1 and connect to Arduino RESET pin | |
#define TFT_DC 9 | |
Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST); | |
// CHARACTER GRAPHICS | |
//fighter graphic | |
static const unsigned char PROGMEM fighterGraphic[] = { | |
B00000100, B00000000, | |
B00001110, B00000000, | |
B00001110, B00000000, | |
B01111111, B11000000, | |
B11111111, B11100000, | |
B01111111, B11000000, | |
}; | |
static const unsigned char PROGMEM explodedFighterGraphic[] { | |
B10000100, B00000000, | |
B00001000, B01000000, | |
B01000110, B00000000, | |
B00110010, B10000000, | |
B10100100, B01000000, | |
B01010011, B00000000 | |
}; | |
// bullet graphic | |
static const unsigned char PROGMEM bulletGraphic[] = { | |
B10000000, | |
B10000000, | |
B10000000, | |
B10000000, | |
B10000000, | |
B10000000, | |
}; | |
// invader graphics | |
//crab 1 | |
static const unsigned char PROGMEM crabGraphicOne[] = { | |
B00100000, B10000000, | |
B00010001, B00000000, | |
B10111111, B10100000, | |
B10101110, B10100000, | |
B11111111, B11100000, | |
B00111111, B10000000, | |
B00100000, B10000000, | |
B01000000, B01000000 | |
}; | |
// crab 2 | |
static const unsigned char PROGMEM crabGraphicTwo[] = { | |
B00100000, B10000000, | |
B00010001, B00000000, | |
B00111111, B10000000, | |
B01101110, B11000000, | |
B11111111, B11100000, | |
B10111111, B10100000, | |
B10100000, B10100000, | |
B00011011, B00000000 | |
}; | |
// squid 1 | |
static const unsigned char PROGMEM squidGraphicOne[] = { | |
B00011000, | |
B00111100, | |
B01111110, | |
B11011011, | |
B11111111, | |
B00100100, | |
B01011010, | |
B10100101 | |
}; | |
// squid 2 | |
static const unsigned char PROGMEM squidGraphicTwo[] = { | |
B00011000, | |
B00111100, | |
B01111110, | |
B11011011, | |
B11111111, | |
B01011010, | |
B10000001, | |
B01000010 | |
}; | |
// octopus 1 | |
const unsigned char PROGMEM octopusGraphicOne [] = { | |
B00001111,B00000000, | |
B01111111,B11100000, | |
B11111111,B11110000, | |
B11100110,B01110000, | |
B11111111,B11110000, | |
B00111001,B11000000, | |
B01000110,B00100000, | |
B10000000,B00010000 | |
}; | |
// octopus 2 | |
const unsigned char PROGMEM octopusGraphicTwo [] = { | |
B00001111,B00000000, | |
B01111111,B11100000, | |
B11111111,B11110000, | |
B11100110,B01110000, | |
B11111111,B11110000, | |
B00111001,B11000000, | |
B01100110,B01100000, | |
B00110000,B11000000 | |
}; | |
//UFO | |
static const unsigned char PROGMEM alienShipGraphic[] = { | |
B00000111, B11100000, | |
B00011111, B11111000, | |
B00111111, B11111100, | |
B01101101, B10110110, | |
B11111111, B11111111, | |
B00111001, B10011100, | |
// B00010000, B00001000 | |
}; | |
// explosion | |
static const unsigned char PROGMEM explosionGraphic[] = { | |
B00001000,B10000000, | |
B01000101,B00010000, | |
B00100000,B00100000, | |
B00010000,B01000000, | |
B11000000,B00011000, | |
B00010000,B01000000, | |
B00100101,B00100000, | |
B01001000,B10010000 | |
}; | |
// invader Bullet | |
static const unsigned char PROGMEM invaderBulletGraphic[] = { | |
B01000000, | |
B10000000, | |
B01000000, | |
B10000000, | |
B01000000, | |
B10000000 | |
}; | |
// SCREEN ***************************************************************** | |
#define SCREEN_MAX_WIDTH 124 | |
#define SCREEN_MIN_WIDTH 3 | |
#define SCREEN_MAX_HEIGHT 9 | |
#define SCREEN_MIN_HEIGHT 160 | |
// GAME EXTRAS******************************************************* | |
int previousPoints = 0; | |
int points = 0; | |
int lives = 3; | |
int invaderSoundCycle = 0; | |
int invaderSoundDuration = 75; | |
// STARTUP ***************************************************************** | |
bool beginGame = false; | |
int crabPosStartUp = 0; | |
#define CRABHEIGHTSTARTUP 3 | |
int fighterPosStartUp = 128; | |
#define FIGHTERHEIGHTSTARTUP 26 | |
int promptPos = 53; // where the startup prompt is-x position | |
bool invaderPhaseSetUp = true; // true/false = different crab graphics | |
int oldInvaderTimeSetUp = 2; | |
StopWatch invaderCycleSetUp(StopWatch::SECONDS); | |
// SWITCHES ***************************************************************** | |
#define LEFT_SWITCH 3 | |
#define RIGHT_SWITCH 2 | |
#define FIRE_SWITCH 6 | |
#define SPEED_SWITCH 7 // switch that lets you switch between speeds | |
// buzzer pin | |
#define BUZZ_PIN 5 | |
//FIGHTER MOVEMENT ***************************************************************** | |
volatile bool moveDirection; | |
volatile bool activeMove = false; | |
volatile int switchState; | |
int fighterHeight = 140; // doesn't change | |
int fighterPos = 55; //subject to change depending on the buttons | |
int fighterSpeed = 1; | |
#define FIGHTER_WIDTH 11 | |
#define FIGHTER_LENGTH 6 | |
//BULLETS ***************************************************************** | |
#define TOTAL_BULLETS 1 // number of bullets available to player | |
const byte ALL_BULLETS = TOTAL_BULLETS + 1; | |
byte availableBullet = 0; // variable to hold on-screen bullets | |
byte bulletSpeed = 4; | |
volatile bool bulletHasBeenShot = false; | |
typedef struct { | |
int xPos; | |
int height; | |
bool fired; | |
} Bullet; | |
Bullet shots[ALL_BULLETS]; // array for holding player bullets | |
#define BULLET_LENGTH 6 | |
// INVADERS ************************************************************** | |
#define INVADERS_EXPLOSION_TIME 25 // cycles that exploded invaders/the UFO stay on screen | |
#define SHIP_EXPLOSION_TIME 100 | |
// UFO | |
#define SHIP_SPEED 1 // amount incremented every loop | |
#define ALIEN_SHIP_HEIGHT 13 | |
#define ALIEN_SHIP_START_POS 130 | |
#define ALIEN_SHIP_END_POS -20 | |
#define ALIEN_SHIP_BOUNDARY 22 // this is the height of the invaders at which the ship will appear | |
#define ALIEN_SHIP_PROB 2 // tenth of a percent probability that mystery ship shows up | |
#define ALIEN_SHIP_WIDTH 16 | |
#define ALIEN_SHIP_LENGTH 7 // vertical length of UFO | |
#define SHIP_SOUND_CYCLE_CONST 10 | |
int shipSoundCycle = SHIP_SOUND_CYCLE_CONST; //is counted down to determine time between "beeps" | |
int squidWidth = 8; | |
#define SQUID_POINTS 30 // points per squid hit | |
int crabWidth = 11; | |
#define CRAB_POINTS 20 // points per crab hit | |
int octopusWidth = 12; | |
#define OCTOPUS_POINTS 10 | |
#define INVADER_WIDTH 12 // width of the crab, because crab is bit bigger | |
#define INVADER_LENGTH 8 // vertical length of both invaders | |
#define INVADER_HEIGHT_DIFF 11 | |
#define ROW_ONE_HEIGHT 10 | |
#define INVADER_ROWS 4 | |
#define INVADER_COLUMNS 6 | |
const int TOTAL_INVADERS = INVADER_ROWS * INVADER_COLUMNS; | |
#define INVADER_SPACE 15 // space between invaders (sideways) | |
int invaderSpeedMultiplier = 1; | |
int INVADER_SPEED = INVADER_ROWS * INVADER_COLUMNS * invaderSpeedMultiplier; | |
#define INVADER_DISTANCE 3 // distance covered by every horizontal invader step | |
#define INVADER_STEP 4 // distance covered by every vertical invader step | |
#define INVADER_BULLET_COUNT 5 // number of available bullets for invaders to shoot on-screen | |
int invaderBulletSpeed = 4; // speed of invader bullets | |
typedef struct { | |
int Status; // 0 = dead, 1 = alive, 2 = exploded | |
int xPos; | |
int yPos; | |
int explosionTime; | |
} Invader; | |
typedef struct { | |
int Status; | |
int xPos; | |
int explosionTime; | |
int points; | |
} AlienShip; | |
#define INVADER_STATUS_DEAD 0 | |
#define INVADER_STATUS_ALIVE 1 | |
#define INVADER_STATUS_EXPLODE 2 | |
Invader invaders[INVADER_ROWS][INVADER_COLUMNS]; | |
#define SHIP_ON_SCREEN 0 | |
#define SHIP_OFF_SCREEN 1 | |
#define SHIP_EXPLODED 2 | |
AlienShip alienShip; | |
Bullet invaderBullets[INVADER_BULLET_COUNT]; // array for holding invader bullets | |
int bulletProb; // probability that bullet is spawned | |
bool justDropped; | |
bool invaderDirection; // used in moveInvader(); to determine which direction aliens should move | |
bool invaderPhase; // the phase that the invaders are on-used in drawCrab(); to determine which bitmap to display | |
int invaderCycleTime; // time it takes for one alien image cycle to occur-is counted down from | |
int slowDown = 1; // variable used to decide when to draw invaders(for no reason but to slow down the game) | |
// forward declarations for functions ************************************************* | |
// fighter/bullet functions | |
void moveRightISR(); | |
void moveLeftISR(); | |
// void drawGraphics(); | |
void crabImage(); | |
void moveFighter(); | |
void fighterShot(); | |
void displayShot(); | |
void bulletShot(); | |
void startGame(); | |
void crabImage(int invaderTimeSetUp); | |
//invader stuff | |
void drawInvader(Invader invaders[][INVADER_COLUMNS], int invaderRow, int invaderColumn); | |
bool updateInvaderPhase(); | |
void moveInvader(Invader invaders[][INVADER_COLUMNS]); | |
void invaderEdges(Invader invaders[][INVADER_COLUMNS], int *left, int *right); | |
void dropInvadersHeight(Invader invaders[][INVADER_COLUMNS]); | |
void animateInvader(Invader invaders[][INVADER_COLUMNS], int invaderRow, int invaderColumn); | |
// UFO stuff | |
void animateAlienShip(Invader invaders[][INVADER_COLUMNS], AlienShip * alienShip); | |
int highestInvader(Invader invaders[][INVADER_COLUMNS], int *highestPoint); | |
// other | |
void killFighter(Bullet invaderBullets[]); | |
void killCharacters(Bullet shots[], Invader invaders[][INVADER_COLUMNS], AlienShip *alienShip); | |
int aliveInvaders(Invader invaders[][INVADER_COLUMNS]); | |
int deadInvaders(Invader invaders[][INVADER_COLUMNS]); | |
// invader shooting | |
void invaderShot(Invader invaders[][INVADER_COLUMNS], Bullet invaderBullets[]); | |
int getInvaderBullet(Bullet invaderBullets[]); | |
void getRandomInvader(Invader invaders[][INVADER_COLUMNS], int * randomRow, int * randomColumn); | |
// graphics stuff | |
void drawGraphics(Invader invaders[][INVADER_COLUMNS]); | |
bool slowGraphics(); | |
void drawStats(); | |
void gameInitScreen(); | |
void drawExplosions(AlienShip * alienShip, Invader invaders[][INVADER_COLUMNS]); | |
void clearInvaders(Invader invaders[][INVADER_COLUMNS]); | |
void clearBullets(bool bulletType, int bulletIndex); | |
// sounds | |
void invaderSounds(); | |
void shipSounds(); | |
void explosionSound(); | |
void shotSound(); | |
void invaderShotSound(); | |
void setup() { | |
Serial.begin(9600); | |
tft.initR(INITR_BLACKTAB); // initialize screen | |
tft.fillScreen(ST77XX_BLACK); // clear screen for game beginning | |
pinMode(LEFT_SWITCH, INPUT_PULLUP); | |
attachInterrupt(digitalPinToInterrupt(LEFT_SWITCH), moveLeftISR, CHANGE); | |
pinMode(RIGHT_SWITCH, INPUT_PULLUP); | |
attachInterrupt(digitalPinToInterrupt(RIGHT_SWITCH), moveRightISR, CHANGE); | |
pinMode(FIRE_SWITCH, INPUT_PULLUP); | |
attachPCINT(digitalPinToPCINT(FIRE_SWITCH), fighterShotISR, FALLING); | |
pinMode(SPEED_SWITCH, INPUT_PULLUP); | |
// player bullet array initialization********************************************************** | |
for (int j = 0; j < TOTAL_BULLETS + 1; j++) { | |
shots[j].xPos = fighterPos; | |
shots[j].height = fighterHeight; | |
shots[j].fired = false; | |
} | |
// invader array initialization****************************************************** | |
justDropped = false; | |
invaderCycleTime = INVADER_SPEED; // set the cycle time to default speed | |
invaderPhase = true; | |
invaderDirection = true; | |
// invader array default setup | |
for (byte invaderRows = 0; invaderRows < INVADER_ROWS; invaderRows++) { | |
for (byte invaderColumns = 0; invaderColumns < INVADER_COLUMNS; invaderColumns++) { | |
if (invaderRows == 0 || invaderRows == 1) { | |
invaders[invaderRows][invaderColumns].xPos = 19 + invaderColumns * INVADER_SPACE; | |
} | |
else if (invaderRows == 2 ) { | |
invaders[invaderRows][invaderColumns].xPos = 18 + invaderColumns * INVADER_SPACE; | |
} | |
else if (invaderRows == 3) { | |
invaders[invaderRows][invaderColumns].xPos = 17 + invaderColumns * INVADER_SPACE; | |
} | |
invaders[invaderRows][invaderColumns].yPos = ROW_ONE_HEIGHT + (invaderRows * INVADER_HEIGHT_DIFF); | |
invaders[invaderRows][invaderColumns].Status = INVADER_STATUS_ALIVE; | |
invaders[invaderRows][invaderColumns].explosionTime = INVADERS_EXPLOSION_TIME; | |
} | |
} | |
// alien ship default setup | |
alienShip.Status = SHIP_OFF_SCREEN; | |
alienShip.xPos = ALIEN_SHIP_START_POS; | |
alienShip.explosionTime = SHIP_EXPLOSION_TIME; | |
alienShip.points = 0; | |
// invader bullet initialization | |
for (int i = 0; i < INVADER_BULLET_COUNT; i++) { | |
invaderBullets[i].fired = false; | |
invaderBullets[i].xPos = 0; | |
invaderBullets[i].height = 0; | |
} | |
bulletProb = 1; // probability that bullet is spawned | |
invaderCycleSetUp.start(); // start timer that changes invader phases | |
// startGame(); | |
delay(1000); | |
tft.fillScreen(ST77XX_BLACK); | |
tft.drawBitmap(fighterPos, fighterHeight, fighterGraphic, 11, 6, ST77XX_WHITE); | |
} | |
void loop() { | |
INVADER_SPEED = aliveInvaders(invaders) * invaderSpeedMultiplier; | |
// if (aliveInvaders(invaders) == 6) { | |
// invaderBulletSpeed = 3; | |
// bulletSpeed = 3; | |
// } | |
if (digitalRead(SPEED_SWITCH)) fighterSpeed = 2; //allows for different fighter speeds | |
else fighterSpeed = 1; | |
moveFighter(); | |
bulletShot(); // checks if bullet has been shot | |
killCharacters(shots, invaders, &alienShip); | |
killFighter(invaderBullets); | |
// sounds | |
shipSounds(); | |
drawGraphics(invaders); | |
} | |
//***********************display graphics*********************** | |
void drawGraphics(Invader invaders[][INVADER_COLUMNS]) { | |
// tft.fillScreen(ST77XX_BLACK); | |
// clearScreen(invaders, invaderBullets, shots); | |
displayShot(); | |
tft.drawBitmap(fighterPos, fighterHeight, fighterGraphic, 11, 6, ST77XX_WHITE); | |
moveInvader(invaders); | |
animateAlienShip(invaders, &alienShip); | |
drawStats(); | |
drawExplosions(&alienShip, invaders); | |
invaderShot(invaders, invaderBullets); | |
} | |
bool slowGraphics() { | |
if (slowDown == 4) { | |
slowDown = 0; | |
return true; | |
} | |
else { | |
slowDown++; | |
return false; | |
} | |
} | |
void clearInvaders(Invader invaders[][INVADER_COLUMNS]) { | |
int invaderCornerX = 0; | |
int invaderCornerY = 0; | |
for (int i = 0; i < INVADER_COLUMNS; i++) { | |
for (int x = 0; x < INVADER_ROWS; x++) { | |
if (invaders[x][i].Status == INVADER_STATUS_ALIVE || invaders[x][i].Status == INVADER_STATUS_EXPLODE) { | |
invaderCornerX = invaders[x][i].xPos; | |
invaderCornerY = invaders[x][i].yPos; | |
tft.fillRect(invaderCornerX - 2, invaderCornerY, INVADER_SPACE * INVADER_COLUMNS, INVADER_HEIGHT_DIFF * INVADER_ROWS + 2, ST77XX_BLACK); | |
break; | |
} | |
} | |
} | |
} | |
void clearBullets(bool bulletType, int bulletIndex) { | |
switch(bulletType) { | |
case true: //fighter bullet | |
if (shots[bulletIndex].fired) { | |
tft.fillRect(shots[bulletIndex].xPos, shots[bulletIndex].height, 1, BULLET_LENGTH, ST77XX_BLACK); | |
} | |
break; | |
case false: //invader bullet | |
if (invaderBullets[bulletIndex].fired) { | |
tft.fillRect(invaderBullets[bulletIndex].xPos, invaderBullets[bulletIndex].height, 1, BULLET_LENGTH, ST77XX_BLACK); | |
} | |
break; | |
} | |
} | |
void drawStats() { | |
if (points != previousPoints) { | |
previousPoints = points; | |
tft.fillRect(0, 0, 25, 8, ST77XX_BLACK); | |
} | |
tft.setCursor(0,0); | |
tft.setTextColor(ST77XX_WHITE); | |
tft.setTextSize(1); | |
tft.println(points); | |
tft.setCursor(120, 0); | |
tft.println(lives); | |
tft.drawLine(0, 9, 125, 9, ST77XX_WHITE); | |
tft.setCursor(50, 100); | |
tft.fillRect(50, 100, 10, 10, ST77XX_BLACK); | |
tft.println(aliveInvaders(invaders)); | |
} | |
void gameInitScreen() { | |
tft.fillScreen(ST77XX_BLACK); | |
tft.setCursor(35, 0); | |
tft.println("Lives: " + String(lives)); | |
tft.setCursor(35, 10); | |
tft.println("Points: " + String(points)); | |
tft.setCursor(35, 20); | |
tft.println("Level: 1"); | |
delay(3000); | |
tft.fillScreen(ST77XX_BLACK); | |
} | |
// sounds********************************************************************** | |
void invaderSounds() { | |
if ((alienShip.Status == SHIP_OFF_SCREEN) || (alienShip.Status == SHIP_EXPLODED)) { | |
if (aliveInvaders(invaders)) { | |
if (invaderSoundCycle == 0) { | |
tone(BUZZ_PIN, 100, invaderSoundDuration); | |
invaderSoundCycle++; | |
} | |
else if (invaderSoundCycle == 1) { | |
tone(BUZZ_PIN, 150, invaderSoundDuration); | |
invaderSoundCycle++; | |
} | |
else if (invaderSoundCycle == 2) { | |
tone(BUZZ_PIN, 100, invaderSoundDuration); | |
invaderSoundCycle++; | |
} | |
else if (invaderSoundCycle == 3) { | |
tone(BUZZ_PIN, 75, invaderSoundDuration); | |
invaderSoundCycle = 0; | |
} | |
} | |
} | |
} | |
void shipSounds() { | |
if (alienShip.Status == SHIP_ON_SCREEN) { | |
shipSoundCycle--; | |
if (shipSoundCycle == 0) { | |
shipSoundCycle = SHIP_SOUND_CYCLE_CONST; | |
tone(5,1400, 100); | |
} | |
} | |
} | |
void shotSound() { | |
tone(BUZZ_PIN, 740, 100); | |
} | |
void invaderShotSound() { | |
tone(BUZZ_PIN, 700, 100); | |
} | |
void explosionSound() { | |
tone(BUZZ_PIN, 311, 100); | |
tone(BUZZ_PIN, 261, 50); | |
} | |
// check if invaders are alive-used for invader sounds | |
int aliveInvaders(Invader invaders[][INVADER_COLUMNS]) { | |
int aliveInvadersCount = 0; | |
for (int x = 0; x < INVADER_ROWS; x++) { | |
for (int i = 0; i < INVADER_COLUMNS; i++) { | |
if (invaders[x][i].Status == INVADER_STATUS_ALIVE) | |
aliveInvadersCount++; | |
} | |
} | |
return aliveInvadersCount; | |
} | |
int deadInvaders(Invader invaders[][INVADER_COLUMNS]) { | |
int deadInvadersCount = 0; | |
for (int x = 0; x < INVADER_ROWS; x++) { | |
for (int i = 0; i < INVADER_COLUMNS; i++) { | |
if (invaders[x][i].Status == INVADER_STATUS_DEAD) | |
deadInvadersCount++; | |
} | |
} | |
return deadInvadersCount; | |
} | |
//*******************fighter movement******************** | |
void moveFighter() { | |
switch (activeMove) { | |
case true: // a switch is pressed, not sure what switch it is though | |
tft.fillRect(fighterPos, fighterHeight, FIGHTER_WIDTH, FIGHTER_LENGTH, ST77XX_BLACK); | |
switch (moveDirection) { | |
case true: //true = move right | |
fighterPos += fighterSpeed; | |
if (fighterPos + FIGHTER_WIDTH > SCREEN_MAX_WIDTH) | |
fighterPos = SCREEN_MAX_WIDTH - FIGHTER_WIDTH; | |
break; | |
case false: //false = move left | |
fighterPos -= fighterSpeed; | |
if (fighterPos < SCREEN_MIN_WIDTH) | |
fighterPos = SCREEN_MIN_WIDTH; | |
break; | |
} | |
case false: | |
break; | |
} | |
} | |
//********************fighter movement ISRs********************** | |
void moveRightISR() { | |
switchState = digitalRead(RIGHT_SWITCH); | |
switch (switchState) { | |
case false: // pin low, switch is pressed | |
moveDirection = true; //true = turn right | |
activeMove = true; // whether or not fighter should move- true = move, false = don't move | |
break; | |
case true: // pin high, switch is not pressed | |
moveDirection = true; | |
activeMove = false; | |
break; | |
} | |
} | |
void moveLeftISR() { | |
switchState = digitalRead(LEFT_SWITCH); | |
switch (switchState) { | |
case false: // pin low, switch is pressed | |
moveDirection = false; //true = turn right | |
activeMove = true; // whether or not fighter should move- true = move, false = don't move | |
break; | |
case true: // pin high, switch is not pressed | |
moveDirection = false; | |
activeMove = false; | |
break; | |
} | |
} | |
//***************************bullet ISR********************* | |
void fighterShotISR() { | |
bulletHasBeenShot = true; | |
} | |
//********************Bullet functions********************** | |
void bulletShot() { | |
if (bulletHasBeenShot) { | |
for (availableBullet = 0; availableBullet < TOTAL_BULLETS; availableBullet++ ) { | |
if (shots[availableBullet].fired) { | |
bulletHasBeenShot = false; | |
continue; | |
} | |
shotSound(); | |
shots[availableBullet].xPos = fighterPos + 5; | |
shots[availableBullet].fired = true; | |
bulletHasBeenShot = false; | |
break; | |
} | |
} | |
else | |
bulletHasBeenShot = false; // just in case... | |
} | |
void displayShot() { | |
for (int bulletNum = 0; bulletNum < TOTAL_BULLETS; bulletNum++) { | |
clearBullets(true, bulletNum); // clear on-screen bullet graphic | |
if (shots[bulletNum].fired){ | |
shots[bulletNum].height = shots[bulletNum].height - bulletSpeed; // "-" because y axis pixels are laid from top down | |
if (shots[bulletNum].height < SCREEN_MAX_HEIGHT){ | |
shots[bulletNum] = shots[TOTAL_BULLETS]; | |
continue; | |
} | |
tft.drawBitmap(shots[bulletNum].xPos, shots[bulletNum].height, bulletGraphic, 1, BULLET_LENGTH, ST77XX_ORANGE); | |
} | |
} | |
} | |
//***********************Startup functions********************* | |
void startGame() { | |
while (!beginGame) { | |
if (!digitalRead(LEFT_SWITCH) || !digitalRead(RIGHT_SWITCH) || !digitalRead(FIRE_SWITCH)) | |
beginGame = true; | |
tft.fillScreen(ST77XX_BLACK); | |
//crab scroll | |
crabImage(invaderCycleSetUp.elapsed() + 2); | |
crabPosStartUp++; | |
if (crabPosStartUp > 128) | |
crabPosStartUp = -5; | |
//fighter scroll | |
tft.drawBitmap(fighterPosStartUp, FIGHTERHEIGHTSTARTUP, fighterGraphic, 11, 6, ST77XX_WHITE); | |
fighterPosStartUp--; | |
if (fighterPosStartUp < -5) | |
fighterPosStartUp = 128; | |
// prompt | |
tft.setTextSize(1); | |
tft.setTextColor(ST77XX_WHITE); | |
tft.setCursor(promptPos, 14); | |
tft.println("Play"); | |
} | |
} | |
// crab | |
void crabImage(int invaderTimeSetUp){ | |
if (oldInvaderTimeSetUp != invaderTimeSetUp) { | |
oldInvaderTimeSetUp = invaderTimeSetUp; | |
invaderPhaseSetUp = !invaderPhaseSetUp; | |
} | |
switch (invaderPhaseSetUp) { | |
case true: | |
tft.drawBitmap(crabPosStartUp, CRABHEIGHTSTARTUP, crabGraphicOne, 11, 8, ST77XX_WHITE); | |
break; | |
case false: | |
tft.drawBitmap(crabPosStartUp, CRABHEIGHTSTARTUP, crabGraphicTwo, 11, 8, ST77XX_WHITE); | |
break; | |
} | |
} | |
// INVADER STUFF**************************************************************** | |
//***************************************************************** moveInvader | |
void moveInvader(Invader invaders[][INVADER_COLUMNS]) { | |
int leftMost, rightMost; | |
if (updateInvaderPhase()) { | |
clearInvaders(invaders); | |
invaderEdges(invaders, &leftMost, &rightMost); | |
if (!justDropped) { | |
if (leftMost < SCREEN_MIN_WIDTH || rightMost > SCREEN_MAX_WIDTH) { | |
dropInvadersHeight(invaders); | |
invaderDirection = !invaderDirection; | |
justDropped = true; | |
return; | |
} | |
} | |
for (int x = 0; x < INVADER_ROWS; x++) { | |
for (int i = 0; i < INVADER_COLUMNS; i++) { | |
animateInvader(invaders, x, i); | |
} | |
} | |
justDropped = false; | |
} | |
else { | |
if (slowGraphics()) { | |
slowDown = false; | |
for (byte x = 0; x < INVADER_ROWS; x++) { | |
for (int i = 0; i < INVADER_COLUMNS; i++) { | |
if (invaders[x][i].Status == INVADER_STATUS_ALIVE) | |
drawInvader(invaders, x, i); | |
} | |
} | |
} | |
} | |
} | |
//******************************************************************* change direction | |
void animateInvader(Invader invaders[][INVADER_COLUMNS], int invaderRow, int invaderColumn) { | |
if (invaders[invaderRow][invaderColumn].Status == INVADER_STATUS_ALIVE) { | |
switch (invaderDirection) { | |
case true: | |
invaders[invaderRow][invaderColumn].xPos += INVADER_DISTANCE; | |
drawInvader(invaders, invaderRow, invaderColumn); | |
break; | |
case false: | |
invaders[invaderRow][invaderColumn].xPos -= INVADER_DISTANCE; | |
drawInvader(invaders, invaderRow, invaderColumn); | |
break; | |
} | |
} | |
} | |
//***************************************************************** switchInvaderState | |
bool updateInvaderPhase() { | |
invaderCycleTime--; | |
if (invaderCycleTime <= 0) { | |
invaderPhase = !invaderPhase; | |
invaderCycleTime = INVADER_SPEED; // reset "timer" (invaderCycleTime is counted down every run) | |
invaderSounds(); | |
return true; | |
} | |
return false; | |
} | |
//***************************************************************** find invader array edges | |
void invaderEdges(Invader invaders[][INVADER_COLUMNS], int *left, int *right) { | |
*left = SCREEN_MAX_WIDTH; | |
*right = SCREEN_MIN_WIDTH; | |
for (byte x = 0; x < INVADER_ROWS; x++) { | |
for (byte i = 0; i < INVADER_COLUMNS; i++) { | |
if (invaders[x][i].Status == INVADER_STATUS_ALIVE) { | |
if (invaders[x][i].xPos < *left) | |
*left = invaders[x][i].xPos; | |
if (invaders[x][i].xPos + INVADER_WIDTH > *right) | |
*right = invaders[x][i].xPos + INVADER_WIDTH; | |
} | |
} | |
} | |
} | |
//*********************************************************************** drop invader height | |
void dropInvadersHeight(Invader invaders[][INVADER_COLUMNS]) { | |
for (byte x = 0; x < INVADER_ROWS; x++) { | |
for (byte i = 0; i < INVADER_COLUMNS; i++) { | |
if (invaders[x][i].Status == INVADER_STATUS_ALIVE) { | |
invaders[x][i].yPos += INVADER_STEP; | |
drawInvader(invaders, x, i); | |
} | |
} | |
} | |
} | |
//******************************************************************************************* draw invader | |
void drawInvader(Invader invaders[][INVADER_COLUMNS], int invaderRow, int invaderColumn) { | |
int xPos = invaders[invaderRow][invaderColumn].xPos; | |
int yPos = invaders[invaderRow][invaderColumn].yPos; | |
if (invaderRow == 0 || invaderRow == 1) { | |
switch (invaderPhase) { | |
case true: | |
tft.drawBitmap(xPos, yPos, squidGraphicOne, 8, 8, ST77XX_CYAN); | |
break; | |
case false: | |
tft.drawBitmap(xPos, yPos, squidGraphicTwo, 8, 8, ST77XX_CYAN); | |
break; | |
} | |
} | |
else if (invaderRow == 2) { | |
switch (invaderPhase) { | |
case true: | |
tft.drawBitmap(xPos, yPos, crabGraphicOne, 11, 8, ST77XX_CYAN); | |
break; | |
case false: | |
tft.drawBitmap(xPos, yPos, crabGraphicTwo, 11, 8, ST77XX_CYAN); | |
break; | |
} | |
} | |
else if (invaderRow == 3) { | |
switch (invaderPhase) { | |
case true: | |
tft.drawBitmap(xPos, yPos, octopusGraphicOne, 12, 8, ST77XX_CYAN); | |
break; | |
case false: | |
tft.drawBitmap(xPos, yPos, octopusGraphicTwo, 12, 8, ST77XX_CYAN); | |
break; | |
} | |
} | |
} | |
// UFO stuff | |
void animateAlienShip(Invader invaders[][INVADER_COLUMNS], AlienShip * alienShip) { | |
if (alienShip->Status == SHIP_OFF_SCREEN) { | |
int highestPoint = highestInvader(invaders); | |
if (highestPoint >= ALIEN_SHIP_BOUNDARY) { | |
if (random(0,1000) < ALIEN_SHIP_PROB) | |
alienShip->Status = SHIP_ON_SCREEN; | |
} | |
} | |
else if (alienShip->Status == SHIP_ON_SCREEN) { | |
tft.fillRect(alienShip->xPos, ALIEN_SHIP_HEIGHT, ALIEN_SHIP_WIDTH, ALIEN_SHIP_LENGTH, ST77XX_BLACK); | |
alienShip->xPos -= SHIP_SPEED; | |
tft.drawBitmap(alienShip->xPos, ALIEN_SHIP_HEIGHT, alienShipGraphic, 16, 6, ST77XX_RED); | |
if (alienShip->xPos <= ALIEN_SHIP_END_POS) { | |
alienShip->Status = SHIP_OFF_SCREEN; | |
alienShip->xPos = ALIEN_SHIP_START_POS; | |
} | |
} | |
} | |
int highestInvader(Invader invaders[][INVADER_COLUMNS]) { | |
int highestPoint = SCREEN_MIN_HEIGHT; | |
for (byte x = 0; x < INVADER_ROWS; x++) { | |
for (byte i = 0; i < INVADER_COLUMNS; i++) { | |
if (invaders[x][i].Status == INVADER_STATUS_ALIVE) { | |
if (invaders[x][i].yPos < highestPoint) | |
highestPoint = invaders[x][i].yPos; | |
} | |
} | |
} | |
return highestPoint; | |
} | |
// Kill off invaders-set them to INVADER_STATUS_EXPLODE | |
void killCharacters(Bullet shots[], Invader invaders[][INVADER_COLUMNS], AlienShip * alienShip) { | |
for (byte b = 0; b < TOTAL_BULLETS; b++) { | |
for (byte x = 0; x < INVADER_ROWS; x++ ) { | |
for (byte i = 0; i < INVADER_COLUMNS; i++) { | |
if (shots[b].fired) { | |
int invaderXPos = invaders[x][i].xPos; | |
int invaderHeight = invaders[x][i].yPos; | |
int invaderStatus = invaders[x][i].Status; | |
int invaderPoints = SQUID_POINTS; | |
int bulletPos = shots[b].xPos; | |
int bulletHeight = shots[b].height; | |
int invaderWidth = squidWidth; // this is the width of the squids only | |
if (x == 2) { | |
invaderWidth = crabWidth; | |
invaderPoints = CRAB_POINTS; | |
} | |
else if (x == 3) { | |
invaderWidth = octopusWidth; | |
invaderPoints = OCTOPUS_POINTS; | |
} | |
if (invaderStatus == 1) { | |
if ((bulletPos >= invaderXPos && bulletPos <= invaderXPos + invaderWidth) && (bulletHeight >= invaderHeight && bulletHeight <= invaderHeight + INVADER_LENGTH)) { | |
clearBullets(true, b); | |
tft.fillRect(invaderXPos, invaderHeight, invaderWidth, INVADER_LENGTH, ST77XX_BLACK); | |
invaders[x][i].Status = INVADER_STATUS_EXPLODE; | |
shots[b] = shots[TOTAL_BULLETS]; | |
points += invaderPoints; | |
explosionSound(); | |
} | |
} | |
if (alienShip->Status == SHIP_ON_SCREEN) { | |
if ((bulletPos >= alienShip->xPos && bulletPos <= alienShip->xPos + ALIEN_SHIP_WIDTH) && (bulletHeight >= ALIEN_SHIP_HEIGHT && bulletHeight <= ALIEN_SHIP_HEIGHT + ALIEN_SHIP_LENGTH)) { | |
clearBullets(true, b); | |
shots[b] = shots[TOTAL_BULLETS]; | |
alienShip->Status = SHIP_EXPLODED; | |
alienShip->points = random(10, 30) * 10; | |
points += alienShip->points; | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
void killFighter(Bullet invaderBullets[]) { | |
for (int i = 0; i < INVADER_BULLET_COUNT; i++) { | |
if (invaderBullets[i].fired == true) { | |
int bulletXPos = invaderBullets[i].xPos; | |
int bulletYPos = invaderBullets[i].height; | |
if ((bulletXPos >= fighterPos) && (bulletXPos <= fighterPos + FIGHTER_WIDTH) && (bulletYPos + BULLET_LENGTH >= fighterHeight - 3)) { | |
lives--; | |
invaderBullets[i].fired = false; | |
invaderBullets[i].height = 0; | |
invaderBullets[i].xPos = 0; | |
tone(BUZZ_PIN, 900, 100); //death sound! | |
tft.fillRect(fighterPos, fighterHeight, FIGHTER_WIDTH, FIGHTER_LENGTH, ST77XX_BLACK); | |
tft.drawBitmap(fighterPos, fighterHeight, explodedFighterGraphic, 11, 6, ST77XX_YELLOW); | |
delay(2000); | |
gameInitScreen(); | |
} | |
} | |
} | |
} | |
void drawExplosions(AlienShip * alienShip, Invader invaders[][INVADER_COLUMNS]) { | |
// UFO explosion maintenence | |
if (alienShip->Status == SHIP_EXPLODED) { | |
alienShip->explosionTime--; | |
if (alienShip->explosionTime <= 0) { | |
tft.fillRect(alienShip->xPos, ALIEN_SHIP_HEIGHT, ALIEN_SHIP_WIDTH + 5, ALIEN_SHIP_LENGTH, ST77XX_BLACK); | |
alienShip->explosionTime = SHIP_EXPLOSION_TIME; | |
alienShip->Status = SHIP_OFF_SCREEN; | |
alienShip->xPos = ALIEN_SHIP_START_POS; | |
} | |
else { | |
tft.setCursor(alienShip->xPos, ALIEN_SHIP_HEIGHT); // ALIEN_SHIP_HEIGHT alienShip->xPos | |
tft.setTextColor(ST77XX_WHITE); | |
tft.setTextSize(1); | |
tft.println(alienShip->points); | |
} | |
} | |
// invader explosion maintenence | |
for (int x = 0; x < INVADER_ROWS; x++) { | |
for (int i = 0; i < INVADER_COLUMNS; i++) { | |
if (invaders[x][i].Status == INVADER_STATUS_EXPLODE) { | |
invaders[x][i].explosionTime--; | |
tft.drawBitmap(invaders[x][i].xPos, invaders[x][i].yPos, explosionGraphic, 13, 8, ST77XX_YELLOW); | |
if (invaders[x][i].explosionTime <= 0) { | |
tft.fillRect(invaders[x][i].xPos, invaders[x][i].yPos, 13, 8, ST77XX_BLACK); | |
invaders[x][i].Status = INVADER_STATUS_DEAD; | |
} | |
} | |
} | |
} | |
} | |
// INVADER BULLETS******************************************** | |
void invaderShot(Invader invaders[][INVADER_COLUMNS], Bullet invaderBullets[]) { | |
int randomRow, randomColumn; | |
getRandomInvader(invaders, &randomRow, &randomColumn); | |
// randomRow = random(0, INVADER_ROWS); | |
// randomColumn = random(0, INVADER_COLUMNS); | |
if (random(0,100) <= bulletProb) { | |
if (invaders[randomRow][randomColumn].Status == INVADER_STATUS_ALIVE) { | |
int openBullet = getInvaderBullet(invaderBullets); | |
if (openBullet != 10){ | |
invaderShotSound(); | |
invaderBullets[openBullet].fired = true; | |
invaderBullets[openBullet].height = invaders[randomRow][randomColumn].yPos; | |
invaderBullets[openBullet].xPos = invaders[randomRow][randomColumn].xPos + 4; // + 4 so that it comes out the middle of the invader | |
} | |
} | |
} | |
// update bullets that are on-screen | |
for (int i = 0; i < INVADER_BULLET_COUNT; i++) { | |
if (invaderBullets[i].fired) { | |
clearBullets(false, i); // clear on-screen bullet graphic | |
invaderBullets[i].height += invaderBulletSpeed; | |
tft.drawBitmap(invaderBullets[i].xPos, invaderBullets[i].height, bulletGraphic, 1, BULLET_LENGTH, ST77XX_GREEN); | |
if (invaderBullets[i].height >= SCREEN_MIN_HEIGHT) { | |
invaderBullets[i].height = 0; | |
invaderBullets[i].fired = false; | |
invaderBullets[i].xPos = 0; | |
} | |
} | |
} | |
} | |
int getInvaderBullet(Bullet invaderBullets[]) { | |
for (int openBullet = 0; openBullet < INVADER_BULLET_COUNT; openBullet++) { | |
if (invaderBullets[openBullet].fired == false) { | |
return openBullet; | |
} | |
} | |
return 10; // random number so that invaderShot knows that no bullets are open | |
} | |
void getRandomInvader(Invader invaders[][INVADER_COLUMNS], int * randomRow, int * randomColumn) { | |
if (aliveInvaders(invaders)) { | |
while(true) { | |
int randomInvader = random(0, TOTAL_INVADERS); | |
if (randomInvader > 17){ | |
*randomRow = 4; | |
*randomColumn = randomInvader - 17; | |
} | |
else if (randomInvader > 11){ | |
*randomRow = 3; | |
*randomColumn = randomInvader - 11; | |
} | |
else if (randomInvader > 5){ | |
*randomRow = 2; | |
*randomColumn = randomInvader - 5; | |
} | |
else { | |
*randomRow = 1; | |
*randomColumn = randomInvader; | |
} | |
if(invaders[*randomRow][*randomColumn].Status == INVADER_STATUS_ALIVE) | |
break; | |
} | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment