#include <LiquidCrystal.h>
#include <Keypad.h>
// HARDWARE
LiquidCrystal lcd(
12, // Register Selector (RS)
13, // Enable (E)
2, 3, 4, 5, 6, 7, 8, 9 // Data Lanes (D0 - D7), 0 & 1 are Serial
);
const int ROWS = 4, COLUMNS = 4;
char keys[ROWS][COLUMNS] = {
{'1','2','3','A'},
{'4','5','6','B'},
{'7','8','9','C'},
{'*','0','#','D'}
};
byte pin_rows[] = {51,49,47,45};
byte pin_columns[] = {43,41,39,37};
Keypad keypad = Keypad( makeKeymap(keys), pin_rows, pin_columns, ROWS, COLUMNS );
// TIMING CONSTANTS
const int UPDATE_DELAY = 50;
const int JUMP_TIME = 250;
const int JUMP_DEBOUNCE = 50;
// GLYPH PALETTE
const byte PLAYER_WALK_CONTACT = 0;
const byte PLAYER_WALK_RECOIL = 1;
const byte PLAYER_WALK_PASSING = 2;
const byte PLAYER_WALK_HIGH_POINT = 3;
const byte PLAYER_WALK_ANIM_GLYPHS[4][8] = {
{
B01110,
B01010,
B01110,
B00100,
B00100,
B00100,
B01010,
B10001,
},
{
B00000,
B01110,
B01010,
B01110,
B00100,
B00100,
B01010,
B10100,
},
{
B01110,
B01010,
B01110,
B00100,
B00100,
B00100,
B00110,
B01000,
},
{
B01110,
B01010,
B01110,
B00100,
B00100,
B00100,
B01010,
B10001,
},
};
const byte CACTUS = 4;
const byte CACTUS_GLYPH[8] = {
B00000,
B00100,
B00100,
B00101,
B10111,
B11100,
B00100,
B00100,
};
const byte RIGHT_ARROW = 5;
const byte RIGHT_ARROW_GLYPH[8] = {
B00000,
B00100,
B00110,
B11111,
B00110,
B00100,
B00000,
B00000,
};
// ANIMATIONS
int PLAYER_WALK_ANIM_FRAME_DELAY = 100;
const int PLAYER_WALK_ANIM_FRAME_COUNT = 4;
const byte PLAYER_WALK_ANIM_FRAMES[] = {
PLAYER_WALK_CONTACT,
PLAYER_WALK_RECOIL,
PLAYER_WALK_PASSING,
PLAYER_WALK_HIGH_POINT,
};
// ANIMATION UTILITIES
void progressAnimation(int* currentFrame, int frameCount, int* currentTime, int frameTime) {
if (*currentTime >= frameTime) {
*currentTime -= frameTime;
(*currentFrame)++;
if (*currentFrame >= frameCount) *currentFrame = 0;
}
}
// ANIMATION PROGRESSORS
int playerWalkAnim = 0;
// TIME PROGRESSORS
int playerWalkAnimTimer = 0;
int cactusMovementTimer = 0;
int cactusSpawnTimer = 0;
int jumpTime = JUMP_TIME + JUMP_DEBOUNCE;
// CACTI
boolean starting = true;
const int CACTI_START_TIME = 2000;
const int CACTI_MOVEMENT_DELAY = 100;
const int CACTI_SPAWN_INTERVAL = 1000;
const int MAX_CACTI = 8;
const int CACTI_SPAWN_SEPERATION_MIN = 1;
const int CACTI_SPAWN_SEPERATION_MAX = 10;
int cactiPositions[MAX_CACTI] = { -1, -1, -1, -1, -1, -1, -1, -1 };
// TIME
int time = millis();
// GAME STATE
int score = 0;
boolean jumping = false;
boolean isHighscore = false;
int highscore = 0;
boolean gameOver = false;
void setup() {
// Initialize Serial for debugging.
Serial.begin(9600);
// Initialize our display with 16 columns and 2 rows.
lcd.begin(16, 2);
// Register our custom glyphs.
lcd.createChar(PLAYER_WALK_CONTACT, PLAYER_WALK_ANIM_GLYPHS[0]);
lcd.createChar(PLAYER_WALK_RECOIL, PLAYER_WALK_ANIM_GLYPHS[1]);
lcd.createChar(PLAYER_WALK_PASSING, PLAYER_WALK_ANIM_GLYPHS[2]);
lcd.createChar(PLAYER_WALK_HIGH_POINT, PLAYER_WALK_ANIM_GLYPHS[3]);
lcd.createChar(CACTUS, CACTUS_GLYPH);
lcd.createChar(RIGHT_ARROW, RIGHT_ARROW_GLYPH);
}
void loop() {
processInputs();
if (gameOver) {
lcd.setCursor(0, 0);
lcd.print("Game Over ");
lcd.write(CACTUS);
lcd.setCursor(0, 1);
String scoreString = String(score);
lcd.print("Score: " + scoreString);
if (isHighscore) lcd.print("!");
lcd.setCursor(14, 0);
lcd.print("A");
lcd.write(RIGHT_ARROW);
} else {
redraw();
time += UPDATE_DELAY;
playerWalkAnimTimer += UPDATE_DELAY;
cactusMovementTimer += UPDATE_DELAY;
cactusSpawnTimer += UPDATE_DELAY;
if (jumping) jumpTime += UPDATE_DELAY;
score += UPDATE_DELAY / 10;
updateGameState();
}
delay(UPDATE_DELAY);
}
void gameLoop() {
redraw();
time += UPDATE_DELAY;
playerWalkAnimTimer += UPDATE_DELAY;
cactusMovementTimer += UPDATE_DELAY;
cactusSpawnTimer += UPDATE_DELAY;
if (jumping) jumpTime += UPDATE_DELAY;
score += UPDATE_DELAY / 10;
processInputs();
updateGameState();
}
void redraw() {
// Clear the opposite player position, before drawing cacti.
lcd.setCursor(0, jumping ? 1 : 0);
lcd.print(" ");
drawCacti();
drawPlayer();
}
void drawPlayer() {
lcd.setCursor(0, jumping ? 0 : 1);
lcd.write(PLAYER_WALK_ANIM_FRAMES[playerWalkAnim]);
progressAnimation(
&playerWalkAnim, PLAYER_WALK_ANIM_FRAME_COUNT,
&playerWalkAnimTimer, PLAYER_WALK_ANIM_FRAME_DELAY
);
}
void drawCacti() {
if (cactusMovementTimer < CACTI_MOVEMENT_DELAY) { return; }
cactusMovementTimer -= CACTI_MOVEMENT_DELAY;
for (int i = 0; i < MAX_CACTI; i++) {
if (cactiPositions[i] >= 0) {
lcd.setCursor(cactiPositions[i] + 1, 1);
lcd.print(" ");
lcd.setCursor(cactiPositions[i]--, 1);
lcd.write(CACTUS);
}
}
if (starting) {
if (cactusSpawnTimer >= CACTI_START_TIME) {
starting = false;
cactusSpawnTimer -= CACTI_START_TIME;
}
return;
}
if (cactusSpawnTimer >= CACTI_SPAWN_INTERVAL) {
cactusSpawnTimer -= CACTI_SPAWN_INTERVAL;
for (int i = 0; i < MAX_CACTI; i++) {
if (cactiPositions[i] == -1) {
cactiPositions[i] = 16 + map(
rand(), 0, RAND_MAX,
CACTI_SPAWN_SEPERATION_MIN, CACTI_SPAWN_SEPERATION_MAX
);
break;
}
}
}
}
void processInputs() {
char key = keypad.getKey();
switch (key) {
case '2':
if (!jumping && jumpTime >= JUMP_TIME + JUMP_DEBOUNCE) {
jumpTime = 0;
jumping = true;
}
break;
case 'A':
if (gameOver) {
starting = true;
isHighscore = false;
for (int i = 0; i < MAX_CACTI; i++)
cactiPositions[i] = -1;
score = 0;
playerWalkAnimTimer = 0;
cactusMovementTimer = 0;
cactusSpawnTimer = 0;
jumpTime = JUMP_TIME + JUMP_DEBOUNCE;
gameOver = false;
lcd.clear();
}
break;
}
}
void updateGameState() {
for (int i = 0; i < MAX_CACTI; i++) {
if (cactiPositions[i] == 0 && !jumping) {
gameOver = true;
if (score > highscore) {
highscore = score;
isHighscore = true;
}
lcd.clear();
}
}
if (jumping && jumpTime >= JUMP_TIME + JUMP_DEBOUNCE) {
jumping = false;
}
}