Cant implement Seed RGB LED 8x8 matrix

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 :slight_smile:

//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);
  }
}


Little update. The code works with matrix.displayEmoji(1, 5000, true);, wherever i put it. It looks like the problem comes from displayFrames.

Sorry... ignore all that... my computer was choking as I was "not finding" things... and then it crashed.

Back on line... with matrix.displayFrames(angry, 2000, true, 1); in the compile, here is the report on a Nano...

Sketch uses 22176 bytes (72%) of program storage space. Maximum is 30720 bytes.
Global variables use 2018 bytes (98%) of dynamic memory, leaving 30 bytes for local variables. Maximum is 2048 bytes.

It is possible memory runs out during run-time.

For which board was this written? Perhaps the Mega2560 or maybe ESP?

When you comment-out the matrix.displayFrames(); function, that allows enough memory to start, but not "work."

It is written for an arduino uno to which i added a baseshield.

I think I got what you mean. So i should simply try to find a way to optimize memory usage?

I changed some of my variables using PROGMEM

const int PROGMEM 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};
const int PROGMEM marioDurations[] = {4,4,4,4,4,4,8,8,4,4,4,4,4,4,4,4,8,2};
const int PROGMEM end1Notes[]     = {NOTE_C4,NOTE_E4,NOTE_G4,NOTE_C5,NOTE_G4,NOTE_E4,NOTE_C4,0,NOTE_G4,NOTE_C5};
const int PROGMEM end1Durations[] = {4,4,4,2,4,4,2,4,4,2};

const int PROGMEM end3Notes[]     = {NOTE_E4,NOTE_G4,NOTE_C5,NOTE_E5,NOTE_G5,NOTE_E5,NOTE_C5,NOTE_G4,NOTE_E4};
const int PROGMEM end3Durations[] = {4,4,4,4,4,4,4,4,2};

const int PROGMEM end4Notes[]     = {NOTE_G4,NOTE_E4,NOTE_A4,NOTE_F4,NOTE_D4,NOTE_B3,NOTE_G3,0,NOTE_C4,NOTE_E4};
const int PROGMEM end4Durations[] = {4,4,4,4,4,4,4,2,4,2};

const int PROGMEM end5Notes[]     = {NOTE_F4,NOTE_A4,NOTE_D5,NOTE_C5,NOTE_B4,NOTE_G4,NOTE_E4,NOTE_C4,NOTE_D4,NOTE_F4};
const int PROGMEM end5Durations[] = {4,4,4,4,4,4,4,4,4,2};

const int PROGMEM end6Notes[]     = {NOTE_C4,NOTE_E4,NOTE_G4,NOTE_B4,NOTE_G4,NOTE_E4,NOTE_C4,0,NOTE_G4,NOTE_C5};
const int PROGMEM end6Durations[] = {4,4,4,4,4,4,2,2,4,2};

const int PROGMEM end7Notes[]     = {NOTE_E4,NOTE_A4,NOTE_C5,NOTE_B4,NOTE_G4,NOTE_E4,NOTE_C4,NOTE_A3,NOTE_E4,NOTE_C4};
const int PROGMEM end7Durations[] = {4,4,4,4,4,4,4,4,4,2};

const int PROGMEM end8Notes[]     = {NOTE_C4,NOTE_D4,NOTE_E4,NOTE_F4,NOTE_G4,NOTE_A4,NOTE_B4,NOTE_C5,NOTE_G4,NOTE_E4};
const int PROGMEM end8Durations[] = {4,4,4,4,4,4,4,2,4,2};

const int PROGMEM end9Notes[]     = {NOTE_E4,NOTE_G4,NOTE_B4,NOTE_E5,NOTE_D5,NOTE_B4,NOTE_G4,NOTE_E4,NOTE_G4,NOTE_B4};
const int PROGMEM end9Durations[] = {4,4,4,2,4,4,2,4,4,2};

const int PROGMEM end10Notes[]     = {NOTE_G4,NOTE_B4,NOTE_D5,NOTE_G5,NOTE_D5,NOTE_B4,NOTE_G4,NOTE_D4,NOTE_G4,NOTE_B4};
const int PROGMEM end10Durations[] = {4,4,4,2,4,4,2,4,4,2};

it saved like 15% of the memory. Thank you

1 Like

How did you use the notes in the code?
You can probably put all the shapes in PROGMEM, too, but only if that improves performance.