Compare commits

1 Commits
main ... flappy

Author SHA1 Message Date
f939312640 intro flappy 2024-01-17 12:20:39 +01:00
5 changed files with 422 additions and 1 deletions

View File

@@ -14,6 +14,7 @@ board = esp32doit-devkit-v1
framework = arduino
monitor_speed = 115200
upload_protocol = esptool
#monitor_port = /dev/cu.usbserial-2120
#upload_flags = -b 115200
monitor_flags =
@@ -46,6 +47,7 @@ build_flags =
-DLOAD_GLCD=1
-DLOAD_GFXFF=1
-DSPI_FREQUENCY=40000000
-DDISABLE_ALL_LIBRARY_WARNINGS=1
[env:mainOTA]
extends = env:main

View File

@@ -9,6 +9,7 @@
#include <json11.hpp>
#include "gif_player.h"
#include "flappy.h"
using namespace json11;
@@ -311,7 +312,12 @@ void DisplayTask::run() {
tft_.fillScreen(TFT_BLACK);
tft_.setTextSize(2);
tft_.setTextDatum(TC_DATUM);
tft_.drawString("Merry Christmas!", center, 10);
if (isChristmas()){
tft_.drawString("Merry Christmas!", center, 10);
}
else {
tft_.drawString("Credits", center, 10);
}
tft_.setTextSize(1);
tft_.drawString("Designed and handmade", center, 50);
tft_.drawString("by Scott Bezek", center, 60);
@@ -364,6 +370,28 @@ void DisplayTask::run() {
tft_.fillScreen(TFT_BLACK);
delay(200);
}
if (left_button){
main_task_.setOtaEnabled(false);
state = State::PLAY_FLAPPY;
tft_.fillScreen(TFT_BLACK);
tft_.setTextSize(2);
tft_.setTextDatum(TC_DATUM);
tft_.drawString("Lets Flap!", tft_.width()/2, 10);
tft_.setTextSize(1);
delay(200);
}
break;
case State::PLAY_FLAPPY:
if (right_button) {
// Exit credits
main_task_.setOtaEnabled(false);
state = State::CHOOSE_GIF;
tft_.fillScreen(TFT_BLACK);
delay(200);
return;
}
game_loop(left_button);
break;
}
}

View File

@@ -12,6 +12,7 @@ enum class State {
CHOOSE_GIF,
PLAY_GIF,
SHOW_CREDITS,
PLAY_FLAPPY,
};
class DisplayTask : public Task<DisplayTask>, public Logger {

351
Firmware/src/flappy.cpp Normal file
View File

@@ -0,0 +1,351 @@
#include "flappy.h"
// By Ponticelli Domenico.
// 12NOV2020 EEPROM Working now, Modified by Zontex
// https://github.com/pcelli85/M5Stack_FlappyBird_game
// #include <EEPROM.h>
// #include <M5StickC.h>
TFT_eSPI tft_;
int address = 0;
int maxScore = 0; // EEPROM.readInt(address);
const int buttonPin = 2;
// background
const unsigned int BCKGRDCOL = tft_.color565(138, 235, 244);
// bird
const unsigned int BIRDCOL = tft_.color565(255, 254, 174);
// pipe
const unsigned int PIPECOL = tft_.color565(99, 255, 78);
// pipe highlight
const unsigned int PIPEHIGHCOL = tft_.color565(250, 255, 250);
// pipe seam
const unsigned int PIPESEAMCOL = tft_.color565(0, 0, 0);
// floor
const unsigned int FLOORCOL = tft_.color565(246, 240, 163);
// grass (col2 is the stripe color)
const unsigned int GRASSCOL = tft_.color565(141, 225, 87);
const unsigned int GRASSCOL2 = tft_.color565(156, 239, 88);
// bird sprite
// bird sprite colors (Cx name for values to keep the array readable)
#define C0 BCKGRDCOL
#define C1 tft_.color565(195, 165, 75)
#define C2 BIRDCOL
#define C3 TFT_WHITE
#define C4 TFT_RED
#define C5 tft_.color565(251, 216, 114)
static unsigned int birdcol[] = {
C0, C0, C1, C1, C1, C1, C1, C0, C0, C0, C1, C1, C1, C1, C1, C0, C0, C1, C2,
C2, C2, C1, C3, C1, C0, C1, C2, C2, C2, C1, C3, C1, C0, C2, C2, C2, C2, C1,
C3, C1, C0, C2, C2, C2, C2, C1, C3, C1, C1, C1, C1, C2, C2, C3, C1, C1, C1,
C1, C1, C2, C2, C3, C1, C1, C1, C2, C2, C2, C2, C2, C4, C4, C1, C2, C2, C2,
C2, C2, C4, C4, C1, C2, C2, C2, C1, C5, C4, C0, C1, C2, C2, C2, C1, C5, C4,
C0, C0, C1, C2, C1, C5, C5, C5, C0, C0, C1, C2, C1, C5, C5, C5, C0, C0, C0,
C1, C5, C5, C5, C0, C0, C0, C0, C1, C5, C5, C5, C0, C0};
// bird structure
static struct BIRD
{
long x, y, old_y;
long col;
float vel_y;
} bird;
// pipe structure
static struct PIPES
{
long x, gap_y;
long col;
} pipes;
// score
int score;
// temporary x and y var
static short tmpx, tmpy;
// ---------------
// draw pixel
// ---------------
// faster drawPixel method by inlining calls and using setAddrWindow and
// pushColor using macro to force inlining
#define drawPixel(a, b, c) \
tft_.setAddrWindow(a, b, a, b); \
tft_.pushColor(c)
// ---------------
// game loop
// ---------------
double delta, old_time, next_game_tick, current_time;
FlpState state;
unsigned char GAMEH = TFTH - FLOORH;
long grassx = TFTW;
int loops;
// passed pipe flag to count score
bool passed_pipe = false;
// temp var for setAddrWindow
unsigned char px;
void game_loop(bool button)
{
switch (state)
{
case FlpState::START_FLAPPY:
{
tft_.fillScreen(TFT_BLACK);
tft_.fillRect(0, TFTH2 - 10, TFTW, 1, TFT_WHITE);
tft_.fillRect(0, TFTH2 + 15, TFTW, 1, TFT_WHITE);
tft_.setTextColor(TFT_WHITE);
tft_.setTextSize(1);
// half width - num char * char width in pixels
tft_.setCursor(TFTW2 - 15, TFTH2 - 6);
tft_.println("FLAPPY");
tft_.setTextSize(1);
tft_.setCursor(TFTW2 - 15, TFTH2 + 6);
tft_.println("-BIRD-");
tft_.setTextSize(1);
tft_.setCursor(TFTW2 - 40, TFTH2 + 21);
tft_.println("please press home");
if (button)
{
state = FlpState::INIT_FLAPPY;
}
}
break;
case FlpState::INIT_FLAPPY:
{
// clear screen
tft_.fillScreen(BCKGRDCOL);
// reset score
score = 0;
// init bird
bird.x = 30;
bird.y = bird.old_y = TFTH2 - BIRDH;
bird.vel_y = -JUMP_FORCE;
tmpx = tmpy = 0;
// generate new random seed for the pipe gape
randomSeed(analogRead(0));
// init pipe
pipes.x = 0;
pipes.gap_y = random(20, TFTH - 60);
// ===============
// prepare game variables
// draw floor
// ===============
// instead of calculating the distance of the floor from the screen height
// each time store it in a variable
// draw the floor once, we will not overwrite on this area in-game
// black line
tft_.drawFastHLine(0, GAMEH, TFTW, TFT_BLACK);
// grass and stripe
tft_.fillRect(0, GAMEH + 1, TFTW2, GRASSH, GRASSCOL);
tft_.fillRect(TFTW2, GAMEH + 1, TFTW2, GRASSH, GRASSCOL2);
// black line
tft_.drawFastHLine(0, GAMEH + GRASSH, TFTW, TFT_BLACK);
// mud
tft_.fillRect(0, GAMEH + GRASSH + 1, TFTW, FLOORH - GRASSH, FLOORCOL);
// grass x position (for stripe animation)
// game loop time variables
next_game_tick = current_time = millis();
loops = 0;
state = FlpState::RUN_FLAPPY;
}
break;
case FlpState::RUN_FLAPPY:
{
if (millis() > next_game_tick && loops < MAX_FRAMESKIP)
{
if (button)
{
// while(digitalRead(M5_BUTTON_HOME) == LOW);
if (bird.y > BIRDH2 * 0.5)
bird.vel_y = -JUMP_FORCE;
// else zero velocity
else
bird.vel_y = 0;
}
// ===============
// update
// ===============
// calculate delta time
// ---------------
old_time = current_time;
current_time = millis();
delta = (current_time - old_time) / 1000;
// bird
// ---------------
bird.vel_y += GRAVITY * delta;
bird.y += bird.vel_y;
// pipe
// ---------------
pipes.x -= SPEED;
// if pipe reached edge of the screen reset its position and gap
if (pipes.x < -PIPEW)
{
pipes.x = TFTW;
pipes.gap_y = random(10, GAMEH - (10 + GAPHEIGHT));
}
// ---------------
next_game_tick += SKIP_TICKS;
loops++;
}
else
{
return;
}
// ===============
// draw
// ===============
// pipe
// ---------------
// we save cycles if we avoid drawing the pipe when outside the screen
if (pipes.x >= 0 && pipes.x < TFTW)
{
// pipe color
tft_.drawFastVLine(pipes.x + 3, 0, pipes.gap_y, PIPECOL);
tft_.drawFastVLine(pipes.x + 3, pipes.gap_y + GAPHEIGHT + 1,
GAMEH - (pipes.gap_y + GAPHEIGHT + 1),
PIPECOL);
// highlight
tft_.drawFastVLine(pipes.x, 0, pipes.gap_y, PIPEHIGHCOL);
tft_.drawFastVLine(pipes.x, pipes.gap_y + GAPHEIGHT + 1,
GAMEH - (pipes.gap_y + GAPHEIGHT + 1),
PIPEHIGHCOL);
// bottom and top border of pipe
drawPixel(pipes.x, pipes.gap_y, PIPESEAMCOL);
drawPixel(pipes.x, pipes.gap_y + GAPHEIGHT, PIPESEAMCOL);
// pipe seam
drawPixel(pipes.x, pipes.gap_y - 6, PIPESEAMCOL);
drawPixel(pipes.x, pipes.gap_y + GAPHEIGHT + 6, PIPESEAMCOL);
drawPixel(pipes.x + 3, pipes.gap_y - 6, PIPESEAMCOL);
drawPixel(pipes.x + 3, pipes.gap_y + GAPHEIGHT + 6, PIPESEAMCOL);
}
#if 1
// erase behind pipe
if (pipes.x <= TFTW)
tft_.drawFastVLine(pipes.x + PIPEW, 0, GAMEH, BCKGRDCOL);
// tft_.drawFastVLine(pipes.x, 0, GAMEH, BCKGRDCOL);
// PIPECOL
#endif
// bird
// ---------------
tmpx = BIRDW - 1;
do
{
px = bird.x + tmpx + BIRDW;
// clear bird at previous position stored in old_y
// we can't just erase the pixels before and after current position
// because of the non-linear bird movement (it would leave 'dirty'
// pixels)
tmpy = BIRDH - 1;
do
{
drawPixel(px, bird.old_y + tmpy, BCKGRDCOL);
} while (tmpy--);
// draw bird sprite at new position
tmpy = BIRDH - 1;
do
{
drawPixel(px, bird.y + tmpy, birdcol[tmpx + (tmpy * BIRDW)]);
} while (tmpy--);
} while (tmpx--);
// save position to erase bird on next draw
bird.old_y = bird.y;
// grass stripes
// ---------------
grassx -= SPEED;
if (grassx < 0)
grassx = TFTW;
tft_.drawFastVLine(grassx % TFTW, GAMEH + 1, GRASSH - 1, GRASSCOL);
tft_.drawFastVLine((grassx + 64) % TFTW, GAMEH + 1, GRASSH - 1,
GRASSCOL2);
// ===============
// collision
// ===============
// if the bird hit the ground game over
if (bird.y > GAMEH - BIRDH)
break;
// checking for bird collision with pipe
if (bird.x + BIRDW >= pipes.x - BIRDW2 &&
bird.x <= pipes.x + PIPEW - BIRDW)
{
// bird entered a pipe, check for collision
if (bird.y < pipes.gap_y ||
bird.y + BIRDH > pipes.gap_y + GAPHEIGHT)
break;
else
passed_pipe = true;
}
// if bird has passed the pipe increase score
else if (bird.x > pipes.x + PIPEW - BIRDW && passed_pipe)
{
passed_pipe = false;
// erase score with background color
tft_.setTextColor(BCKGRDCOL);
tft_.setCursor(TFTW2, 4);
tft_.print(score);
// set text color back to white for new score
tft_.setTextColor(TFT_WHITE);
// increase score since we successfully passed a pipe
score++;
}
// update score
// ---------------
tft_.setCursor(2, 4);
tft_.print(score);
delay(2000);
state = FlpState::GAME_OVER;
}
break;
case FlpState::GAME_OVER:
{
tft_.fillScreen(TFT_BLACK);
// maxScore = EEPROM.readInt(address);
if (score > maxScore)
{
// EEPROM.writeInt(address, score);
// EEPROM.commit();
maxScore = score;
tft_.setTextColor(TFT_RED);
tft_.setTextSize(1);
tft_.setCursor(0, TFTH2 - 16);
tft_.println("NEW HIGHSCORE");
}
tft_.setTextColor(TFT_WHITE);
tft_.setTextSize(1);
// half width - num char * char width in pixels
tft_.setCursor(TFTW2 - 25, TFTH2 - 6);
tft_.println("GAME OVER");
tft_.setTextSize(1);
tft_.setCursor(1, 10);
tft_.print("score: ");
tft_.print(score);
tft_.setCursor(5, TFTH2 + 6);
tft_.println("press button");
tft_.setCursor(1, 21);
tft_.print("Max Score:");
tft_.print(maxScore);
if (button)
{
state = FlpState::INIT_FLAPPY;
}
}
}
}

39
Firmware/src/flappy.h Normal file
View File

@@ -0,0 +1,39 @@
#pragma once
#include <TFT_eSPI.h>
#define TFTW 240 // screen width
#define TFTH 135 // screen height
#define TFTW2 120 // half screen width
#define TFTH2 67 // half screen height
// game constant
#define SPEED 1
#define GRAVITY 9.8
#define JUMP_FORCE 2.15
#define SKIP_TICKS 20.0 // 1000 / 50fps
#define MAX_FRAMESKIP 5
// bird size
#define BIRDW 8 // bird width
#define BIRDH 8 // bird height
#define BIRDW2 4 // half width
#define BIRDH2 4 // half height
// pipe size
#define PIPEW 15 // pipe width
#define GAPHEIGHT 30 // pipe gap height
// floor size
#define FLOORH 20 // floor height (from bottom of the screen)
// grass size
#define GRASSH 4 // grass height (inside floor, starts at floor y)
enum class FlpState {
START_FLAPPY,
INIT_FLAPPY,
RUN_FLAPPY,
GAME_OVER,
};
void game_loop(bool button);