Upload files to "dinorunner"

This commit is contained in:
2026-09-30 10:56:44 +00:00
parent d48c8c7457
commit f52fff4058
+304
View File
@@ -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;
}
}
}