Hello everyone. I am currently working on a tinkercad project. Here is my full code.
We will focus on the Sound_Emission function.
/*************************************************
* 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
#include <LiquidCrystal_I2C.h>
#include <Keypad.h>
LiquidCrystal_I2C lcd(0x20, 16, 2);
#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;
char colorKey;
char keykey;
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 Sound_Emission(int round);
void transitToRound(int steps, String topText, String botText = "");
void Color_Generation (int theme);
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); // Add this line before setup()
void setup() {
Serial.begin(9600);
Wire.begin();
lcd.init();
lcd.backlight();
pinMode(redPin, OUTPUT);
pinMode(greenPin, OUTPUT);
pinMode(bluePin, OUTPUT);
pinMode(speakerPin, OUTPUT);
menu();
}
void loop() {
char key = KP.getKey();
if (key) {
Serial.print("Key pressed: ");
Serial.println(key);
if (key == 'R') {
char roundKey = KP.waitForKey();
Wire.beginTransmission(9);
Wire.write(roundKey);
Wire.endTransmission();
if (roundKey == '1') { Round1(); }
else if (roundKey == '2') { Round2(); }
else if (roundKey == '3') { Round3(); }
else if (roundKey == '4') { Round4(); }
else if (roundKey == '5') { Round5(); }
else if (roundKey == '6') { Round6(); }
else if (roundKey == '7') { Round7(); }
else if (roundKey == '8') { Round8(); }
else if (roundKey == '9') { //Round9();
}
else if (roundKey == 'T') { //Round10();
}
}
}
else if (key == '#') {
if (KP.waitForKey() == 'C') {
char colorKey = KP.waitForKey();
// only accept 1–9 or T
if ((colorKey >= '1' && colorKey <= '9') || colorKey == 'T') {
currentSpecialColorKey = colorKey;
// inline mapColorKeyToTheme:
int theme = (colorKey >= '1' && colorKey <= '9')
? (colorKey - '1') // '1'→0 ... '9'→8
: 9; // 'T'→9
// // if you want to guard against invalid:
if (theme >= 0) {
Color_Generation(theme);
}
}
}
}
// Handle color reset (&C followed by matching color key)
// else if (key == '&') {
// // Serial.println("test");
// // char roundKey = KP.waitForKey();
// // Serial.print("Next key: ");
// // if (roundKey == 'C') {
// // char colorKey = KP.waitForKey();
// // Serial.print("colorKey: ");
// // Serial.println(colorKey);
// // if (colorKey == currentSpecialColorKey) {
// // // analogWrite(redPin, 0); // Turn off LED
// // // analogWrite(greenPin, 0);
// // // analogWrite(bluePin, 0);
// // currentSpecialColorKey = '\0';
// // }
// // }
// }
// // // Handle sound playback (S followed by 1-9 or T)
// // else if (key == 'S') {
// // char soundKey = KP.waitForKey();
// // Serial.print("soundKey: ");
// // Serial.println(soundKey);
// // if ((soundKey >= '1' && soundKey <= '9') || soundKey == 'T') {
// // int melodyIndex = (soundKey == 'T') ? 9 : (soundKey - '1');
// // Serial.print("Playing melody index: ");
// // Serial.println(melodyIndex);
// // Speaker_Sound(melodies[melodyIndex], speakerPin);
// // Serial.println("Sound finished");
// // } else {
// // Serial.println("Invalid soundKey");
// // }
// // }
// }
}
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
}
}
*The Sound_Emission function:*
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");
// Sound_Emission(1);
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 Round3() {
transitToRound(16, " ");
currentTheme = 2; // Set the theme for Round 1
currentSpecialColorKey = '\0'; // Reset special color
Color_Generation(currentTheme);
transitToRound(16, "Monster Chase!", "Jump wisely");
lcd.createChar(P_CODE, specialCharacters[CHAR_PLAYER]);
lcd.createChar(O_CODE, specialCharacters[CHAR_OBSTACLE]);
lcd.createChar(M_CODE, specialCharacters[MONSTER]);
obstacleX[0] = 4;
obstacleY[0] = 1;
playerX = 0;
playerY = 1;
lastMove = millis();
monsterX = 15;
monsterY = 1;
monsterActive = true;
lastMonsterMove = millis();
bool jumpedOnBlock = false;
bool droppedOffBlock = false;
bool monsterArrived = false;
bool hasSquished = false;
lcd.clear();
lcd.setCursor(obstacleX[0], obstacleY[0]); lcd.write(O_CODE);
lcd.setCursor(monsterX, monsterY); lcd.write(M_CODE);
lcd.setCursor(playerX, playerY); lcd.write(P_CODE);
while (playerX < 16 && !hasSquished) {
unsigned long now = millis();
jumpedOnBlock = handlePlayerApproach(now, jumpedOnBlock);
if (jumpedOnBlock) {
monsterArrived = waitForMonster(monsterArrived, obstacleX[0]);
}
if (jumpedOnBlock && monsterArrived && !droppedOffBlock) {
droppedOffBlock = dropFromMonster();
}
if (droppedOffBlock) {
continueWalking(now, obstacleX[0] + 5);
}
if (droppedOffBlock && playerX + 1 == monsterX && playerY == 1 && monsterActive) {
squish();
hasSquished = true;
}
}
slideToEnd();
// Speaker_Sound(melodies[S_ROUND3]);
showCompletionMessage("Round complete");
}
void Round4() {
transitToRound(16, " ");
currentTheme = 3; // Set the theme for Round 1
currentSpecialColorKey = '\0'; // Reset special color
Color_Generation(currentTheme);
transitToRound(16, " ");
doorMode = false;
const byte spikePositions[][2] = {{3, 1}};
const byte wallPositions[][2] = {{9, 1}, {9, 0}};
const byte spikeCount = sizeof(spikePositions) / sizeof(spikePositions[0]);
const byte wallCount = sizeof(wallPositions) / sizeof(wallPositions[0]);
lcd.createChar(CHAR_PLAYER, specialCharacters[CHAR_PLAYER]);
lcd.createChar(SPYKE, specialCharacters[SPYKE]);
lcd.createChar(CHAR_OBSTACLE, specialCharacters[CHAR_OBSTACLE]);
lcd.createChar(CROSS, specialCharacters[CROSS]);
lcd.createChar(BWALL, specialCharacters[BWALL]);
lcd.clear();
lcd.setCursor(spikePositions[0][0], spikePositions[0][1]);
lcd.write(SPYKE);
for (byte i = 0; i < wallCount; i++) {
lcd.setCursor(wallPositions[i][0], wallPositions[i][1]);
lcd.write(CHAR_OBSTACLE);
}
activeObstacles = spikeCount + wallCount;
obstacleX[0] = spikePositions[0][0];
obstacleY[0] = spikePositions[0][1];
for (byte i = 0; i < wallCount; i++) {
obstacleX[i + 1] = wallPositions[i][0];
obstacleY[i + 1] = wallPositions[i][1];
}
playerX = -1;
playerY = 1;
isJumping = false;
jumpCounter = 0;
lastMove = millis();
while (playerX < 16) {
if (readyToMove(lastMove, MOVE_INTERVAL)) {
updateGame();
if (crossOut) {
removeWallLogicAndDraw(wallPositions, wallCount, BWALL);
}
drawScene();
lastMove = millis();
}
}
// Speaker_Sound(melodies[S_ROUND4]);
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 Round6() {
transitToRound(16, " ");
currentTheme = 5; // Set the theme for Round 1
currentSpecialColorKey = '\0'; // Reset special color
Color_Generation(currentTheme);
byte playerX, playerY;
byte bossX, bossY;
byte crossX, crossY;
bool roundOver = false;
unsigned long lastMove = millis();
const byte spykePositions[6][2] = {
{0,1},{1,1},{2,1},{13,1},{14,1},{15,1}
};
doorMode = false;
transitToRound(16, "FIGHT");
activeObstacles = 0;
lcd.createChar(CHAR_PLAYER, specialCharacters[CHAR_PLAYER]);
lcd.createChar(SPYKE, specialCharacters[SPYKE]);
lcd.createChar(BOSS, specialCharacters[BOSS]);
lcd.createChar(CROSS, specialCharacters[CROSS]);
// Initialize player(4,1), boss(12,1), draw spykes
initRoundBase(
playerX, playerY, 4, 1,
bossX, bossY, 12, 1,
spykePositions, 6
);
// Draw boss, player, cross
lcd.setCursor(bossX, bossY); lcd.write(BOSS);
lcd.setCursor(playerX, playerY); lcd.write(CHAR_PLAYER);
crossX = bossX - 1; crossY = bossY;
lcd.setCursor(crossX, crossY); lcd.write(CROSS);
while (!roundOver) {
if (!readyToMove(lastMove, CROSS_MOVE_INTERVAL)) continue;
lcd.setCursor(crossX, crossY); lcd.write(' ');
moveLeft(crossX);
lcd.setCursor(crossX, crossY); lcd.write(CROSS);
if (checkHit(crossX, crossY, playerX, playerY)) {
doHitSequence(playerX, playerY, crossX, crossY, CHAR_PLAYER, CROSS);
roundOver = true;
}
}
// Speaker_Sound(melodies[S_ROUND6]);
showCompletionMessage("Round complete");
}
void Round7() {
transitToRound(16, " ");
currentTheme = 6; // Set the theme for Round 1
currentSpecialColorKey = '\0'; // Reset special color
Color_Generation(currentTheme);
byte playerX, playerY;
byte bossX, bossY;
byte mSpykeX, mSpykeY;
bool roundOver = false;
bool bossMoving = false;
bool spikeActive = true;
unsigned long lastMove = millis();
unsigned long lastBossMove = millis();
const byte spykePositions[6][2] = {
{0,1},{1,1},{2,1},{13,1},{14,1},{15,1}
};
doorMode = false;
transitToRound(16, "FIGHT");
lcd.createChar(CHAR_PLAYER, specialCharacters[CHAR_PLAYER]);
lcd.createChar(SPYKE, specialCharacters[SPYKE]);
lcd.createChar(BOSS, specialCharacters[BOSS]);
// Initialize player(4,1), boss(12,0), draw spykes
initRoundBase(
playerX, playerY, 4, 1,
bossX, bossY, 12, 0,
spykePositions, 6
);
// Draw boss, player, moving spyke
lcd.setCursor(bossX, bossY); lcd.write(BOSS);
lcd.setCursor(playerX, playerY);lcd.write(CHAR_PLAYER);
mSpykeX = bossX; mSpykeY = bossY + 1; // bossY+1 = 1
lcd.setCursor(mSpykeX, mSpykeY);lcd.write(SPYKE);
while (!roundOver) {
if (spikeActive && readyToMove(lastMove, CROSS_MOVE_INTERVAL)) {
lcd.setCursor(mSpykeX, mSpykeY); lcd.write(' ');
mSpykeX--;
lcd.setCursor(mSpykeX, mSpykeY); lcd.write(SPYKE);
if (checkHit(mSpykeX, mSpykeY, playerX, playerY)) {
doHitSequence(playerX, playerY, mSpykeX, mSpykeY, CHAR_PLAYER, SPYKE);
spikeActive = false;
bossMoving = true;
}
}
if (bossMoving && millis() - lastBossMove >= BOSS_MOVE_INTERVAL) {
lcd.setCursor(bossX, bossY); lcd.write(' ');
lcd.setCursor(playerX, playerY); lcd.write(' ');
lcd.setCursor(mSpykeX, mSpykeY); lcd.write(' ');
bossX = min(bossX + 1, (byte)15);
lcd.setCursor(bossX, bossY); lcd.write(BOSS);
if (bossX == 15) roundOver = true;
lastBossMove = millis();
}
}
//Speaker_Sound(melodies[S_ROUND7]);
showCompletionMessage("Boss Escaped!");
}
void Round8() {
transitToRound(16, " ");
currentTheme = 7; // Set the theme for Round 1
currentSpecialColorKey = '\0'; // Reset special color
Color_Generation(currentTheme);
byte playerX, playerY;
byte bossX, bossY;
bool roundOver = false;
unsigned long lastMove = millis();
unsigned long actionDelay = 0;
enum BossState { APPROACHING, ON_PLAYER, RETREATING_UP, RETREATING_RIGHT };
BossState bossState = APPROACHING;
const byte spykePositions[6][2] = {
{0,1},{1,1},{2,1},{13,1},{14,1},{15,1}
};
// Setup
doorMode = false;
transitToRound(16, "BOSS SHOWDOWN");
lcd.createChar(CHAR_PLAYER, specialCharacters[CHAR_PLAYER]);
lcd.createChar(BOSS, specialCharacters[BOSS]);
lcd.createChar(SPYKE, specialCharacters[SPYKE]);
// Initialize and draw spykes at start
// Player at (4,1), Boss at (0,0)
initRoundBase(
playerX, playerY, // out refs
4, 1, // initial player position
bossX, bossY, // out refs
0, 0, // initial boss position
spykePositions, 6
);
// Draw boss & player on top
lcd.setCursor(bossX, bossY); lcd.write(BOSS);
lcd.setCursor(playerX, playerY);lcd.write(CHAR_PLAYER);
// Main loop
while (!roundOver) {
switch (bossState) {
case APPROACHING:
if (millis() - lastMove >= BOSS_MOVE_INTERVAL) {
// Horizontal approach
if (bossX < playerX) {
lcd.setCursor(bossX, bossY);
lcd.print(' ');
bossX++;
}
// Vertical approach
else if (bossY < playerY) {
lcd.setCursor(bossX, bossY);
lcd.print(' ');
bossY++;
}
// Reach player position
else {
bossState = ON_PLAYER;
actionDelay = millis();
// Move player right
lcd.setCursor(playerX, playerY);
lcd.print(' ');
playerX++;
lcd.setCursor(playerX, playerY);
lcd.write(CHAR_PLAYER);
}
lcd.setCursor(bossX, bossY);
lcd.write(BOSS);
lastMove = millis();
}
break;
case ON_PLAYER:
if (millis() - actionDelay >= 500) {
bossState = RETREATING_UP;
lastMove = millis();
}
break;
case RETREATING_UP:
if (readyToMove(lastMove, BOSS_MOVE_INTERVAL)) {
if (bossY > 0) {
lcd.setCursor(bossX, bossY); lcd.print(' ');
bossY--;
lcd.setCursor(bossX, bossY); lcd.write(BOSS);
} else {
bossState = RETREATING_RIGHT;
lastMove = millis();
}
}
break;
case RETREATING_RIGHT:
if (readyToMove(lastMove, BOSS_MOVE_INTERVAL)) {
if (bossX < playerX + 3) {
lcd.setCursor(bossX, bossY); lcd.print(' ');
bossX++;
lcd.setCursor(bossX, bossY); lcd.write(BOSS);
} else {
roundOver = true;
}
}
break;
}
}
// Speaker_Sound(melodies[S_ROUND8]);
showCompletionMessage("BOSS RAN AWAY");
}
void Round9() {
currentTheme = 8;
Color_Generation(currentTheme);
currentSpecialColorKey = '\0';
byte playerX, playerY;
byte bossX, bossY;
bool roundOver = false;
bool bossDead = false;
bool projectileActive = false;
byte projectileX, projectileY;
unsigned long lastCrossMove = 0;
unsigned long hitTimestamp = 0;
const byte spykePositions[6][2] = {{0,1},{1,1},{2,1},{13,1},{14,1},{15,1}};
doorMode = false;
transitToRound(16, "BOSS SHOWDOWN");
lcd.createChar(CHAR_PLAYER, specialCharacters[CHAR_PLAYER]);
lcd.createChar(BOSS, specialCharacters[BOSS]);
lcd.createChar(SPYKE, specialCharacters[SPYKE]);
lcd.createChar(CROSS, specialCharacters[CROSS]);
lcd.createChar(BWALL, specialCharacters[BWALL]);
// Initialize positions using valid slots
initRoundBase(
playerX, playerY, 5, 1,
bossX, bossY, 8, 0,
spykePositions, 6
);
// Draw using assigned slots
lcd.setCursor(bossX, bossY); lcd.write(BOSS); // Boss in slot 1
lcd.setCursor(playerX, playerY);lcd.write(CHAR_PLAYER); // Player in slot 0
unsigned long shootTime = millis() + 1000;
while (!roundOver) {
if (tryFireProjectile(projectileActive, playerX, playerY, projectileX, projectileY, shootTime)) {
lastCrossMove = millis();
continue;
}
if (updateProjectile(projectileX, projectileY, projectileActive, bossDead, bossX, bossY, lastCrossMove)) {
hitTimestamp = millis();
continue;
}
if (bossDead && handleBossFall(bossX, bossY, hitTimestamp)) {
lcd.setCursor(projectileX, projectileY);
lcd.write(' ');
roundOver = true;
}
}
delay(2000);
showCompletionMessage("BOSS DEAD");
}
void Round10() {
transitToRound(16, " ");
currentTheme = 9; // Set the theme for Round 1
Color_Generation(currentTheme);
currentSpecialColorKey = '\0'; // Reset special color
lcd.createChar(CHAR_PLAYER, specialCharacters[CHAR_PLAYER]);
lcd.createChar(CHAR_KEY, specialCharacters[CHAR_KEY]);
lcd.createChar(CHAR_DOOR_CLOSED, specialCharacters[CHAR_DOOR_CLOSED]);
lcd.createChar(CHAR_DOOR_OPEN, specialCharacters[CHAR_DOOR_OPEN]);
lcd.createChar(CHAR_PLANT, specialCharacters[CHAR_PLANT]);
lcd.createChar(CHAR_OBSTACLE, specialCharacters[CHAR_OBSTACLE]);
doorMode = false;
transitToRound(16, "Find the key", "in the temple");
// Speaker_Sound(melodies[S_ROUND2], speakerPin); // Uncomment if sound is needed
loadRound(1); // index 1 = Round 2
playerX = 0; playerY = 1; isJumping = false; jumpCounter = 0; lastMove = millis();
lcd.clear();
activeKEYs = 1;
KEYX[0] = 10; KEYY[0] = 0;
for (byte i = 0; i < activeObstacles; i++) {
lcd.setCursor(obstacleX[i], obstacleY[i]);
lcd.write((uint8_t)CHAR_OBSTACLE);
}
for (byte i = 0; i < activeKEYs; i++) {
lcd.setCursor(KEYX[i], KEYY[i]);
lcd.write(CHAR_KEY);
}
drawScene();
while (playerX < 17) {
if (millis() - lastMove >= MOVE_INTERVAL) {
updateGame();
drawScene();
lastMove = millis();
}
}
// Check if key was obtained
if (activeKEYs == 0) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Key obtained");
delay(3000);
// Phase 2: Find the door (Round3 logic)
doorMode = true;
transitToRound(17, "Find the door", "to end the game");
currentZone = 0;
loadZone(currentZone);
while (currentZone < ZONES) {
if (millis() - lastMove >= MOVE_INTERVAL) {
updateGame();
drawScene();
lastMove = millis();
}
}
lcd.clear();
lcd.setCursor(0, 4);
lcd.print("STUDENT WINS!!!");
// Speaker_Sound(melodies[S_ROUND10]);
delay(3000);
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;
}
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 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);
}
}
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;
}
}
}
}
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];
}
}
/**
* @brief Plays a melody on a speaker corresponding to the specified round number.
*
* This function selects a melody from the global `melodies` array based on the input round number
* and plays it using the Arduino `tone` function on a predefined speaker pin.
* @param round The round number (1 to 10) that determines which melody to play.
* - Round 1 maps to `melodies[0]`
*/
void Sound_Emission(int round) {
// Validate round number (1 to 10)
if (round < 1 || round > 10) {
return; // Do nothing for invalid rounds
}
// Map round number to melody index (1-based to 0-based)
int index = round - 1;
const Melody& melody = melodies[index]; // Reference to the melody
// Timing constants
const int whole_note_duration = 2000; // Whole note duration in milliseconds
const int gap = 10; // Gap between notes in milliseconds
// Play each note in the melody
for (int i = 0; i < melody.length; i++) {
int note = melody.notes[i]; // Frequency of the note
int d = melody.durations[i]; // Duration denominator
int duration_ms = whole_note_duration / d; // Calculate note duration
if (note != 0) {
// Play note
tone(2, note);
delay(duration_ms); // Wait for note to play
noTone(2); // Stop the note
} else {
// Rest (silence)
delay(duration_ms); // Wait for rest duration
}
delay(gap); // Add small gap between notes
}
}
In fact, the function should work and debugs perfectly on the Arduino IDE. The code as a whole work practially on an arduino uno board. However, whenever I try to call that exact function inside my Round1, or any other one, I am getting this error.
The Sound_Emission function:
/**
* @brief Plays a melody on a speaker corresponding to the specified round number.
*
* This function selects a melody from the global `melodies` array based on the input round number
* and plays it using the Arduino `tone` function on a predefined speaker pin.
* @param round The round number (1 to 10) that determines which melody to play.
* - Round 1 maps to `melodies[0]` (e.g., Mario theme).
* - Round 2 maps to `melodies[1]`, and so on up to Round 10 (`melodies[9]`).
* If the round is outside this range, the function does nothing.
*/
void Sound_Emission(int round) {
// Validate round number (1 to 10)
if (round < 1 || round > 10) {
return; // Do nothing for invalid rounds
}
// Map round number to melody index (1-based to 0-based)
int index = round - 1;
const Melody& melody = melodies[index]; // Reference to the melody
// Timing constants
const int whole_note_duration = 2000; // Whole note duration in milliseconds
const int gap = 10; // Gap between notes in milliseconds
// Play each note in the melody
for (int i = 0; i < melody.length; i++) {
int note = melody.notes[i]; // Frequency of the note
int d = melody.durations[i]; // Duration denominator
int duration_ms = whole_note_duration / d; // Calculate note duration
if (note != 0) {
// Play note
tone(2, note);
delay(duration_ms); // Wait for note to play
noTone(2); // Stop the note
} else {
// Rest (silence)
delay(duration_ms); // Wait for rest duration
}
delay(gap); // Add small gap between notes
}
}
The Round1 function:
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");
//Sound_Emission(1);
delay(2500);
transitToRound(16, " ", " ");
}
The error:
invalid header file
If anyone has a clue about what is happening, feel free to answer ![]()
I have been getting this error for four days, not a single clue about what is wrong.