// ILI9341 LCD Display to Arduino Mega Pin Connections:
// VCC -> VIN
// CS -> D10
// RST -> D8
// DC -> 9
// MOSI -> D51
// SCK -> D52
// LED -> 3.3V
// MISO -> D12/D50
// If using (LegA->GND/LegB->DP) use INPUT_PULLUP
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
#define TFT1_DC 10
#define TFT1_CS 11
#define TFT2_DC 13
#define TFT2_CS 12
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT1_CS, TFT1_DC);
Adafruit_ILI9341 tftD = Adafruit_ILI9341(TFT2_CS, TFT2_DC);
#define LASER tft.color565(250, 37, 147)
#define TLPRT tft.color565(107, 143, 59)
#define IMRTL tft.color565(61, 70, 147)
#define TRISG tft.color565(213, 100, 97)
#define DUBSG tft.color565(90, 10, 200)
#define ICE tft.color565(143, 239, 244)
#define SPDUP tft.color565(154, 247, 197)
#define GHOST tft.color565(41, 184, 255)
#define OP tft.color565(240, 173, 208)
#define ColorGold tft.color565(224, 161, 47)
#define MAGENTA tft.color565(255, 0, 255)
#define TEAL tft.color565(48, 250, 210)
#define SILVER tft.color565(197, 194, 197)
#define GRAY tft.color565(127, 127, 127)
#define TRNSCLR 0x1234
#define RED ILI9341_RED
#define BLUE 0xAD5C
#define projClr tft.color565(213, 213, 213)
struct Obstacle {
int x, y, opX, opY, oldY, obstHealth;
uint16_t color;
bool active;
};
struct PowerUp {
bool written;
unsigned long powerupDuration = 0;
unsigned long powerupCollectTime = 0;
const char* type = "NONE";
uint16_t color = ILI9341_WHITE;
};
struct Projectile {
int x, y, opX;
bool active;
};
const int OBSTACLE_SIZE = 10;
const int SCREEN_HEIGHT = 320;
const int SCREEN_WIDTH = 240;
const int playAreaX = 10;
const int playAreaY = 100;
const int playAreaW = 100;
const int playAreaH = 200;
const int absoluteMaxObstacles = 30;
const int absoluteMaxQueue = 16; // "MAX_QUEUE"
const int MaxProjectiles = 30;
const int dmgImmuneDuration = 5000;
const int colorChangeDuration = 2500;
const int diffIncreaseTimer = 60000;
int maxPlayerHealth = 20;
int maxPlayerShield = 5;
int playerX = playAreaX + 50, playerY = playAreaY + playAreaH - 30, playerW = 10, playerH = 10, oldPlayerX, oldPlayerY;
int tillNextSpawn;
int playerHealth = 20;
int playerShield = 0;
int gameScore = 0;
int prevRandX = 0;
int currentShieldUses = 0;
int printedScore = -1;
int printedHealth = 0;
int printedShield = -1;
int printedShieldUses = 0;
int mltp1 = 1; // Multiplier
int mltp2 = 1;
int spdup = 1;
int currentActivePowerUp;
int MAX_OBSTACLES = 10;
int MAX_QUEUE = 6;
int QueueSlotsWritten;
int ObstUpdateMin = 1000;
int RandPlayerX;
int difficultyMultiplier;
int dbgObstLoop = 0;
int dbgPwrUpLoop = 0;
int currentValIndex = 0;
Obstacle obstacles[absoluteMaxObstacles];
PowerUp powerupQueue[absoluteMaxQueue];
Projectile projectiles[MaxProjectiles];
const char* btnPins[] = {2, 3, 4, 5, 6, 21}; // Up, Down, Left, Right, Special, Toggle Debug Screen
const char* printedPowerUpType = "temp";
const char* powerup = "";
unsigned long lastSpawnTime = 0;
unsigned long lastDamageTaken = 0;
unsigned long lastObstacleMovement = 0;
unsigned long lastColorChange = 0;
unsigned long lastPowerUpUsed = 0;
unsigned long lastDBGScreenRefresh = 0;
unsigned long lastDiffIncrease = 0;
unsigned long* dbgULongList[] = {&lastSpawnTime, &lastDamageTaken, &lastObstacleMovement, &lastColorChange, &lastPowerUpUsed};
bool firstStart = true;
bool damageInvulnerable = false;
bool PowerUpActive = false;
bool tgglDbgScrnOn = false;
bool ghostActive = false;
uint16_t playerColor = ILI9341_WHITE;
uint16_t hitColor;
enum CollisionDirection {
NONE,
LEFT,
RIGHT,
TOP,
BOTTOM
};
const uint16_t fullHeartBMP[] PROGMEM = {
0x1234, 0x1234, 0x1234, 0x1234, 0x8410, 0x8410, 0x1234, 0x1234, 0x1234, 0x1234,
0x1234, 0x1234, 0x8410, 0x8410, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234,
0x1234, 0x8410, 0xF800, 0xF800, 0x8410, 0x1234, 0x1234, 0x1234, 0x1234, 0x8410,
0xF800, 0xF800, 0x8410, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x8410, 0xF800,
0xF800, 0xF800, 0xF800, 0x8410, 0x1234, 0x1234, 0x8410, 0xF800, 0xF800, 0xF800,
0xF800, 0x8410, 0x1234, 0x1234, 0x1234, 0x8410, 0xF800, 0xF800, 0xF800, 0xF800,
0xF800, 0xF800, 0x8410, 0x8410, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800,
0x8410, 0x1234, 0x1234, 0x1234, 0x8410, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800,
0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0x8410, 0x1234, 0x1234,
0x1234, 0x1234, 0x1234, 0x8410, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800,
0xF800, 0xF800, 0xF800, 0xF800, 0x8410, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234,
0x1234, 0x1234, 0x8410, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800,
0xF800, 0x8410, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234,
0x1234, 0x8410, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0x8410, 0x1234,
0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234,
0x8410, 0xF800, 0xF800, 0xF800, 0xF800, 0x8410, 0x1234, 0x1234, 0x1234, 0x1234,
0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x8410,
0xF800, 0xF800, 0x8410, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234,
0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x8410, 0x8410,
0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234
};
const uint16_t halfHeartBMP[] PROGMEM = {
0x1234, 0x1234, 0x1234, 0x1234, 0x8410, 0x8410, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x8410, 0x8410, 0x1234, 0x1234,
0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x8410, 0xF800, 0xF800, 0x8410, 0x1234, 0x1234, 0x1234, 0x1234, 0x8410, 0x8410, 0x8410,
0x8410, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x8410, 0xF800, 0xF800, 0xF800, 0xF800, 0x8410, 0x1234, 0x1234, 0x8410, 0x8410,
0x8410, 0x8410, 0x8410, 0x8410, 0x1234, 0x1234, 0x1234, 0x8410, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0x8410, 0x8410,
0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x1234, 0x1234, 0x1234, 0x8410, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800,
0xF800, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x8410, 0xF800, 0xF800,
0xF800, 0xF800, 0xF800, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234,
0x8410, 0xF800, 0xF800, 0xF800, 0xF800, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234,
0x1234, 0x1234, 0x1234, 0x8410, 0xF800, 0xF800, 0xF800, 0x8410, 0x8410, 0x8410, 0x8410, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234,
0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x8410, 0xF800, 0xF800, 0x8410, 0x8410, 0x8410, 0x1234, 0x1234, 0x1234, 0x1234,
0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x8410, 0xF800, 0x8410, 0x8410, 0x1234, 0x1234, 0x1234,
0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x8410, 0x8410, 0x1234, 0x1234,
0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234
};
const uint16_t emptyHeartBMP[] PROGMEM = {
0x1234, 0x1234, 0x1234, 0x1234, 0x8410, 0x8410, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x8410, 0x8410, 0x1234, 0x1234,
0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x8410, 0x8410, 0x8410, 0x8410, 0x1234, 0x1234, 0x1234, 0x1234, 0x8410, 0x8410, 0x8410,
0x8410, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x1234, 0x1234, 0x8410, 0x8410,
0x8410, 0x8410, 0x8410, 0x8410, 0x1234, 0x1234, 0x1234, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410,
0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x1234, 0x1234, 0x1234, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410,
0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x8410, 0x8410, 0x8410,
0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234,
0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234,
0x1234, 0x1234, 0x1234, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234,
0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x1234, 0x1234, 0x1234, 0x1234,
0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x8410, 0x8410, 0x8410, 0x8410, 0x1234, 0x1234, 0x1234,
0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x8410, 0x8410, 0x1234, 0x1234,
0x1234, 0x1234, 0x1234, 0x1234, 0x1234, 0x1234
};
const uint16_t fullShieldBMP[] PROGMEM = {
0x1234,0x1234,0x1234,0x1234,0x1234,0x1234,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x1234,0x1234,0x1234,0x1234,0x1234,0x1234,
0x1234,0x1234,0x1234,0x1234,0x8410,0x8410,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0x8410,0x8410,0x1234,0x1234,0x1234,0x1234,
0x1234,0x1234,0x8410,0x8410,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0x8410,0x8410,0x1234,0x1234,
0x1234,0x8410,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0x8410,0x1234,
0x1234,0x8410,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0x8410,0x1234,
0x1234,0x1234,0x8410,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0x8410,0x1234,0x1234,
0x1234,0x1234,0x8410,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0x8410,0x1234,0x1234,
0x1234,0x1234,0x1234,0x8410,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0x8410,0x1234,0x1234,0x1234,
0x1234,0x1234,0x1234,0x1234,0x8410,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0x8410,0x1234,0x1234,0x1234,0x1234,
0x1234,0x1234,0x1234,0x1234,0x1234,0x8410,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0x8410,0x1234,0x1234,0x1234,0x1234,0x1234,
0x1234,0x1234,0x1234,0x1234,0x1234,0x1234,0x8410,0xAD5C,0xAD5C,0xAD5C,0xAD5C,0x8410,0x1234,0x1234,0x1234,0x1234,0x1234,0x1234,
0x1234,0x1234,0x1234,0x1234,0x1234,0x1234,0x1234,0x8410,0xAD5C,0xAD5C,0x8410,0x1234,0x1234,0x1234,0x1234,0x1234,0x1234,0x1234,
0x1234,0x1234,0x1234,0x1234,0x1234,0x1234,0x1234,0x1234,0x8410,0x8410,0x1234,0x1234,0x1234,0x1234,0x1234,0x1234,0x1234,0x1234,
};
const uint16_t emptyShieldBMP[] PROGMEM = {
0x1234,0x1234,0x1234,0x1234,0x1234,0x1234,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x1234,0x1234,0x1234,0x1234,0x1234,0x1234,
0x1234,0x1234,0x1234,0x1234,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x1234,0x1234,0x1234,0x1234,
0x1234,0x1234,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x1234,0x1234,
0x1234,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x1234,
0x1234,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x1234,
0x1234,0x1234,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x1234,0x1234,
0x1234,0x1234,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x1234,0x1234,
0x1234,0x1234,0x1234,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x1234,0x1234,0x1234,
0x1234,0x1234,0x1234,0x1234,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x1234,0x1234,0x1234,0x1234,
0x1234,0x1234,0x1234,0x1234,0x1234,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x1234,0x1234,0x1234,0x1234,0x1234,
0x1234,0x1234,0x1234,0x1234,0x1234,0x1234,0x8410,0x8410,0x8410,0x8410,0x8410,0x8410,0x1234,0x1234,0x1234,0x1234,0x1234,0x1234,
0x1234,0x1234,0x1234,0x1234,0x1234,0x1234,0x1234,0x8410,0x8410,0x8410,0x8410,0x1234,0x1234,0x1234,0x1234,0x1234,0x1234,0x1234,
0x1234,0x1234,0x1234,0x1234,0x1234,0x1234,0x1234,0x1234,0x8410,0x8410,0x1234,0x1234,0x1234,0x1234,0x1234,0x1234,0x1234,0x1234,
};
// Obst Colors: Red, Green, ColorGold, Blue
// Character Colors:
// White = Normal
// Orange = Damage Taken
// Yellow = Coin Collected
// Teal = Health Healed
// Cyan = Powerup Collected
//
//
void setup() {
randomSeed(analogRead(A0));
for (int i = 0; i < 6; i++) {
pinMode(btnPins[i], INPUT_PULLUP);
}
Serial.begin(9600);
tft.begin();
tftD.begin();
tft.fillRect(playAreaX - 3, playAreaY - 3, playAreaW + 6, playAreaH + 6, ILI9341_WHITE);
tft.fillRect(playAreaX, playAreaY, playAreaW, playAreaH, ILI9341_BLACK);
tft.drawRect(playerX, playerY, playerW, playerH, playerColor);
tillNextSpawn = random(5, 10) * 1000;
tft.setTextColor(ILI9341_WHITE);
tftD.setTextColor(ILI9341_WHITE);
tft.setCursor(130, 100);
tft.print("Current Ability:");
tft.setCursor(130, 125);
tft.print("Next Ability:");
tft.setTextSize(2);
tftD.setTextSize(2); tftD.setCursor(48, 2); tftD.print("Debug Screen"); tftD.setTextSize(1);
}
void loop() {
setupUI();
int randomX;
unsigned long currentTime = millis();
if (millis() - lastDiffIncrease >= diffIncreaseTimer) {
difficultyMultiplier += 1;
lastDiffIncrease = millis();
}
if (currentTime - lastSpawnTime >= tillNextSpawn || firstStart == true) {
if (firstStart) {
delay(750);
}
firstStart = false;
lastSpawnTime = currentTime;
tillNextSpawn = random(1, 5) * 1000;
do {
randomX = ((random() % 10) * 10) + 10;
} while (randomX == prevRandX);
prevRandX = randomX;
int randVal = random() % 100;
uint16_t color;
if (randVal < 45) {color = ILI9341_RED;}
else if (randVal < 75) {color = ILI9341_GREEN;}
else if (randVal < 90) {color = ColorGold;}
else {color = ILI9341_BLUE;}
generateObstacle(randomX, color);
}
if (damageInvulnerable) {
if(millis() - lastDamageTaken >= 2500) {
playerColor = ILI9341_WHITE;
damageInvulnerable = false;
}
}
if (playerColor != ILI9341_WHITE && !PowerUpActive && !damageInvulnerable) {
if (millis() - lastColorChange >= colorChangeDuration) {
playerColor = ILI9341_WHITE;
}
}
updateObstacles();
drawObstacles();
checkMovement();
checkCollision();
QueueSlotsWritten = 0;
for (int i = 1; i < MAX_QUEUE; i++) {
if (powerupQueue[i].written) {
QueueSlotsWritten += 1;
}
}
checkAbilityStatus();
handlePowerUps();
}
void setupUI() {
drawHearts();
drawShields();
printTxt();
printDebug();
}
void checkMovement() {
if (digitalRead(2) == LOW) {
if (playerY > playAreaY) {
playerY -= 5;
}
delay(50 / spdup);
}
if (digitalRead(3) == LOW) {
if (playerY + playerH < playAreaY + playAreaH) {
playerY += 5;
}
delay(50 / spdup);
}
if (digitalRead(4) == LOW) {
if (playerX > playAreaX) {
playerX -= 5;
}
delay(50 / spdup);
}
if (digitalRead(5) == LOW) {
if (playerX + playerW < playAreaX + playAreaW) {
playerX += 5;
}
delay(50 / spdup);
}
if (digitalRead(21) == LOW) {
if (!tgglDbgScrnOn) tgglDbgScrnOn = true;
else tgglDbgScrnOn = false;
}
}
void drawPlayer(int collision) {
if (oldPlayerX != playerX || oldPlayerY != playerY) {
tft.fillRect(oldPlayerX, oldPlayerY, playerW, playerH, ILI9341_BLACK);
tft.fillRect(playerX, playerY, playerW, playerH, playerColor);
oldPlayerX = playerX;
oldPlayerY = playerY;
} else if (collision) {
tft.fillRect(playerX, playerY, playerW, playerH, playerColor);
}
tft.fillRect(playerX + 1, playerY + 1, playerW - 2, playerH - 2, powerupQueue[0].color);
tft.fillRect(playerX + 4, playerY, 2, 2, GRAY);
}
CollisionDirection detectCollisionDirection(int ax, int ay, int aw, int ah, int bx, int by, int bw, int bh) {
int dx = (ax + aw / 2) - (bx + bw / 2);
int dy = (ay + ah / 2) - (by + bh / 2);
if (abs(dx) > abs(dy)) {
return (dx > 0) ? LEFT : RIGHT;
} else {
return (dy > 0) ? TOP : BOTTOM;
}
}
bool checkProjectileCollision() {
int obsX, obsY, obsS, projX, projY, projW = 2, projH = 3;
for (int i = 0; i < MAX_OBSTACLES; i++) {
if (obstacles[i].active) {
obsX = obstacles[i].x;
obsY = obstacles[i].y;
obsS = OBSTACLE_SIZE;
for (int j = 0; j < MaxProjectiles; j++) {
if (projectiles[j].active) {
projX = projectiles[j].x;
projY = projectiles[j].y;
}
bool isColliding = projX < obsX + obsS && projX + projW > obsX && projY < obsY + obsS && projY + projH > obsY;
if (isColliding) {
uint16_t hitColor1 = obstacles[i].color;
if (hitColor1 == ILI9341_RED) ersObj(i);
}
}
}
}
}
bool checkCollision() {
int obsX, obsY, obsS;
for (int i = 0; i < MAX_OBSTACLES; i++) {
if (obstacles[i].active) {
obsX = obstacles[i].x;
obsY = obstacles[i].y;
obsS = OBSTACLE_SIZE;
}
bool isColliding = playerX < obsX + obsS && playerX + playerW > obsX && playerY < obsY + obsS && playerY + playerH > obsY;
if (isColliding) {
unsigned long collisionTime = millis();
CollisionDirection dir = detectCollisionDirection(playerX, playerY, playerW, playerH, obsX, obsY, obsS, obsS);
hitColor = obstacles[i].color;
if (powerupQueue[0].type == 'GHOST') {
if (hitColor == ILI9341_RED) return;
}
handleCollision(isColliding, i, dir, collisionTime);
return true;
} else {
drawPlayer(!isColliding);
}
}
return false;
}
void generateObstacle(int x, uint16_t color) {
for (int i = 0; i < MAX_OBSTACLES; i++) {
if (!obstacles[i].active) {
obstacles[i].x = x;
obstacles[i].y = playAreaY + 5;
obstacles[i].color = color;
obstacles[i].active = true;
obstacles[i].opX = x + OBSTACLE_SIZE;
obstacles[i].opY = obstacles[i].y + OBSTACLE_SIZE;
if (obstacles[i].color == ILI9341_RED) obstacles[i].obstHealth = 3 * difficultyMultiplier;
break;
}
}
}
void drawObstacles() {
for (int i = 0; i < MAX_OBSTACLES; i++) {
if (obstacles[i].active) {
tft.fillRect(obstacles[i].x, obstacles[i].y, 10, 10, obstacles[i].color);
}
}
}
void handleCollision(bool check, int obj, CollisionDirection dir, unsigned long ClsnTm) {
if (check) {
if (hitColor == 63488) {
if (!damageInvulnerable && !ghostActive) {
if (!(powerupQueue[0].type == 'immortal' && !(powerupQueue[0].type == 'ghost'))) {
if (playerShield > 0) {
currentShieldUses -= 1;
if (currentShieldUses == 0) {
playerShield -= 1;
if (playerShield > 0) currentShieldUses = 3;
}
} else playerHealth -= 1;
damageInvulnerable = true;
lastDamageTaken = ClsnTm;
playerColor = ILI9341_ORANGE;
}
}
switch (dir) {
case LEFT: playerX -= 5; break;
case RIGHT: playerX += 5; break;
case BOTTOM: playerY -= 5; break;
case TOP: playerY += 5; break;
default: break;
}
} else if (hitColor == 58629) {
ersObj(obj);
gameScore += 25 * mltp1 * mltp2;
} else if (hitColor == 2016) {
ersObj(obj);
healed();
} else if (hitColor == 31) {
ersObj(obj);
gameScore += 15 * mltp1 * mltp2;
PowerUpCollected(ClsnTm);
}
}
drawPlayer(check);
}
void ersObj(int i) {
tft.fillRect(obstacles[i].x, obstacles[i].y, 10, 10, ILI9341_BLACK);
obstacles[i].active = false;
obstacles[i].x = 0;
obstacles[i].y = 0;
obstacles[i].color = 0;
obstacles[i].opX = 0;
obstacles[i].opY = 0;
}
void healed() {
playerColor = TEAL;
if (playerHealth < maxPlayerHealth) playerHealth += 2;
else if (playerShield < maxPlayerShield) {
playerShield += 1;
if (currentShieldUses == 0) currentShieldUses = 3;
}
else gameScore += 10 * mltp1 * mltp2;
if (playerHealth > maxPlayerHealth) {
playerHealth = maxPlayerHealth;
gameScore += 5 * mltp1 * mltp2;
}
}
void PowerUpCollected(unsigned long ColTime) {
int powerupCount = 0;
for (int i = 1; i < MAX_QUEUE; i++) {
if(!powerupQueue[i].written) {
lastColorChange = ColTime;
playerColor = ILI9341_CYAN;
unsigned long time1;
int randVal = random(105);
if (randVal <= 12) powerup = "laser"; // Lasers
else if (randVal <= 24) powerup = "teleport"; // Teleportation
else if (randVal <= 36) powerup = "immortal"; // Immortality
else if (randVal <= 46) powerup = "trsg"; // Triple Score Gain
else if (randVal <= 58) powerup = "ice"; // Ice Trap
else if (randVal <= 70) powerup = "spdup"; // Speed Up
else if (randVal <= 82) powerup = "ghost"; // Fazing
else if (randVal <= 100) powerup = "dbsg"; // Double Score Gain
else if (randVal <= 105) powerup = "op"; // OP
powerupQueue[i].written = true;
if (powerup == "laser") time1 = 0;
else if (powerup == "teleport") time1 = 60000;
else if (powerup == "immortal") time1 = 45000;
else if (powerup == "trsg") time1 = 60000;
else if (powerup == "ice") time1 = 90000;
else if (powerup == "spdup") time1 = 60000;
else if (powerup == "ghost") time1 = 60000;
else if (powerup == "dbsg") time1 = 75000;
else if (powerup == "op") time1 = 40000;
powerupQueue[i].powerupDuration = time1;
powerupQueue[i].powerupCollectTime = ColTime;
powerupQueue[i].type = powerup;
determinePowerUpColor(i, powerup);
break;
} else powerupCount += 1;
}
if (powerupCount == MAX_QUEUE) gameScore += 30 * mltp1 * mltp2;
}
void determinePowerUpColor(int i, String powerup) {
if (powerup == "laser") powerupQueue[i].color = LASER;
else if (powerup == "teleport") powerupQueue[i].color = TLPRT;
else if (powerup == "immortal") powerupQueue[i].color = IMRTL;
else if (powerup == "trsg") powerupQueue[i].color = TRISG;
else if (powerup == "ice") powerupQueue[i].color = ICE;
else if (powerup == "spdup") powerupQueue[i].color = SPDUP;
else if (powerup == "ghost") powerupQueue[i].color = GHOST;
else if (powerup == "dbsg") powerupQueue[i].color = DUBSG;
else if (powerup == "op") powerupQueue[i].color = OP;
else if (powerup == "NONE") powerupQueue[i].color = ILI9341_WHITE;
}
void drawHearts() {
int x = 5;
for (int i = 0; i < 10; i++) {
const uint16_t* bitmap;
int heartValue = playerHealth - (i * 2);
if (heartValue >= 2) {
bitmap = fullHeartBMP;
}
else if (heartValue == 1) {
bitmap = halfHeartBMP;
}
else {
bitmap = emptyHeartBMP;
}
for (int row = 0; row < 11; row++) {
for (int col = 0; col < 18; col++) {
uint16_t pixel = pgm_read_word(&bitmap[row * 18 + col]);
uint16_t color = TRNSCLR;
if (pixel == 0xF800) color = RED;
else if (pixel == 0x8410) color = GRAY;
if (color != TRNSCLR) {
tft.drawPixel(x + col, 5 + row, color);
}
}
}
x += 18;
}
}
void drawShields() {
int x = 5;
for (int i = 0; i < 5; i++) {
const uint16_t* bitmap;
if (i < playerShield) bitmap = fullShieldBMP;
else bitmap = emptyShieldBMP;
for (int row = 0; row < 13; row++) {
for (int col = 0; col < 18; col++) {
uint16_t pixel = pgm_read_word(&bitmap[row * 18 + col]);
uint16_t color = TRNSCLR;
if (pixel == 0xAD5C) color = BLUE;
else if (pixel == 0x8410) color = GRAY;
if (color != TRNSCLR) {
tft.drawPixel(x + col, 32 + row, color);
}
}
}
x += 18;
}
}
void printTxt() {
if (gameScore != printedScore) {
tft.fillRect(4, 64, 200, 14, ILI9341_BLACK);
tft.setTextSize(2);
tft.setCursor(5, 65);
tft.print("Score: ");
tft.print(gameScore);
printedScore = gameScore;
}
tft.setTextSize(1);
if (playerHealth != printedHealth) {
tft.fillRect(4, 19, 200, 10, ILI9341_BLACK);
tft.setCursor(5, 20);
tft.print("Health: "); tft.print(playerHealth); tft.print(" / "); tft.print(maxPlayerHealth);
printedHealth = playerHealth;
}
if (playerShield != printedShield || currentShieldUses != printedShieldUses) {
tft.fillRect(4, 49, 200, 10, ILI9341_BLACK);
tft.setCursor(5, 50);
tft.print("Shields: "); tft.print(playerShield); tft.print(" / "); tft.print(maxPlayerShield); tft.print(" | Shield Uses: "); tft.print(currentShieldUses);
printedShield = playerShield;
printedShieldUses = currentShieldUses;
}
if (powerupQueue[0].type != printedPowerUpType) {
tft.fillRect(129, 109, 60, 10, ILI9341_BLACK);
tft.fillRect(129, 134, 60, 10, ILI9341_BLACK);
tft.setTextColor(powerupQueue[0].color);
tft.setCursor(130, 110);
tft.print(getPwrUpTypeString(0));
tft.setTextColor(powerupQueue[1].color);
tft.setCursor(130, 135);
tft.print(getPwrUpTypeString(1));
printedPowerUpType = powerupQueue[0].type;
}
tft.setTextColor(ILI9341_WHITE);
}
String getPwrUpTypeString(int i) {
if (powerupQueue[i].type == "laser") return "Lasers";
else if (powerupQueue[i].type == "teleport") return "Teleport";
else if (powerupQueue[i].type == "immortal") return "Immortal";
else if (powerupQueue[i].type == "trsg") return "Triple Score Gain";
else if (powerupQueue[i].type == "ice") return "Ice";
else if (powerupQueue[i].type == "spdup") return "Speed Up";
else if (powerupQueue[i].type == "ghost") return "Ghost";
else if (powerupQueue[i].type == "dbsg") return "Double Score Gain";
else if (powerupQueue[i].type == "op") return "OP";
else if (powerupQueue[i].type == "NONE") return "None";
}
void updateObstacles() {
if (millis() - lastObstacleMovement > ObstUpdateMin) {
lastObstacleMovement = millis();
for (int i = 0; i < MAX_OBSTACLES; i++) {
if (obstacles[i].active) {
obstacles[i].oldY = obstacles[i].y;
if (obstacles[i].y < 290) {
obstacles[i].y += 5;
}
tft.fillRect(obstacles[i].x, obstacles[i].oldY, 10, 10, ILI9341_BLACK);
}
if (obstacles[i].y == 290) {
landingAnimation(obstacles[i].x, obstacles[i].y, obstacles[i].color);
ersObj(i);
}
}
}
}
void ersPwrUp(int i) {
powerupQueue[i].written = false;
powerupQueue[i].powerupDuration = 0;
powerupQueue[i].powerupCollectTime = 0;
powerupQueue[i].type = 'NONE';
powerupQueue[i].color = ILI9341_WHITE;
}
void landingAnimation(int obx, int oby, uint16_t clr) {
int t = 0;
for (int i = 0; i < 10; i++) {
tft.fillRect(obx, oby + t, 10, 10 - t, clr);
delay(100);
tft.fillRect(obx, oby + t, 10, 10 - t, ILI9341_BLACK);
t += 1;
}
}
void handlePowerUps() {
if (millis() - powerupQueue[0].powerupCollectTime > powerupQueue[0].powerupDuration) {
DisableAbility();
PowerUpActive = false;
return;
}
if (powerupQueue[0].type == "NONE") return;
else if (powerupQueue[0].type == "laser") laser();
else if (powerupQueue[0].type == "teleport") teleport();
else if (powerupQueue[0].type == "immortal") immortal();
else if (powerupQueue[0].type == "trsg") tripleScore();
else if (powerupQueue[0].type == "ice") ice(1);
else if (powerupQueue[0].type == "spdup") speedup();
else if (powerupQueue[0].type == "ghost") ghost();
else if (powerupQueue[0].type == "dbsg") doubleScore();
else if (powerupQueue[0].type == "op") op();
}
void checkAbilityStatus() {
if (QueueSlotsWritten >= 1 && !PowerUpActive) {
for (int i = 0; i < absoluteMaxQueue - 1; i++) {
powerupQueue[i].written = powerupQueue[i + 1].written;
powerupQueue[i].powerupDuration = powerupQueue[i + 1].powerupDuration;
powerupQueue[i].powerupCollectTime = powerupQueue[i + 1].powerupCollectTime;
powerupQueue[i].type = powerupQueue[i + 1].type;
powerupQueue[i].color = powerupQueue[i + 1].color;
ersPwrUp(i + 1);
}
} else return;
}
void DisableAbility() {
mltp1 = 1;
mltp2 = 1;
ObstUpdateMin = 1000;
spdup = 1;
}
void op() {
doubleScore();
tripleScore();
ghost();
laser();
ice(2);
speedup();
}
void doubleScore() {
mltp1 = 2;
}
void tripleScore() {
mltp2 = 3;
}
void laser() {
if (digitalRead(6) == LOW) {
spawnProjectile();
}
}
void teleport() {
if (digitalRead(6) == LOW) {
RandPlayerX = ((random() % 10) * 10) + 10;
}
playerX = RandPlayerX;
}
void ice(int c) {
if (c == 1) ObstUpdateMin = 3000;
else if (c == 2) ObstUpdateMin = 2000;
}
void speedup() {
spdup = 5;
}
void immortal() {}
void ghost() {}
void spawnProjectile() {
for (int i = 0; i < MaxProjectiles; i++) {
if (!projectiles[i].active) {
projectiles[i].x = playerX + 4;
projectiles[i].y = playerY - 3;
projectiles[i].opX = projectiles[i].x + 2;
projectiles[i].active = true;
}
}
}
void updateProjectile() {
for (int i = 0; i < MaxProjectiles; i++) {
if (projectiles[i].active) {
projectiles[i].y -= 1;
int projW = projectiles[i].opX - projectiles[i].x;
drawProjectile(i, projW);
projectileGone(i, projW);
}
}
}
void drawProjectile(int i, int projW) {
tft.fillRect(projectiles[i].x, projectiles[i].y, projW, 3, projClr);
}
void ersProjectile(int i) {
projectiles[i].x = 0;
projectiles[i].y = 0;
projectiles[i].opX = 0;
projectiles[i].active = false;
}
void projectileGone(int i, int projW) {
if (projectiles[i].y == playAreaY + 1) {
for (int a = 0; a < 3; a++) {
tft.fillRect(projectiles[i].x, projectiles[i].y, projW, 3 - a, projClr);
}
ersProjectile(i);
}
}
char dbgBoolCheck(int a) {
if (a == 1) {
if (obstacles[dbgObstLoop].active) return 'T';
else return 'F';
} else if (a == 2) {
if (powerupQueue[dbgPwrUpLoop].written) return 'T';
else return 'F';
} else if (a == 3) {
if (damageInvulnerable) return 'T';
else return 'F';
} else if (a == 4) {
if (PowerUpActive) return 'T';
else return 'F';
}
}
String obstClr() {
if (obstacles[dbgObstLoop].color == ILI9341_RED) return "Red";
else if (obstacles[dbgObstLoop].color == ILI9341_GREEN) return "Green";
else if (obstacles[dbgObstLoop].color == ILI9341_BLUE) return "Blue";
else if (obstacles[dbgObstLoop].color == ColorGold) return "Gold";
}
String plyrClr() {
if (playerColor == ILI9341_WHITE) return "White";
else if (playerColor == ILI9341_ORANGE) return "Orange";
else if (playerColor == ILI9341_YELLOW) return "Yellow";
else if (playerColor == ILI9341_CYAN) return "Cyan";
else if (playerColor == TEAL) return "Teal";
}
void DbgULong() {
int dbgULL = dbgULongList[currentValIndex];
if (*dbgULongList[currentValIndex] > 999999999) tftD.print("99999+");
else tftD.print(dbgULL / 1000);
tftD.print("sc");
currentValIndex++;
}
void printDebug() {
if (!tgglDbgScrnOn) return;
if (millis() - lastDBGScreenRefresh > 3000) {
if (currentValIndex >= sizeof(dbgULongList) / sizeof(dbgULongList[0])) currentValIndex = 0;
lastDBGScreenRefresh = millis();
int line = 2;
if (dbgObstLoop > MAX_OBSTACLES) dbgObstLoop = 0;
if (dbgPwrUpLoop > MAX_QUEUE) dbgPwrUpLoop = 0;
tftD.fillRect(0, 30, 240, 290, ILI9341_BLACK);
tftD.setCursor(2, ++line * 10);
tftD.print("Player:");
tftD.setCursor(2, ++line * 10 + 5);
tftD.print("X: "); tftD.print(playerX); tftD.print("|Y: "); tftD.print(playerY); tftD.print("|W: "); tftD.print(playerW); tftD.print("|H: "); tftD.print(playerH);
tftD.setCursor(2, ++line * 10 + 5);
tftD.print("Old X:"); tftD.print(oldPlayerX); tftD.print(" | Old Y:"); tftD.print(oldPlayerY); tftD.print(" | Color:"); tftD.print(plyrClr());
tftD.setCursor(2, ++line * 10 + 5);
tftD.print("Obstacles:");
tftD.setCursor(2, line * 10 + 5);
tftD.print("Obstacle "); tftD.print(dbgObstLoop); tftD.print(": X:"); tftD.print(obstacles[dbgObstLoop].x); tftD.print(" | Y:"); tftD.print(obstacles[dbgObstLoop].y); tftD.print(" | OpX:"); tftD.print(obstacles[dbgObstLoop].opX);
tftD.setCursor(2, ++line * 10 + 5);
tftD.print("OpY:"); tftD.print(obstacles[dbgObstLoop].opY); tftD.print(" | OldX:"); tftD.print(obstacles[dbgObstLoop].oldY); tftD.print(" | Clr:"); tftD.print(obstClr()); tftD.print(" | Actv:"); tftD.print(dbgBoolCheck(1));
dbgObstLoop++; ++line;
tftD.setCursor(2, ++line * 10 + 5); line++;
tftD.print("Power Up Queue:");
tftD.setCursor(2, ++line * 10);
tftD.print("PwrUpQueueSlot "); tftD.print(dbgPwrUpLoop); tftD.print(": Written:"); tftD.print(dbgBoolCheck(2)); tftD.print(" PwrDur:"); tftD.print(powerupQueue[dbgPwrUpLoop].powerupDuration / 1000); tftD.print("s");
tftD.setCursor(2, ++line * 10);
tftD.print("Type:"); tftD.print(powerupQueue[dbgPwrUpLoop].type); tftD.print(" Time Collected:");
if (powerupQueue[dbgPwrUpLoop].powerupCollectTime / 1000 > 99999999) tftD.print("99999999+");
else tftD.print(powerupQueue[dbgPwrUpLoop].powerupCollectTime / 1000);
tftD.print("s"); dbgPwrUpLoop++; ++line;
tftD.setCursor(2, ++line * 10);
tftD.print("Booleans:");
tftD.setCursor(2, ++line * 10 + 5);
tftD.print("dmgInvuln:"); tftD.print(dbgBoolCheck(3)); tftD.print(" PwrUpActive:"); tftD.print(dbgBoolCheck(4));
tftD.setCursor(2, ++line * 10 + 5);
tftD.print("Unsigned Longs:");
tftD.setCursor(2, ++line * 10 + 5);
tftD.print("LstSpwnTm: "); DbgULong(); tftD.print("| LstDmgTkn: "); DbgULong();
tftD.setCursor(2, ++line * 10 + 5);
tftD.print("LstObstMove: "); DbgULong(); tftD.print("| LstClrChng: "); DbgULong();
tftD.setCursor(2, ++line * 10 + 5);
tftD.print("LstPwrUse: "); DbgULong();
++line; tftD.setCursor(2, ++line * 10 + 5); ++line;
tftD.print("Integers:");
tftD.setCursor(2, ++line * 10);
tftD.print("MxHealth:"); tftD.print(maxPlayerHealth); tftD.print(" MxShield:"); tftD.print(maxPlayerShield); tftD.print(" NextSpawn:"); tftD.print(tillNextSpawn);
tftD.setCursor(2, ++line * 10);
tftD.print("PlyrHealth:"); tftD.print(playerHealth); tftD.print(" PlyrShield:"); tftD.print(playerShield); tftD.print(" Score:"); tftD.print(gameScore);
tftD.setCursor(2, ++line * 10);
tftD.print("PrvRanX:"); tftD.print(prevRandX); tftD.print(" CrntShldUse:"); tftD.print(currentShieldUses); tftD.print(" PrntScore:"); tftD.print(printedScore);
tftD.setCursor(2, ++line * 10);
tftD.print("Multiply:"); tftD.print(mltp1 * mltp2); tftD.print(" MxObst:"); tftD.print(MAX_OBSTACLES); tftD.print(" MxQue:"); tftD.print(MAX_QUEUE);
tftD.setCursor(2, ++line * 10);
tftD.print("QueSlotWrite:"); tftD.print(QueueSlotsWritten);
}
}