├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── driver.c └── newflappy.gif /.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | *.ko 4 | *.obj 5 | *.elf 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Libraries 12 | *.lib 13 | *.a 14 | *.la 15 | *.lo 16 | 17 | # Shared objects (inc. Windows DLLs) 18 | *.dll 19 | *.so 20 | *.so.* 21 | *.dylib 22 | 23 | # Executables 24 | *.exe 25 | *.out 26 | *.app 27 | *.i*86 28 | *.x86_64 29 | *.hex 30 | 31 | # Debug files 32 | *.dSYM/ 33 | 34 | .project 35 | .cproject 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Hamik Mukelyan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC = gcc 2 | 3 | CFLAGS = -Wall -g 4 | 5 | OBJS = driver.o 6 | 7 | all: flap 8 | 9 | flap: $(OBJS) 10 | $(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS) -lncurses 11 | 12 | clean: 13 | rm -f *.o *~ flap 14 | 15 | .PHONY: all clean 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ASCII Flappy Bird! 2 | 3 | Flap away, my friends. 4 | 5 |
6 | 7 | ## To run 8 | 9 | ``` 10 | git clone https://github.com/hamikm/AsciiBird.git 11 | cd AsciiBird 12 | make 13 | ./flap 14 | ``` 15 | -------------------------------------------------------------------------------- /driver.c: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @author Hamik Mukelyan 4 | * 5 | * Drives a text-based Flappy Bird knock-off that is intended to run in an 6 | * 80 x 24 console. 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | //-------------------------------- Definitions -------------------------------- 18 | 19 | /** 20 | * Represents a vertical pipe through which Flappy The Bird is supposed to fly. 21 | */ 22 | typedef struct vpipe { 23 | 24 | /* 25 | * The height of the opening of the pipe as a fraction of the height of the 26 | * console window. 27 | */ 28 | float opening_height; 29 | 30 | /* 31 | * Center of the pipe is at this column number (e.g. somewhere in [0, 79]). 32 | * When the center + radius is negative then the pipe's center is rolled 33 | * over to somewhere > the number of columns and the opening height is 34 | * changed. 35 | */ 36 | int center; 37 | } vpipe; 38 | 39 | /** Represents Flappy the Bird. */ 40 | typedef struct flappy { 41 | /* Height of Flappy the Bird at the last up arrow press. */ 42 | int h0; 43 | 44 | /* Time since last up arrow pressed. */ 45 | int t; 46 | } flappy; 47 | 48 | //------------------------------ Global Constants ----------------------------- 49 | 50 | /** Gravitational acceleration constant */ 51 | const float GRAV = 0.05; 52 | 53 | /** Initial velocity with up arrow press */ 54 | const float V0 = -0.5; 55 | 56 | /** Number of rows in the console window. */ 57 | const int NUM_ROWS = 24; 58 | 59 | /** Number of columns in the console window. */ 60 | const int NUM_COLS = 80; 61 | 62 | /** Radius of each vertical pipe. */ 63 | const int PIPE_RADIUS = 3; 64 | 65 | /** Width of the opening in each pipe. */ 66 | const int OPENING_WIDTH = 7; 67 | 68 | /** Flappy stays in this column. */ 69 | const int FLAPPY_COL = 10; 70 | 71 | /** Aiming for this many frames per second. */ 72 | const float TARGET_FPS = 24; 73 | 74 | /** Amount of time the splash screen stays up. */ 75 | const float START_TIME_SEC = 3; 76 | 77 | /** Length of the "progress bar" on the status screen. */ 78 | const int PROG_BAR_LEN = 76; 79 | 80 | /** Row number at which the progress bar will show. */ 81 | const int PROG_BAR_ROW = 22; 82 | 83 | const int SCORE_START_COL = 62; 84 | 85 | //------------------------------ Global Variables ----------------------------- 86 | 87 | /** Frame number. */ 88 | int frame = 0; 89 | 90 | /** Number of pipes that have been passed. */ 91 | int score = 0; 92 | 93 | /** Number of digits in the score. */ 94 | int sdigs = 1; 95 | 96 | /** Best score so far. */ 97 | int best_score = 0; 98 | 99 | /** Number of digits in the best score. */ 100 | int bdigs = 1; 101 | 102 | /** The vertical pipe obstacles. */ 103 | vpipe p1, p2; 104 | 105 | //---------------------------------- Functions -------------------------------- 106 | 107 | /** 108 | * Converts the given char into a string. 109 | * 110 | * @param ch Char to convert to a string. 111 | * @param[out] str Receives 'ch' into a null-terminated C string. Assumes 112 | * str had 2 bytes allocated. 113 | */ 114 | void chtostr(char ch, char *str) { 115 | str[0] = ch; 116 | str[1] = '\0'; 117 | } 118 | 119 | /** 120 | * "Moving" floor and ceiling are written into the window array. 121 | * 122 | * @param ceiling_row 123 | * @param floor_row 124 | * @param ch Char to use for the ceiling and floor. 125 | * @param spacing Between chars in the floor and ceiling 126 | * @param col_start Stagger the beginning of the floor and ceiling chars 127 | * by this much 128 | */ 129 | void draw_floor_and_ceiling(int ceiling_row, int floor_row, 130 | char ch, int spacing, int col_start) { 131 | char c[2]; 132 | chtostr(ch, c); 133 | int i; 134 | for (i = col_start; i < NUM_COLS - 1; i += spacing) { 135 | if (i < SCORE_START_COL - sdigs - bdigs) 136 | mvprintw(ceiling_row, i, c); 137 | mvprintw(floor_row, i, c); 138 | } 139 | } 140 | 141 | /** 142 | * Updates the pipe center and opening height for each new frame. If the pipe 143 | * is sufficiently far off-screen to the left the center is wrapped around to 144 | * the right, at which time the opening height is changed. 145 | */ 146 | void pipe_refresh(vpipe *p) { 147 | 148 | // If pipe exits screen on the left then wrap it to the right side of the 149 | // screen. 150 | if(p->center + PIPE_RADIUS < 0) { 151 | p->center = NUM_COLS + PIPE_RADIUS; 152 | 153 | // Get an opening height fraction. 154 | p->opening_height = rand() / ((float) INT_MAX) * 0.5 + 0.25; 155 | score++; 156 | if(sdigs == 1 && score > 9) 157 | sdigs++; 158 | else if(sdigs == 2 && score > 99) 159 | sdigs++; 160 | } 161 | p->center--; 162 | } 163 | 164 | /** 165 | * Gets the row number of the top or bottom of the opening in the given pipe. 166 | * 167 | * @param p The pipe obstacle. 168 | * @param top Should be 1 for the top, 0 for the bottom. 169 | * 170 | * @return Row number. 171 | */ 172 | int get_orow(vpipe p, int top) { 173 | return p.opening_height * (NUM_ROWS - 1) - 174 | (top ? 1 : -1) * OPENING_WIDTH / 2; 175 | } 176 | 177 | /** 178 | * Draws the given pipe on the window using 'vch' as the character for the 179 | * vertical part of the pipe and 'hch' as the character for the horizontal 180 | * part. 181 | * 182 | * @param p 183 | * @param vch Character for vertical part of pipe 184 | * @param hcht Character for horizontal part of top pipe 185 | * @param hchb Character for horizontal part of lower pipe 186 | * @param ceiling_row Start the pipe just below this 187 | * @param floor_row Star the pipe jut above this 188 | */ 189 | void draw_pipe(vpipe p, char vch, char hcht, char hchb, 190 | int ceiling_row, int floor_row) { 191 | int i, upper_terminus, lower_terminus; 192 | char c[2]; 193 | 194 | // Draw vertical part of upper half of pipe. 195 | for(i = ceiling_row + 1; i < get_orow(p, 1); i++) { 196 | if ((p.center - PIPE_RADIUS) >= 0 && 197 | (p.center - PIPE_RADIUS) < NUM_COLS - 1) { 198 | chtostr(vch, c); 199 | mvprintw(i, p.center - PIPE_RADIUS, c); 200 | } 201 | if ((p.center + PIPE_RADIUS) >= 0 && 202 | (p.center + PIPE_RADIUS) < NUM_COLS - 1) { 203 | chtostr(vch, c); 204 | mvprintw(i, p.center + PIPE_RADIUS, c); 205 | } 206 | } 207 | upper_terminus = i; 208 | 209 | // Draw horizontal part of upper part of pipe. 210 | for (i = -PIPE_RADIUS; i <= PIPE_RADIUS; i++) { 211 | if ((p.center + i) >= 0 && 212 | (p.center + i) < NUM_COLS - 1) { 213 | chtostr(hcht, c); 214 | mvprintw(upper_terminus, p.center + i, c); 215 | } 216 | } 217 | 218 | // Draw vertical part of lower half of pipe. 219 | for(i = floor_row - 1; i > get_orow(p, 0); i--) { 220 | if ((p.center - PIPE_RADIUS) >= 0 && 221 | (p.center - PIPE_RADIUS) < NUM_COLS - 1) { 222 | chtostr(vch, c); 223 | mvprintw(i, p.center - PIPE_RADIUS, c); 224 | } 225 | if ((p.center + PIPE_RADIUS) >= 0 && 226 | (p.center + PIPE_RADIUS) < NUM_COLS - 1) { 227 | chtostr(vch, c); 228 | mvprintw(i, p.center + PIPE_RADIUS, c); 229 | } 230 | } 231 | lower_terminus = i; 232 | 233 | // Draw horizontal part of lower part of pipe. 234 | for (i = -PIPE_RADIUS; i <= PIPE_RADIUS; i++) { 235 | if ((p.center + i) >= 0 && 236 | (p.center + i) < NUM_COLS - 1) { 237 | chtostr(hchb, c); 238 | mvprintw(lower_terminus, p.center + i, c); 239 | } 240 | } 241 | } 242 | 243 | /** 244 | * Get Flappy's height along its parabolic arc. 245 | * 246 | * @param f Flappy! 247 | * 248 | * @return height as a row count 249 | */ 250 | int get_flappy_position(flappy f) { 251 | return f.h0 + V0 * f.t + 0.5 * GRAV * f.t * f.t; 252 | } 253 | 254 | /** 255 | * Returns true if Flappy crashed into a pipe. 256 | * 257 | * @param f Flappy! 258 | * @param p The vertical pipe obstacle. 259 | * 260 | * @return 1 if Flappy crashed, 0 otherwise. 261 | */ 262 | int crashed_into_pipe(flappy f, vpipe p) { 263 | if (FLAPPY_COL >= p.center - PIPE_RADIUS - 1 && 264 | FLAPPY_COL <= p.center + PIPE_RADIUS + 1) { 265 | 266 | if (get_flappy_position(f) >= get_orow(p, 1) + 1 && 267 | get_flappy_position(f) <= get_orow(p, 0) - 1) { 268 | return 0; 269 | } 270 | else { 271 | return 1; 272 | } 273 | } 274 | return 0; 275 | } 276 | 277 | /** 278 | * Prints a failure screen asking the user to either play again or quit. 279 | * 280 | * @return 1 if the user wants to play again. Exits the program otherwise. 281 | */ 282 | int failure_screen() { 283 | char ch; 284 | clear(); 285 | mvprintw(NUM_ROWS / 2 - 1, NUM_COLS / 2 - 22, 286 | "Flappy died :-(. to flap, 'q' to quit.\n"); 287 | refresh(); 288 | timeout(-1); // Block until user enters something. 289 | ch = getch(); 290 | switch(ch) { 291 | case 'q': // Quit. 292 | endwin(); 293 | exit(0); 294 | break; 295 | default: 296 | if (score > best_score) 297 | best_score = score; 298 | if (bdigs == 1 && best_score > 9) 299 | bdigs++; 300 | else if(bdigs == 2 && best_score > 99) 301 | bdigs++; 302 | score = 0; 303 | sdigs = 1; 304 | return 1; // Restart game. 305 | } 306 | endwin(); 307 | exit(0); 308 | } 309 | 310 | /** 311 | * Draws Flappy to the screen and shows death message if Flappy collides with 312 | * ceiling or floor. The user can continue to play or can exit if Flappy 313 | * dies. 314 | * 315 | * @param f Flappy the bird! 316 | * 317 | * @return 0 if Flappy was drawn as expected, 1 if the game should restart. 318 | */ 319 | int draw_flappy(flappy f) { 320 | char c[2]; 321 | int h = get_flappy_position(f); 322 | 323 | // If Flappy crashed into the ceiling or the floor... 324 | if (h <= 0 || h >= NUM_ROWS - 1) 325 | return failure_screen(); 326 | 327 | // If Flappy crashed into a pipe... 328 | if (crashed_into_pipe(f, p1) || crashed_into_pipe(f, p2)) { 329 | return failure_screen(); 330 | } 331 | 332 | // If going down, don't flap 333 | if (GRAV * f.t + V0 > 0) { 334 | chtostr('\\', c); 335 | mvprintw(h, FLAPPY_COL - 1, c); 336 | mvprintw(h - 1, FLAPPY_COL - 2, c); 337 | chtostr('0', c); 338 | mvprintw(h, FLAPPY_COL, c); 339 | chtostr('/', c); 340 | mvprintw(h, FLAPPY_COL + 1, c); 341 | mvprintw(h - 1, FLAPPY_COL + 2, c); 342 | } 343 | 344 | // If going up, flap! 345 | else { 346 | // Left wing 347 | if (frame % 6 < 3) { 348 | chtostr('/', c); 349 | mvprintw(h, FLAPPY_COL - 1, c); 350 | mvprintw(h + 1, FLAPPY_COL - 2, c); 351 | } 352 | else { 353 | chtostr('\\', c); 354 | mvprintw(h, FLAPPY_COL - 1, c); 355 | mvprintw(h - 1, FLAPPY_COL - 2, c); 356 | } 357 | 358 | // Body 359 | chtostr('0', c); 360 | mvprintw(h, FLAPPY_COL, c); 361 | 362 | // Right wing 363 | if (frame % 6 < 3) { 364 | chtostr('\\', c); 365 | mvprintw(h, FLAPPY_COL + 1, c); 366 | mvprintw(h + 1, FLAPPY_COL + 2, c); 367 | } 368 | else { 369 | chtostr('/', c); 370 | mvprintw(h, FLAPPY_COL + 1, c); 371 | mvprintw(h - 1, FLAPPY_COL + 2, c); 372 | } 373 | } 374 | 375 | return 0; 376 | } 377 | 378 | /** 379 | * Print a splash screen and show a progress bar. NB the ASCII art was 380 | * generated by patorjk.com. 381 | */ 382 | void splash_screen() { 383 | int i; 384 | int r = NUM_ROWS / 2 - 6; 385 | int c = NUM_COLS / 2 - 22; 386 | 387 | // Print the title. 388 | mvprintw(r, c, " ___ _ ___ _ _ "); 389 | mvprintw(r + 1, c, "| __| |__ _ _ __ _ __ _ _ | _ |_)_ _ __| |"); 390 | mvprintw(r + 2, c, "| _|| / _` | '_ \\ '_ \\ || | | _ \\ | '_/ _` |"); 391 | mvprintw(r + 3, c, "|_| |_\\__,_| .__/ .__/\\_, | |___/_|_| \\__,_|"); 392 | mvprintw(r + 4, c, " |_| |_| |__/ "); 393 | mvprintw(NUM_ROWS / 2 + 1, NUM_COLS / 2 - 10, 394 | "Press to flap!"); 395 | 396 | // Print the progress bar. 397 | mvprintw(PROG_BAR_ROW, NUM_COLS / 2 - PROG_BAR_LEN / 2 - 1, "["); 398 | mvprintw(PROG_BAR_ROW, NUM_COLS / 2 + PROG_BAR_LEN / 2, "]"); 399 | refresh(); 400 | for(i = 0; i < PROG_BAR_LEN; i++) { 401 | usleep(1000000 * START_TIME_SEC / (float) PROG_BAR_LEN); 402 | mvprintw(PROG_BAR_ROW, NUM_COLS / 2 - PROG_BAR_LEN / 2 + i, "="); 403 | refresh(); 404 | } 405 | usleep(1000000 * 0.5); 406 | } 407 | 408 | //------------------------------------ Main ----------------------------------- 409 | 410 | int main() 411 | { 412 | int leave_loop = 0; 413 | int ch; 414 | flappy f; 415 | int restart = 1; 416 | 417 | srand(time(NULL)); 418 | 419 | // Initialize ncurses 420 | initscr(); 421 | raw(); // Disable line buffering 422 | keypad(stdscr, TRUE); 423 | noecho(); // Don't echo() for getch 424 | curs_set(0); 425 | timeout(0); 426 | 427 | splash_screen(); 428 | 429 | while(!leave_loop) { 430 | 431 | // If we're just starting a game then do some initializations. 432 | if (restart) { 433 | timeout(0); // Don't block on input. 434 | 435 | // Start the pipes just out of view on the right. 436 | p1.center = (int)(1.2 * (NUM_COLS - 1)); 437 | p1.opening_height = rand() / ((float) INT_MAX) * 0.5 + 0.25; 438 | p2.center = (int)(1.75 * (NUM_COLS - 1)); 439 | p2.opening_height = rand() / ((float) INT_MAX) * 0.5 + 0.25; 440 | 441 | // Initialize flappy 442 | f.h0 = NUM_ROWS / 2; 443 | f.t = 0; 444 | restart = 0; 445 | } 446 | 447 | usleep((unsigned int) (1000000 / TARGET_FPS)); 448 | 449 | // Process keystrokes. 450 | ch = -1; 451 | ch = getch(); 452 | switch (ch) { 453 | case 'q': // Quit. 454 | endwin(); 455 | exit(0); 456 | break; 457 | case KEY_UP: // Give Flappy a boost! 458 | f.h0 = get_flappy_position(f); 459 | f.t = 0; 460 | break; 461 | default: // Let Flappy fall along his parabola. 462 | f.t++; 463 | } 464 | 465 | clear(); 466 | 467 | // Print "moving" floor and ceiling 468 | draw_floor_and_ceiling(0, NUM_ROWS - 1, '/', 2, frame % 2); 469 | 470 | // Update pipe locations and draw them. 471 | draw_pipe(p1, '|', '=', '=', 0, NUM_ROWS - 1); 472 | draw_pipe(p2, '|', '=', '=', 0, NUM_ROWS - 1); 473 | pipe_refresh(&p1); 474 | pipe_refresh(&p2); 475 | 476 | // Draw Flappy. If Flappy crashed and user wants a restart... 477 | if(draw_flappy(f)) { 478 | restart = 1; 479 | continue; // ...then restart the game. 480 | } 481 | 482 | mvprintw(0, SCORE_START_COL - bdigs - sdigs, 483 | " Score: %d Best: %d", score, best_score); 484 | 485 | // Display all the chars for this frame. 486 | refresh(); 487 | frame++; 488 | } 489 | 490 | endwin(); 491 | 492 | return 0; 493 | } 494 | -------------------------------------------------------------------------------- /newflappy.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hamikm/AsciiBird/94ad6da6e50bb95399353410bb03fd058e6d2c35/newflappy.gif --------------------------------------------------------------------------------