So i fixed the issues coming from lacks of sleep. Now here is a code that works decently, but the same error as quoted before, whenever i unquote updateGame(); from Round5 or drawScene(); from Round2.
invalid header file
Here is the code that works. Note that the error only displays when i unquote those functions, which are well defined.
/*************************************************
* Public Constants
*************************************************/
#define NOTE_B0 31
#define NOTE_C1 33
#define NOTE_CS1 35
#define NOTE_D1 37
#define NOTE_DS1 39
#define NOTE_E1 41
#define NOTE_F1 44
#define NOTE_FS1 46
#define NOTE_G1 49
#define NOTE_GS1 52
#define NOTE_A1 55
#define NOTE_AS1 58
#define NOTE_B1 62
#define NOTE_C2 65
#define NOTE_CS2 69
#define NOTE_D2 73
#define NOTE_DS2 78
#define NOTE_E2 82
#define NOTE_F2 87
#define NOTE_FS2 93
#define NOTE_G2 98
#define NOTE_GS2 104
#define NOTE_A2 110
#define NOTE_AS2 117
#define NOTE_B2 123
#define NOTE_C3 131
#define NOTE_CS3 139
#define NOTE_D3 147
#define NOTE_DS3 156
#define NOTE_E3 165
#define NOTE_F3 175
#define NOTE_FS3 185
#define NOTE_G3 196
#define NOTE_GS3 208
#define NOTE_A3 220
#define NOTE_AS3 233
#define NOTE_B3 247
#define NOTE_C4 262
#define NOTE_CS4 277
#define NOTE_D4 294
#define NOTE_DS4 311
#define NOTE_E4 330
#define NOTE_F4 349
#define NOTE_FS4 370
#define NOTE_G4 392
#define NOTE_GS4 415
#define NOTE_A4 440
#define NOTE_AS4 466
#define NOTE_B4 494
#define NOTE_C5 523
#define NOTE_CS5 554
#define NOTE_D5 587
#define NOTE_DS5 622
#define NOTE_E5 659
#define NOTE_F5 698
#define NOTE_FS5 740
#define NOTE_G5 784
#define NOTE_GS5 831
#define NOTE_A5 880
#define NOTE_AS5 932
#define NOTE_B5 988
#define NOTE_C6 1047
#define NOTE_CS6 1109
#define NOTE_D6 1175
#define NOTE_DS6 1245
#define NOTE_E6 1319
#define NOTE_F6 1397
#define NOTE_FS6 1480
#define NOTE_G6 1568
#define NOTE_GS6 1661
#define NOTE_A6 1760
#define NOTE_AS6 1865
#define NOTE_B6 1976
#define NOTE_C7 2093
#define NOTE_CS7 2217
#define NOTE_D7 2349
#define NOTE_DS7 2489
#define NOTE_E7 2637
#define NOTE_F7 2794
#define NOTE_FS7 2960
#define NOTE_G7 3136
#define NOTE_GS7 3322
#define NOTE_A7 3520
#define NOTE_AS7 3729
#define NOTE_B7 3951
#define NOTE_C8 4186
#define NOTE_CS8 4435
#define NOTE_D8 4699
#define NOTE_DS8 4978
//Lib & More
#define SERIAL Serial
#include <Keypad.h>
#include <Adafruit_LiquidCrystal.h>
Adafruit_LiquidCrystal lcd(0x20);
#define ROWS 4
#define COLS 4
#define MAX_OBSTACLES 10
#define MAX_ROUNDS 10
#define MAX_OBS_PER_ROUND 10
// Constants
const int redPin = 11;
const int greenPin = 12;
const int bluePin = 13;
const int speakerPin = 2;
int currentTheme = 0; // Tracks the current round's theme
char currentSpecialColorKey = '\0'; // Tracks the active special color key, '\0' means none
// Player state
int playerX;
int playerY;
bool isJumping;
int jumpCounter;
unsigned long lastMove;
const unsigned long MOVE_INTERVAL = 500;
const int JUMP_HEIGHT = 2; // two-frame arc: up then down
int jumpDistance; // How far to jump horizontally
int jumpProgress; // Tracks jump animation progress
int monsterX;
int monsterY;
bool monsterActive;
unsigned long lastMonsterMove;
bool waitingForMonster;
const unsigned long MONSTER_MOVE_INTERVAL = 1000;
const unsigned long BOSS_MOVE_INTERVAL = 400;
const int CROSS_MOVE_INTERVAL = 400;
bool doorMode = false;
bool crossOut = false;
const uint8_t P_CODE = 0; // player glyph slot
const uint8_t O_CODE = 1; // obstacle block slot
const uint8_t M_CODE = 2; // monster slot
// Obstacle storage
byte obstacleX[MAX_OBSTACLES];
byte obstacleY[MAX_OBSTACLES];
byte activeObstacles;
byte KEYX[MAX_OBSTACLES];
byte KEYY[MAX_OBSTACLES];
byte activeKEYs;
// Keypad
const char keys[ROWS][COLS] = {{'1','2','3','R'},{'4','5','6','S'},{'7','8','9','C'},{'T','#','&','E'}};
byte rowPins[ROWS] = {3,4,5,6};
byte colPins[COLS] = {7,8,9,10};
Keypad KP = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);
// Music
struct Melody {int* notes; int* durations; int length;};
enum MelodyID {
S_ROUND1,
S_ROUND2,
S_ROUND3,
S_ROUND4,
S_ROUND5,
S_ROUND6,
S_ROUND7,
S_ROUND8,
S_ROUND9,
S_ROUND10,
MELODY_COUNT //sizeof
};
int marioNotes[] = {NOTE_C4,NOTE_E4,NOTE_G4,NOTE_E4,NOTE_A4,NOTE_B4,NOTE_AS4,NOTE_A4,NOTE_G4,NOTE_E5,NOTE_G5,NOTE_A5,NOTE_F5,NOTE_G5,NOTE_E5,NOTE_C5,NOTE_D5,NOTE_B4};
int marioDurations[] = {4,4,4,4,4,4,8,8,4,4,4,4,4,4,4,4,8,2};
int end1Notes[] = {NOTE_C4,NOTE_E4,NOTE_G4,NOTE_C5,NOTE_G4,NOTE_E4,NOTE_C4,0,NOTE_G4,NOTE_C5};
int end1Durations[] = {4,4,4,2,4,4,2,4,4,2};
int end3Notes[] = {NOTE_E4,NOTE_G4,NOTE_C5,NOTE_E5,NOTE_G5,NOTE_E5,NOTE_C5,NOTE_G4,NOTE_E4};
int end3Durations[] = {4,4,4,4,4,4,4,4,2};
int end4Notes[] = {NOTE_G4,NOTE_E4,NOTE_A4,NOTE_F4,NOTE_D4,NOTE_B3,NOTE_G3,0,NOTE_C4,NOTE_E4};
int end4Durations[] = {4,4,4,4,4,4,4,2,4,2};
int end5Notes[] = {NOTE_F4,NOTE_A4,NOTE_D5,NOTE_C5,NOTE_B4,NOTE_G4,NOTE_E4,NOTE_C4,NOTE_D4,NOTE_F4};
int end5Durations[] = {4,4,4,4,4,4,4,4,4,2};
int end6Notes[] = {NOTE_C4,NOTE_E4,NOTE_G4,NOTE_B4,NOTE_G4,NOTE_E4,NOTE_C4,0,NOTE_G4,NOTE_C5};
int end6Durations[] = {4,4,4,4,4,4,2,2,4,2};
int end7Notes[] = {NOTE_E4,NOTE_A4,NOTE_C5,NOTE_B4,NOTE_G4,NOTE_E4,NOTE_C4,NOTE_A3,NOTE_E4,NOTE_C4};
int end7Durations[] = {4,4,4,4,4,4,4,4,4,2};
int end8Notes[] = {NOTE_C4,NOTE_D4,NOTE_E4,NOTE_F4,NOTE_G4,NOTE_A4,NOTE_B4,NOTE_C5,NOTE_G4,NOTE_E4};
int end8Durations[] = {4,4,4,4,4,4,4,2,4,2};
int end9Notes[] = {NOTE_E4,NOTE_G4,NOTE_B4,NOTE_E5,NOTE_D5,NOTE_B4,NOTE_G4,NOTE_E4,NOTE_G4,NOTE_B4};
int end9Durations[] = {4,4,4,2,4,4,2,4,4,2};
int end10Notes[] = {NOTE_G4,NOTE_B4,NOTE_D5,NOTE_G5,NOTE_D5,NOTE_B4,NOTE_G4,NOTE_D4,NOTE_G4,NOTE_B4};
int end10Durations[] = {4,4,4,2,4,4,2,4,4,2};
Melody melodies[MELODY_COUNT] = {
{marioNotes, marioDurations, 18},
{end1Notes, end1Durations, 10},
{end3Notes, end3Durations, 9},
{end4Notes, end4Durations, 10},
{end5Notes, end5Durations, 10},
{end6Notes, end6Durations, 10},
{end7Notes, end7Durations, 10},
{end8Notes, end8Durations, 10},
{end9Notes, end9Durations, 10},
{end10Notes, end10Durations, 10}
};
// Keep ALL original characters but manage slots carefully
enum CustomChars {
// Core Game Characters (0-7)
CHAR_PLAYER, // 0
CHAR_OBSTACLE, // 1
CHAR_KEY, // 2
CHAR_DOOR_CLOSED, // 3
CHAR_DOOR_OPEN, // 4
CHAR_PLANT, // 5
CROSS, // 6
SPYKE, // 7
MONSTER, // 8
BOSS, // 9
BWALL // 10
};
byte specialCharacters[11][8] = {
// 0-7: Core characters (same as before)
{B01110, B01110, B00100, B01110, B10101, B00100, B01010, B10001}, // player
{B11111, B11111, B11111, B11111, B11111, B11111, B11111, B11111}, // obstacle
{B00100, B01110, B11011, B01110, B00100, B00110, B00100, B00110}, // key
{B11111, B10001, B10001, B11001, B11101, B10001, B10001, B11111}, // door closed
{B00011, B00011, B00011, B00011, B00011, B00011, B00011, B00011}, // door open
{B10111, B01010, B11101, B01010, B11011, B10010, B10100, B00100}, // plant
{B00000, B00000, B00100, B00100, B11111, B00100, B00100, B00000}, // cross
{B00000, B00000, B00000, B00100, B01110, B01110, B11111, B11111}, // SPYKE
// 8-10: Round-specific characters
{B00000, B00000, B00000, B00100, B01110, B10101, B11111, B01010}, // monster
{B10101, B01110, B11111, B10101, B11111, B11011, B11111, B11111}, // boss
{B00000, B00000, B00000, B00000, B00010, B00011, B10011, B11111} // bwall
};
byte RGB_LED_CK[11][3] = {
/* index 0 unused */ {65, 65, 65}, // Dummy placeholder
/* 1 */ {255, 255, 255}, // White
/* 2 */ {255, 255, 0}, // Yellow
/* 3 */ {0, 255, 0}, // Green
/* 4 */ {0, 255, 255}, // Cyan
/* 5 */ {0, 0, 255}, // Blue
/* 6 */ {255, 0, 255}, // Magenta
/* 7 */ {75, 0, 130}, // Indigo (approximate mix)
/* 8 */ {128, 128, 128}, // Gray
/* 9 */ {255, 165, 0}, // Orange
/* 10*/ {255, 0, 0} // Red
};
// Zone definitions
#define ZONES 3
#define MAX_DOORS 4
#define MAX_PLANTS 8
// Round definitions
const byte roundObstacleX[MAX_ROUNDS][MAX_OBS_PER_ROUND] = {
{4, 8, 12}, // Round 1
{4, 8, 9, 9, 10, 12}, // Round 2
{}, // Round 3
{9, 9, 1}, // Round 4
};
const byte roundObstacleY[MAX_ROUNDS][MAX_OBS_PER_ROUND] = {
{1, 1, 1}, // Round 1
{1, 1, 1, 0, 1, 1}, // Round 2
{}, // Round 3
{1, 0, 3}, // Round 4
};
const byte roundObstacleCount[MAX_ROUNDS] = {
3, // Round 1
6, // Round 2
0, // Round 3
3, // Round 4
};
// Doors: X, Y per zone
const byte zoneDoorX[ZONES][MAX_DOORS] = {
{2, 7, 12, 0}, // Zone1 (3 doors)
{3, 9, 0, 0}, // Zone2 (2 doors)
{15,0, 0, 0} // Zone3 (exit door)
};
const byte zoneDoorY[ZONES][MAX_DOORS] = {
{1,1,1,0},
{1,1,0,0},
{1,0,0,0}
};
const byte zoneDoorCount[ZONES] = {3, 2, 1};
// Plants: X, Y per zone
const byte zonePlantX[ZONES][MAX_PLANTS] = {
{2, 9, 0,0,0,0,0,0}, // Zone1
{0, 3, 7,0,0,0,0,0}, // Zone2
{ } // Zone3 no plants
};
const byte zonePlantY[ZONES][MAX_PLANTS] = {
{0, 0, 0,0,0,0,0,0},
{0, 0, 0,0,0,0,0,0},
{ }
};
const byte zonePlantCount[ZONES] = {2, 3, 0};
// Correct door X per zone
const byte correctDoorX[ZONES] = {7, 9, 15};
// Key position (only in Zone3)
const byte keyX = 0;
const byte keyY = 1;
// Current zone index
byte currentZone = 0;
bool doorOpenFlags[ZONES][MAX_DOORS] = {{false}};
// Function declarations (corrected)
void loadRound(byte round);
void Round1();
void Round2();
void Round3();
void Round4();
void Round5();
void Round6();
void Round7();
void Round8();
void Round9();
void Round10();
void updateGame();
void drawScene();
void menu();
void transitToRound(int steps, String topText, String botText = "");
void Color_Generation (int theme);
void Speaker_Sound(Melody m, int pin);
void animateDoor(byte doorX, byte doorY, bool isCorrect);
bool isObstacleAt(byte x, byte y);
void loadZone(byte z);
void moveMonster();
void squish();
bool readyToMove(unsigned long &lastMove, unsigned long interval);
void showCompletionMessage();
bool waitForMonster(bool monsterArrived, byte obsX);
void doHitSequence(byte playerX, byte &playerY,
byte &actorX, byte actorY,
byte playerGlyph, byte actorGlyph,
unsigned long onHitDelay = 500);
void removeWallLogicAndDraw(
const byte wallPositions[][2],
byte wallCount,
byte bwallChar
);
void initRoundBase(
byte &playerX, byte &playerY,
byte initPlayerX, byte initPlayerY,
byte &bossX, byte &bossY,
byte initBossX, byte initBossY,
const byte spykePos[][2], byte spykeCount);
bool tryFireProjectile(
bool &projectileActive,
byte playerX, byte playerY,
byte &projectileX, byte &projectileY,
unsigned long shootTime);
bool updateProjectile(
byte &projectileX, byte &projectileY,
bool &projectileActive, bool &bossDead,
byte bossX, byte bossY,
unsigned long &lastCrossMove);
bool handleBossFall(
byte bossX,
byte &bossY,
unsigned long hitTimestamp
);
void moveMonster(uint8_t startX, uint8_t distance);
bool handlePlayerApproach(unsigned long now, bool jumpedOnBlock);
void moveLeft(byte &x);
bool checkHit(byte actorX, byte actorY, byte playerX, byte playerY);
bool dropFromMonster();
void continueWalking(unsigned long now, byte stopX);
void slideToEnd();
void loadZone(byte z);
void showCompletionMessage(String text);
void Round_bar_Number(int roundNum);
int mapColorKeyToTheme(char colorKey);
void setup() {
Serial.begin(9600);
Wire.begin();
lcd.begin(16,2);
pinMode(redPin, OUTPUT);
pinMode(greenPin, OUTPUT);
pinMode(bluePin, OUTPUT);
pinMode(speakerPin, OUTPUT);
menu();
}
void loop() {
char key = KP.getKey();
if (key) {
char roundKey = KP.waitForKey();
if (roundKey == '1') { Round1(); Serial.println("R1 STARTED"); }
else if (roundKey == '2') { Round2(); } // Round2() not called, so no linker errors
else if (roundKey == '3') { Serial.println("ca"); }
else if (roundKey == '4') { Serial.println("ca"); }
else if (roundKey == '5') { Round5(); Serial.println("Round started");}
else if (roundKey == '6') { Serial.println("ca"); }
else if (roundKey == '7') { Serial.println("ca"); }
else if (roundKey == '8') { Serial.println("ca"); }
else if (roundKey == '9') { Serial.println("ca"); }
else if (roundKey == 'T') { Serial.println("ca"); }
}
}
void Round1() {
transitToRound(16, " ", " ");
currentSpecialColorKey = '\0'; // Reset special color
const byte round1Chars[] = {6, 0, 7, 8, 1, 9, 10}; // CROSS, PLAYER, SPYKE, MONSTER, OBSTACLE, BOSS, BWALL
// Create custom characters for Round 1 (codes 0 to 6)
for (byte i = 0; i < 7; i++) {
lcd.createChar(i, specialCharacters[round1Chars[i]]);
}
// Prepare the display array for row 0: space, char, space, char, ...
byte row0[16];
memset(row0, ' ', 16); // Fill all with space to prevent character adding themselves
for (int i = 0; i < 7; i++) {
row0[2 * i] = ' '; // Space before each character
row0[2 * i + 1] = i; // Custom character code (0 to 6)
}
// Clear the LCD and display everything
lcd.clear();
lcd.setCursor(0, 0);
for (int i = 0; i < 16; i++) {
lcd.write(row0[i]); // Write each character individually
}
lcd.setCursor(0, 1);
lcd.print("START ADVENTURE");
delay(2500);
transitToRound(16, " ", " ");
}
void Round2() {
currentTheme = 1; // Set the theme for Round 1
currentSpecialColorKey = '\0'; // Reset special color
transitToRound(16, "Jumps!", " ");
int obs1X = 6; int obs1Y = 1;
int obs2X = 10; int obs2Y = 1;
doorMode = false;
// Create custom characters
lcd.createChar(0, specialCharacters[CHAR_PLAYER]); // Code 0: Player
lcd.createChar(1, specialCharacters[SPYKE]); // Code 1: Spyke
lcd.createChar(2, specialCharacters[CHAR_OBSTACLE]); // Code 2: Obstacle
// Clear the LCD and place obstacles
lcd.clear();
lcd.setCursor(obs1X, obs1Y); // Spyke at (7,1)
lcd.write(1);
lcd.setCursor(obs2X, obs2Y); // Obstacle at (11,1)
lcd.write(2);
// Set up obstacles for game logic
activeObstacles = 2;
obstacleX[0] = obs1X; obstacleY[0] = obs1Y; // Spyke
obstacleX[1] = obs2X; obstacleY[1] = obs2Y; // Obstacle
// Initialize player state
playerX = 0;
playerY = 1;
isJumping = false;
jumpCounter = 0;
lastMove = millis();
while (playerX < 16) {
if (readyToMove(lastMove, MOVE_INTERVAL)) {
// updateGame(); // Handle movement and jumping
// drawScene(); // Update display
lastMove = millis();
}
}
showCompletionMessage("Round complete");
}
void Round5() {
transitToRound(16, " ", " ");
currentTheme = 4; // Set the theme for Round 1
currentSpecialColorKey = '\0'; // Reset special color
Color_Generation(currentTheme);
doorMode = false;
transitToRound(16, "A boss is", "comming...");
activeObstacles = 0;
// Character setup
lcd.createChar(CHAR_PLAYER, specialCharacters[CHAR_PLAYER]);
lcd.createChar(SPYKE, specialCharacters[SPYKE]);
lcd.createChar(BOSS, specialCharacters[BOSS]);
lcd.clear();
// Initialize positions
playerX = 0;
playerY = 1;
byte bossX = 15;
byte bossY = 0;
// Spyke positions [X, Y]
const byte spykePositions[6][2] = {
{0, 1}, {1, 1}, {2, 1},
{13, 1}, {14, 1}, {15, 1}
};
// Timers
unsigned long lastMove = millis();
unsigned long lastBossMove = millis();
// Phase 1: Player movement to X=5
while (playerX < 5) {
if (millis() - lastMove >= MOVE_INTERVAL) {
lcd.clear();
lcd.setCursor(playerX, playerY);
lcd.write(CHAR_PLAYER);
//updateGame();
lastMove = millis();
}
}
playerX = playerX-1;
// Phase 2: Initial boss draw
lcd.setCursor(bossX, bossY); // Now valid (15,0)
lcd.write(BOSS);
delay(200);
// Phase 3: Boss movement (15 -> 12)
while (bossX > 12) { // FIXED: Stop at 12 (not 11)
if (millis() - lastBossMove >= BOSS_MOVE_INTERVAL) {
// Update boss position
lcd.setCursor(bossX, bossY);
lcd.print(' ');
moveLeft(bossX);
lcd.setCursor(bossX, bossY);
lcd.write(BOSS);
lastBossMove = millis();
}
}
delay(500);
// Final persistent draw
lcd.clear();
// Draw Spykes first
for (byte i = 0; i < 6; i++) {
lcd.setCursor(spykePositions[i][0], spykePositions[i][1]);
lcd.write(SPYKE);
}
lcd.setCursor(playerX, playerY);
lcd.write(CHAR_PLAYER);
lcd.setCursor(bossX, bossY);
lcd.write(BOSS);
lcd.setCursor(0,0);
lcd.print("I WILL END U");
delay(2500);
menu();
}
void transitToRound(int steps, String topText, String botText) {
for (int i = 0; i < steps; i++) {
lcd.scrollDisplayLeft();
delay(50);
}
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(topText);
if (botText.length() > 0) {
lcd.setCursor(0, 1);
lcd.print(botText);
}
delay(1000);
for (int i = 0; i < steps; i++) {
lcd.scrollDisplayLeft();
delay(50);
}
}
void Color_Generation(int theme) {
if (theme >= 0 && theme <= 10) {
analogWrite(redPin, RGB_LED_CK[theme][0]);
analogWrite(greenPin, RGB_LED_CK[theme][1]);
analogWrite(bluePin, RGB_LED_CK[theme][2]);
}
}
void menu() {
lcd.clear();
lcd.print("Press R 1-9/T");
lcd.setCursor(0, 1);
lcd.print("to start round");
Color_Generation(0); // Ensure LED is off at start
}
bool readyToMove(unsigned long &lastMove, unsigned long interval) {
if (millis() - lastMove < interval) return false;
lastMove = millis();
return true;
}
void showCompletionMessage(String text) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(text);
delay(1000);
transitToRound(16, " ", " ");
menu();
}
void removeWallLogicAndDraw(
const byte wallPositions[][2],
byte wallCount,
byte bwallChar
) {
// Draw destroyed animation
delay(300);
for (byte i = 0; i < wallCount; i++) {
lcd.setCursor(wallPositions[i][0], wallPositions[i][1]);
lcd.write(bwallChar);
}
delay(200);
lcd.setCursor(wallPositions[1][0], wallPositions[1][1]);
lcd.print(' ');
lcd.setCursor(wallPositions[0][0] + 1, wallPositions[0][1]);
lcd.write(bwallChar);
// Remove wall from logic arrays
for (byte i = 0; i < wallCount; i++) {
for (byte j = 0; j < activeObstacles; j++) {
if (obstacleX[j] == wallPositions[i][0] && obstacleY[j] == wallPositions[i][1]) {
obstacleX[j] = 255;
obstacleY[j] = 255;
}
}
}
crossOut = false;
delay(400);
}
void initRoundBase(
byte &playerX, byte &playerY,
byte initPlayerX, byte initPlayerY,
byte &bossX, byte &bossY,
byte initBossX, byte initBossY,
const byte spykePos[][2], byte spykeCount) {
// 1) Set positions
playerX = initPlayerX;
playerY = initPlayerY;
bossX = initBossX;
bossY = initBossY;
// 2) Draw spykes
lcd.clear();
for (byte i = 0; i < spykeCount; ++i) {
lcd.setCursor(spykePos[i][0], spykePos[i][1]);
lcd.write(SPYKE);
}
}
bool tryFireProjectile(
bool &projectileActive,
byte playerX, byte playerY,
byte &projectileX, byte &projectileY,
unsigned long shootTime) {
if (!projectileActive && millis() > shootTime) {
projectileActive = true;
projectileX = playerX + 1;
projectileY = playerY;
lcd.setCursor(projectileX, projectileY);
lcd.write(CROSS);
delay(500);
return true;
}
return false;
}
bool updateProjectile(
byte &projectileX, byte &projectileY,
bool &projectileActive, bool &bossDead,
byte bossX, byte bossY,
unsigned long &lastCrossMove) {
if (projectileActive && millis() - lastCrossMove >= CROSS_MOVE_INTERVAL && !bossDead) {
// Erase old
lcd.setCursor(projectileX, projectileY);
lcd.print(' ');
// Move
projectileX++;
if (projectileY > 0) projectileY--;
lcd.setCursor(projectileX, projectileY);
lcd.write(' ');
// Draw new
lcd.setCursor(projectileX, projectileY);
lcd.write(CROSS);
lastCrossMove = millis();
// Hit check
if (projectileX == bossX - 1 && projectileY == bossY) {
projectileActive = false;
delay(500);
lcd.setCursor(bossX, bossY);
lcd.write(BWALL);
lcd.setCursor(projectileX, projectileY);
lcd.write(' ');
bossDead = true;
return true;
}
}
return false;
}
bool handleBossFall(
byte bossX, // pass in the boss’s X coordinate
byte &bossY, // reference to boss’s Y coordinate
unsigned long hitTimestamp
) {
// Step 1: fall after 500ms
if (bossY < 1 && millis() - hitTimestamp > 500) {
lcd.setCursor(bossX, bossY);
lcd.print(' ');
bossY = 1;
lcd.setCursor(bossX, bossY);
lcd.write(BWALL);
}
// Step 2: finish round 1000ms after fall
if (bossY == 1 && millis() - hitTimestamp > 1500) {
return true;
}
return false;
}
int mapColorKeyToTheme(char colorKey) {
if (colorKey >= '1' && colorKey <= '9') {
return colorKey - '1'; // '1' -> 0, '2' -> 1, ..., '9' -> 8
} else if (colorKey == 'T') {
return 9; // 'T' -> 9
} else {
return -1; // Invalid key
}
}
void moveLeft(byte &x) {
x--;
}
bool checkHit(byte actorX, byte actorY, byte playerX, byte playerY) {
return (actorX == playerX && actorY == playerY);
}
void doHitSequence(byte playerX, byte &playerY, byte &actorX, byte actorY, byte playerGlyph, byte actorGlyph, unsigned long onHitDelay) {
lcd.setCursor(playerX, playerY);
lcd.write(actorGlyph);
delay(CROSS_MOVE_INTERVAL);
lcd.setCursor(actorX, actorY);
lcd.write(' ');
moveLeft(actorX);
lcd.setCursor(actorX, actorY);
lcd.write(actorGlyph);
lcd.setCursor(playerX, playerY);
lcd.write(' ');
playerY = 0;
lcd.setCursor(playerX, playerY);
lcd.write(playerGlyph);
delay(onHitDelay);
}
void moveMonster(uint8_t startX, uint8_t distance) {
if (!monsterActive || monsterX <= startX + distance) return;
if (millis() - lastMonsterMove < MONSTER_MOVE_INTERVAL) return;
// erase old
lcd.setCursor(monsterX, monsterY); lcd.print(' ');
monsterX--;
// draw new
lcd.setCursor(monsterX, monsterY); lcd.write(M_CODE);
lastMonsterMove = millis();
}
void squish() {
// erase player on ground
lcd.setCursor(playerX, playerY); lcd.print(' ');
// small jump up to monster (Y=0)
lcd.setCursor(monsterX, 0); lcd.write(P_CODE);
delay(300);
lcd.setCursor(monsterX, 0); lcd.print(' ');
// clear monster
lcd.setCursor(monsterX, monsterY); lcd.print(' ');
// land on monster's cell (Y=1)
playerX = monsterX;
playerY = 1;
lcd.setCursor(playerX, playerY); lcd.write(P_CODE);
monsterActive = false;
// no need to update lastMove here if we finish immediately
}
bool handlePlayerApproach(unsigned long now, bool jumpedOnBlock) {
if (!jumpedOnBlock && now - lastMove >= MOVE_INTERVAL) {
lcd.setCursor(playerX, playerY);
lcd.print(' ');
playerX++;
if (playerX == obstacleX[0]) {
playerY = 0;
jumpedOnBlock = true;
}
lcd.setCursor(playerX, playerY);
lcd.write(P_CODE);
lastMove = now;
}
return jumpedOnBlock;
}
bool waitForMonster(bool monsterArrived, byte obsX) {
if (!monsterArrived) {
moveMonster(obsX, 5);
if (monsterX <= obsX + 5) {
return true;
}
}
return monsterArrived;
}
bool dropFromMonster() {
delay(200);
lcd.setCursor(playerX, playerY);
lcd.print(' ');
playerY = 1;
playerX++;
lcd.setCursor(playerX, playerY);
lcd.write(P_CODE);
lastMove = millis();
return true;
}
void continueWalking(unsigned long now, byte stopX) {
if (now - lastMove >= MOVE_INTERVAL && playerX < stopX) {
lcd.setCursor(playerX, playerY);
lcd.print(' ');
playerX++;
lcd.setCursor(playerX, playerY);
lcd.write(CHAR_PLAYER);
lastMove = now;
}
}
void slideToEnd() {
while (playerX < 16) {
if (millis() - lastMove >= MOVE_INTERVAL) {
lcd.setCursor(playerX, playerY);
lcd.print(' ');
playerX++;
lcd.setCursor(playerX, playerY);
lcd.write(P_CODE);
lastMove = millis();
}
}
}
void loadZone(byte z) {
lcd.clear();
for (byte i = 0; i < zonePlantCount[z]; i++) {
lcd.setCursor(zonePlantX[z][i], zonePlantY[z][i]);
lcd.write((uint8_t)CHAR_PLANT);
}
for (byte i = 0; i < zoneDoorCount[z]; i++) {
lcd.setCursor(zoneDoorX[z][i], zoneDoorY[z][i]);
lcd.write((uint8_t)CHAR_DOOR_CLOSED);
}
for (byte d = 0; d < zoneDoorCount[z]; d++) {
doorOpenFlags[z][d] = false;
}
playerX = 0;
playerY = 1;
isJumping = false;
jumpCounter = 0;
lastMove = millis();
drawScene();
}
void updateGame() {
// ——— Wall-Proximity Halt & Cross Display ———
// If player at X=7, spot 8 is clear, and there's a wall at X=9 (stacked blocks),
// then show cross at (8, playerY) and halt movement this frame.
// there were a huge struggle to make this general
if (!isObstacleAt(playerX + 1, playerY) && isObstacleAt(playerX + 2, 0) && isObstacleAt(playerX + 2, 1) && !doorMode) {
lcd.setCursor(8, playerY);
lcd.write((uint8_t)CROSS);
crossOut = true;
return;
}
bool shouldTeleport = false;
int teleportDistance = 0;
byte landingY = 1; // Ground level (Y=1)
if (isJumping && jumpCounter == 1) { // Player is landing from a jump
int landingX = playerX;
// Check if landing on a block at Y=1
for (byte i = 0; i < activeObstacles; i++) {
if (landingX == obstacleX[i] && landingY == obstacleY[i]) {
int currentX = landingX;
// Find consecutive blocks ahead at Y=1
while (true) {
bool foundNext = false;
for (byte j = 0; j < activeObstacles; j++) {
if (obstacleX[j] == currentX + 1 && obstacleY[j] == 1) {
foundNext = true;
currentX++;
teleportDistance++;
break;
}
}
if (!foundNext) break;
}
if (teleportDistance > 0) {
shouldTeleport = true;
int targetX = currentX;
playerY = isObstacleAt(targetX, 1) ? 0 : 1;
playerX = targetX;
}
}
}
}
if (!shouldTeleport) {
if (!doorMode && !isJumping) {
for (byte i = 0; i < activeObstacles; i++) {
if (playerY == obstacleY[i] && playerX + 1 == obstacleX[i]) {
isJumping = true;
jumpCounter = 0;
break;
}
}
}
playerX++;
if (isJumping) {
if (jumpCounter == 0) {
playerY = 0;
jumpCounter++;
} else {
playerY = 1;
isJumping = false;
jumpCounter = 0;
}
}
}
// Round 2 Key Logic
if (!doorMode && activeKEYs > 0) {
for (byte i = 0; i < activeKEYs; i++) {
if (playerX == KEYX[i] && playerY == KEYY[i]) activeKEYs = 0;
}
}
// Round 3 Door Logic
if (doorMode && !isJumping && playerY == 1) {
for (byte d = 0; d < zoneDoorCount[currentZone]; d++) {
if (playerX == zoneDoorX[currentZone][d] && !doorOpenFlags[currentZone][d]) {
doorOpenFlags[currentZone][d] = true;
bool isCorrect = (playerX == correctDoorX[currentZone]);
animateDoor(playerX, playerY, isCorrect);
for (byte i = 0; i < zoneDoorCount[currentZone]; i++) {
lcd.setCursor(zoneDoorX[currentZone][i], zoneDoorY[currentZone][i]);
lcd.write((uint8_t)(doorOpenFlags[currentZone][i] ? CHAR_DOOR_OPEN : CHAR_DOOR_CLOSED));
}
if (isCorrect) {
currentZone++;
if (currentZone < ZONES) loadZone(currentZone);
return;
}
return;
}
}
}
}
void animateDoor(byte doorX, byte doorY, bool isCorrect) {
// Blink door on LCD
for (int i = 0; i < 2; i++) {
lcd.setCursor(doorX, doorY);
lcd.write((uint8_t)CHAR_DOOR_CLOSED);
delay(200);
lcd.setCursor(doorX, doorY);
lcd.write((uint8_t)CHAR_DOOR_OPEN);
delay(200);
}
// Final state
lcd.setCursor(doorX, doorY);
lcd.write((uint8_t)CHAR_DOOR_OPEN);
// Show transition text for correct door
if (isCorrect) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Door Unlocked!");
lcd.setCursor(0, 1);
if (currentZone == ZONES - 1) {
lcd.print(" ");
} else {
lcd.print("Next Zone");
}
delay(2000);
}
}
bool isObstacleAt(byte x, byte y) {
for (byte i = 0; i < activeObstacles; i++) {
if (obstacleX[i] == x && obstacleY[i] == y) {
return true;
}
}
return false;
}
void drawScene() {
static byte lastX = playerX, lastY = playerY;
lcd.setCursor(lastX, lastY);
bool restored = false;
if (doorMode) {
for (byte d = 0; d < zoneDoorCount[currentZone]; d++) {
if (lastX == zoneDoorX[currentZone][d] && lastY == zoneDoorY[currentZone][d]) {
lcd.write((uint8_t)(doorOpenFlags[currentZone][d] ? CHAR_DOOR_OPEN : CHAR_DOOR_CLOSED));
restored = true;
break;
}
}
for (byte p = 0; p < zonePlantCount[currentZone]; p++) {
if (lastX == zonePlantX[currentZone][p] && lastY == zonePlantY[currentZone][p]) {
lcd.write((uint8_t)CHAR_PLANT);
restored = true;
break;
}
}
} else {
if (isObstacleAt(lastX, lastY)) {
lcd.write((byte)CHAR_OBSTACLE);
restored = true;
}
for (byte i = 0; i < activeKEYs; i++) {
if (lastX == KEYX[i] && lastY == KEYY[i]) {
lcd.write((byte)CHAR_KEY);
restored = true;
break;
}
}
}
if (!restored) {
lcd.print(' ');
}
lcd.setCursor(playerX, playerY);
lcd.write((byte)CHAR_PLAYER);
lastX = playerX;
lastY = playerY;
}
void loadRound(byte round) {
activeObstacles = roundObstacleCount[round];
for (byte i=0;i<activeObstacles;i++) {
obstacleX[i]=roundObstacleX[round][i];
obstacleY[i]=roundObstacleY[round][i];
}
}