Skip to content

Instantly share code, notes, and snippets.

@seanboe
Last active July 18, 2022 16:47
Show Gist options
  • Save seanboe/3b5eddb7c466c64d1a83809813f12887 to your computer and use it in GitHub Desktop.
Save seanboe/3b5eddb7c466c64d1a83809813f12887 to your computer and use it in GitHub Desktop.
#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