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