FlappyBird.ino 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. // By Ponticelli Domenico.
  2. // 12NOV2020 EEPROM Fix added, modified by Zontex
  3. // https://github.com/pcelli85/M5Stack_FlappyBird_game
  4. #include <EEPROM.h>
  5. #include <M5Stack.h>
  6. #define TFTW 320 // screen width
  7. #define TFTH 240 // screen height
  8. #define TFTW2 160 // half screen width
  9. #define TFTH2 120 // half screen height
  10. // game constant
  11. #define SPEED 1
  12. #define GRAVITY 9.8
  13. #define JUMP_FORCE 2.15
  14. #define SKIP_TICKS 20.0 // 1000 / 50fps
  15. #define MAX_FRAMESKIP 5
  16. // bird size
  17. #define BIRDW 16 // bird width
  18. #define BIRDH 16 // bird height
  19. #define BIRDW2 8 // half width
  20. #define BIRDH2 8 // half height
  21. // pipe size
  22. #define PIPEW 24 // pipe width
  23. #define GAPHEIGHT 42 // pipe gap height
  24. // floor size
  25. #define FLOORH 30 // floor height (from bottom of the screen)
  26. // grass size
  27. #define GRASSH 4 // grass height (inside floor, starts at floor y)
  28. int address = 0;
  29. int maxScore = EEPROM.readInt(address);
  30. const int buttonPin = 2;
  31. // background
  32. const unsigned int BCKGRDCOL = M5.Lcd.color565(138, 235, 244);
  33. // bird
  34. const unsigned int BIRDCOL = M5.Lcd.color565(255, 254, 174);
  35. // pipe
  36. const unsigned int PIPECOL = M5.Lcd.color565(99, 255, 78);
  37. // pipe highlight
  38. const unsigned int PIPEHIGHCOL = M5.Lcd.color565(250, 255, 250);
  39. // pipe seam
  40. const unsigned int PIPESEAMCOL = M5.Lcd.color565(0, 0, 0);
  41. // floor
  42. const unsigned int FLOORCOL = M5.Lcd.color565(246, 240, 163);
  43. // grass (col2 is the stripe color)
  44. const unsigned int GRASSCOL = M5.Lcd.color565(141, 225, 87);
  45. const unsigned int GRASSCOL2 = M5.Lcd.color565(156, 239, 88);
  46. // bird sprite
  47. // bird sprite colors (Cx name for values to keep the array readable)
  48. #define C0 BCKGRDCOL
  49. #define C1 M5.Lcd.color565(195, 165, 75)
  50. #define C2 BIRDCOL
  51. #define C3 TFT_WHITE
  52. #define C4 TFT_RED
  53. #define C5 M5.Lcd.color565(251, 216, 114)
  54. static unsigned int birdcol[] = {
  55. C0, C0, C1, C1, C1, C1, C1, C0, C0, C0, C1, C1, C1, C1, C1, C0, C0, C1, C2,
  56. C2, C2, C1, C3, C1, C0, C1, C2, C2, C2, C1, C3, C1, C0, C2, C2, C2, C2, C1,
  57. C3, C1, C0, C2, C2, C2, C2, C1, C3, C1, C1, C1, C1, C2, C2, C3, C1, C1, C1,
  58. C1, C1, C2, C2, C3, C1, C1, C1, C2, C2, C2, C2, C2, C4, C4, C1, C2, C2, C2,
  59. C2, C2, C4, C4, C1, C2, C2, C2, C1, C5, C4, C0, C1, C2, C2, C2, C1, C5, C4,
  60. C0, C0, C1, C2, C1, C5, C5, C5, C0, C0, C1, C2, C1, C5, C5, C5, C0, C0, C0,
  61. C1, C5, C5, C5, C0, C0, C0, C0, C1, C5, C5, C5, C0, C0};
  62. // bird structure
  63. static struct BIRD {
  64. long x, y, old_y;
  65. long col;
  66. float vel_y;
  67. } bird;
  68. // pipe structure
  69. static struct PIPES {
  70. long x, gap_y;
  71. long col;
  72. } pipes;
  73. // score
  74. int score;
  75. // temporary x and y var
  76. static short tmpx, tmpy;
  77. // ---------------
  78. // draw pixel
  79. // ---------------
  80. // faster drawPixel method by inlining calls and using setAddrWindow and
  81. // pushColor using macro to force inlining
  82. #define drawPixel(a, b, c) \
  83. M5.Lcd.setAddrWindow(a, b, a, b); \
  84. M5.Lcd.pushColor(c)
  85. void setup() {
  86. // put your setup code here, to run once:
  87. M5.begin();
  88. M5.Power.begin();
  89. EEPROM.begin(1000);
  90. // resetMaxScore();
  91. Serial.println("last score:");
  92. Serial.println(EEPROM.readInt(address));
  93. }
  94. void loop() {
  95. // put your main code here, to run repeatedly:
  96. game_start();
  97. game_loop();
  98. game_over();
  99. }
  100. // ---------------
  101. // game loop
  102. // ---------------
  103. void game_loop() {
  104. // ===============
  105. // prepare game variables
  106. // draw floor
  107. // ===============
  108. // instead of calculating the distance of the floor from the screen height
  109. // each time store it in a variable
  110. unsigned char GAMEH = TFTH - FLOORH;
  111. // draw the floor once, we will not overwrite on this area in-game
  112. // black line
  113. M5.Lcd.drawFastHLine(0, GAMEH, TFTW, TFT_BLACK);
  114. // grass and stripe
  115. M5.Lcd.fillRect(0, GAMEH + 1, TFTW2, GRASSH, GRASSCOL);
  116. M5.Lcd.fillRect(TFTW2, GAMEH + 1, TFTW2, GRASSH, GRASSCOL2);
  117. // black line
  118. M5.Lcd.drawFastHLine(0, GAMEH + GRASSH, TFTW, TFT_BLACK);
  119. // mud
  120. M5.Lcd.fillRect(0, GAMEH + GRASSH + 1, TFTW, FLOORH - GRASSH, FLOORCOL);
  121. // grass x position (for stripe animation)
  122. long grassx = TFTW;
  123. // game loop time variables
  124. double delta, old_time, next_game_tick, current_time;
  125. next_game_tick = current_time = millis();
  126. int loops;
  127. // passed pipe flag to count score
  128. bool passed_pipe = false;
  129. // temp var for setAddrWindow
  130. unsigned char px;
  131. while (1) {
  132. loops = 0;
  133. while (millis() > next_game_tick && loops < MAX_FRAMESKIP) {
  134. // ===============
  135. // input
  136. // ===============
  137. if (M5.BtnB.wasPressed()) {
  138. // if the bird is not too close to the top of the screen apply
  139. // jump force
  140. if (bird.y > BIRDH2 * 0.5) bird.vel_y = -JUMP_FORCE;
  141. // else zero velocity
  142. else
  143. bird.vel_y = 0;
  144. }
  145. M5.update();
  146. // ===============
  147. // update
  148. // ===============
  149. // calculate delta time
  150. // ---------------
  151. old_time = current_time;
  152. current_time = millis();
  153. delta = (current_time - old_time) / 1000;
  154. // bird
  155. // ---------------
  156. bird.vel_y += GRAVITY * delta;
  157. bird.y += bird.vel_y;
  158. // pipe
  159. // ---------------
  160. pipes.x -= SPEED;
  161. // if pipe reached edge of the screen reset its position and gap
  162. if (pipes.x < -PIPEW) {
  163. pipes.x = TFTW;
  164. pipes.gap_y = random(10, GAMEH - (10 + GAPHEIGHT));
  165. }
  166. // ---------------
  167. next_game_tick += SKIP_TICKS;
  168. loops++;
  169. }
  170. // ===============
  171. // draw
  172. // ===============
  173. // pipe
  174. // ---------------
  175. // we save cycles if we avoid drawing the pipe when outside the screen
  176. if (pipes.x >= 0 && pipes.x < TFTW) {
  177. // pipe color
  178. M5.Lcd.drawFastVLine(pipes.x + 3, 0, pipes.gap_y, PIPECOL);
  179. M5.Lcd.drawFastVLine(pipes.x + 3, pipes.gap_y + GAPHEIGHT + 1,
  180. GAMEH - (pipes.gap_y + GAPHEIGHT + 1),
  181. PIPECOL);
  182. // highlight
  183. M5.Lcd.drawFastVLine(pipes.x, 0, pipes.gap_y, PIPEHIGHCOL);
  184. M5.Lcd.drawFastVLine(pipes.x, pipes.gap_y + GAPHEIGHT + 1,
  185. GAMEH - (pipes.gap_y + GAPHEIGHT + 1),
  186. PIPEHIGHCOL);
  187. // bottom and top border of pipe
  188. drawPixel(pipes.x, pipes.gap_y, PIPESEAMCOL);
  189. drawPixel(pipes.x, pipes.gap_y + GAPHEIGHT, PIPESEAMCOL);
  190. // pipe seam
  191. drawPixel(pipes.x, pipes.gap_y - 6, PIPESEAMCOL);
  192. drawPixel(pipes.x, pipes.gap_y + GAPHEIGHT + 6, PIPESEAMCOL);
  193. drawPixel(pipes.x + 3, pipes.gap_y - 6, PIPESEAMCOL);
  194. drawPixel(pipes.x + 3, pipes.gap_y + GAPHEIGHT + 6, PIPESEAMCOL);
  195. }
  196. // erase behind pipe
  197. if (pipes.x <= TFTW)
  198. M5.Lcd.drawFastVLine(pipes.x + PIPEW, 0, GAMEH, BCKGRDCOL);
  199. // bird
  200. // ---------------
  201. tmpx = BIRDW - 1;
  202. do {
  203. px = bird.x + tmpx + BIRDW;
  204. // clear bird at previous position stored in old_y
  205. // we can't just erase the pixels before and after current position
  206. // because of the non-linear bird movement (it would leave 'dirty'
  207. // pixels)
  208. tmpy = BIRDH - 1;
  209. do {
  210. drawPixel(px, bird.old_y + tmpy, BCKGRDCOL);
  211. } while (tmpy--);
  212. // draw bird sprite at new position
  213. tmpy = BIRDH - 1;
  214. do {
  215. drawPixel(px, bird.y + tmpy, birdcol[tmpx + (tmpy * BIRDW)]);
  216. } while (tmpy--);
  217. } while (tmpx--);
  218. // save position to erase bird on next draw
  219. bird.old_y = bird.y;
  220. // grass stripes
  221. // ---------------
  222. grassx -= SPEED;
  223. if (grassx < 0) grassx = TFTW;
  224. M5.Lcd.drawFastVLine(grassx % TFTW, GAMEH + 1, GRASSH - 1, GRASSCOL);
  225. M5.Lcd.drawFastVLine((grassx + 64) % TFTW, GAMEH + 1, GRASSH - 1,
  226. GRASSCOL2);
  227. // ===============
  228. // collision
  229. // ===============
  230. // if the bird hit the ground game over
  231. if (bird.y > GAMEH - BIRDH) break;
  232. // checking for bird collision with pipe
  233. if (bird.x + BIRDW >= pipes.x - BIRDW2 &&
  234. bird.x <= pipes.x + PIPEW - BIRDW) {
  235. // bird entered a pipe, check for collision
  236. if (bird.y < pipes.gap_y ||
  237. bird.y + BIRDH > pipes.gap_y + GAPHEIGHT)
  238. break;
  239. else
  240. passed_pipe = true;
  241. }
  242. // if bird has passed the pipe increase score
  243. else if (bird.x > pipes.x + PIPEW - BIRDW && passed_pipe) {
  244. passed_pipe = false;
  245. // erase score with background color
  246. M5.Lcd.setTextColor(BCKGRDCOL);
  247. M5.Lcd.setCursor(TFTW2, 4);
  248. M5.Lcd.print(score);
  249. // set text color back to white for new score
  250. M5.Lcd.setTextColor(TFT_WHITE);
  251. // increase score since we successfully passed a pipe
  252. score++;
  253. }
  254. // update score
  255. // ---------------
  256. M5.Lcd.setCursor(TFTW2, 4);
  257. M5.Lcd.print(score);
  258. }
  259. // add a small delay to show how the player lost
  260. delay(1200);
  261. }
  262. // ---------------
  263. // game start
  264. // ---------------
  265. void game_start() {
  266. M5.Lcd.fillScreen(TFT_BLACK);
  267. M5.Lcd.fillRect(10, TFTH2 - 20, TFTW - 20, 1, TFT_WHITE);
  268. M5.Lcd.fillRect(10, TFTH2 + 32, TFTW - 20, 1, TFT_WHITE);
  269. M5.Lcd.setTextColor(TFT_WHITE);
  270. M5.Lcd.setTextSize(3);
  271. // half width - num char * char width in pixels
  272. M5.Lcd.setCursor(TFTW2 - (6 * 9), TFTH2 - 16);
  273. M5.Lcd.println("FLAPPY");
  274. M5.Lcd.setTextSize(3);
  275. M5.Lcd.setCursor(TFTW2 - (6 * 9), TFTH2 + 8);
  276. M5.Lcd.println("-BIRD-");
  277. M5.Lcd.setTextSize(2);
  278. M5.Lcd.setCursor(10, TFTH2 - 36);
  279. M5.Lcd.println("M5Stack");
  280. M5.Lcd.setCursor(TFTW2 - (17 * 9), TFTH2 + 36);
  281. M5.Lcd.println("Premi il bottone centrale");
  282. while (1) {
  283. // wait for push button
  284. if (M5.BtnB.wasPressed()) {
  285. break;
  286. }
  287. M5.update();
  288. }
  289. // init game settings
  290. game_init();
  291. }
  292. void game_init() {
  293. // clear screen
  294. M5.Lcd.fillScreen(BCKGRDCOL);
  295. // reset score
  296. score = 0;
  297. // init bird
  298. bird.x = 144;
  299. bird.y = bird.old_y = TFTH2 - BIRDH;
  300. bird.vel_y = -JUMP_FORCE;
  301. tmpx = tmpy = 0;
  302. // generate new random seed for the pipe gape
  303. randomSeed(analogRead(0));
  304. // init pipe
  305. pipes.x = 0;
  306. pipes.gap_y = random(20, TFTH - 60);
  307. }
  308. // ---------------
  309. // game over
  310. // ---------------
  311. void game_over() {
  312. M5.Lcd.fillScreen(TFT_BLACK);
  313. maxScore = EEPROM.readInt(address);
  314. if (score > maxScore) {
  315. EEPROM.writeInt(address, score);
  316. EEPROM.commit();
  317. maxScore = score;
  318. M5.Lcd.setTextColor(TFT_RED);
  319. M5.Lcd.setTextSize(2);
  320. M5.Lcd.setCursor(TFTW2 - (13 * 6), TFTH2 - 26);
  321. M5.Lcd.println("NEW HIGHSCORE");
  322. }
  323. M5.Lcd.setTextColor(TFT_WHITE);
  324. M5.Lcd.setTextSize(3);
  325. // half width - num char * char width in pixels
  326. M5.Lcd.setCursor(TFTW2 - (9 * 9), TFTH2 - 6);
  327. M5.Lcd.println("GAME OVER");
  328. M5.Lcd.setTextSize(2);
  329. M5.Lcd.setCursor(10, 10);
  330. M5.Lcd.print("score: ");
  331. M5.Lcd.print(score);
  332. M5.Lcd.setCursor(TFTW2 - (12 * 6), TFTH2 + 18);
  333. M5.Lcd.println("press button");
  334. M5.Lcd.setCursor(10, 28);
  335. M5.Lcd.print("Max Score:");
  336. M5.Lcd.print(maxScore);
  337. while (1) {
  338. // wait for push button
  339. if (M5.BtnB.wasPressed()) {
  340. break;
  341. }
  342. M5.update();
  343. }
  344. }
  345. void resetMaxScore() {
  346. EEPROM.writeInt(address, 0);
  347. EEPROM.commit();
  348. }