#include <EEPROM.h>
#include <LiquidCrystal.h>
// Class for cycling through a sequence of animation frames
class Animation {
private:
int _current_frame;
int _frame_count;
int *_frames;
public:
Animation(int frame_count, int *frames) {
_current_frame = 0;
_frame_count = frame_count;
_frames = frames;
}
int next() {
_current_frame++;
if (_current_frame == _frame_count) {
_current_frame = 0;
}
return _frames[_current_frame];
}
int prev() {
_current_frame++;
if (_current_frame < 0) {
_current_frame += _frame_count;
}
return _frames[_current_frame];
}
int current() {
return _frames[_current_frame];
}
};
// Constants for identifying each sprite
const byte SPRITE_TAIL0 = 0;
const byte SPRITE_TAIL1 = 1;
const byte SPRITE_BODY0 = 2;
const byte SPRITE_BODY1 = 3;
const byte SPRITE_WALLB = 5;
const byte SPRITE_WALLT = 6;
const byte SPRITE_EXPL = 7;
// Sprite custom character data
byte sprite_tail0[8] = {0b00000, 0b00000, 0b00000, 0b10001, 0b01111, 0b00101, 0b00000, 0b00001};
byte sprite_tail1[8] = {0b00000, 0b00000, 0b00000, 0b00101, 0b01111, 0b10001, 0b00000, 0b00001};
byte sprite_body0[8] = {0b00000, 0b00100, 0b00100, 0b11110, 0b10101, 0b11110, 0b10100, 0b11111};
byte sprite_body1[8] = {0b00000, 0b11111, 0b00100, 0b11110, 0b10101, 0b11110, 0b10100, 0b11111};
byte sprite_wallt[8] = {0b00000, 0b00100, 0b01001, 0b11110, 0b01001, 0b00100, 0b00000, 0b00000};
byte sprite_wallb[8] = {0b00000, 0b00100, 0b00100, 0b01010, 0b01110, 0b01010, 0b01110, 0b01010};
byte sprite_expl[8] = {0b00100, 0b10100, 0b01101, 0b01010, 0b01010, 0b10110, 0b00101, 0b00100};
// Animation sequences
int seq_tail[] = {SPRITE_TAIL0, SPRITE_TAIL1};
int seq_body[] = {SPRITE_BODY0, SPRITE_BODY1};
Animation anim_tail(2, seq_tail);
Animation anim_body(2, seq_body);
// LCD settings
//LiquidCrystal lcd(13, 12, 11, 10, 9, 8, 7, 6, 5, 4);
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
//const int lcd_backlight = 3;
// Button pin constants
//const int button = 2;
//Speaker pin contstant
const int speaker = 3;
// Some game constants that can be adjusted to control difficulty
const unsigned long frame_rate = 125;
unsigned long frame_next = 0;
int game_mode = 0;
unsigned long score = 0;
boolean new_highscore = false;
boolean first = true;
boolean button_state = false;
const unsigned debounce_time = 20;
//volatile boolean button_toggled = true;
volatile unsigned long next_read = 0;
// Do "smart" spawning of walls so the game is never unplayable
unsigned int mask_wall = 0b1111000000000000;
// Helicopter collision detection mask
unsigned int mask_heli = 0b0000000000000011;
// Bitwise representations of obstacle locations
unsigned int walls_top = 0x0000;
unsigned int walls_bot = 0x0000;
// Maximum and minimum wall advance rates
unsigned long max_wall_advance_rate = 350;
unsigned long min_wall_advance_rate = 140;
unsigned long wall_advance_rate = max_wall_advance_rate;
unsigned long wall_advance_next = 0;
// Death animation and flash times
unsigned long death_rate = 150;
unsigned long death_hold = 1500;
void setup() {
pinMode(speaker,OUTPUT);
read_button();
// Seed the random number generator using noise from analog 0
unsigned long seed = 0;
for (int n = 0; n < 32; ++n) {
randomSeed(seed);
delay(random() & 0xf);
seed <<= 1;
seed |= analogRead(1) & 1;
}
// Generate the custom characters for the games sprites
lcd.createChar(SPRITE_TAIL0, sprite_tail0);
lcd.createChar(SPRITE_TAIL1, sprite_tail1);
lcd.createChar(SPRITE_BODY0, sprite_body0);
lcd.createChar(SPRITE_BODY1, sprite_body1);
lcd.createChar(SPRITE_WALLB, sprite_wallb);
lcd.createChar(SPRITE_WALLT, sprite_wallt);
lcd.createChar(SPRITE_EXPL, sprite_expl);
// Start up the LCD
lcd.begin(16, 2);
lcd.noCursor();
lcd.clear();
lcd.home();
// Start at the home screen
set_game_mode(0);
}
void loop() {
boolean update = false;
// Update animation sequences
unsigned long now = millis();
if (frame_next < now) {
anim_tail.next();
anim_body.next();
frame_next = now + frame_rate;
update = true;
}
read_button();
update = true;
// Take appropriate action depending on the current game mode
switch (game_mode) {
case 0: game_home(update) ; break;
case 1: game_play(update) ; break;
case 2: game_over(update); break;
default: game_mode = 0;
}
}
// The home state
void game_home(boolean update) {
if (first) {
first = false;
score = get_highscore();
lcd.clear();
}
read_button();
if (!button_state) {
set_game_mode(1);
}
if (update) {
lcd.setCursor(0, 0);
lcd.print("Helicopter! ");
lcd.write(anim_tail.current());
lcd.write(anim_body.current());
lcd.setCursor(0, 1);
lcd.print("Best: ");
lcd.print(score);
}
}
// The game play state
void game_play(boolean update) {
if (first) {
first = false;
score = 0;
new_highscore = false;
walls_bot = 0;
walls_top = 0;
wall_advance_rate = max_wall_advance_rate;
lcd.clear();
}
unsigned long now = millis();
// Is it time to advance the obstacles?
if (now > wall_advance_next) {
if (wall_advance_rate > min_wall_advance_rate) {
wall_advance_rate--;
}
wall_advance_next = now + wall_advance_rate;
walls_top >>= 1;
walls_bot >>= 1;
++score;
// Can we spawn a new obstacle for the player?
if (((walls_top | walls_bot) & mask_wall) == 0) {
if (random() & 1) {
walls_top |= 0x8000;
} else {
walls_bot |= 0x8000;
}
}
update = true;
}
// Render the next frame
if (update) {
lcd.setCursor(0, button_state ? 0 : 1);
lcd.write(0x20);
lcd.write(0x20);
for (int n = 0; n < 16; ++n) {
lcd.setCursor(n, 0);
if (walls_top & (1 << n)) {
lcd.write(SPRITE_WALLT);
lcd.write(0x20);
}
}
for (int n = 0; n < 16; ++n) {
lcd.setCursor(n, 1);
if (walls_bot & (1 << n)) {
lcd.write(SPRITE_WALLB);
lcd.write(0x20);
}
}
// Handle a collision with an obstacle
lcd.setCursor(0, button_state ? 1 : 0);
if (mask_heli & (button_state ? walls_bot : walls_top)) {
lcd.write(SPRITE_EXPL);
lcd.write(SPRITE_EXPL);
tone(speaker,329, 100);
boolean ramp = false;
unsigned long curr = millis(), prev = curr, next = curr + death_rate;
unsigned long death_stop = millis() + death_hold;
// Flash the LCDs backlight
while (curr <= death_stop) {
curr = millis();
if (curr > next) {
prev = curr;
next = prev + death_rate;
}
// Fade the backlight on and off
unsigned long v = map(curr, prev, next, 0, 255);
if (!ramp) {
v = 255 - v;
}
//analogWrite(lcd_backlight, v);
}
//analogWrite(lcd_backlight, 255);
// Store the new highscore
if (score > get_highscore()) {
set_highscore(score);
new_highscore = true;
}
set_game_mode(2);
} else {
lcd.write(anim_tail.current());
lcd.write(anim_body.current());
}
}
}
// The game over state
// Displays the players score
void game_over(boolean update) {
if (first) {
first = false;
lcd.clear();
}
read_button();
if (!button_state) {
set_game_mode(0);
}
if (update) {
lcd.setCursor(0, 0);
lcd.print(new_highscore ? "New High Score!" : "Game Over");
lcd.setCursor(0, 1);
lcd.print("Score: ");
lcd.print(score);
}
}
// Change the game mode and reset some state information
void set_game_mode(int mode) {
button_state = true;
game_mode = mode;
first = true;
lcd.clear();
}
// Retrieve the high score from EEPROM
unsigned long get_highscore() {
unsigned long b0 = EEPROM.read(0);
unsigned long b1 = EEPROM.read(1);
unsigned long b2 = EEPROM.read(2);
unsigned long b3 = EEPROM.read(3);
unsigned long cs = EEPROM.read(4);
if (((b0 + b1 + b2 + b3) & 0xff) == cs) {
return b0 | (b1 << 8) | (b2 << 16) | (b3 << 24);
}
return 0;
}
// Store the new highscore in EEPROM
void set_highscore(unsigned long score) {
byte b0 = (byte)((score >> 0) & 0xff);
byte b1 = (byte)((score >> 8) & 0xff);
byte b2 = (byte)((score >> 16) & 0xff);
byte b3 = (byte)((score >> 24) & 0xff);
byte cs = b0 + b1 + b2 + b3;
EEPROM.write(0, b0);
EEPROM.write(1, b1);
EEPROM.write(2, b2);
EEPROM.write(3, b3);
EEPROM.write(4, cs);
}
void read_button() {
if (millis() > next_read) {
next_read = millis() + 200;
button_state = (analogRead(A0) > 1000);
}
}
Intuitive Machines™ Biotronics™ Zoikrons™ Autognorics™ ELFS™ IM™
are original trademarks and logos
solely distributed by
L.A.W.S.I.N.

Comments
Post a Comment