// Include the libraries we need to communicate with the display
// for moving monkey: https://mischianti.org/2021/07/14/ssd1306-oled-display-draw-images-splash-and-animations-2/
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
#define TFT_DC 9 //49 bei mega
#define TFT_CS 10 //53 bei mega
#define TFT_rst 40
#define TFT_MOSI 51
#define TFT_MISO 50
#define TFT_CLK 52
#define STARTING_SCREEN 2
#define txt_size 2
//Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC,TFT_MOSI, TFT_CLK, TFT_rst, TFT_MISO);
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC);
// pin the flap button is attached to
#define FLAP_BUTTON 2 //übernimmt funktion des start button
//#define START_BUTTON 3 // start button pin
#define PAUSE_BUTTON 4 // pause button pin
#define RESUME_BUTTON 5 // resume button pin
// Initialise 'sprites'
#define SPRITE_HEIGHT 13
#define SPRITE_WIDTH 32
// Two frames of animation
const uint8_t PROGMEM batman[] = {
// 'th', 108x61px
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xbf, 0xdf, 0xff, 0xff,
0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0x9f, 0x8f, 0xff, 0xff, 0xff, 0x80,
0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0x1f, 0xff, 0x0f, 0x8f, 0xff, 0x8f, 0xff, 0xe0, 0x00, 0x00,
0x00, 0x01, 0xff, 0xf0, 0x3f, 0xff, 0x00, 0x0f, 0xff, 0xc0, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x03,
0xff, 0x80, 0x3f, 0xff, 0x00, 0x0f, 0xff, 0xc0, 0x1f, 0xfe, 0x00, 0x00, 0x00, 0x0f, 0xfe, 0x00,
0x3f, 0xff, 0x00, 0x0f, 0xff, 0xe0, 0x07, 0xff, 0x00, 0x00, 0x00, 0x1f, 0xf8, 0x00, 0x3f, 0xff,
0x00, 0x0f, 0xff, 0xc0, 0x00, 0xff, 0x80, 0x00, 0x00, 0x3f, 0xe0, 0x00, 0x3f, 0xff, 0x00, 0x07,
0xff, 0xc0, 0x00, 0x7f, 0xc0, 0x00, 0x00, 0x7f, 0x80, 0x00, 0x1f, 0xfe, 0x00, 0x07, 0xff, 0x80,
0x00, 0x1f, 0xe0, 0x00, 0x00, 0xff, 0x00, 0x00, 0x0f, 0xfe, 0x00, 0x07, 0xff, 0x00, 0x00, 0x0f,
0xf0, 0x00, 0x01, 0xfe, 0x00, 0x00, 0x03, 0xfe, 0x00, 0x07, 0xfe, 0x00, 0x00, 0x07, 0xf8, 0x00,
0x01, 0xfc, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x60, 0x00, 0x00, 0x03, 0xfc, 0x00, 0x03, 0xf8,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xfc, 0x00, 0x03, 0xf8, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x07, 0xf0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x07, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x07, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xfe, 0x00, 0x07, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x7e, 0x00, 0x07, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00,
0x07, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x07, 0xf0,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x03, 0xf0, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x03, 0xf0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x01, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, 0xfc, 0x00, 0x01, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0xf8, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
0xf0, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf0, 0x00,
0x00, 0x3e, 0x00, 0x07, 0xf0, 0x3c, 0x00, 0x03, 0xc0, 0xff, 0x00, 0x07, 0xe0, 0x00, 0x00, 0x1f,
0x80, 0x0f, 0xfc, 0x7f, 0x00, 0x07, 0xe3, 0xff, 0x00, 0x0f, 0xc0, 0x00, 0x00, 0x0f, 0xc0, 0x0f,
0xff, 0xff, 0x80, 0x1f, 0xff, 0xff, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x03, 0xe0, 0x0f, 0xff, 0xff,
0xc0, 0x1f, 0xff, 0xff, 0x80, 0x7e, 0x00, 0x00, 0x00, 0x01, 0xf8, 0x0f, 0xff, 0xff, 0xe0, 0x3f,
0xff, 0xff, 0x01, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x0f, 0xff, 0xff, 0xe0, 0x7f, 0xff, 0xff,
0x07, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xc7, 0xff, 0xff, 0xf0, 0xff, 0xff, 0xfe, 0x1f, 0xc0,
0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xfe, 0xfe, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff,
0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff,
0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
// Game variables
#define GAME_SPEED 50
int game_state = STARTING_SCREEN; // 0 = game over screen, 1 = in game
int score = 0; // current game score
int high_score = 0; // highest score since the nano was reset
int bird_x = (int)tft.height() / 4; // birds x position (along) - initialised to 1/4 the way along the screen
int bird_y; // birds 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 = 70; // size of the wall wall_gap in pixels
int wall_width = 20; // width of the wall in pixels
void setup() {
tft.begin();
tft.setRotation(1); //für horizontale Darstellung
tft.setTextSize(txt_size);
pinMode(PAUSE_BUTTON, INPUT_PULLUP);
pinMode(FLAP_BUTTON, INPUT_PULLUP);
//tft.drawBitmap(32, 32, batman, 32, 13, ILI9341_YELLOW);
// Initialise the random number generator
randomSeed(analogRead(0));
}
void loop() {
switch (game_state) {
case STARTING_SCREEN:
startingScreen();
break;
case 0:
inGame();
break;
case 1:
gameOverScreen();
break;
case 3:
pauseScreen();
break;
}
}
void startingScreen() {
textAtCenter(1, "Flappy BATMAN");
textAtCenter(tft.height() / 2 - 8, "Press X to start");
textAtCenter(tft.height() - 40, "HIGH SCORE");
textAtCenter(tft.height() - 15, String(high_score));
// wait until the user presses the button
while (digitalRead(FLAP_BUTTON) == HIGH);
del_textAtCenter(1, "Flappy BATMAN");
del_textAtCenter(tft.height() / 2 - 8, "Press X to start");
del_textAtCenter(tft.height() - 40, "HIGH SCORE");
del_textAtCenter(tft.height() - 15, String(high_score));
// start the game
game_state = 0;
}
void inGame() {
// in game
// If the flap button is currently pressed, reduce the downward force on the bird a bit.
// Once this foce goes negative the bird goes up, otherwise it falls towards the ground
// gaining speed
if (digitalRead(FLAP_BUTTON) == LOW) {
momentum = -4;
}
// increase the downward force on the bird
momentum += 1;
// add the downward force to the bird position to determine it's new position
bird_y += momentum;
// make sure the bird doesn't fly off the top of the screen
if (bird_y < 0 ) {
bird_y = 0;
}
// make sure the bird doesn't fall off the bottom of the screen
// give it a slight positive lift so it 'waddles' along the ground.
if (bird_y >= tft.width() - SPRITE_HEIGHT) {
game_state = 1;
}
// display the bird
// if the momentum on the bird is negative the bird is going up!
tft.drawBitmap(bird_x, bird_y, batman, SPRITE_WIDTH, SPRITE_HEIGHT, ILI9341_YELLOW);
// now we draw the walls and see if the player has hit anything
//for (int i = 0 ; i < 2; i++) {
// draw the top half of the wall
tft.fillRect(wall_x[0], 0, wall_width, wall_y[0], ILI9341_WHITE);
// draw the bottom half of the wall
tft.fillRect(wall_x[0], wall_y[0] + wall_gap, wall_width, tft.width() - wall_y[0] - wall_gap, ILI9341_WHITE);
// if the wall has hit the edge of the screen
// reset it back to the other side with a new gap position
if (wall_x[0] < 0) {
//an einer randomisierten Stelle wird die y-pos der Lücke bestimmt
tft.fillRect(wall_x[0], 0, wall_width, wall_y[0], ILI9341_BLACK);
tft.fillRect(wall_x[0], wall_y[0] + wall_gap, wall_width, tft.width() - wall_y[0] - wall_gap, ILI9341_BLACK);
wall_y[0] = random(0, tft.height() - wall_gap);
wall_x[0] = tft.height();
tft.fillRect(wall_x[0], 0, wall_width, wall_y[0], ILI9341_BLACK);
tft.fillRect(wall_x[0], wall_y[0] + wall_gap, wall_width, tft.width() - wall_y[0] - wall_gap, ILI9341_BLACK);
}
// if the bird has passed the wall, update the score
if (wall_x[0] == bird_x) {
score++;
// highscore is whichever is bigger, the current high score or the current score
high_score = max(score, high_score);
}
// if the bird is level with the wall and not level with the gap - game over!
if (
(bird_x + SPRITE_WIDTH > wall_x[0] && bird_x < wall_x[0] + wall_width) // level with wall
&&
(bird_y < wall_y[0] || bird_y + SPRITE_HEIGHT > wall_y[0] + wall_gap) // not level with the gap
) {
// display the crash and pause 1/2 a second
//display.display();
delay(500);
// switch to game over state
game_state = 1;
}
// move the wall left 4 pixels
//remove old sprit and walls
//delay(GAME_SPEED);
tft.drawBitmap(bird_x, bird_y, batman, SPRITE_WIDTH, SPRITE_HEIGHT, ILI9341_BLACK);
//for (int i = 0 ; i < 2; i++) {
// draw the top half of the wall
tft.fillRect(wall_x[0], 0, wall_width, wall_y[0], ILI9341_BLACK);
// draw the bottom half of the wall
tft.fillRect(wall_x[0], wall_y[0] + wall_gap, wall_width, tft.width() - wall_y[0] - wall_gap, ILI9341_BLACK);
//}
wall_x[0] -= 2;
//}
// display the current score
//boldTextAtCenter(0, (String)score);
if (digitalRead(PAUSE_BUTTON) == LOW) {
game_state = 3; // Change game state to pause screen
}
// now display everything to the user and wait a bit to keep things playable
//display.display();
delay(100);
}
void gameOverScreen() {
// game over screen
screenWipe(10);
textAtCenter(tft.height() / 2 - 15, "GAME OVER");
textAtCenter(tft.height() / 2 - 5, String(score));
boldTextAtCenter(tft.height() - 20, "HIGH SCORE");
boldTextAtCenter(tft.height() - 10, String(high_score));
//display.display();
// wait while the user stops pressing the button
while (digitalRead(FLAP_BUTTON) == LOW);
// setup a new game
bird_y = tft.width() / 2;
momentum = -4;
wall_x[0] = tft.height() ;
wall_y[0] = tft.width() / 2 - wall_gap / 2;
wall_x[1] = tft.height() + tft.height() / 2;
wall_y[1] = tft.width() / 2 - wall_gap / 1;
score = 0;
// wait until the user presses the button
while (digitalRead(FLAP_BUTTON) == HIGH);
// start a new game
screenWipe(1);
game_state = 0;
}
void pauseScreen() {
//clearDisplay(&tft);
textAtCenter(tft.height() / 2 - 8, "Game Paused");
textAtCenter(tft.height() / 2 + 8, "Score: " + String(score));
//display.display();
// Check if the resume button is pressed
if (digitalRead(RESUME_BUTTON) == LOW) {
// Wait for the button to be released
while (digitalRead(RESUME_BUTTON) == LOW);
// Display a countdown to give the player a chance to react
displayCountdown();
game_state = 0; // Change game state back to in-game
}
}
void displayCountdown() {
for (int countdown = 3; countdown > 0; countdown--) {
tft.fillScreen(ILI9341_BLACK);
// Display the current game state
tft.drawBitmap(bird_x, bird_y, batman, SPRITE_WIDTH, SPRITE_HEIGHT, ILI9341_YELLOW);
for (int i = 0 ; i < 2; i++) {
tft.fillRect(wall_x[i], 0, wall_width, wall_y[i], ILI9341_WHITE);
tft.fillRect(wall_x[i], wall_y[i] + wall_gap, wall_width, tft.height() - wall_y[i] + wall_gap, ILI9341_WHITE);
}
boldTextAtCenter(0, (String)score);
// Display the countdown timer
tft.setTextSize(2);
tft.setCursor(tft.height() / 2 - 12, tft.width() / 2 - 8);
tft.print(countdown);
tft.setTextSize(1);
//display.display();
delay(1000);
}
}
/**
* displays txt at x,y coordinates
*/
void textAt(int x, int y, String txt) {
tft.setCursor(x, y);
tft.setTextColor(ILI9341_WHITE);
tft.print(txt);
}
/**
* deletes txt at x,y coordinates
*/
void del_textAt(int x, int y, String txt) {
tft.setCursor(x, y);
tft.setTextColor(ILI9341_BLACK);
tft.print(txt);
}
/**
* displays text centered on the line
*/
void textAtCenter(int y, String txt) {
textAt(tft.width() / 2 - txt.length() * 3 * txt_size, y, txt);
}
/**
* deletes text centered on the line
*/
void del_textAtCenter(int y, String txt) {
del_textAt(tft.width() / 2 - txt.length() * 3 * txt_size, y, txt);
}
/**
* displays bold text centered on the line
*/
void boldTextAtCenter(int y, String txt) {
int x = tft.width() / 2 - txt.length() * 3 * txt_size;
textAt(x, y, txt);
textAt(x + 1, y, txt);
}
/**
* clear the screen using a wipe down animation
*/
void screenWipe(int speed) {
// progressivly fill screen with white
for (int i = 0; i < tft.width(); i += speed) {
tft.fillRect(0, i, tft.height(), speed, ILI9341_WHITE);
}
// progressively fill the screen with black
for (int i = 0; i < tft.width(); i += speed) {
tft.fillRect(0, i, tft.height(), speed, ILI9341_BLACK);
}
}