diff --git a/Firmware/platformio.ini b/Firmware/platformio.ini index d0cff6a..bb68f76 100644 --- a/Firmware/platformio.ini +++ b/Firmware/platformio.ini @@ -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 diff --git a/Firmware/src/display_task.cpp b/Firmware/src/display_task.cpp index 17bc965..cd13e8b 100644 --- a/Firmware/src/display_task.cpp +++ b/Firmware/src/display_task.cpp @@ -9,6 +9,7 @@ #include #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; } } diff --git a/Firmware/src/display_task.h b/Firmware/src/display_task.h index 4248322..e5bb04a 100644 --- a/Firmware/src/display_task.h +++ b/Firmware/src/display_task.h @@ -12,6 +12,7 @@ enum class State { CHOOSE_GIF, PLAY_GIF, SHOW_CREDITS, + PLAY_FLAPPY, }; class DisplayTask : public Task, public Logger { diff --git a/Firmware/src/flappy.cpp b/Firmware/src/flappy.cpp new file mode 100644 index 0000000..09eb2c6 --- /dev/null +++ b/Firmware/src/flappy.cpp @@ -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 +// #include +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; + } + } + } +} diff --git a/Firmware/src/flappy.h b/Firmware/src/flappy.h new file mode 100644 index 0000000..0029fb0 --- /dev/null +++ b/Firmware/src/flappy.h @@ -0,0 +1,39 @@ +#pragma once + +#include + + +#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);