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)

                            // 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