Hello, I am having a kind of big trouble at the moment. I am working on an arduino project, and I am having a pretty big issue when it comes to implement any device on my baseshield (Groove led bar and seed rdg led 8x8 matrix). This post treats only the LED Matrix.
Since the code is massive, I will invite you to focus on the setup, loop and round1() since this is where the problem starts. In fact, when i add matrix.displayFrames(angry, 2000, true, 1); anywhere into my main loop, the code will not even start running properly, it will stop mid setup(). However, when i clear my entire loop and just add the matrix.displayFrames(angry, 2000, true, 1);, it works. So I assume the problem is comming from the loop(). If anyone has a clue, let me know
//Lib & More
#define SERIAL Serial
#include "grove_two_rgb_led_matrix.h"
#include <Wire.h>
#include "rgb_lcd.h"
#include <Grove_LED_Bar.h>
#include <Keypad.h>
#include "pitches.h"
Grove_LED_Bar bar(7, 6, 0, LED_BAR_10);
rgb_lcd lcd;
GroveTwoRGBLedMatrixClass matrix;
#define ROWS 4
#define COLS 4
#define MAX_OBSTACLES 10
#define MAX_ROUNDS 10
#define MAX_OBS_PER_ROUND 10
// Constants
const int redPin = 2;
const int greenPin = 3;
const int bluePin = 4;
const int speakerPin = 5;
byte currentTheme = 0; // Tracks the current round's theme
char currentSpecialColorKey = '\0'; // Tracks the active special color key, '\0' means none
// Player state
byte playerX;
byte playerY;
bool isJumping;
byte jumpCounter;
unsigned long lastMove;
const unsigned long MOVE_INTERVAL = 500;
const byte JUMP_HEIGHT = 2; // two-frame arc: up then down
byte jumpDistance; // How far to jump horizontally
byte jumpProgress; // Tracks jump animation progress
byte monsterX;
byte 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
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] = {9,8,7,6};
byte colPins[COLS] = {13,12,11,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
};
int RGB_LED_CK[][3] = {{255,255,255},{255,255,0},{0,255,0},{0,255,255},{0,0,255},{255,0,255},{75,0,130},{128,128,128},{255,165,0},{255,0,0}};
// 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}};
// Forward declarations
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);
uint8_t angry[] = {
0xfe,0xfe,0x00,0x00,0x00,0x00,0xfe,0xfe,
0xfe,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,
0x00,0xff,0x00,0x00,0x00,0x00,0xff,0x00,
0x00,0xff,0xff,0x00,0x00,0xff,0xff,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0xff,0xff,0x00,0x00,0x00,
0xfe,0x00,0xff,0xff,0xff,0xff,0x00,0xfe,
0xfe,0xfe,0x00,0x00,0x00,0x00,0xfe,0xfe
};
void setup() {
Wire.begin();
Serial.begin(9600);
delay(1000);
delay(100);
bar.begin();
delay(50); // give bar time to settle
matrix.stopDisplay();
uint32_t version = 0;
version = matrix.getTestVersion();
Serial.print("version = ");
Serial.println(version, HEX);
Serial.println("Matrix init success!!!");
lcd.begin(2,16);
Serial.print("LCD BEGINED");
menu();
}
enum AppState { MENU, ANIMATING, IN_ROUND };
AppState currentState = MENU;
byte currentRound = 0;
void loop() {
char key = KP.getKey();
if (key) {
Serial.println(key);
if (key == 'R') {
handleRoundSelection();
}else if (key == '#') {
char nextKey = KP.waitForKey();
Serial.println(nextKey);
if (nextKey == 'C') {
char colorKey = KP.waitForKey();
Serial.print("colorKey: ");
Serial.println(colorKey);
if ((colorKey >= '1' && colorKey <= '9') || colorKey == 'T') {
currentSpecialColorKey = colorKey;
int theme = mapColorKeyToTheme(colorKey);
Serial.print("Theme: ");
Serial.println(theme);
if (theme >= 0 && theme <= 9) {
int r = RGB_LED_CK[theme][0];
int g = RGB_LED_CK[theme][1];
int b = RGB_LED_CK[theme][2];
lcd.setRGB(r, g, b);
}
}
}
} else if (key == '&') {
char nextKey = KP.waitForKey();
Serial.println(nextKey);
if (nextKey == 'C') {
char colorKey = KP.waitForKey();
if (colorKey == currentSpecialColorKey) {
lcd.setRGB(66, 61, 61);
currentSpecialColorKey = '\0';
}
}
} 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]);
Serial.println("Sound finished");
} else {
Serial.println("Invalid soundKey");
}
}
}
}
void handleRoundSelection() {
unsigned long timeout = millis() + 5000; // 5-second timeout
char roundKey = '\0';
lcd.clear();
lcd.print("Select Round 1-0");
while(millis() < timeout) {
roundKey = KP.getKey();
if(roundKey) break;
}
if(!roundKey) {
lcd.print("Timeout!");
delay(1000);
menu();
return;
}
executeRound(roundKey);
}
void executeRound(char roundKey) {
switch(roundKey) {
case '1':
Round1();
break;
case '2':
Round2();
break;
case '3':
Round3();
break;
case '4':
Round4();
break;
case '5':
Round5();
break;
case '6':
Round6();
break;
case '7':
Round7();
break;
case '8':
Round8();
break;
case '9':
Round9();
break;
case 'T':
Round10();
break;
default:
Serial.print("Invalid round: ");
Serial.println(roundKey);
break;
}
}
void Round1() {
transitToRound(16, " ");
currentTheme = 0; // Set the theme for Round 1
currentSpecialColorKey = '\0'; // Reset special color
Color_Generation(currentTheme); // Use currentTheme instead of hard-coded 0
// Define the special character indices directly (matching specialCharacters array)
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);
lcd.write(row0, 16); // Write the entire first row at once
lcd.setCursor(0, 1);
lcd.print("START ADVENTURE");
//matrix.displayFrames(angry, 2000, true, 1);
Speaker_Sound(melodies[S_ROUND1]);
delay(2500);
transitToRound(16, " ");
//
menu();
}
void Round2() {
transitToRound(16, " ");
currentTheme = 1; // Set the theme for Round 1
currentSpecialColorKey = '\0'; // Reset special color
Color_Generation(currentTheme);
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();
// Game loop: Move player until the end
while (playerX < 16) {
if (readyToMove(lastMove, MOVE_INTERVAL)) {
updateGame(); // Handle movement and jumping
drawScene(); // Update display
lastMove = millis();
}
}
// Show completion message
Speaker_Sound(melodies[S_ROUND2]);
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");
Speaker_Sound(melodies[S_ROUND5]);
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() {
transitToRound(16, " ");
currentTheme = 8; // Set the theme for Round 1
Color_Generation(currentTheme);
currentSpecialColorKey = '\0'; // Reset special color
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;
// Phase 0: initial setup
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]);
// init positions + spykes
initRoundBase(
playerX, playerY, 5, 1,
bossX, bossY, 8, 0,
spykePositions, 6
);
lcd.setCursor(bossX, bossY); lcd.write(BOSS);
lcd.setCursor(playerX, playerY);lcd.write(CHAR_PLAYER);
unsigned long shootTime = millis() + 1000;
while (!roundOver) {
// Phase 1: autoâfire
if (tryFireProjectile(
projectileActive,
playerX, playerY,
projectileX, projectileY,
shootTime
)) {
// record start of flight
lastCrossMove = millis();
continue;
}
// Phase 2: projectile flight & hit
if (updateProjectile(
projectileX, projectileY,
projectileActive, bossDead,
bossX, bossY,
lastCrossMove
)) {
// record when boss died
hitTimestamp = millis();
continue;
}
// Phase 3: boss falling & round end
if (bossDead && handleBossFall(bossX, bossY, hitTimestamp)) {
lcd.setCursor(projectileX, projectileY);
lcd.write(' ');
roundOver = true;
continue;
}
}
delay(2000);
Speaker_Sound(melodies[S_ROUND9]);
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();
}
}
//here you have some space to add some functions Rivo or Galy
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
}
}
bool readyToMove(unsigned long &lastMove, unsigned long interval) {
if (millis() - lastMove < interval) return false;
lastMove = millis();
return true;
}
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 = 500) {
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() {
// 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;
}
}
// if (currentZone == 2 && lastX == keyX && lastY == keyY) { pour mettre une clé à la fin
// lcd.write((uint8_t)CHAR_KEY);
// restored = true;
// }
} 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];
}
}
void menu() {
Serial.println(F("Menu displayed"));
lcd.setRGB(66, 61, 61);
lcd.clear();
lcd.setCursor(0,0);
lcd.print("R + number");
lcd.setCursor(0,1);
lcd.print("to start a round");
}
void showCompletionMessage(String text) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(text);
delay(1000);
transitToRound(16, " ");
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 Round_bar_Number(int roundNum) {
uint8_t ledsOn;
switch (roundNum) {
case 1: ledsOn = 1; break;
case 2: ledsOn = 2; break;
case 3: ledsOn = 3; break;
case 4: ledsOn = 4; break;
case 5: ledsOn = 5; break;
case 6: ledsOn = 6; break;
case 7: ledsOn = 7; break;
case 8: ledsOn = 8; break;
case 9: ledsOn = 9; break;
case 10: ledsOn = 10; break;
default: ledsOn = 0; break;
}
for (uint8_t idx = 0; idx < 10; idx++) {
bool state = (idx < ledsOn);
bar.setLed(idx, state);
}
}
void Color_Generation(int theme) {
if (theme >= 0 && theme <= 9) {
int r = RGB_LED_CK[theme][0];
int g = RGB_LED_CK[theme][1];
int b = RGB_LED_CK[theme][2];
lcd.setRGB(r, g, b);
}
}
void Speaker_Sound(Melody m) {
for (int i=0;i<m.length;i++) {
int d=1000/m.durations[i];
if (m.notes[i]) tone(speakerPin,m.notes[i],d);
delay(d*1.3);
noTone(speakerPin);
}
}