// Initial Code for Maze Run
// Written for EDD 111 2025
// Written By: Christopher Shortt
// Edited by: Koen Gieskes
// "All_Star.ino" tone sequences sourced from:
// AnonymousAlly (2021). Arduino-Music-Codes: All_Star.ino
// GitHub repository: https://github.com/AnonymousAlly/Arduino-Music-Codes/blob/master/All_Star.ino
// Adapted by: Joshua Hu
#include <Wire.h>
#include <LiquidCrystal.h>
LiquidCrystal lcd(12,11,5,4,3,2);
int rightpin = 10; // right goes into 10
int leftpin = 9; // left goes into 9
int straightpin = 8; // up goes into 8
int backpin = 7; // down goes into 7
int piezoPin = 13; // speaker goes into 13
// ===================================
byte bar0[8] = { B00000,B00000,B00000,B00000,B00000,B00000,B00000,B00000 };
byte bar1[8] = { B10000,B10000,B10000,B10000,B10000,B10000,B10000,B10000 };
byte bar2[8] = { B11000,B11000,B11000,B11000,B11000,B11000,B11000,B11000 };
byte bar3[8] = { B11100,B11100,B11100,B11100,B11100,B11100,B11100,B11100 };
byte bar4[8] = { B11110,B11110,B11110,B11110,B11110,B11110,B11110,B11110 };
byte bar5[8] = { B11111,B11111,B11111,B11111,B11111,B11111,B11111,B11111 };
byte heartFull[8] = { // Full Heart Character
B00000,
B01010,
B11111,
B11111,
B11111,
B01110,
B00100,
B00000
};
byte heartEmpty[8] = { // Lost Heart Character
B00000,
B01010,
B10001,
B10001,
B10001,
B01010,
B00100,
B00000
};
//three different mazes
int maze1[20][19] = {
{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
{1,1,0,1,1,0,1,0,1,1,1,1,0,0,0,0,1,0,2},
{1,1,0,1,0,0,1,0,1,0,1,1,0,1,1,0,1,0,1},
{1,1,0,1,0,1,1,0,1,0,0,1,0,0,1,0,1,0,1},
{1,0,0,0,0,1,0,0,1,0,1,1,0,1,1,0,1,0,1},
{1,1,1,0,1,1,0,1,1,0,1,1,0,0,1,0,1,0,1},
{1,1,0,0,0,0,0,0,0,0,0,0,0,1,1,0,1,0,1},
{1,1,0,1,1,1,1,1,1,1,0,1,1,1,0,0,1,0,1},
{1,1,0,0,0,0,0,0,1,0,0,1,0,0,0,1,1,0,1},
{1,1,1,1,1,1,1,0,1,0,1,1,0,1,1,1,1,0,1},
{1,0,0,0,0,0,1,0,1,1,1,0,0,1,1,0,0,0,1},
{1,0,1,1,1,0,1,0,0,0,1,0,1,1,1,0,1,1,1},
{1,0,1,0,0,0,1,1,1,0,1,0,1,1,1,0,1,0,1},
{1,0,1,1,1,0,0,0,1,0,1,0,0,0,0,0,1,0,1},
{1,0,0,0,1,0,1,1,1,4,1,1,1,0,1,1,1,0,1},
{1,1,1,0,1,0,1,1,1,0,1,0,0,0,1,0,0,0,1},
{1,1,1,0,1,0,1,0,0,0,1,1,0,1,1,0,1,0,1},
{1,1,1,0,1,0,0,0,1,0,1,1,0,0,0,0,1,0,1},
{3,0,0,0,1,1,0,1,1,0,1,1,0,1,1,0,1,0,1},
{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}
};
//maze positioning array
int a,b,c,d;
int arr[] =
{a,b,c,d};
int Position = 999; // placing user away from exit
int i=18; // row user starts in
int j =1; // column user starts in
int lives = 5; // amount of lives user has
unsigned long startTime = 0; // when the game begins
unsigned long elapsedTime = 0; // how many seconds has past once game begun
int lastArr[4] = {-1, -1, -1, -1}; // checks to see if anything has changed, if not don't draw anything
// these variables make sure one press = one move
int lastRight = 0;
int lastLeft = 0;
int lastUp = 0;
int lastDown = 0;
// prevents one click to make two moves
unsigned long debounceDelay = 120;
unsigned long lastPressTime = 0;
// start of game make sure the key is set to false
bool keyFiona = false;
int allStarFreq[] = { // All Star Frequency Taken from other coder
370, 554, 466, 466, 415,
370, 370, 494, 466, 466,
415, 415, 370, 370, 554,
466, 466, 415, 370, 370,
311, 277, 370, 370, 554,
466, 466, 415, 415, 369,
369, 494, 466, 466
};
int allStarDuration[] = { // All Star Duration Taken from other coder
600, 300, 300, 600, 300,
300, 300, 600, 300, 300,
300, 300, 600, 300, 300,
300, 600, 300, 300, 300,
600, 900, 300, 300, 300,
300, 300, 300, 300, 300,
300, 600, 300, 300
};
int allStarLength = 34; // number of notes / freq
int startNote = 0; // starting from index 0 or starting from first note
unsigned long allStarLastTime = 0; // allows speaker to play while intro runs
void setup()
{
// button initialization
pinMode(straightpin, INPUT);
pinMode(leftpin, INPUT);
pinMode(rightpin, INPUT);
pinMode(backpin, INPUT);
pinMode(piezoPin, OUTPUT);
// LCD initialization
lcd.begin(20,4);
// Progress Bar initialization
lcd.createChar(0, bar0);
lcd.createChar(1, bar1);
lcd.createChar(2, bar2);
lcd.createChar(3, bar3);
lcd.createChar(4, bar4);
lcd.createChar(5, bar5);
// Lives initialization
lcd.createChar(6,heartFull);
lcd.createChar(7,heartEmpty);
}
void drawX () {
lcd.setCursor(9,1);
lcd.print("X");
}
void drawO () {
lcd.setCursor(9,1);
lcd.print("O");
}
void drawProgressBar(int level1, int level2, int level3) { // allows for 3 rectangles for the progress bar
int baseCol = 17; // putting the bar on top right
lcd.setCursor(baseCol + 0, 0); lcd.write(level1);
lcd.setCursor(baseCol + 1, 0); lcd.write(level2);
lcd.setCursor(baseCol + 2, 0); lcd.write(level3);
}
void updateProgressBar(int dist) {
const int maxDist = 25; // distance to exit (its a consistent number that never changes)
int progress = maxDist - dist; // distance from exit - current distance. As you gain a higher number you gain progress
if (progress < 0) progress = 0; // making sure progress is within the range, if it goes out of it it just has 0 progress
if (progress > maxDist) progress = maxDist; // anything above maxdistance progress bar is filled
int units = map(progress, 0, maxDist, 0, 15); // turning the distance into a unit for the progress bar
int l1 = 0, l2 = 0, l3 = 0; // each levels filled boxes
// if/else to fill the box properly
if (units <= 5) {
l1 = units;
} else if (units <= 10) {
l1 = 5;
l2 = units - 5;
} else {
l1 = 5;
l2 = 5;
l3 = units - 10;
}
drawProgressBar(l1, l2, l3);
}
void intro(){
lcd.clear();
lcd.setCursor(2,1);
lcd.print("You are Shrek... ");
waitIntro(2200);
lcd.clear();
lcd.setCursor(0,0);
lcd.print(" You are");
lcd.setCursor(0,1);
lcd.print(" Entering The");
lcd.setCursor(0,2);
lcd.print(" Dragon's Keep...");
waitIntro(2200);
lcd.clear();
lcd.setCursor(1,1);
lcd.print(" Go in and save");
lcd.setCursor(6,2);
lcd.print(" Fiona!");
waitIntro(2200);
lcd.clear();
lcd.setCursor(4,1);
lcd.print(" Press up");
lcd.setCursor(6,2);
lcd.print("To begin");
while(digitalRead(straightpin) == 0){ // checks to see if button is clicked. Loops until button is pressed
playAllStar();
}
noTone(piezoPin);
delay(150);
lcd.clear();
}
void findFiona(){
keyFiona = true;
lcd.clear();
lcd.setCursor(3,1);
lcd.print("You found Fiona!");
lcd.setCursor(4,2);
lcd.print("Now escape!");
tone(piezoPin, 600, 200);
delay(200);
tone(piezoPin, 800, 200);
delay(200);
tone(piezoPin, 1000, 200);
delay(2000);
lcd.clear();
}
// after WALL animation, need to reset screen
void fixWall(){
lcd.clear();
showLives();
mazeposition(arr);
int dist = distance (i,j);
updateProgressBar(dist);
showTimer();
}
void playAllStar(){
unsigned long now = millis();
if(now - allStarLastTime >= allStarDuration[startNote]){
//not = time in millis
//allStarLastTime = time that the last note played
//durration = how long note is played for
// now - lasttime = how long current note is played for
// now we take that number and compare it to the duration and once its equivalent the next note will be played
int frq = allStarFreq[startNote]; // pulls freq from the stored array
if (frq == 0){
noTone(piezoPin);
} else{
tone(piezoPin, frq, allStarDuration[startNote] - 20);
}
//reset
allStarLastTime = now;
startNote++; // move to next note
// loop back to beginning
if(startNote >= allStarLength){
startNote = 0;
}
}
}
void waitIntro(unsigned long ms){
unsigned long start = millis();
while(millis() - start < ms){ // this is looping until the amount of time passed "start" is less then the waittime
playAllStar(); // while its looping it will play ALl Star
}
}
void effectWin(){
lcd.clear(); // for win screen clear everything
lcd.setCursor(2,1);
lcd.print("YOU SAVED FIONA!");
for (int k = 0; k < 40; k++ ){ // loop runs 40 time and will add 40 star one at a time
int x = random(0,20);
int y = random(0,4);
if(!(y == 1 && x >= 2 && x <= 18)){ // don't let * be printed onto the text
// if its not in the textbox then based off the random x y go to it and print *
lcd.setCursor(x,y);
lcd.print("*");
}
delay(40); // waits 40 ms each print
}
}
//===================================
//Loop Function
void loop() {
// Start screen
lcd.setCursor(0,0); lcd.print(" Get Outta My Maze! ");
lcd.setCursor(0,1); lcd.print(" Press Up ");
lcd.setCursor(0,2); lcd.print(" To Start ");
if (digitalRead(straightpin) == 1) {
lcd.clear();
while (digitalRead(straightpin) == 1);
waitIntro(150);
intro();
// Reset game state
lives = 5;
keyFiona = false;
showLives();
i = 18;
j = 1;
Position = 999;
startTime = millis();
for (int z = 0; z < 4; z++){
lastArr[z] = -1;
}
while ((Position != 1 || keyFiona == false) && lives > 0) {
// Read maze surroundings
readvals(i, j);
// Draw X/O view
bool changed = false;
for (int k = 0; k < 4; k++) {
if (arr[k] != lastArr[k]){
changed = true;
break;
}
}
if (changed) {
mazeposition(arr);
for (int k = 0; k < 4; k++) lastArr[k] = arr[k];
}
showLives();
// Progress bar (distance-based)
int dist = distance(i, j);
updateProgressBar(dist);
showTimer ();
// Read movement buttons
int rval = digitalRead(rightpin);
int lval = digitalRead(leftpin);
int upval = digitalRead(straightpin);
int dval = digitalRead(backpin);
unsigned long now = millis();
// Movement – count steps only if player actually moves
if (rval == 1 && lastRight == 0 && now - lastPressTime > debounceDelay) {
j = moveright(i, j);
lastPressTime = now;
}
lastRight = rval;
if (lval == 1 && lastLeft == 0 && now - lastPressTime > debounceDelay) {
j = moveleft(i, j);
lastPressTime = now;
}
lastLeft = lval;
if (upval == 1 && lastUp == 0 && now - lastPressTime > debounceDelay) {
i = moveup(i, j);
lastPressTime = now;
}
lastUp = upval;
if (dval == 1 && lastDown == 0 && now - lastPressTime > debounceDelay) {
i = movedown(i, j);
lastPressTime = now;
}
lastDown = dval;
// Recalculate distance for exit condition
Position = sqrt((i)*(i) + (18 - j)*(18 - j));
}
// End screens
lcd.clear();
if (lives <= 0) {
lcd.setCursor(4,1); lcd.print(" YOU FAILED");
lcd.setCursor(2,2); lcd.print(" Up to Restart!");
while (digitalRead(straightpin) == 0);
lcd.clear();
} else {
lcd.clear();
lcd.setCursor(4,1); lcd.print("Great Job!");
elapsedTime = (millis() - startTime) / 1000;
int minutes = elapsedTime / 60;
int seconds = elapsedTime % 60;
lcd.setCursor(1,2);
lcd.print("Your Time: ");
if (minutes < 10) lcd.print("0");
lcd.print(minutes);
lcd.print(":");
if (seconds < 10) lcd.print("0");
lcd.print(seconds);
delay(2500);
effectWin();
delay(3000);
lcd.clear();
}
}
}
void showTimer (){
elapsedTime = (millis() - startTime) / 1000;
int minutes = elapsedTime / 60;
int seconds = elapsedTime % 60;
lcd.setCursor(14,3);
if (minutes < 10) lcd.print("0");
lcd.print(minutes);
lcd.print(":");
if (seconds < 10) lcd.print("0");
lcd.print(seconds);
}
void wallAnimation() {
lcd.clear();
lcd.setCursor(6,1);
lcd.print(" W A L L !");
tone(piezoPin, 120, 150);
delay(200);
for (int shake = 0; shake < 3; shake++) {
lcd.command(0x18);
delay(60);
lcd.command(0x1C);
delay(60);
}
}
void clearTopRow(){
lcd.setCursor(0,1);
lcd.print(" ");
}
//===================================================
//Local Functions
//Person Function
//Direction Functions
void RightTurn(byte i, byte j){
j=j+1;
}
void mazeposition(int* abcd) {
for (int r=1; r<=3; r++) {
lcd.setCursor(8,r);
lcd.print(" ");
}
// abcd = {top, right, bottom, left}
// North (top)
lcd.setCursor(9,1);
if (arr[0] == 1) lcd.print("X");
else lcd.print("O");
// West (left)
lcd.setCursor(8,2);
if (arr[3] == 1) lcd.print("X");
else lcd.print("O");
// East (right)
lcd.setCursor(10,2);
if (arr[1] == 1) lcd.print("X");
else lcd.print("O");
// South (bottom)
lcd.setCursor(9,3);
if (arr[2] == 1) lcd.print("X");
else lcd.print("O");
}
int* readvals( int i, int j){
arr[0]=maze1[i-1][j];
arr[1]=maze1[i][j+1];
arr[2]=maze1[i+1][j];
arr[3]=maze1[i][j-1];
return arr;
}
int moveright(int ii, int jj){
if (maze1[ii][jj+1] == 0 || maze1[ii][jj+1] == 3 || maze1[ii][jj+1] == 4 || maze1[ii][jj+1] == 2) {
if (maze1[ii][jj+1] == 4) findFiona();
jj = jj + 1;
checkExit(ii,jj);
return jj;
}
lives--;
wallAnimation();
fixWall();
return jj;
}
int moveleft(int ii, int jj){
if (maze1[ii][jj-1] == 0 || maze1[ii][jj-1] == 3 || maze1[ii][jj-1] == 4 || maze1[ii][jj-1] == 2) {
if(maze1[ii][jj-1] == 4) findFiona();
jj = jj - 1;
checkExit(ii,jj);
return jj;
}
lives--;
wallAnimation();
fixWall();
return jj;
}
int moveup(int ii, int jj){
if (maze1[ii-1][jj] == 0 || maze1[ii-1][jj] == 3 || maze1[ii-1][jj] == 4 || maze1[ii-1][jj] == 2) {
if (maze1[ii-1][jj] == 4) findFiona();
ii = ii - 1;
checkExit(ii,jj);
return ii;
}
lives--;
wallAnimation();
fixWall();
return ii;
}
int movedown(int ii, int jj){
if (maze1[ii+1][jj] == 0 || maze1[ii+1][jj] == 3 || maze1[ii+1][jj] == 4 || maze1[ii+1][jj] == 2){
if (maze1[ii+1][jj] == 4) findFiona();
ii = ii + 1;
checkExit(ii,jj);
return ii;
}
lives--;
wallAnimation();
fixWall();
return ii;
}
void checkExit(int ii, int jj) {
if (maze1[ii][jj] == 2 && keyFiona == true) {
Position = 1;
}
}
int distance(int i, int j){
int dist = sqrt((i)*(i) + (18-j)*(18-j));
return dist;
}
void showLives() {
lcd.setCursor(0,0);
for (int h = 0; h < 5; h++) {
if (h < lives)
lcd.write(6); // full heart
else
lcd.write(7); // empty heartf
}
}