#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include "pitches.h"
// Initialise the display
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
#define OLED_RESET 4 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// Pin definitions
#define FLAP_BUTTON 2
#define END_BUTTON 3
// Initialise 'sprites'
#define SPRITE_HEIGHT 16
#define SPRITE_WIDTH 16
// Buzzer pin
#define BUZZER_PIN 9
// Two frames of animation
static const unsigned char PROGMEM wing_down_bmp[] = {
B00000000, B00000000,
B00000000, B00000000,
B00000011, B11000000,
B00011111, B11110000,
B00111111, B00111000,
B01111111, B11111110,
B11111111, B11000001,
B11011111, B01111110,
B11011111, B01111000,
B11011111, B01111000,
B11001110, B01111000,
B11110001, B11110000,
B01111111, B11100000,
B00111111, B11000000,
B00000111, B00000000,
B00000000, B00000000
};
static const unsigned char PROGMEM wing_up_bmp[] = {
B00000000, B00000000,
B00000000, B00000000,
B00000011, B11000000,
B00011111, B11110000,
B00111111, B00111000,
B01110001, B11111110,
B11101110, B11000001,
B11011111, B01111110,
B11011111, B01111000,
B11111111, B11111000,
B11111111, B11111000,
B11111111, B11110000,
B01111111, B11100000,
B00111111, B11000000,
B00000111, B00000000,
B00000000, B00000000
};
// Game variables
#define LEVEL_1_SPEED 80
#define LEVEL_2_SPEED 50
#define LEVEL_3_SPEED 30
int game_state = 0; // 0 = welcome screen, 1 = start screen, 2 = in game, 3 = level complete, 4 = game over, 5 = congratulations
int score = 0; // current game score
int high_score = 0; // highest score since the nano was reset
int bird_x = (int)display.width() / 4; // bird's x position (along) - initialised to 1/4 the way along the screen
int bird_y; // bird's y position (down)
int momentum = 0; // how much force is pulling the bird down
int wall_x[2]; // an array to hold the walls' x positions
int wall_y[2]; // an array to hold the walls' y positions
int wall_gap = 30; // size of the wall gap in pixels
int wall_width = 10; // width of the wall in pixels
int current_level = 1; // current game level
int game_speed = LEVEL_1_SPEED; // initial game speed
void setup() {
Serial.begin(9600);
// Initialise the display
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3C for 128x64
Serial.println(F("SSD1306 allocation failed"));
for (;;); // Don't proceed, loop forever
}
display.setTextColor(WHITE);
display.clearDisplay();
display.display();
// Setup the buttons for input
pinMode(FLAP_BUTTON, INPUT_PULLUP);
pinMode(END_BUTTON, INPUT_PULLUP);
// Initialise the random number generator
randomSeed(analogRead(0));
//displayWelcomeScreen();
//delay(3000); // Display welcome screen for 3 seconds
// Initial game setup
resetGame(current_level, LEVEL_1_SPEED);
}
void loop() {
switch (game_state) {
case 0:
showStartScreen();
break;
case 1:
runGame();
break;
case 2:
showLevelCompleteScreen();
break;
case 3:
showGameOverScreen();
break;
case 4:
showCongratulationsScreen();
break;
}
}
void resetGame(int nivel_actual, int velocidad) {
bird_y = display.height() / 2;
momentum = -4;
wall_x[0] = display.width();
wall_y[0] = random(0, display.height() - wall_gap);
wall_x[1] = display.width() + display.width() / 2;
wall_y[1] = random(0, display.height() - wall_gap);
score = 0;
current_level = nivel_actual;
game_speed = velocidad;
}
void showStartScreen() {
display.clearDisplay();
textAtCenter(display.height() / 2 - 8, "FLAPPY BIRD");
boldTextAtCenter(display.height() / 2, "PRESS TO START");
display.display();
// Wait until the user presses the flap button to start the game
if (digitalRead(FLAP_BUTTON) == LOW) {
screenWipe(10);
game_state = 1;
}
}
void runGame() {
display.clearDisplay();
if (digitalRead(FLAP_BUTTON) == LOW) {
momentum = -4;
tone(BUZZER_PIN, 1000, 100); // Play tone for flap
}
momentum += 1;
bird_y += momentum;
if (bird_y < 0) bird_y = 0;
if (bird_y > display.height() - SPRITE_HEIGHT) {
bird_y = display.height() - SPRITE_HEIGHT;
momentum = -2;
}
if (momentum < 0) {
if (random(2) == 0) {
display.drawBitmap(bird_x, bird_y, wing_down_bmp, 16, 16, WHITE);
} else {
display.drawBitmap(bird_x, bird_y, wing_up_bmp, 16, 16, WHITE);
}
} else {
display.drawBitmap(bird_x, bird_y, wing_up_bmp, 16, 16, WHITE);
}
for (int i = 0; i < 2; i++) {
display.fillRect(wall_x[i], 0, wall_width, wall_y[i], WHITE);
display.fillRect(wall_x[i], wall_y[i] + wall_gap, wall_width, display.height() - wall_y[i] - wall_gap, WHITE);
if (wall_x[i] < 0) {
wall_y[i] = random(0, display.height() - wall_gap);
wall_x[i] = display.width();
}
if (wall_x[i] == bird_x) {
score++;
high_score = max(score, high_score);
if (score % 3 == 0) {
game_state = 2; // Move to level complete state
}
}
if ((bird_x + SPRITE_WIDTH > wall_x[i] && bird_x < wall_x[i] + wall_width) &&
(bird_y < wall_y[i] || bird_y + SPRITE_HEIGHT > wall_y[i] + wall_gap)) {
display.display();
tone(BUZZER_PIN, 200, 500); // Play crash tone
delay(500);
game_state = 3; // Move to game over state
}
wall_x[i] -= 4;
}
boldTextAtCenter(0, String(score));
display.display();
delay(game_speed);
if (digitalRead(END_BUTTON) == LOW) {
game_state = 3; // Move to game over state
}
}
void showLevelCompleteScreen() {
display.clearDisplay();
textAtCenter(display.height() / 2 - 8, "LEVEL COMPLETE");
boldTextAtCenter(display.height() / 2, "LEVEL " + String(current_level));
display.display();
delay(2000);
current_level++;
if (current_level > 3) {
game_state = 4; // Move to congratulations state
} else {
if (current_level == 2) {
game_speed = LEVEL_2_SPEED;
} else if (current_level == 3) {
game_speed = LEVEL_3_SPEED;
}
game_state = 1; // Move back to in-game state
}
resetGame(current_level, game_speed);
}
void showGameOverScreen() {
display.clearDisplay();
textAtCenter(display.height() / 4 - 8, "GAME OVER");
textAtCenter(display.height() / 4 + 8, "SCORE: " + String(score));
boldTextAtCenter(display.height() / 2 + 8, "HIGH SCORE");
boldTextAtCenter(display.height() / 2 + 16, String(high_score));
display.display();
playGameOverMelody();
delay(2000);
resetGame(current_level, game_speed);
game_state = 0;
}
void showCongratulationsScreen() {
display.clearDisplay();
textAtCenter(display.height() / 2 - 8, "CONGRATULATIONS");
textAtCenter(display.height() / 2, "YOU WON!");
boldTextAtCenter(display.height() - 16, "HIGH SCORE");
boldTextAtCenter(display.height() - 8, String(high_score));
display.display();
delay(2000);
resetGame(current_level, game_speed);
game_state = 0;
}
void screenWipe(int speed) {
for (int i = 0; i < display.height(); i += speed) {
display.fillRect(0, i, display.width(), speed, WHITE);
display.display();
}
for (int i = 0; i < display.height(); i += speed) {
display.fillRect(0, i, display.width(), speed, BLACK);
display.display();
}
}
void textAt(int x, int y, String txt) {
display.setCursor(x, y);
display.print(txt);
}
void textAtCenter(int y, String txt) {
textAt(display.width() / 2 - txt.length() * 3, y, txt);
}
void outlineTextAtCenter(int y, String txt) {
int x = display.width() / 2 - txt.length() * 3;
display.setTextColor(WHITE);
textAt(x - 1, y, txt);
textAt(x + 1, y, txt);
textAt(x, y - 1, txt);
textAt(x, y + 1, txt);
display.setTextColor(BLACK);
textAt(x, y, txt);
display.setTextColor(WHITE);
}
void boldTextAtCenter(int y, String txt) {
int x = display.width() / 2 - txt.length() * 3;
textAt(x, y, txt);
textAt(x + 1, y, txt);
}
void displayWelcomeScreen() {
display.clearDisplay();
display.setTextSize(1);
display.setCursor(0, 0);
display.println(" Aplicaciones con");
display.setCursor(0, 12);
display.println(" Sistemas Embebidos");
display.setCursor(0, 29);
display.println(" Flappy Bird Game");
display.setCursor(0, 45);
display.println("\t Selena Anamise");
display.setCursor(0, 53);
display.println("\t Stalin Sarmiento");
display.display();
playMelody();
}
void playMelody() {
int melody[] = {NOTE_C4, NOTE_D4, NOTE_E4, NOTE_F4, NOTE_G4, NOTE_A4, NOTE_B4, NOTE_C5};
int noteDurations[] = {4, 4, 4, 4, 4, 4, 4, 4};
for (int thisNote = 0; thisNote < 8; thisNote++) {
int noteDuration = 1000 / noteDurations[thisNote];
tone(BUZZER_PIN, melody[thisNote], noteDuration);
int pauseBetweenNotes = noteDuration * 1.30;
delay(pauseBetweenNotes);
noTone(BUZZER_PIN);
}
}
void playGameOverMelody() {
int melody[] = {262, 196, 196, 220, 196, 0, 247, 262};
int noteDurations[] = {4, 8, 8, 4, 4, 4, 4, 4};
for (int thisNote = 0; thisNote < 8; thisNote++) {
int noteDuration = 1000 / noteDurations[thisNote];
tone(BUZZER_PIN, melody[thisNote], noteDuration);
int pauseBetweenNotes = noteDuration * 1.30;
delay(pauseBetweenNotes);
noTone(BUZZER_PIN);
}
}