├── .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
--------------------------------------------------------------------------------