Upload files to "dinorunner"
This commit is contained in:
@@ -0,0 +1,304 @@
|
|||||||
|
#include <LiquidCrystal.h>
|
||||||
|
#include <EEPROM.h>
|
||||||
|
|
||||||
|
// Standard pinout for 16x2 LCD Keypad Shield
|
||||||
|
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
|
||||||
|
|
||||||
|
// Optional audio output pin (Pin 3 is unused by standard LCD shields)
|
||||||
|
const int BUZZER_PIN = 3;
|
||||||
|
|
||||||
|
// Button definitions on analog pin A0
|
||||||
|
#define BTN_RIGHT 0
|
||||||
|
#define BTN_UP 1
|
||||||
|
#define BTN_DOWN 2
|
||||||
|
#define BTN_LEFT 3
|
||||||
|
#define BTN_SELECT 4
|
||||||
|
#define BTN_NONE 5
|
||||||
|
|
||||||
|
// Custom 5x8 pixel bitmaps
|
||||||
|
byte runnerStanding[8] = {
|
||||||
|
B01110,
|
||||||
|
B01101,
|
||||||
|
B01111,
|
||||||
|
B00110,
|
||||||
|
B01111,
|
||||||
|
B11101,
|
||||||
|
B01010,
|
||||||
|
B10001
|
||||||
|
};
|
||||||
|
|
||||||
|
byte runnerDucking[8] = {
|
||||||
|
B00000,
|
||||||
|
B00000,
|
||||||
|
B00000,
|
||||||
|
B01110,
|
||||||
|
B11111,
|
||||||
|
B01111,
|
||||||
|
B10101,
|
||||||
|
B00000
|
||||||
|
};
|
||||||
|
|
||||||
|
byte obstacleGround[8] = {
|
||||||
|
B00100,
|
||||||
|
B00101,
|
||||||
|
B10101,
|
||||||
|
B10111,
|
||||||
|
B11101,
|
||||||
|
B00100,
|
||||||
|
B00100,
|
||||||
|
B00100
|
||||||
|
};
|
||||||
|
|
||||||
|
// Redesigned airborne hazard: Sharp flying pterodactyl / bird facing left
|
||||||
|
byte obstacleAir[8] = {
|
||||||
|
B00000,
|
||||||
|
B10001, // Wings up
|
||||||
|
B11011, // Wings + body
|
||||||
|
B01110, // Torso
|
||||||
|
B11100, // Beak pointing left
|
||||||
|
B00100, // Tail / claw
|
||||||
|
B00000,
|
||||||
|
B00000
|
||||||
|
};
|
||||||
|
|
||||||
|
const byte CHAR_RUNNER = 0;
|
||||||
|
const byte CHAR_DUCK = 1;
|
||||||
|
const byte CHAR_GROUND = 2;
|
||||||
|
const byte CHAR_AIR = 3;
|
||||||
|
|
||||||
|
// Player & Game parameters
|
||||||
|
const int PLAYER_COL = 1;
|
||||||
|
enum PlayerPose { RUNNING, JUMPING, DUCKING };
|
||||||
|
PlayerPose playerState = RUNNING;
|
||||||
|
|
||||||
|
int jumpTimer = 0;
|
||||||
|
const int JUMP_DURATION = 3;
|
||||||
|
|
||||||
|
byte terrain[16];
|
||||||
|
unsigned long lastTick = 0;
|
||||||
|
int gameSpeed = 220;
|
||||||
|
unsigned int score = 0;
|
||||||
|
unsigned int highScore = 0;
|
||||||
|
bool isNewHigh = false;
|
||||||
|
bool gameOver = false;
|
||||||
|
|
||||||
|
// EEPROM addresses & signature
|
||||||
|
const int EEPROM_INIT_ADDR = 0;
|
||||||
|
const int EEPROM_HS_ADDR = 1;
|
||||||
|
const byte EEPROM_MAGIC = 0x4D;
|
||||||
|
|
||||||
|
// Read buttons from resistor ladder on A0
|
||||||
|
int readButtons() {
|
||||||
|
int adc = analogRead(A0);
|
||||||
|
if (adc > 1000) return BTN_NONE;
|
||||||
|
if (adc < 50) return BTN_RIGHT;
|
||||||
|
if (adc < 200) return BTN_UP;
|
||||||
|
if (adc < 400) return BTN_DOWN;
|
||||||
|
if (adc < 600) return BTN_LEFT;
|
||||||
|
if (adc < 850) return BTN_SELECT;
|
||||||
|
return BTN_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sound effects (harmless if no buzzer is connected to pin 3)
|
||||||
|
void playJumpSound() {
|
||||||
|
tone(BUZZER_PIN, 600, 40);
|
||||||
|
}
|
||||||
|
|
||||||
|
void playDuckSound() {
|
||||||
|
tone(BUZZER_PIN, 300, 30);
|
||||||
|
}
|
||||||
|
|
||||||
|
void playCrashSound() {
|
||||||
|
tone(BUZZER_PIN, 150, 250);
|
||||||
|
}
|
||||||
|
|
||||||
|
void playNewHighSound() {
|
||||||
|
tone(BUZZER_PIN, 523, 80);
|
||||||
|
delay(90);
|
||||||
|
tone(BUZZER_PIN, 659, 80);
|
||||||
|
delay(90);
|
||||||
|
tone(BUZZER_PIN, 784, 120);
|
||||||
|
}
|
||||||
|
|
||||||
|
void resetHighScoreInEEPROM() {
|
||||||
|
highScore = 0;
|
||||||
|
EEPROM.write(EEPROM_INIT_ADDR, EEPROM_MAGIC);
|
||||||
|
EEPROM.put(EEPROM_HS_ADDR, highScore);
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadHighScore() {
|
||||||
|
// If LEFT is held during power-on or board reset, force clear EEPROM
|
||||||
|
if (readButtons() == BTN_LEFT) {
|
||||||
|
resetHighScoreInEEPROM();
|
||||||
|
lcd.clear();
|
||||||
|
lcd.setCursor(0, 0);
|
||||||
|
lcd.print("High Score Reset");
|
||||||
|
delay(1000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check magic byte signature
|
||||||
|
if (EEPROM.read(EEPROM_INIT_ADDR) != EEPROM_MAGIC) {
|
||||||
|
resetHighScoreInEEPROM();
|
||||||
|
} else {
|
||||||
|
EEPROM.get(EEPROM_HS_ADDR, highScore);
|
||||||
|
// Sanity check to clear uninitialized residual bytes
|
||||||
|
if (highScore > 10000) {
|
||||||
|
resetHighScoreInEEPROM();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void saveHighScore(unsigned int newScore) {
|
||||||
|
highScore = newScore;
|
||||||
|
EEPROM.put(EEPROM_HS_ADDR, highScore);
|
||||||
|
}
|
||||||
|
|
||||||
|
void resetGame() {
|
||||||
|
for (int i = 0; i < 16; i++) {
|
||||||
|
terrain[i] = 0;
|
||||||
|
}
|
||||||
|
score = 0;
|
||||||
|
gameSpeed = 220;
|
||||||
|
jumpTimer = 0;
|
||||||
|
playerState = RUNNING;
|
||||||
|
gameOver = false;
|
||||||
|
isNewHigh = false;
|
||||||
|
lcd.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
pinMode(BUZZER_PIN, OUTPUT);
|
||||||
|
lcd.begin(16, 2);
|
||||||
|
|
||||||
|
lcd.createChar(CHAR_RUNNER, runnerStanding);
|
||||||
|
lcd.createChar(CHAR_DUCK, runnerDucking);
|
||||||
|
lcd.createChar(CHAR_GROUND, obstacleGround);
|
||||||
|
lcd.createChar(CHAR_AIR, obstacleAir);
|
||||||
|
|
||||||
|
loadHighScore();
|
||||||
|
randomSeed(analogRead(A1));
|
||||||
|
resetGame();
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
int btn = readButtons();
|
||||||
|
|
||||||
|
if (gameOver) {
|
||||||
|
// Press any button to restart
|
||||||
|
if (btn != BTN_NONE) {
|
||||||
|
delay(250); // Debounce
|
||||||
|
resetGame();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Player controls
|
||||||
|
if ((btn == BTN_UP || btn == BTN_SELECT) && playerState == RUNNING) {
|
||||||
|
playerState = JUMPING;
|
||||||
|
jumpTimer = JUMP_DURATION;
|
||||||
|
playJumpSound();
|
||||||
|
} else if (btn == BTN_DOWN && playerState == RUNNING) {
|
||||||
|
playerState = DUCKING;
|
||||||
|
playDuckSound();
|
||||||
|
} else if (btn != BTN_DOWN && playerState == DUCKING) {
|
||||||
|
playerState = RUNNING;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Game clock tick
|
||||||
|
if (millis() - lastTick >= (unsigned long)gameSpeed) {
|
||||||
|
lastTick = millis();
|
||||||
|
|
||||||
|
// Jump duration counter
|
||||||
|
if (playerState == JUMPING) {
|
||||||
|
jumpTimer--;
|
||||||
|
if (jumpTimer <= 0) {
|
||||||
|
playerState = RUNNING;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shift terrain left
|
||||||
|
for (int i = 0; i < 15; i++) {
|
||||||
|
terrain[i] = terrain[i + 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spawn obstacles at column 15
|
||||||
|
if (terrain[14] == 0 && terrain[13] == 0 && random(0, 100) < 38) {
|
||||||
|
terrain[15] = (random(0, 100) < 65) ? 1 : 2; // 1 = Ground, 2 = Air
|
||||||
|
} else {
|
||||||
|
terrain[15] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collision detection
|
||||||
|
byte currentObstacle = terrain[PLAYER_COL];
|
||||||
|
if (currentObstacle == 1 && playerState != JUMPING) {
|
||||||
|
gameOver = true;
|
||||||
|
} else if (currentObstacle == 2 && playerState != DUCKING) {
|
||||||
|
gameOver = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gameOver) {
|
||||||
|
playCrashSound();
|
||||||
|
|
||||||
|
if (score > highScore) {
|
||||||
|
saveHighScore(score);
|
||||||
|
isNewHigh = true;
|
||||||
|
playNewHighSound();
|
||||||
|
}
|
||||||
|
|
||||||
|
lcd.clear();
|
||||||
|
lcd.setCursor(0, 0);
|
||||||
|
if (isNewHigh) {
|
||||||
|
lcd.print("NEW RECORD! ");
|
||||||
|
lcd.print(score);
|
||||||
|
} else {
|
||||||
|
lcd.print("OVER! Score:");
|
||||||
|
lcd.print(score);
|
||||||
|
}
|
||||||
|
|
||||||
|
lcd.setCursor(0, 1);
|
||||||
|
lcd.print("Best: ");
|
||||||
|
lcd.print(highScore);
|
||||||
|
lcd.print(" [Press]");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw frame
|
||||||
|
for (int r = 0; r < 2; r++) {
|
||||||
|
lcd.setCursor(0, r);
|
||||||
|
for (int c = 0; c < 16; c++) {
|
||||||
|
if (c == PLAYER_COL) {
|
||||||
|
if (r == 0) {
|
||||||
|
lcd.write(playerState == JUMPING ? CHAR_RUNNER : ' ');
|
||||||
|
} else {
|
||||||
|
if (playerState == RUNNING) lcd.write(CHAR_RUNNER);
|
||||||
|
else if (playerState == DUCKING) lcd.write(CHAR_DUCK);
|
||||||
|
else lcd.print(" ");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (r == 1 && terrain[c] == 1) {
|
||||||
|
lcd.write(CHAR_GROUND);
|
||||||
|
} else if (r == 0 && terrain[c] == 2) {
|
||||||
|
lcd.write(CHAR_AIR);
|
||||||
|
} else {
|
||||||
|
lcd.print(" ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Score display (top-right corner)
|
||||||
|
lcd.setCursor(12, 0);
|
||||||
|
if (score < 10) lcd.print(" ");
|
||||||
|
else if (score < 100) lcd.print(" ");
|
||||||
|
else if (score < 1000) lcd.print(" ");
|
||||||
|
lcd.print(score);
|
||||||
|
|
||||||
|
score++;
|
||||||
|
|
||||||
|
// Accelerate gameplay every 25 points down to 90ms/frame
|
||||||
|
if (score % 25 == 0 && gameSpeed > 90) {
|
||||||
|
gameSpeed -= 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user