Components and supplies
1
Solderless Breadboard Half Size
1
Jumper wires (generic)
1
16×2 LCD Display
1
Arduino UNO
1
Tactile Switch, Top Actuated
Apps and platforms
1
Arduino IDE
Project description
Code
Flappy_bird.ino
arduino
1 2// please subscribe to The Screwdriver 3// Flappy Bird Game ............. 4 5#include <LiquidCrystal_I2C.h> 6LiquidCrystal_I2C lcd(0x20, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE); 7 8#define PIN_BUTTON 2 9#define PIN_AUTOPLAY 1 10#define PIN_READWRITE 10 11#define PIN_CONTRAST 12 12 13#define SPRITE_RUN1 1 14#define SPRITE_RUN2 2 15#define SPRITE_JUMP 3 16#define SPRITE_JUMP_UPPER '.' // Use the '.' character for the head 17#define SPRITE_JUMP_LOWER 4 18#define SPRITE_TERRAIN_EMPTY ' ' // User the ' ' character 19#define SPRITE_TERRAIN_SOLID 5 20#define SPRITE_TERRAIN_SOLID_RIGHT 6 21#define SPRITE_TERRAIN_SOLID_LEFT 7 22 23#define HERO_HORIZONTAL_POSITION 1 // Horizontal position of hero on screen 24 25#define TERRAIN_WIDTH 16 26#define TERRAIN_EMPTY 0 27#define TERRAIN_LOWER_BLOCK 1 28#define TERRAIN_UPPER_BLOCK 2 29 30#define HERO_POSITION_OFF 0 // Hero is invisible 31#define HERO_POSITION_RUN_LOWER_1 1 // Hero is running on lower row (pose 1) 32#define HERO_POSITION_RUN_LOWER_2 2 // (pose 2) 33 34#define HERO_POSITION_JUMP_1 3 // Starting a jump 35#define HERO_POSITION_JUMP_2 4 // Half-way up 36#define HERO_POSITION_JUMP_3 5 // Jump is on upper row 37#define HERO_POSITION_JUMP_4 6 // Jump is on upper row 38#define HERO_POSITION_JUMP_5 7 // Jump is on upper row 39#define HERO_POSITION_JUMP_6 8 // Jump is on upper row 40#define HERO_POSITION_JUMP_7 9 // Half-way down 41#define HERO_POSITION_JUMP_8 10 // About to land 42 43#define HERO_POSITION_RUN_UPPER_1 11 // Hero is running on upper row (pose 1) 44#define HERO_POSITION_RUN_UPPER_2 12 // (pose 2) 45static char terrainUpper[TERRAIN_WIDTH + 1]; 46static char terrainLower[TERRAIN_WIDTH + 1]; 47static bool buttonPushed = false; 48 49void initializeGraphics() { 50 static byte graphics[] = { 51 // Run position 1 52 53 B00000, 54 B01110, 55 B01101, 56 B00110, 57 B11110, 58 B01110, 59 B10010, 60 B00000, 61 62 // Run position 2 63 B00000, 64 B01110, 65 B01101, 66 B00110, 67 B11110, 68 B01110, 69 B01100, 70 B00000, 71 72 73 // Jump 74 B00000, 75 B01110, 76 B01101, 77 B11110, 78 B00010, 79 B01110, 80 B00000, 81 B00000, 82 83 // Jump lower 84 B01110, 85 B00000, 86 B00000, 87 B10000, 88 B00000, 89 B00000, 90 B00000, 91 B00000, 92 // Ground 93 B11111, 94 B11111, 95 B11111, 96 B11111, 97 B11111, 98 B11111, 99 B11111, 100 B11111, 101 // Ground right 102 B00011, 103 B00011, 104 B00011, 105 B00011, 106 B00011, 107 B00011, 108 B00011, 109 B00011, 110 // Ground left 111 B11000, 112 B11000, 113 B11000, 114 B11000, 115 B11000, 116 B11000, 117 B11000, 118 B11000, 119 }; 120 int i; 121 // Skip using character 0, this allows lcd.print() to be used to 122 // quickly draw multiple characters 123 for (i = 0; i < 7; ++i) { 124 lcd.createChar(i + 1, &graphics[i * 8]); 125 } 126 for (i = 0; i < TERRAIN_WIDTH; ++i) { 127 terrainUpper[i] = SPRITE_TERRAIN_EMPTY; 128 terrainLower[i] = SPRITE_TERRAIN_EMPTY; 129 } 130} 131 132// Slide the terrain to the left in half-character increments 133// 134void advanceTerrain(char* terrain, byte newTerrain) { 135 for (int i = 0; i < TERRAIN_WIDTH; ++i) { 136 char current = terrain[i]; 137 char next = (i == TERRAIN_WIDTH - 1) ? newTerrain : terrain[i + 1]; 138 switch (current) { 139 case SPRITE_TERRAIN_EMPTY: 140 terrain[i] = (next == SPRITE_TERRAIN_SOLID) ? SPRITE_TERRAIN_SOLID_RIGHT : SPRITE_TERRAIN_EMPTY; 141 break; 142 case SPRITE_TERRAIN_SOLID: 143 terrain[i] = (next == SPRITE_TERRAIN_EMPTY) ? SPRITE_TERRAIN_SOLID_LEFT : SPRITE_TERRAIN_SOLID; 144 break; 145 case SPRITE_TERRAIN_SOLID_RIGHT: 146 terrain[i] = SPRITE_TERRAIN_SOLID; 147 break; 148 case SPRITE_TERRAIN_SOLID_LEFT: 149 terrain[i] = SPRITE_TERRAIN_EMPTY; 150 break; 151 } 152 } 153} 154 155bool drawHero(byte position, char* terrainUpper, char* terrainLower, unsigned int score) { 156 bool collide = false; 157 char upperSave = terrainUpper[HERO_HORIZONTAL_POSITION]; 158 char lowerSave = terrainLower[HERO_HORIZONTAL_POSITION]; 159 byte upper, lower; 160 switch (position) { 161 case HERO_POSITION_OFF: 162 upper = lower = SPRITE_TERRAIN_EMPTY; 163 break; 164 case HERO_POSITION_RUN_LOWER_1: 165 upper = SPRITE_TERRAIN_EMPTY; 166 lower = SPRITE_RUN1; 167 break; 168 case HERO_POSITION_RUN_LOWER_2: 169 upper = SPRITE_TERRAIN_EMPTY; 170 lower = SPRITE_RUN2; 171 break; 172 case HERO_POSITION_JUMP_1: 173 case HERO_POSITION_JUMP_8: 174 upper = SPRITE_TERRAIN_EMPTY; 175 lower = SPRITE_JUMP; 176 break; 177 case HERO_POSITION_JUMP_2: 178 case HERO_POSITION_JUMP_7: 179 upper = SPRITE_JUMP_UPPER; 180 lower = SPRITE_JUMP_LOWER; 181 break; 182 case HERO_POSITION_JUMP_3: 183 case HERO_POSITION_JUMP_4: 184 case HERO_POSITION_JUMP_5: 185 case HERO_POSITION_JUMP_6: 186 upper = SPRITE_JUMP; 187 lower = SPRITE_TERRAIN_EMPTY; 188 break; 189 case HERO_POSITION_RUN_UPPER_1: 190 upper = SPRITE_RUN1; 191 lower = SPRITE_TERRAIN_EMPTY; 192 break; 193 case HERO_POSITION_RUN_UPPER_2: 194 upper = SPRITE_RUN2; 195 lower = SPRITE_TERRAIN_EMPTY; 196 break; 197 } 198 if (upper != ' ') { 199 terrainUpper[HERO_HORIZONTAL_POSITION] = upper; 200 collide = (upperSave == SPRITE_TERRAIN_EMPTY) ? false : true; 201 } 202 if (lower != ' ') { 203 terrainLower[HERO_HORIZONTAL_POSITION] = lower; 204 collide |= (lowerSave == SPRITE_TERRAIN_EMPTY) ? false : true; 205 } 206 207 byte digits = (score > 9999) ? 5 : (score > 999) ? 4 : (score > 99) ? 3 : (score > 9) ? 2 : 1; 208 209 // Draw the scene 210 terrainUpper[TERRAIN_WIDTH] = '\\0'; 211 terrainLower[TERRAIN_WIDTH] = '\\0'; 212 char temp = terrainUpper[16 - digits]; 213 terrainUpper[16 - digits] = '\\0'; 214 lcd.setCursor(0, 0); 215 lcd.print(terrainUpper); 216 terrainUpper[16 - digits] = temp; 217 lcd.setCursor(0, 1); 218 lcd.print(terrainLower); 219 220 lcd.setCursor(16 - digits, 0); 221 lcd.print(score); 222 223 terrainUpper[HERO_HORIZONTAL_POSITION] = upperSave; 224 terrainLower[HERO_HORIZONTAL_POSITION] = lowerSave; 225 return collide; 226} 227 228// Handle the button push as an interrupt 229void buttonPush() { 230 buttonPushed = true; 231} 232 233void setup() { 234 pinMode(PIN_READWRITE, OUTPUT); 235 digitalWrite(PIN_READWRITE, LOW); 236 pinMode(PIN_CONTRAST, OUTPUT); 237 digitalWrite(PIN_CONTRAST, LOW); 238 pinMode(PIN_BUTTON, INPUT); 239 digitalWrite(PIN_BUTTON, HIGH); 240 pinMode(PIN_AUTOPLAY, OUTPUT); 241 digitalWrite(PIN_AUTOPLAY, HIGH); 242 243 // Digital pin 2 maps to interrupt 0 244 attachInterrupt(0/*PIN_BUTTON*/, buttonPush, FALLING); 245 246 initializeGraphics(); 247 248 lcd.begin(16, 2); 249} 250 251void loop() { 252 static byte heroPos = HERO_POSITION_RUN_LOWER_1; 253 static byte newTerrainType = TERRAIN_EMPTY; 254 static byte newTerrainDuration = 1; 255 static bool playing = false; 256 static bool blink = false; 257 static unsigned int distance = 0; 258 259 if (!playing) { 260 drawHero((blink) ? HERO_POSITION_OFF : heroPos, terrainUpper, terrainLower, distance >> 3); 261 if (blink) { 262 lcd.setCursor(0, 0); 263 lcd.print("FlappyBird"); 264 } 265 delay(250); 266 blink = !blink; 267 if (buttonPushed) { 268 initializeGraphics(); 269 heroPos = HERO_POSITION_RUN_LOWER_1; 270 playing = true; 271 buttonPushed = false; 272 distance = 0; 273 } 274 return; 275 } 276 277 // Shift the terrain to the left 278 advanceTerrain(terrainLower, newTerrainType == TERRAIN_LOWER_BLOCK ? SPRITE_TERRAIN_SOLID : SPRITE_TERRAIN_EMPTY); 279 advanceTerrain(terrainUpper, newTerrainType == TERRAIN_UPPER_BLOCK ? SPRITE_TERRAIN_SOLID : SPRITE_TERRAIN_EMPTY); 280 281 // Make new terrain to enter on the right 282 if (--newTerrainDuration == 0) { 283 if (newTerrainType == TERRAIN_EMPTY) { 284 newTerrainType = (random(3) == 0) ? TERRAIN_UPPER_BLOCK : TERRAIN_LOWER_BLOCK; 285 newTerrainDuration = 2 + random(10); 286 } else { 287 newTerrainType = TERRAIN_EMPTY; 288 newTerrainDuration = 10 + random(10); 289 } 290 } 291 292 if (buttonPushed) { 293 if (heroPos <= HERO_POSITION_RUN_LOWER_2) heroPos = HERO_POSITION_JUMP_1; 294 buttonPushed = false; 295 } 296 297 if (drawHero(heroPos, terrainUpper, terrainLower, distance >> 3)) { 298 playing = false; // The hero collided with something. Too bad. 299 } else { 300 if (heroPos == HERO_POSITION_RUN_LOWER_2 || heroPos == HERO_POSITION_JUMP_8) { 301 heroPos = HERO_POSITION_RUN_LOWER_1; 302 } else if ((heroPos >= HERO_POSITION_JUMP_3 && heroPos <= HERO_POSITION_JUMP_5) && terrainLower[HERO_HORIZONTAL_POSITION] != SPRITE_TERRAIN_EMPTY) { 303 heroPos = HERO_POSITION_RUN_UPPER_1; 304 } else if (heroPos >= HERO_POSITION_RUN_UPPER_1 && terrainLower[HERO_HORIZONTAL_POSITION] == SPRITE_TERRAIN_EMPTY) { 305 heroPos = HERO_POSITION_JUMP_5; 306 } else if (heroPos == HERO_POSITION_RUN_UPPER_2) { 307 heroPos = HERO_POSITION_RUN_UPPER_1; 308 } else { 309 ++heroPos; 310 } 311 ++distance; 312 313 digitalWrite(PIN_AUTOPLAY, terrainLower[HERO_HORIZONTAL_POSITION + 2] == SPRITE_TERRAIN_EMPTY ? HIGH : LOW); 314 } 315 delay(85); 316} 317
Downloadable files
circuit diagram
circuit diagram

circuit diagram
circuit diagram

Comments
Only logged in users can leave comments