As most of you will know, in 2013 the App "Flappy Bird" made by Dong Nguyen from Vietnam was published, and only two years later, an Arduino clone made by Themistokle Benetatos was published. Some people reported difficulties because authors of the TFT libraries were mixing upper-case and lower-case in function names and some other reasons. I was not happy with the clone of Themistikle because his code was hard to read and modify, and his "bird" was only 8x8 pixels small. That is why I decided to make one of my own. Opposite to Dong and Themistokle, I added a second button for "DOWN". If you want to, you can replace that button by gravity. As you can see, I had to write everything from scratch. And I had no time to translate the comments.
Edit:
You will find an updated version in the Attachment section (flappy13.zip).
The connection to the TFT display is described in the TFT file. The buttons have to be connected to pins 2 and 3. If you run out of GND pins, you may use pin 4 instead. In case your Flappy will be used frequently, I strongly recommend to replace the jumper wires by soldering the wires (different pins selected in flappy13).
The source files:/*
file name: flappy11.ino
Version 11:
Kollisionserkennung
Zum Neustart BEIDE Buttons druecken.
Die x-Position von Bird wurde auf 30
verlegt, damit der Bird eine Chance hat.
Um das Rechteck zu erkennen, in dem der
Bird lebt, sind die Farben der Ecken
auf SCHWARZ (M statt N) gesetzt.
*/
#include "colors.h"
#include "display.h"
#include "bird.h"
#include "pipe.h"
#include "buttons.h"
void setup() {
Serial.begin(115200);
Serial.println(__FILE__); // sonst wird nichts gedruckt
initDisplay();
initButtons();
initPipe();
drawBird((h - bh) / 2);
}
void loop() {
warte();
draw2Pipes();
// was ist die aktuelle y-Position des Bird?
byte yNeu = birdY;
// nach oben, aber vermeide anstossen
if (button(btnUp)) if (birdY > 0) yNeu = birdY - 1;
// nach unten, aber vermeide anstossen
if (button(btnDn)) if (birdY < h - bh) yNeu = birdY + 1;
// wenn geaendert, neu loeschen und neu zeichnen
drawBird(yNeu);
/*
wenn die Saeule bei rechtem Rand von Bird angekommen ist:
vergleiche oberen Rand von BIRD mit unterem
Rand der oberen Saeule: birdY > yG ? und
vergleiche unteren Rand von BIRD mit oberem
Rand der unteren Saeule: birdY+bh < yG+gap?
*/
if (xD == birdX + bw)
if ( (birdY < yG) || (birdY + bh > yG + gap) ) {
// Kollision erkannt.
drawBird(h - bh);
// warte, bis beide Buttons gedrueckt sind:
while (!button(btnUp) || !button(btnDn)) warte();
tft.fillScreen(N);
initPipe();
drawBird((h - bh) / 2);
}
}
void warte() {
const long DT = 20; // Millisekunden
static long t = millis() + DT;
while (millis() < t);
t = t + DT;
}As you can see you also will need these five #include files. This is the "bird":
/*
file name: bird.h
six colors used by bird defined in colors.h
*/
const byte bw = 32; // breite
const byte bh = 24; // hoehe
const byte birdX = 30; // x-Position von bird
byte birdY = -1; // unmoeglicher Wert, zwingt zum Neuzeichnen beim ersten Aufruf
PROGMEM const word bird[] = {
M, N, N, N, N, N, N, N, N, M, M, M, M, M, M, M, M, M, M, M, M, M, N, N, N, N, N, N, N, N, N, M,
// a lot of lines deleted for readability. Check the ZIP file.
M, N, N, N, N, N, N, N, N, N, M, M, M, M, M, M, M, M, N, N, N, N, N, N, N, N, N, N, N, N, N, M
};
void drawBird(int yNeu) {
if (yNeu == birdY) return;
tft.fillRect(birdX, birdY, bw, bh, N);
birdY = yNeu;
tft.setAddrWindow(birdX, birdY, birdX + bw - 1, birdY + bh - 1);
for (int i = 0; i < bw * bh; i++)
tft.pushColor(pgm_read_word(bird + i));
}Now see the buttons. Depending on your wiring you might run out of GND pins, so pin-4 will serve as an additional GND pin:
/*
file name: buttons.h
*/
const byte btnUp = 2; // Button fuer AUFWAERTS
const byte btnDn = 3; // Button fuer ABWAERTS
const byte ground = 4; // Ersatz fuer GND
void initButtons() {
pinMode(btnUp, INPUT_PULLUP);
pinMode(btnDn, INPUT_PULLUP);
pinMode(ground, OUTPUT);
}
// wenn Knopf "n" gedrueckt,
// liefere TRUE als Ergebnis
boolean button(int n) {
return !digitalRead(n);
}For the bird and the pipes we need some colors. here they are:
/*
file name: colors.h
*/
const word M = 0x0000; // 0 = schwarz
const word N = 0xF800; // 1 = blau
const word O = 0x001F; // 2 = rot
const word P = 0x07FF; // 3 = gelb
const word Q = 0x03FF; // 4 = orange
const word R = 0xFFFF; // 5 = weissNow, let us have a look at the display. Actually, in the ARDUINO IDE, the TFT library is marked as RETIRED. But it still works. You also can use the ADAFRUIT TFT library.
/*
file name: display.h
*/
#include <TFT.h>
#define cs 10
#define dc 9
#define rst 8
TFT tft = TFT(cs, dc, rst);
byte w, h;
void initDisplay() {
tft.initR(3);
tft.setRotation(1);
tft.fillScreen(N);
w = tft.width();
h = tft.height();
}And this is for the pipes:
/*
file name: pipe.h
*/
const byte pW = 32; // Breite von Pipe
const byte gap = bh + 5; // Spalt Hoehe
byte xD; // linker Rand von Pipe
byte xE; // rechter Rand von Pipe
byte yG; // Position von Gap
void initPipe() {
xD = w;
yG = h / 4 + random(h / 2);
// Linie am linken Bildrand loeschen:
tft.drawFastVLine(0, 0, h, N);
}
void draw2Pipes() {
tft.drawRect(xD, 0, pW, yG, ST7735_GREEN);
tft.drawRect(xD, yG + gap, pW, h, ST7735_GREEN);
// wenn die Saeule den linken Rand erreicht hat,
// muss eine neue erzeugt werden:
if (xD == 0) initPipe(); // linker Rand von Pipe
xE = xD + pW; // rechte Linie von Pipe loeschen:
if (xE > w) xE = xE - w;
if (xE <= pW) tft.drawFastVLine(xE, 0, h, N);
else {
tft.drawFastVLine(xE, 0, yG, N);
tft.drawFastVLine(xE, yG + gap, h, N);
}
xD--;
}Bear in mind that there are some dependencies between these files so you cannot change their order.
If you are not happy using analog pins for digital purpose, change them as you like and feel free to modify whatever you want!


_ztBMuBhMHo.jpg?auto=compress%2Cformat&w=48&h=48&fit=fill&bg=ffffff)




Comments