Flappy chip (Arduino game)

Introduction

Flappy Chip is a small game running on the Arduino Uno
board (or standalone atmega328 microcontroller). The purpose of the game is to make the chip pass by as many tubes as you can without hitting them, by pressing the button which makes the chip climb (otherwise it falls). The score represents the number of tube pairs that have been avoided.

Hardware

The graphic LCD display that I used comes from an old Nokia 3310 cellphone. It is connected to the board using 5 resistive voltage deviders (one for each pin) which act as logic level converters. They are needed because the LCD driver works on 3.3 V whereas the microcontroller works on 5 V. The pins used for the display are digital pins 3 to 7 which have the following functions :

  • pin 3 – LCD reset (RST)
  • pin 4 – LCD chip select (CS)
  • pin 5 – Data/Command select (D/C)
  • pin 6 – Serial data out (DIN)
  • pin 7 – Serial clock out (SCLK)

The button is connected to pin 2, which is pulled up internally. When the button is pressed, it pulls the pin to the ground, which triggers an interrupt that changes the position of the chip.

 Software

Each graphic element was drawn in Paint, saved as a monochrome bitmap and converted to hex values using Image2Code. The bitmaps are stored in the Flash memory of the microcontroller. In order to put elements in front of the background, I had to make solid black bitmaps that are dislpayed negatively so that they clear the pixels of the background where the elements need to be displayed.(otherwise the front elements and the background mix) The whole graphic section is based on the Adafruit PCD8544 and GFX libraries, which makes it easy to use the Nokia display.

After displaying the startup image, the MCU waits for the user to press the button which starts the actual game. Then, the ISR that modifies the chip position is attached and the gameloop begins and runs as long as the chip ,,is alive”. Each time the gameloop is executed, the display is cleared, then the backround is displayed, followed by the chip. The maximum number of the tubes is 8 (4 pairs), since 4 sets of variables have been declared for this purpose. The game starts with one pair. The pairs are cycled in and out, gradually decreasing distance between the tubes and gradually adding more tubes until all four pairs are needed.  A series of if statements checks whether one of the tubes have gone offscreen and makes the corresponding variable false. Then if the rightmost tube is at the desired distance from the right margin of the screen, a new pair of tubes is reintroduced by reinitializing their corresponding variables. The vertical position is set at random. Another series of if statements moves the onscreen tubes to the left by one pixel. Afterwards, the tubes and the score are displayed. Then, the coordinates of the chip and the coordinates of the tubes are compared and in case of collision the alive variable is set to false and procedure dead is called which makes the chip fall. The chip is not allowed to hit the margins of the screen so if that happens it is considered ,,dead” as well. Finally, the score is incremented as each pair of tubes is avoided, the chip’s vertical position is incremented (so that it falls unless you press the button) and the delay keeps the game from running at lightning speed (or not really, with only 16 MHz clock speed). After the gameloop, the button interrupt is detached and the score is compared to the highscore (if there is one in the EEPROM). If there is no highscore, or your current score is higher than the highscore, a new higscore is written in the EEPROM so that it will not be lost when the MCU loses power. And just in case you become addicted to this game, you don’t have to worry about EEPROM wear out because a memory wear leveling algorithm writes the highscore at a different adress each time.

 The code


/*Written by Stelian Saracut and Ruxandra Avram

for the Nokia 3310 display driven by Adafruit's

PCD8544 library. Developped in Arduino version 1.5.2 */

#include <Adafruit_GFX.h>

#include <Adafruit_PCD8544.h>

#include <EEPROM.h>

Adafruit_PCD8544 display = Adafruit_PCD8544(7, 6, 5, 4, 3);

boolean alive;

//becomes false when the chip touches a tube or the upper/lower margin of the display

int x1,x2,x3,x4,h1,h2,h3,h4,count = 1;

//x1,2... for current horizontal position of each tube

//h1,2... for current vertical position of each tube

//count counts each moment of the game in order to gradually increase difficulty

boolean x1os=true,x2os=false,x3os=false,x4os=false; //true if the coresponding tube is being displayed

//the first tube appears on screen when the game starts

byte dh, dv,score;

//dh - current horizontal distance between any 2 tubes (gradually decreases)

//dv - current vertical distance between any 2 tubes (gradually decreases)

volatile byte v;

//current vertical position of the chip

//decremented in the interrupt routine triggered by the pressed button, incremented once every gameloop execution

//so that the chip falls unless you press the button

#define XCHIP 30 //x position of chip (constant)

#define DHINIT 50 //initial horizontal distance between two tubes

#define DVINIT 35 //initial vertical distance between two tubes

#define DELAY 100 //game speed

//functions that check if the chip hits the tube by comparing coordinates

boolean in1 (int l, int r, int left, int right) {

return ((r>=left)&&(l<= right));

}

boolean in2(int d, int u, int down,int up) {

return ((u<=up)||(d>= down));

}

//returns maximum value of 4 parameters

int maxim (int n1, int n2, int n3, int n4) {

int m;

m = n1;

if (n2>m) m=n2;

if (n3>m) m=n3;

if (n4>m) m=n4;

return m;

}

//bitmaps of the graphic elements

//lower tube contour

static unsigned char PROGMEM tubjos[] = {0xFF,0xF0,

0x80,0x10,

0xDA,0xB0,

0x6D,0x60,

0x5E,0x20,

0x6D,0xA0,

0x5E,0x60,

0x6D,0x20,

0x5E,0xA0,

0x6D,0x60,

0x5E,0x20,

0x6D,0xA0,

0x5E,0x60,

0x6D,0x20,

0x5E,0xA0,

0x6D,0x60,

0x5E,0x20,

0x6D,0xA0,

0x5E,0x60,

0x6D,0x20,

0x5E,0xA0,

0x6D,0x60,

0x5E,0x20,

0x6D,0x20,

0x5E,0xA0,

0x6D,0x60,

0x5E,0x20,

0x6D,0xA0,

0x5E,0x60,

0x6D,0x20,

0x5E,0xA0,

0x6D,0x60,

};

//solid color lower tube

static unsigned char PROGMEM tubjosplin[] = {0xFF,0xF0,

0xFF,0xF0,

0xFF,0xF0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

};

//upper tube contour

static unsigned char PROGMEM tubsus[] = {0x6D,0x60,

0x5E,0x20,

0x6D,0xA0,

0x5E,0x60,

0x6D,0x20,

0x5E,0xA0,

0x6D,0x60,

0x5E,0x20,

0x6D,0xA0,

0x5E,0x60,

0x6D,0x20,

0x5E,0xA0,

0x6D,0x60,

0x5E,0x20,

0x6D,0xA0,

0x5E,0xA0,

0x6D,0x60,

0x5E,0x20,

0x6D,0xA0,

0x5E,0x60,

0x6D,0x20,

0x5E,0xA0,

0x6D,0x60,

0x5E,0x20,

0x6D,0xA0,

0x5E,0x60,

0x6D,0x20,

0x5E,0xA0,

0x6D,0x60,

0xDA,0xB0,

0x80,0x10,

0xFF,0xF0,

};

//solid color upper tube

static unsigned char PROGMEM tubsusplin[] = {0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0xFF,0xF0,

0xFF,0xF0,

0xFF,0xF0};

//static background

static unsigned char PROGMEM fundal[] = {0x00,0x38,0x00,0x00,0x00,0x00,0x3C,0x00,0x00,0x03,0xC0,

0x03,0xC7,0x80,0x03,0xF0,0x01,0xC3,0x80,0x00,0x04,0x30,

0x04,0x28,0x60,0x04,0x48,0x06,0x10,0x40,0xFC,0x08,0x80,

0x1A,0x81,0x18,0x18,0x04,0x08,0x00,0x33,0x03,0x14,0x20,

0x62,0x00,0x06,0x22,0x8A,0x10,0x00,0x8C,0x08,0xA0,0x80,

0x88,0x0C,0x21,0x48,0x21,0x68,0x91,0x10,0x84,0x48,0x20,

0x92,0x20,0x00,0x80,0x28,0x82,0x01,0x00,0x01,0x42,0x80,

0x00,0x84,0x20,0x42,0x00,0x00,0x24,0x24,0x28,0x00,0x00,

0x08,0x00,0x04,0x80,0x04,0x14,0x80,0x85,0x4A,0x10,0xA0,

0x60,0x21,0x10,0x10,0x81,0x28,0x00,0x11,0x11,0x15,0x00,

0x02,0x2A,0x02,0x15,0x24,0x00,0x29,0x20,0x40,0x42,0x40,

0x00,0x8C,0x80,0x44,0x40,0x85,0x40,0x40,0x04,0x41,0x00,

0x49,0x82,0x24,0x81,0x00,0x10,0x09,0x24,0xC1,0x02,0x20,

0x02,0x04,0x21,0x02,0x09,0x28,0x41,0x04,0x04,0x12,0x00,

0x09,0x22,0x04,0x91,0x00,0x40,0x08,0x03,0x20,0x90,0x20,

0x00,0x20,0x00,0x10,0x21,0x24,0xD0,0x48,0x41,0x0C,0x80,

0x40,0x19,0x20,0x0C,0x80,0x00,0x01,0x05,0x12,0x21,0x00,

0x82,0x42,0x41,0x21,0x08,0x03,0x32,0x90,0x20,0x14,0x40,

0x08,0x28,0x84,0x10,0x50,0x48,0x40,0x08,0x4A,0x42,0x80,

0x94,0x81,0x4A,0x40,0x81,0x04,0x10,0x00,0x00,0x00,0x00,

};

//small chip contour

static unsigned char PROGMEM chip[] = {0x55,0x40,

0xFF,0xE0,

0x80,0x20,

0x80,0x60,

0x80,0x20,

0xFF,0xE0,

0x55,0x40};

//small chip solid colour

static unsigned char PROGMEM chipplin[] = {0xFF,0xE0,

0xFF,0xE0,

0xFF,0xE0,

0xFF,0xE0,

0xFF,0xE0,

0xFF,0xE0,

0xFF,0xE0};

//startup chip

static unsigned char PROGMEM start[] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,

0x63,0x18,0xC6,0x31,0x8C,0x63,0x18,0xC6,0x31,0x8C,0x60,

0x63,0x18,0xC6,0x31,0x8C,0x63,0x18,0xC6,0x31,0x8C,0x60,

0x63,0x18,0xC6,0x31,0x8C,0x63,0x18,0xC6,0x31,0x8C,0x60,

0x63,0x18,0xC6,0x31,0x8C,0x63,0x18,0xC6,0x31,0x8C,0x60,

0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xF0,

0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xF0,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0xF0,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x30,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x30,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x30,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x30,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0xF0,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,

0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xF0,

0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xF0,

0x63,0x18,0xC6,0x31,0x8C,0x63,0x18,0xC6,0x31,0x8C,0x60,

0x63,0x18,0xC6,0x31,0x8C,0x63,0x18,0xC6,0x31,0x8C,0x60,

0x63,0x18,0xC6,0x31,0x8C,0x63,0x18,0xC6,0x31,0x8C,0x60,

0x63,0x18,0xC6,0x31,0x8C,0x63,0x18,0xC6,0x31,0x8C,0x60,

0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,

};

void setup() {

Serial.begin(9600);

PORTD = B100;

display.begin();

display.setContrast(55);

display.clearDisplay();

display.drawBitmap(0, 0,start, 84, 48, 1);

display.setCursor(6,20);

display.print("FLAPPY CHIP");

display.display();

delay(3000);

display.clearDisplay();

}

void loop() {

gameLoop();

finalScore();

}

void gameLoop() {

v = 20;

x1 = 84;

x2 = 0;

x3 = 0;

x4 = 0;

score = 0;

count = 1;

x1os = true;

x2os = false;

x3os = false;

x4os = false;

dh = DHINIT;

dv = DVINIT;

beginning();

while (digitalRead(2) == HIGH) ; //waits for the button to be pressed

alive = true;

attachInterrupt(0, button, FALLING); //attaching the ISR that makes the chip climb when the button is pressed (on the falling edge of digital pin 2)

while (alive) {

randomSeed(analogRead(A1)); //feeding the random number generator a "random" value by reading an unused analog pin

dispChip(); //displays the chip

//checks if the tubes go offscreen

if (x1<-12) {

x1=0;

x1os=false;

}

if (x2<-12) {

x2=0;

x2os=false;

}

if (x3<-12) {

x3=0;

x3os=false;

}

if (x4<-12) {

x4=0;

x4os=false;

}

//reintroduces tubes

if ((x1os == false) && ((84-maxim(x1,x2,x3,x4)) == dh)) {

x1=84;

h1 = random(5,35);

x1os = true;

}

if ((x2os == false) && ((84-maxim(x1,x2,x3,x4)) == dh)) {

x2=84;

h2 = random(5,35);

x2os = true;

}

if ((x3os == false) && ((84-maxim(x1,x2,x3,x4)) == dh)) {

x3=84;

h3 = random(5,35);

x3os = true;

}

if ((x4os == false) && ((84-maxim(x1,x2,x3,x4)) == dh)) {

x4=84;

h4 = random(5,35);

x4os = true;

}

//moves the tubes to the left

if (x1os) x1--;

if (x2os) x2--;

if (x3os) x3--;

if (x4os) x4--;

count++;

dispTube();

display.print("           ");

display.print(score);

display.display();

//gradually decreases horizontal and vertical distance between tubes

if (count%50 == 0) {

dv = dv-1;

dh= dh-1;

}

//checks if the chip hits the tubes

if (x1os)

if (in1(XCHIP,XCHIP+11,x1,x1+12))

if (in2(v+7,v,h1+dv,h1)) {

dead(v);

alive = false;

}

if (x2os)

if (in1(XCHIP,XCHIP+11,x2,x2+12))

if (in2(v+7,v,h2+dv,h2)) {

dead(v);

alive = false;

}

if (x3os)

if (in1(XCHIP,XCHIP+11,x3,x3+12))

if (in2(v+7,v,h3+dv,h3)) {

dead(v);

alive = false;

}

if (x4os)

if (in1(XCHIP,XCHIP+11,x4,x4+12))

if (in2(v+7,v,h4+dv,h4)) {

dead(v);

alive = false;

}

if (v > 41) alive = false; //checks if the chip hits the lower screen margin

if ((v >=250) && (v <=255)) //checks if the chip hits the upper screen margin

dead(0);

//increments the score as you pass by each tube

if ((x1os && (x1==XCHIP))||(x2os && (x2==XCHIP))||(x3os && (x3==XCHIP))

|| (x4os && (x4==XCHIP))) score++;

v += 1; //makes the chip fall

delay(DELAY);

}

detachInterrupt(0);

}

//ISR that runs each time you press the button

void button ()  {

v -= 4;

delayMicroseconds(5000);

}

//display the chip

void dispChip () {

display.clearDisplay();

display.drawBitmap(0, 33,fundal, 84, 20, 1);

display.drawBitmap(XCHIP, v,chipplin, 11, 7, 0);

display.drawBitmap(XCHIP, v,chip, 11, 7, 1);

}

//display the tubes

void dispTube () {

if (x1os) {

display.drawBitmap(x1, h1+dv,tubjosplin, 12, 32, 0);

display.drawBitmap(x1, h1+dv,tubjos, 12, 32, 1);

display.drawBitmap(x1, -32+h1,tubsusplin, 12, 32, 0);

display.drawBitmap(x1, -32+h1,tubsus, 12, 32, 1);

}

if (x2os) {

display.drawBitmap(x2, h2+dv,tubjosplin, 12, 32, 0);

display.drawBitmap(x2, h2+dv,tubjos, 12, 32, 1);

display.drawBitmap(x2, -32+h2,tubsusplin, 12, 32, 0);

display.drawBitmap(x2, -32+h2,tubsus, 12, 32, 1);

}

if (x3os) {

display.drawBitmap(x3, h3+dv,tubjosplin, 12, 32, 0);

display.drawBitmap(x3, h3+dv,tubjos, 12, 32, 1);

display.drawBitmap(x3, -32+h3,tubsusplin, 12, 32, 0);

display.drawBitmap(x3, -32+h3,tubsus, 12, 32, 1);

}

if (x4os) {

display.drawBitmap(x4, h4+dv,tubjosplin, 12, 32, 0);

display.drawBitmap(x4, h4+dv,tubjos, 12, 32, 1);

display.drawBitmap(x4, -32+h4,tubsusplin, 12, 32, 0);

display.drawBitmap(x4, -32+h4,tubsus, 12, 32, 1);

}

}

//makes the chip fall

void dead (byte vi) {

for (v=vi; v<41; v++) {

dispChip();

dispTube();

display.print("           ");

display.print(score);

display.display();

delay(20);

}

}

//game startup

void beginning () {

display.clearDisplay();

display.drawBitmap(0, 33,fundal, 84, 20, 1);

display.drawBitmap(30, v,chip, 11, 7, 1);

display.print("Press to start");

display.display();

}

//searches highscore in EEPROM

byte getHighScore() {

int i = 0;

byte hs = 0;

boolean found = false;

while ((i<1022) && (!found)) {

if (EEPROM.read(i) == 'H')

if (EEPROM.read(i+1) == 'S') {

hs = EEPROM.read(i+2);

found = true;

}

i++;

}

return hs;

}

//sets new highscore in EEPROM

void setHighScore (byte hs) {

int i = 0, j=0;

boolean found = false;

while ((i<1022) && (!found)) {

if (EEPROM.read(i) == 'H')

if (EEPROM.read(i+1) == 'S') {

EEPROM.write(i,0xFF);

j = i+2;

found = true;

}

i++;

}

EEPROM.write(j,'H');

EEPROM.write(j+1,'S');

EEPROM.write(j+2,hs);

}

//displays score at the end

void finalScore() {

display.clearDisplay();

display.drawBitmap(0, 0,start, 84, 48, 1);

if (score <= getHighScore()) {

display.setCursor(4,10);

display.print("Your score:");

display.print(score);

display.setCursor(6,30);

display.print("Highscore:");

display.print(getHighScore());

display.display();

}

else

{

display.setCursor(4,10);

display.print("New Highscore");

display.setCursor(37,22);

display.print(score);

display.display();

setHighScore(score);

}

//do nothing until the button is pressed

while (digitalRead(2) == HIGH) ;

} 

Download project files

Click here to download project files