run or gone!

concept:

This entire journey starst with me almost losing an eyeI was in the hospital due to an eye operation procedure that presented personal, medical, and academic circumstances I did not expect. After healing and returning home, my family and I had to go to our Emirate, Fujairah. As a result, I found myself without the Arduino kit we were given in class, but instead, a smaller one that I previously had when teaching myself coding throughout the summer. The Arduino kit did not have all of the equipment that a complete kit would offer but only included a potentiometer, a couple of wires, two buttons, a couple of resistors, and a Liquid Display Screen. Yet, rather than being demotivated and giving up, I took this as an opportunity to explore coding on the LCD!

This opened new doors allowing me to have more than one medium to display imagery. I ended up utilizing this by having the breadboard double as both a controller and a screen while the p5.js sketch is presented as a ‘game state’ indicator. Like all projects I have worked on throughout this course, I wanted to add an artistic element, so I ended up drawing all illustrations presented on p5.js to provide a sense of attraction to the user.
I wish I could have presented this during the IM showcase, as I would have loved to make it a competitive game where the highest score would win a prize and, as a result, attract more people. Nevertheless, despite the limitations and circumstances, I am happy with how the project turned out. I have attached the assets, code (including references), and demonstration video below (presenting the game from a stranger’s perspective).

code:

(code that is altered and commented is due to an Arduino update that messed around with the code. Please reach out if you have any questions)

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// HAMAD ALSHAMSI
// RUN OR DONE: FINAL PROJECT
// code
// const int buttonPin = 2; // pin for the button
// int buttonState = 0; // variable to store the button state
// void setup() {
// pinMode(buttonPin, INPUT); // set the button pin as an input
// Serial.begin(9600); // start the serial connection
// }
// void loop() {
// buttonState = digitalRead(buttonPin); // read the button state
// // if the button is pressed, send a "1" over the serial connection
// if (buttonState == HIGH) {
// Serial.println("1");
// }
// delay(100); // delay to prevent sending too many "1"s
// }
//allow the incorporation of the LCD for the project
#include <LiquidCrystal.h>
//indicate used pin variables
#define pinPress 2
#define pinPlay 1
#define pinWriteNRead 10
#define pinBrightness 12
//indicate variables for game animations used
#define runAnimation1 1
#define runAnimation2 2
#define jumpAnimation 3
#define topJumpAnimation '.'
#define bottomJumpAnimation 4
#define noObstacleAnimation ' '
#define yesObstacleAnimation 5
#define yesRightObstacleAnimation 6
#define yesLeftObstacleAnimation 7
//fix position for character
#define characterPosition 1
//define obstacle attributes
#define obstacleYSize 16
#define obstacleBlank 0
#define obstacleBottom 1
#define obstacleTop 2
//define character running attributes and poses when on floor
#define characterLocationNul 0
#define characterLocationBottom1 1
#define characterLocationBottom2 2
//define character hopping attributes and poses
#define characterLocationHop1 3
#define characterLocationHop2 4
#define characterLocationHop3 5
#define characterLocationHop4 6
#define characterLocationHop5 7
#define characterLocationHop6 8
#define characterLocationHop7 9
#define characterLocationHop8 10
//define character running attributes and poses when on obstacle
#define characterLocationRunTop1 11
#define characterLocationRunTop2 12
//LCD attributes and pixel arrangement inspired from Rees5286 on YouTube
LiquidCrystal lcd(11, 9, 6, 5, 4, 3);
static char obstaclePresentTop[obstacleYSize + 1];
static char obstaclePresentBottom[obstacleYSize + 1];
static bool pushButton = false;
//assign specific pixels to light up for corresponding poses
void drawCanvasNPixels(){
static byte canvasNPixels[] = {
//first running pose
B01100,
B01100,
B00000,
B01110,
B11100,
B01100,
B11010,
B10011,
//second running pose
B01100,
B01100,
B00000,
B01100,
B01100,
B01100,
B01100,
B01110,
//high hop
B01100,
B01100,
B00000,
B11110,
B01101,
B11111,
B10000,
B00000,
//low hop
B11110,
B01101,
B11111,
B10000,
B00000,
B00000,
B00000,
B00000,
//on ground
B11111,
B11111,
B11111,
B11111,
B11111,
B11111,
B11111,
B11111,
//right side on ground
B00011,
B00011,
B00011,
B00011,
B00011,
B00011,
B00011,
B00011,
//left side on ground
B11000,
B11000,
B11000,
B11000,
B11000,
B11000,
B11000,
B11000,
};
int i;
//code, referenced from AymaanRahman on YouTube, to skip using '0' and allow rapid character alterations
for (i = 0; i < 7; ++i) {
lcd.createChar(i + 1, &canvasNPixels[i * 8]);
}
for (i = 0; i < obstacleYSize; ++i) {
obstaclePresentTop[i] = noObstacleAnimation;
obstaclePresentBottom[i] = noObstacleAnimation;
}
}
//move obstacle
void generateObstacles(char* obstacle, byte newObstacle){
for (int i = 0; i < obstacleYSize; ++i) {
char current = obstacle[i];
char next = (i == obstacleYSize-1) ? newObstacle : obstacle[i+1];
switch (current){
case noObstacleAnimation:
obstacle[i] = (next == yesObstacleAnimation) ? yesRightObstacleAnimation : noObstacleAnimation;
break;
case yesObstacleAnimation:
obstacle[i] = (next == noObstacleAnimation) ? yesLeftObstacleAnimation : yesObstacleAnimation;
break;
case yesRightObstacleAnimation:
obstacle[i] = yesObstacleAnimation;
break;
case yesLeftObstacleAnimation:
obstacle[i] = noObstacleAnimation;
break;
}
}
}
//move character
bool characterDraw(byte position, char* obstaclePresentTop, char* obstaclePresentBottom, unsigned int score) {
bool collision = false;
char topStore = obstaclePresentTop[characterPosition];
char bottomStore = obstaclePresentBottom[characterPosition];
byte top, bottom;
switch (position) {
case characterLocationNul:
top = bottom = noObstacleAnimation;
break;
case characterLocationBottom1:
top = noObstacleAnimation;
bottom = runAnimation1;
break;
case characterLocationBottom2:
top = noObstacleAnimation;
bottom = runAnimation2;
break;
case characterLocationHop1:
case characterLocationHop8:
top = noObstacleAnimation;
bottom = jumpAnimation;
break;
case characterLocationHop2:
case characterLocationHop7:
top = topJumpAnimation;
bottom = bottomJumpAnimation;
break;
case characterLocationHop3:
case characterLocationHop4:
case characterLocationHop5:
case characterLocationHop6:
top = jumpAnimation;
bottom = noObstacleAnimation;
break;
case characterLocationRunTop1:
top = runAnimation1;
bottom = noObstacleAnimation;
break;
case characterLocationRunTop2:
top = runAnimation2;
bottom = noObstacleAnimation;
break;
}
if (top != ' ') {
obstaclePresentTop[characterPosition] = top;
collision = (topStore == noObstacleAnimation) ? false : true;
}
if (bottom != ' ') {
obstaclePresentBottom[characterPosition] = bottom;
collision |= (bottomStore == noObstacleAnimation) ? false : true;
}
byte digits = (score > 9999) ? 5 : (score > 999) ? 4 : (score > 99) ? 3 : (score > 9) ? 2 : 1;
//create canvas for game
obstaclePresentTop[obstacleYSize] = '\0';
obstaclePresentBottom[obstacleYSize] = '\0';
char temp = obstaclePresentTop[16-digits];
obstaclePresentTop[16-digits] = '\0';
lcd.setCursor(0,0);
lcd.print(obstaclePresentTop);
obstaclePresentTop[16-digits] = temp;
lcd.setCursor(0,1);
lcd.print(obstaclePresentBottom);
lcd.setCursor(16 - digits,0);
lcd.print(score);
obstaclePresentTop[characterPosition] = topStore;
obstaclePresentBottom[characterPosition] = bottomStore;
return collision;
}
//take in digital button signal
void buttonPush() {
pushButton = true;
}
//allow the button to act as an interruption to make the character hop
void setup(){
pinMode(pinWriteNRead, OUTPUT);
digitalWrite(pinWriteNRead, LOW);
pinMode(pinBrightness, OUTPUT);
digitalWrite(pinBrightness, LOW);
pinMode(pinPress, INPUT);
digitalWrite(pinPress, HIGH);
pinMode(pinPlay, OUTPUT);
digitalWrite(pinPlay, HIGH);
attachInterrupt(0/*pinPress*/, buttonPush, FALLING);
drawCanvasNPixels();
lcd.begin(16, 2);
}
//constantly check for new obstacle generation
void loop(){
static byte characterLoc = characterLocationBottom1;
static byte newObstacleType = obstacleBlank;
static byte newObstacleDuration = 1;
static bool playing = false;
static bool blink = false;
static unsigned int distance = 0;
if (!playing) {
characterDraw((blink) ? characterLocationNul : characterLoc, obstaclePresentTop, obstaclePresentBottom, distance >> 3);
if (blink) {
lcd.setCursor(0,0);
lcd.print("Press Start");
}
delay(250);
blink = !blink;
if (pushButton) {
drawCanvasNPixels();
characterLoc = characterLocationBottom1;
playing = true;
pushButton = false;
distance = 0;
}
return;
}
//constantly move obstacles towards the character
generateObstacles(obstaclePresentBottom, newObstacleType == obstacleBottom ? yesObstacleAnimation : noObstacleAnimation);
generateObstacles(obstaclePresentTop, newObstacleType == obstacleTop ? yesObstacleAnimation : noObstacleAnimation);
//create obstacle repeatedly
if (--newObstacleDuration == 0) {
if (newObstacleType == obstacleBlank) {
newObstacleType = (random(3) == 0) ? obstacleTop : obstacleBottom;
newObstacleDuration = 2 + random(10);
} else {
newObstacleType = obstacleBlank;
newObstacleDuration = 10 + random(10);
}
}
//allow character to jump if interruption senses
if (pushButton) {
if (characterLoc <= characterLocationBottom2) characterLoc = characterLocationHop1;
pushButton = false;
}
//constantly check if character is in collision, if so game ends
if (characterDraw(characterLoc, obstaclePresentTop, obstaclePresentBottom, distance >> 3)) {
playing = false;
} else {
if (characterLoc == characterLocationBottom2 || characterLoc == characterLocationHop8) {
characterLoc = characterLocationBottom1;
} else if ((characterLoc >= characterLocationHop3 && characterLoc <= characterLocationHop5) && obstaclePresentBottom[characterPosition] != noObstacleAnimation) {
characterLoc = characterLocationRunTop1;
} else if (characterLoc >= characterLocationRunTop1 && obstaclePresentBottom[characterPosition] == noObstacleAnimation) {
characterLoc = characterLocationHop5;
} else if (characterLoc == characterLocationRunTop2) {
characterLoc = characterLocationRunTop1;
} else {
++characterLoc;
}
++distance;
digitalWrite(pinPlay, obstaclePresentBottom[characterPosition + 2] == noObstacleAnimation ? HIGH : LOW);
}
delay(100);
}
// references
//inspired projects
//https://www.youtube.com/@rees5286
//https://rishanda.github.io/BSE_Template_Portfolio/
//learning how to incorporate LCD in project
//https://www.youtube.com/watch?v=EAeuxjtkumM&ab_channel=AymaanRahman
//https://www.youtube.com/watch?v=dZZynJLmTn8&ab_channel=HowToMechatronics
// HAMAD ALSHAMSI // RUN OR DONE: FINAL PROJECT // code // const int buttonPin = 2; // pin for the button // int buttonState = 0; // variable to store the button state // void setup() { // pinMode(buttonPin, INPUT); // set the button pin as an input // Serial.begin(9600); // start the serial connection // } // void loop() { // buttonState = digitalRead(buttonPin); // read the button state // // if the button is pressed, send a "1" over the serial connection // if (buttonState == HIGH) { // Serial.println("1"); // } // delay(100); // delay to prevent sending too many "1"s // } //allow the incorporation of the LCD for the project #include <LiquidCrystal.h> //indicate used pin variables #define pinPress 2 #define pinPlay 1 #define pinWriteNRead 10 #define pinBrightness 12 //indicate variables for game animations used #define runAnimation1 1 #define runAnimation2 2 #define jumpAnimation 3 #define topJumpAnimation '.' #define bottomJumpAnimation 4 #define noObstacleAnimation ' ' #define yesObstacleAnimation 5 #define yesRightObstacleAnimation 6 #define yesLeftObstacleAnimation 7 //fix position for character #define characterPosition 1 //define obstacle attributes #define obstacleYSize 16 #define obstacleBlank 0 #define obstacleBottom 1 #define obstacleTop 2 //define character running attributes and poses when on floor #define characterLocationNul 0 #define characterLocationBottom1 1 #define characterLocationBottom2 2 //define character hopping attributes and poses #define characterLocationHop1 3 #define characterLocationHop2 4 #define characterLocationHop3 5 #define characterLocationHop4 6 #define characterLocationHop5 7 #define characterLocationHop6 8 #define characterLocationHop7 9 #define characterLocationHop8 10 //define character running attributes and poses when on obstacle #define characterLocationRunTop1 11 #define characterLocationRunTop2 12 //LCD attributes and pixel arrangement inspired from Rees5286 on YouTube LiquidCrystal lcd(11, 9, 6, 5, 4, 3); static char obstaclePresentTop[obstacleYSize + 1]; static char obstaclePresentBottom[obstacleYSize + 1]; static bool pushButton = false; //assign specific pixels to light up for corresponding poses void drawCanvasNPixels(){ static byte canvasNPixels[] = { //first running pose B01100, B01100, B00000, B01110, B11100, B01100, B11010, B10011, //second running pose B01100, B01100, B00000, B01100, B01100, B01100, B01100, B01110, //high hop B01100, B01100, B00000, B11110, B01101, B11111, B10000, B00000, //low hop B11110, B01101, B11111, B10000, B00000, B00000, B00000, B00000, //on ground B11111, B11111, B11111, B11111, B11111, B11111, B11111, B11111, //right side on ground B00011, B00011, B00011, B00011, B00011, B00011, B00011, B00011, //left side on ground B11000, B11000, B11000, B11000, B11000, B11000, B11000, B11000, }; int i; //code, referenced from AymaanRahman on YouTube, to skip using '0' and allow rapid character alterations for (i = 0; i < 7; ++i) { lcd.createChar(i + 1, &canvasNPixels[i * 8]); } for (i = 0; i < obstacleYSize; ++i) { obstaclePresentTop[i] = noObstacleAnimation; obstaclePresentBottom[i] = noObstacleAnimation; } } //move obstacle void generateObstacles(char* obstacle, byte newObstacle){ for (int i = 0; i < obstacleYSize; ++i) { char current = obstacle[i]; char next = (i == obstacleYSize-1) ? newObstacle : obstacle[i+1]; switch (current){ case noObstacleAnimation: obstacle[i] = (next == yesObstacleAnimation) ? yesRightObstacleAnimation : noObstacleAnimation; break; case yesObstacleAnimation: obstacle[i] = (next == noObstacleAnimation) ? yesLeftObstacleAnimation : yesObstacleAnimation; break; case yesRightObstacleAnimation: obstacle[i] = yesObstacleAnimation; break; case yesLeftObstacleAnimation: obstacle[i] = noObstacleAnimation; break; } } } //move character bool characterDraw(byte position, char* obstaclePresentTop, char* obstaclePresentBottom, unsigned int score) { bool collision = false; char topStore = obstaclePresentTop[characterPosition]; char bottomStore = obstaclePresentBottom[characterPosition]; byte top, bottom; switch (position) { case characterLocationNul: top = bottom = noObstacleAnimation; break; case characterLocationBottom1: top = noObstacleAnimation; bottom = runAnimation1; break; case characterLocationBottom2: top = noObstacleAnimation; bottom = runAnimation2; break; case characterLocationHop1: case characterLocationHop8: top = noObstacleAnimation; bottom = jumpAnimation; break; case characterLocationHop2: case characterLocationHop7: top = topJumpAnimation; bottom = bottomJumpAnimation; break; case characterLocationHop3: case characterLocationHop4: case characterLocationHop5: case characterLocationHop6: top = jumpAnimation; bottom = noObstacleAnimation; break; case characterLocationRunTop1: top = runAnimation1; bottom = noObstacleAnimation; break; case characterLocationRunTop2: top = runAnimation2; bottom = noObstacleAnimation; break; } if (top != ' ') { obstaclePresentTop[characterPosition] = top; collision = (topStore == noObstacleAnimation) ? false : true; } if (bottom != ' ') { obstaclePresentBottom[characterPosition] = bottom; collision |= (bottomStore == noObstacleAnimation) ? false : true; } byte digits = (score > 9999) ? 5 : (score > 999) ? 4 : (score > 99) ? 3 : (score > 9) ? 2 : 1; //create canvas for game obstaclePresentTop[obstacleYSize] = '\0'; obstaclePresentBottom[obstacleYSize] = '\0'; char temp = obstaclePresentTop[16-digits]; obstaclePresentTop[16-digits] = '\0'; lcd.setCursor(0,0); lcd.print(obstaclePresentTop); obstaclePresentTop[16-digits] = temp; lcd.setCursor(0,1); lcd.print(obstaclePresentBottom); lcd.setCursor(16 - digits,0); lcd.print(score); obstaclePresentTop[characterPosition] = topStore; obstaclePresentBottom[characterPosition] = bottomStore; return collision; } //take in digital button signal void buttonPush() { pushButton = true; } //allow the button to act as an interruption to make the character hop void setup(){ pinMode(pinWriteNRead, OUTPUT); digitalWrite(pinWriteNRead, LOW); pinMode(pinBrightness, OUTPUT); digitalWrite(pinBrightness, LOW); pinMode(pinPress, INPUT); digitalWrite(pinPress, HIGH); pinMode(pinPlay, OUTPUT); digitalWrite(pinPlay, HIGH); attachInterrupt(0/*pinPress*/, buttonPush, FALLING); drawCanvasNPixels(); lcd.begin(16, 2); } //constantly check for new obstacle generation void loop(){ static byte characterLoc = characterLocationBottom1; static byte newObstacleType = obstacleBlank; static byte newObstacleDuration = 1; static bool playing = false; static bool blink = false; static unsigned int distance = 0; if (!playing) { characterDraw((blink) ? characterLocationNul : characterLoc, obstaclePresentTop, obstaclePresentBottom, distance >> 3); if (blink) { lcd.setCursor(0,0); lcd.print("Press Start"); } delay(250); blink = !blink; if (pushButton) { drawCanvasNPixels(); characterLoc = characterLocationBottom1; playing = true; pushButton = false; distance = 0; } return; } //constantly move obstacles towards the character generateObstacles(obstaclePresentBottom, newObstacleType == obstacleBottom ? yesObstacleAnimation : noObstacleAnimation); generateObstacles(obstaclePresentTop, newObstacleType == obstacleTop ? yesObstacleAnimation : noObstacleAnimation); //create obstacle repeatedly if (--newObstacleDuration == 0) { if (newObstacleType == obstacleBlank) { newObstacleType = (random(3) == 0) ? obstacleTop : obstacleBottom; newObstacleDuration = 2 + random(10); } else { newObstacleType = obstacleBlank; newObstacleDuration = 10 + random(10); } } //allow character to jump if interruption senses if (pushButton) { if (characterLoc <= characterLocationBottom2) characterLoc = characterLocationHop1; pushButton = false; } //constantly check if character is in collision, if so game ends if (characterDraw(characterLoc, obstaclePresentTop, obstaclePresentBottom, distance >> 3)) { playing = false; } else { if (characterLoc == characterLocationBottom2 || characterLoc == characterLocationHop8) { characterLoc = characterLocationBottom1; } else if ((characterLoc >= characterLocationHop3 && characterLoc <= characterLocationHop5) && obstaclePresentBottom[characterPosition] != noObstacleAnimation) { characterLoc = characterLocationRunTop1; } else if (characterLoc >= characterLocationRunTop1 && obstaclePresentBottom[characterPosition] == noObstacleAnimation) { characterLoc = characterLocationHop5; } else if (characterLoc == characterLocationRunTop2) { characterLoc = characterLocationRunTop1; } else { ++characterLoc; } ++distance; digitalWrite(pinPlay, obstaclePresentBottom[characterPosition + 2] == noObstacleAnimation ? HIGH : LOW); } delay(100); } // references //inspired projects //https://www.youtube.com/@rees5286 //https://rishanda.github.io/BSE_Template_Portfolio/ //learning how to incorporate LCD in project //https://www.youtube.com/watch?v=EAeuxjtkumM&ab_channel=AymaanRahman //https://www.youtube.com/watch?v=dZZynJLmTn8&ab_channel=HowToMechatronics
                            // HAMAD ALSHAMSI

                      // RUN OR DONE: FINAL PROJECT










                                // code







// const int buttonPin = 2; // pin for the button
// int buttonState = 0; // variable to store the button state

// void setup() {
//   pinMode(buttonPin, INPUT); // set the button pin as an input
//   Serial.begin(9600); // start the serial connection
// }

// void loop() {
//   buttonState = digitalRead(buttonPin); // read the button state

//   // if the button is pressed, send a "1" over the serial connection
//   if (buttonState == HIGH) {
//     Serial.println("1");
//   }

//   delay(100); // delay to prevent sending too many "1"s
// }



//allow the incorporation of the LCD for the project
#include <LiquidCrystal.h>

//indicate used pin variables
#define pinPress 2
#define pinPlay 1
#define pinWriteNRead 10
#define pinBrightness 12

//indicate variables for game animations used
#define runAnimation1 1
#define runAnimation2 2
#define jumpAnimation 3
#define topJumpAnimation '.'
#define bottomJumpAnimation 4
#define noObstacleAnimation ' '
#define yesObstacleAnimation 5
#define yesRightObstacleAnimation 6
#define yesLeftObstacleAnimation 7

//fix position for character
#define characterPosition 1
//define obstacle attributes
#define obstacleYSize 16
#define obstacleBlank 0
#define obstacleBottom 1
#define obstacleTop 2
//define character running attributes and poses when on floor
#define characterLocationNul 0
#define characterLocationBottom1 1
#define characterLocationBottom2 2
//define character hopping attributes and poses
#define characterLocationHop1 3
#define characterLocationHop2 4
#define characterLocationHop3 5
#define characterLocationHop4 6
#define characterLocationHop5 7
#define characterLocationHop6 8
#define characterLocationHop7 9
#define characterLocationHop8 10
//define character running attributes and poses when on obstacle
#define characterLocationRunTop1 11
#define characterLocationRunTop2 12

//LCD attributes and pixel arrangement inspired from Rees5286 on YouTube
LiquidCrystal lcd(11, 9, 6, 5, 4, 3);
static char obstaclePresentTop[obstacleYSize + 1];
static char obstaclePresentBottom[obstacleYSize + 1];
static bool pushButton = false;

//assign specific pixels to light up for corresponding poses
void drawCanvasNPixels(){
  static byte canvasNPixels[] = {
    //first running pose
    B01100,
    B01100,
    B00000,
    B01110,
    B11100,
    B01100,
    B11010,
    B10011,
    //second running pose
    B01100,
    B01100,
    B00000,
    B01100,
    B01100,
    B01100,
    B01100,
    B01110,
    //high hop
    B01100,
    B01100,
    B00000,
    B11110,
    B01101,
    B11111,
    B10000,
    B00000,
    //low hop
    B11110,
    B01101,
    B11111,
    B10000,
    B00000,
    B00000,
    B00000,
    B00000,
    //on ground
    B11111,
    B11111,
    B11111,
    B11111,
    B11111,
    B11111,
    B11111,
    B11111,
    //right side on ground
    B00011,
    B00011,
    B00011,
    B00011,
    B00011,
    B00011,
    B00011,
    B00011,
    //left side on ground
    B11000,
    B11000,
    B11000,
    B11000,
    B11000,
    B11000,
    B11000,
    B11000,
  };

  int i;
  //code, referenced from AymaanRahman on YouTube, to skip using '0' and allow rapid character alterations
  for (i = 0; i < 7; ++i) {
      lcd.createChar(i + 1, &canvasNPixels[i * 8]);
  }
  for (i = 0; i < obstacleYSize; ++i) {
    obstaclePresentTop[i] = noObstacleAnimation;
    obstaclePresentBottom[i] = noObstacleAnimation;
  }
}

//move obstacle
void generateObstacles(char* obstacle, byte newObstacle){
  for (int i = 0; i < obstacleYSize; ++i) {
    char current = obstacle[i];
    char next = (i == obstacleYSize-1) ? newObstacle : obstacle[i+1];
    switch (current){
      case noObstacleAnimation:
        obstacle[i] = (next == yesObstacleAnimation) ? yesRightObstacleAnimation : noObstacleAnimation;
        break;
      case yesObstacleAnimation:
        obstacle[i] = (next == noObstacleAnimation) ? yesLeftObstacleAnimation : yesObstacleAnimation;
        break;
      case yesRightObstacleAnimation:
        obstacle[i] = yesObstacleAnimation;
        break;
      case yesLeftObstacleAnimation:
        obstacle[i] = noObstacleAnimation;
        break;
    }
  }
}

//move character
bool characterDraw(byte position, char* obstaclePresentTop, char* obstaclePresentBottom, unsigned int score) {
  bool collision = false;
  char topStore = obstaclePresentTop[characterPosition];
  char bottomStore = obstaclePresentBottom[characterPosition];
  byte top, bottom;
  switch (position) {
    case characterLocationNul:
      top = bottom = noObstacleAnimation;
      break;
    case characterLocationBottom1:
      top = noObstacleAnimation;
      bottom = runAnimation1;
      break;
    case characterLocationBottom2:
      top = noObstacleAnimation;
      bottom = runAnimation2;
      break;
    case characterLocationHop1:
    case characterLocationHop8:
      top = noObstacleAnimation;
      bottom = jumpAnimation;
      break;
    case characterLocationHop2:
    case characterLocationHop7:
      top = topJumpAnimation;
      bottom = bottomJumpAnimation;
      break;
    case characterLocationHop3:
    case characterLocationHop4:
    case characterLocationHop5:
    case characterLocationHop6:
      top = jumpAnimation;
      bottom = noObstacleAnimation;
      break;
    case characterLocationRunTop1:
      top = runAnimation1;
      bottom = noObstacleAnimation;
      break;
    case characterLocationRunTop2:
      top = runAnimation2;
      bottom = noObstacleAnimation;
      break;
  }
  if (top != ' ') {
    obstaclePresentTop[characterPosition] = top;
    collision = (topStore == noObstacleAnimation) ? false : true;
  }
  if (bottom != ' ') {
    obstaclePresentBottom[characterPosition] = bottom;
    collision |= (bottomStore == noObstacleAnimation) ? false : true;
  }

  byte digits = (score > 9999) ? 5 : (score > 999) ? 4 : (score > 99) ? 3 : (score > 9) ? 2 : 1;

  //create canvas for game
  obstaclePresentTop[obstacleYSize] = '\0';
  obstaclePresentBottom[obstacleYSize] = '\0';
  char temp = obstaclePresentTop[16-digits];
  obstaclePresentTop[16-digits] = '\0';
  lcd.setCursor(0,0);
  lcd.print(obstaclePresentTop);
  obstaclePresentTop[16-digits] = temp;
  lcd.setCursor(0,1);
  lcd.print(obstaclePresentBottom);

  lcd.setCursor(16 - digits,0);
  lcd.print(score);

  obstaclePresentTop[characterPosition] = topStore;
  obstaclePresentBottom[characterPosition] = bottomStore;
  return collision;
}

//take in digital button signal
void buttonPush() {
  pushButton = true;
}

//allow the button to act as an interruption to make the character hop
void setup(){
  pinMode(pinWriteNRead, OUTPUT);
  digitalWrite(pinWriteNRead, LOW);
  pinMode(pinBrightness, OUTPUT);
  digitalWrite(pinBrightness, LOW);
  pinMode(pinPress, INPUT);
  digitalWrite(pinPress, HIGH);
  pinMode(pinPlay, OUTPUT);
  digitalWrite(pinPlay, HIGH);
  attachInterrupt(0/*pinPress*/, buttonPush, FALLING);
  drawCanvasNPixels();
  lcd.begin(16, 2);
}

//constantly check for new obstacle generation
void loop(){
  static byte characterLoc = characterLocationBottom1;
  static byte newObstacleType = obstacleBlank;
  static byte newObstacleDuration = 1;
  static bool playing = false;
  static bool blink = false;
  static unsigned int distance = 0;

  if (!playing) {
    characterDraw((blink) ? characterLocationNul : characterLoc, obstaclePresentTop, obstaclePresentBottom, distance >> 3);
    if (blink) {
      lcd.setCursor(0,0);
      lcd.print("Press Start");
    }
    delay(250);
    blink = !blink;
    if (pushButton) {
      drawCanvasNPixels();
      characterLoc = characterLocationBottom1;
      playing = true;
      pushButton = false;
      distance = 0;
    }
    return;
  }

  //constantly move obstacles towards the character
  generateObstacles(obstaclePresentBottom, newObstacleType == obstacleBottom ? yesObstacleAnimation : noObstacleAnimation);
  generateObstacles(obstaclePresentTop, newObstacleType == obstacleTop ? yesObstacleAnimation : noObstacleAnimation);

  //create obstacle repeatedly
  if (--newObstacleDuration == 0) {
    if (newObstacleType == obstacleBlank) {
      newObstacleType = (random(3) == 0) ? obstacleTop : obstacleBottom;
      newObstacleDuration = 2 + random(10);
    } else {
      newObstacleType = obstacleBlank;
      newObstacleDuration = 10 + random(10);
    }
  }

  //allow character to jump if interruption senses
  if (pushButton) {
    if (characterLoc <= characterLocationBottom2) characterLoc = characterLocationHop1;
    pushButton = false;
  }

//constantly check if character is in collision, if so game ends
  if (characterDraw(characterLoc, obstaclePresentTop, obstaclePresentBottom, distance >> 3)) {
    playing = false;
  } else {
    if (characterLoc == characterLocationBottom2 || characterLoc == characterLocationHop8) {
      characterLoc = characterLocationBottom1;
    } else if ((characterLoc >= characterLocationHop3 && characterLoc <= characterLocationHop5) && obstaclePresentBottom[characterPosition] != noObstacleAnimation) {
      characterLoc = characterLocationRunTop1;
    } else if (characterLoc >= characterLocationRunTop1 && obstaclePresentBottom[characterPosition] == noObstacleAnimation) {
      characterLoc = characterLocationHop5;
    } else if (characterLoc == characterLocationRunTop2) {
      characterLoc = characterLocationRunTop1;
    } else {
      ++characterLoc;
    }
    ++distance;

    digitalWrite(pinPlay, obstaclePresentBottom[characterPosition + 2] == noObstacleAnimation ? HIGH : LOW);
  }
  delay(100);
}




                                      // references

//inspired projects
//https://www.youtube.com/@rees5286
//https://rishanda.github.io/BSE_Template_Portfolio/

//learning how to incorporate LCD in project
//https://www.youtube.com/watch?v=EAeuxjtkumM&ab_channel=AymaanRahman
//https://www.youtube.com/watch?v=dZZynJLmTn8&ab_channel=HowToMechatronics

Leave a Reply