//**************************************************************//
// Name : LCD dino game
// Author : Luiz Paulo Grafetti Terres
// Date : 31 Mar, 2024
// Modified: Mar, 2024
// Version :
// Notes : first time working with LCD displays
//****************************************************************
#include <LiquidCrystal.h>
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
byte DINO[8] = {
0b00000,
0b01110,
0b01110,
0b01000,
0b11100,
0b01000,
0b11100,
0b10100
};
byte CACTUS[8] = {
0b00000,
0b00000,
0b00010,
0b01010,
0b01101,
0b00101,
0b01110,
0b00100
};
byte METEOR[8] = {
0b01111,
0b01111,
0b10111,
0b11011,
0b01100,
0b00111,
0b00000,
0b00000
};
#define BTN_PIN 8
void setup() {
lcd.createChar(0, DINO);
lcd.createChar(1, CACTUS);
lcd.createChar(2, METEOR);
lcd.begin(16, 2);
randomSeed(analogRead(0));
pinMode(BTN_PIN, INPUT_PULLUP);
Serial.begin(9600);
}
byte button_state = 0;
byte last_button_state = 0;
long long int clear_dino_pos_millis = millis();
long long int shift_map_millis = millis();
byte dino_row = 2;
byte dino_col = 2;
#define MAX_COL 15
#define MIN_COL 0
#define SKY_ROW 0
#define GROUND_ROW 2
/* o conteudo desses arrays = mapa */
byte sky_obstacles[16] = {0,0,0,0 ,0,0,0,0, 0,0,0,0, 0,0,0,0};
byte ground_obstacles[16] = {0,0,0,0 ,0,0,0,0, 0,0,0,0, 0,0,0,0};
byte sky_obstacle;
byte ground_obstacle;
/* salva em qual linha foi gerado o ultimo obstaculo */
byte last_row_generated = 1; // 1 is a neutral choice
byte GAME_DIFFICULTY = 0; /* de 0 a 2 */
/* estados do jogo */
byte STATE = 0;
#define PLAYING 0
#define GAME_WON 1
#define GAME_OVER 2
int delay_to_shift = 500; /* velocidade de jogo */
/* contador de segundos passados (para a velocidade de obstacle_shift dinamica)*/
long long int capture_seconds_millis = millis();
char hex_counter[2]; /* contador em HEX de obstaculos ultrapassados */
byte obstacles_on_display = 0; /* obstaculos atualmente na tela*/
/* desenhos */
byte dino_px = 0;
byte cactus_px = 1;
byte meteor_px = 2;
// GAME STATS variables
#define MAX_OBSTACLES_COUNT 255
#define MIN_OBSTACLES_COUNT 25
byte MAX_OBSTACLES_TO_LVL = MIN_OBSTACLES_COUNT; /* maximo num de obstaculos gerados por level*/
byte obstacles_generated = 0; /* num de obst gerados durante uma jogada */
byte obstacles_left_behind = 0; /* obstaculos ja ultrapassados pelo dino */
byte obstacles_left_behind_record = 0; /* recorde (maior num obstaculos ultrapassados) */
void clear_position(int col, int row) {
lcd.setCursor(col, row);
lcd.print(" ");
}
void shift_obstacles_positions() {
for (byte i = 1; i <= MAX_COL; i++) {
// acrescenta contador de obstaculos ultrapassados
if ((i == dino_col) && (ground_obstacles[i] || sky_obstacles[i])) {
obstacles_left_behind++;
// calcula recorde
if (obstacles_left_behind > obstacles_left_behind_record)
obstacles_left_behind_record = obstacles_left_behind;
}
if ((i == 1) && (ground_obstacles[i] || sky_obstacles[i]))
obstacles_on_display--;
ground_obstacles[i - 1] = ground_obstacles[i];
ground_obstacles[i] = 0;
sky_obstacles[i - 1] = sky_obstacles[i];
sky_obstacles[i] = 0;
}
}
void game_restart() {
// resetting game stats
if (GAME_DIFFICULTY == 0) MAX_OBSTACLES_TO_LVL = MIN_OBSTACLES_COUNT;
MAX_OBSTACLES_TO_LVL += MAX_OBSTACLES_TO_LVL * GAME_DIFFICULTY;
obstacles_generated = 0;
obstacles_left_behind = 0;
obstacles_on_display = 0;
for (byte i = 0; i <= MAX_COL; i++) {
ground_obstacles[i] = 0;
sky_obstacles[i] = 0;
}
delay_to_shift = 500;
}
void display_obstacles() {
for (byte i = MIN_COL; i <= MAX_COL; i++) {
if (ground_obstacles[i]) {
// if there is an obstacle on cell, writes it
lcd.setCursor(i, 2);
lcd.write(cactus_px);
lcd.setCursor(i, 0);
lcd.print(" ");
} else if (sky_obstacles[i]) {
lcd.setCursor(i, 0);
lcd.write(meteor_px);
lcd.setCursor(i, 2);
lcd.print(" ");
} else {
// otherwise, clean cell
lcd.setCursor(i, 0);
lcd.print(" ");
lcd.setCursor(i, 2);
lcd.print(" ");
}
}
}
void randomly_append_obstacle() {
if (obstacles_generated > MAX_OBSTACLES_TO_LVL)
return;
// appends obstacle to sky or ground
ground_obstacle = byte(random(100) > 60);
sky_obstacle = byte(random(100) > 60);
// the alternate pattern condition works only 25% of the time
byte altern_patter = byte(random(100) > 75);
// gives preference to generate alternate rows obstacles pattern
/* tem 25% de chances de trocar a linha do obstaculo gerado, caso
o ultimo obstaculo gerado tenha sido na mesma linha (tenta diminuir
a incidencia de uma linha muito tempo vazia*/
if (((sky_obstacle) && (last_row_generated == SKY_ROW)) && altern_patter) {
ground_obstacle = 1;
sky_obstacle = 0;
}
else if (((ground_obstacle) && (last_row_generated == GROUND_ROW) )&& altern_patter) {
ground_obstacle = 0;
sky_obstacle = 1;
}
// adds obstacles to arrays
// generates obstacles only and only if DINO can pass through them
if ((sky_obstacle) && !(ground_obstacles[MAX_COL]) && !(ground_obstacles[MAX_COL - 1])) {
sky_obstacles[MAX_COL] = sky_obstacle;
last_row_generated = SKY_ROW;
obstacles_generated++;
obstacles_on_display++;
}
if ((ground_obstacle) && !(sky_obstacles[MAX_COL]) && !(sky_obstacles[MAX_COL - 1])) {
ground_obstacles[MAX_COL] = ground_obstacle;
last_row_generated = GROUND_ROW;
obstacles_generated++;
obstacles_on_display++;
}
}
void display_osbtacles_passed_by_counter() {
// apresenta pontuacao em hex
sprintf (hex_counter, "%02x", obstacles_left_behind);
lcd.setCursor(MAX_COL, 0);
lcd.print(hex_counter[1]);
lcd.setCursor(MAX_COL - 1, 0);
lcd.print(hex_counter[0]);
}
void display_record() {
// conteudo primeira linha
if (STATE == GAME_OVER) {
lcd.setCursor(3, 0);
lcd.print("YOU LOST!!");
} else {
// se ganhou a fase
lcd.setCursor(0, 0);
lcd.print("YOU WON!!!");
lcd.setCursor(MAX_COL - 2, 0);
lcd.print(GAME_DIFFICULTY + 1);
lcd.setCursor(MAX_COL - 1, 0);
lcd.print("/");
lcd.setCursor(MAX_COL, 0);
lcd.print("3");
}
// conteudo segunda linha
lcd.setCursor(MIN_COL, 2);
char hex_record[2];
// apresenta pontuacao atual em hex
lcd.print("you:");
lcd.print(hex_counter[0]);
lcd.print(hex_counter[1]);
lcd.print("/");
// apresenta recorde em hex
lcd.print("record:");
sprintf (hex_record, "%02x", obstacles_left_behind_record);
lcd.print(hex_record[0]);
lcd.print(hex_record[1]);
}
void loop() {
switch (STATE) {
case PLAYING: //0
// show dino in its place display[(0 || 2)][2]
lcd.setCursor(dino_col, dino_row);
lcd.write(dino_px);
// capture map shift speed
if ((millis() - capture_seconds_millis > 1000) && delay_to_shift > 150) {
delay_to_shift -= delay_to_shift * 0.025;
capture_seconds_millis = millis();
}
// play the map
if (millis() - shift_map_millis > delay_to_shift) {
// update obstacles indices
shift_map_millis = millis();
shift_obstacles_positions();
randomly_append_obstacle();
// print on display
display_obstacles();
display_osbtacles_passed_by_counter();
}
// check collision
if ((dino_row == GROUND_ROW) && (ground_obstacles[dino_col])) {
Serial.println("CRASH ON CACTUS");
STATE = GAME_OVER;
lcd.clear();
}
if ((dino_row == SKY_ROW) && (sky_obstacles[dino_col])) {
Serial.println("CRASH ON METEOR");
STATE = GAME_OVER;
lcd.clear();
}
// check if WIN
if ((obstacles_generated > MAX_OBSTACLES_TO_LVL) && (obstacles_generated == obstacles_left_behind) && (!obstacles_on_display)) {
STATE = GAME_WON;
lcd.clear();
}
// capture button value (change dino pos)
button_state = digitalRead(BTN_PIN);
if (button_state != last_button_state) {
if (button_state == LOW) {
if (dino_row == 2) {
dino_row = 0;
clear_position(dino_col, 2);
}
else {
dino_row = 2;
clear_position(dino_col, 0);
}
}
delay(50);
}
last_button_state = button_state;
break;
default:
display_record();
// capture button value (jump STATE)
button_state = digitalRead(BTN_PIN);
if (button_state != last_button_state) {
if (button_state == LOW) {
// altera o proximo estado com base no estado atual
if (STATE == GAME_OVER)
GAME_DIFFICULTY = 0;
else
GAME_DIFFICULTY = (GAME_DIFFICULTY + 1) % 3;
// atualizar game stats para próxima jogada
STATE = 0;
game_restart();
}
delay(50);
}
last_button_state = button_state;
break;
}
}