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