Fitting Flappy Bird Into a Tweet

Gameplay of 278-byte Flappy Bird

Since Twitter recently extended the character limit to 280, I’ve challenged myself to write programs or patterns within 280 bytes such as a tweet-sized Flappy Bird (game by dotGEARS about flying a bird through pipes without touching them) I’ll share in this post. I once thought about writing functional apps under 140, but there was just too little room to do anything significant.

The inspiration came from studying posts on a Twitter-like site called Dwitter where you share 140-byte snippets of functions for canvas animations. I also studied some 2012 challenge to write a Tron in the fewest bytes possible.

This practice of writing minimal code is known as “code golfing“ because using the least bytes to write a functional program is like using the least strokes to complete a hole in regular golf.

I’ll walk through how I made a 278-byte, black-and-white version of the once viral Flappy Bird using HTML and JavaScript. Due to the amount of minimum logic needed for the number of game mechanics (e.g. flapping, collision with pipes), there wasn’t enough room to make everything 100% accurate with the current JavaScript features available. The goal was rather to fit as many of those mechanics 280 characters could hold.

The Code

Here’s the entire source code, and I added some line breaks and tabs so that you can easily see its parts.

<body id=B onload="
    c=C.getContext('2d');
    o=[90,-92,98];
    y=0;
    x=225;
    s=0;
    setInterval('
        c.fillRect(0,0,W=150,W);
        for(i in o)
            c.clearRect(+i?x:37,o[i],+i?14:9,+i?W:9);
        b=o[0];
        b<W-9&&(x<23||x>46||58<b&&b<89)?
            (y+=.04,x<-13?(x=W,++s):--x,o[0]+=y):B.innerHTML=s
    ',9)
">
<canvas id=C onclick=y=-1>

I kept all variable names and IDs at a single-letter because it’s a great strategy for writing minimal code. Pretty small and weird, isn’t it?

Tags and Attributes

<body id=B onload="">
<canvas id=C onclick=y=-1>

The skeleton of the game consists of only opening <body> and <canvas> tags. It may normally be a good habit to close these, but it doesn’t really matter here because there are no other elements coming after the canvas. Therefore, we can save 16 bytes by not including </body> and </canvas> (7 + 9)!

Then we have IDs B and C. Luckily, JavaScript allows us to use IDs assigned to elements as variable names. In that case, we can use B for displaying the score on game over and C for accessing the canvas.

The main game code goes in the onload event listening attribute and then an onclick with a variable change to explain later.

All of these attributes except onload don’t have quotes. Since three need only one value, we save 6 bytes by not including the quotes. The case for onload is different because it’ll contain a for...in loop with two spaces, and the browser will mistake for(i plus code before it, in, and o) plus code after it as attributes if quotes aren’t surrounding them. Furthermore, the browser will treat > before the 46 as the end of the opening <body> tag and then think everything after 58 is an another opening tag.

onload without quotes
What the browser will see if onload is unquoted

Context and Main Variables

c=C.getContext('2d');

For the canvas context, I simply used the canvas ID like a variable instead of using document.getElementById("C") or document.querySelector("#C"). Either way it saves 27 bytes (28 – 1 for C)!

o=[90,-92,98];

o holds the Y positions of the bird (90) and pipes (-92 and 98).

y=0;

y is the additional Y distance added to the bird, and this is constantly increased later in the code. When the player clicks the canvas, this value goes back to -1, and that is what you see in the element’s onclick. This helps the bird flap because y being negative moves it upward. When y is over zero, then the bird starts falling again.

x=225;
s=0;

x is the X position of the pipes, which starts offscreen at 225 pixels, and s is the score.

By the way, you may have noticed that I used neither var, let, nor const to declare these variables. Without those keywords, variables are treated as var by default as long as strict mode ("use strict") isn’t enabled.

Game Functionality

setInterval('
    c.fillRect(0,0,W=150,W);
    for(i in o)
        c.clearRect(+i?x:37,o[i],+i?14:9,+i?W:9);
    b=o[0];
    b<W-9&&(x<23||x>46||58<b&&b<89)?
        (y+=.04,x<-13?(x=W,++s):--x,o[0]+=y):B.innerHTML=s
',9)

The setInterval() contains the core of the game logic. Since this function can accept code in a string as the first argument, we can save bytes using ' ' instead of function(){} or ()=>{}. Be advised, however, that the syntax is a security hole according to Mozilla. It may not matter for little games like what I’m explaining here, but please don’t consider the syntax for reducing code in production!

Drawing the Screen

    c.fillRect(0,0,W=150,W);

This fillRect() is for the background, and you can immediately throw in a new variable W as the width and height parameters. This is valid as long as you’ve defined it in the first place, and it even becomes available within the scope of setInterval(). Occupying the whole default canvas height at 150×150 is fine; the screen is easiest to see, and it’s closest to being portrait like the original Flappy Bird.

    for(i in o)
        c.clearRect(+i?x:37,o[i],+i?14:9,+i?W:9);

The for...in loop that follows draws the bird and pipes. Using for...in to loop through an array is a lot simpler than the classic for(i = 0; i < n; ++i) boilerplate.

To use only one clearRect() and the smallest arguments to draw the bird or pipes, I took advantage of the way JavaScript handles single-variable expressions. If a variable equals 0 and you check if it’s true, it returns false. That’s because JavaScript first thinks 0 is a boolean. Since the counter in a for...in loop is initially a string though, you must convert it to a number by placing a + before it in order to get the desired result. Then for clearRect() to draw the bird when i is 0, i must return false, or else clearRect() draws a pipe.

If you’re wondering why fillRect() and clearRect() are where I used them, it’s because if I swapped their roles, clearRect() wouldn’t be able to erase the pipes when offscreen. For instance:

Playing game with fillRect() and clearRect() swapped
Note: Slightly cropped to focus on pipe “trail”

Collision Detection

    b=o[0];
    b<W-9&&(x<23||x>46||58<b&&b<89)?

Next, I set the conditions for avoiding the pipes and ground, but first I used b for a copy of the bird’s Y position. Normally I’d use o[0], but it saves a few bytes to use a shorter reference to an array item. Then here are the conditions broken down:

  • b<W-9: The top of the bird must not be 9 less the canvas height, which is touching the ground.
  • x<23: The left side of pipes must be more than their diameter (14) behind the X position of the bird (37). To keep this short, I wrote the result of 23.
  • x>46: If the previous condition is false, then check if the pipes are ahead of the bird plus its width (37 + 9 = 46).
  • 58<b&&b<89: Then if false again, compare the top of the bird to the visible horizontal sides both pipes. Because the top of the first pipe is offscreen at -92, add the pipe height, which is the same value as the screen width W (-92 + 150 = 58). That’s the first value in this condition for the bird to avoid game over. The other value 89 is the top of the second pipe minus the bird height (98 – 9 = 89).

My original plan for the last comparison was to use o[1]+W<b&&b<o[2]. That way, I could use Math.random() somewhere to generate different pipe positions and not use magic numbers for the same ones, yet it requires me to sacrifice scoring or collision detection.

Object Movement

        (y+=.04,x<-13?(x=W,++s):--x,o[0]+=y):B.innerHTML=s

As long as the bird is in play, every variable adjustment in this part of the ternary operation influences the bird and pipes. Using commas, you can put multiple statements in one expression as long as that expression is enclosed by ( ). An error will occur, however, if you slip in semicolons or keywords like if or else. For example, the console will complain about an unexpected token for y+=.04,if(x<-13)x=W,++s;else--x.

In the first expression, the extra distance y is increased slightly by .04 and then compounded on the bird as shown by o[0]+=y. As I noted before, this is part of the flapping and at the same time simulates gravity. If I were to decrease this increment, the bird would fall more slowly as if on the moon. If greater, it would fall faster thus making it harder to keep midair.

Then an inner ternary operation x<-13?(x=W,++s):--x controls the pipes. When the x position of the pipes is less than the pipe diameter negated plus one (-14 + 1 = -13), then they are moved back to the right edge of the screen at W, the screen width. At the same time, the player scores a point (as ++s shows). In the original game though you score points for just passing a set of pipes. To make it look that way in this version, I set the y increment so that the bird shouldn’t be able to hit the ground before the pipe exits. If these changes aren’t to happen yet otherwise, the pipes should keep moving left.

Finally in the second expression for b<W-9..., the entire canvas is replaced with the player’s score on game over.

Interval

setInterval('
    . . .
',9)

The last part to cover is the interval. To run animations very smoothly at least 60 fps, 1000/60 is typically the value to use. We could simplify it to 16.6667 then round to 17, but the bird and pipes would move too slowly. To fix that, we could change y from .04 to a higher value and --x to maybe x-=2, but that would cost some extra bytes. Because I wanted to reduce bytes wherever I could, I used a single digit value of 9, which would still set a comfortable game speed.

Conclusion

Writing such a tiny program has taught me to find ways to write less code. All in all, these sort of programs are created for fun and in code golfing competitions. Plus, you can learn from golfing how to better minify or compress your code for reduced load time! Just be mindful of what might lead to bugs or errors.

To try the 278-byte Flappy Bird yourself, run the Pen and click the canvas to flap. Avoid the pipes. Click Rerun to replay.


Posted in: JavaScript