📜 ⬆️ ⬇️

“Flappy Bird” up to 1KB

The week of the 30 lower case JS is long gone, but inspired by the posts. Developing Flappy Bird on the Phaser (Part I) and How Minkowski played during Flappy Bird , I could not help but try to write an ASCII version of the game “Flappy Bird” in JavaScript and keep within in 1024 characters.

You can see what came of this (and play) here , and see the uncompressed source here .
For implementation details, please under the cat.

About the game


I’ll leave out the boring details that you yourself can find on the wiki or try to play the original game. In short, you control a bird flying between obstacles. With the “up” key you can slightly push it higher for a short while and thus maneuver.

Field


I did not strive for sufficient authenticity, and the limitations are very strict. Therefore, I went to the maximum victims and, by trial and error, picked up a less-than-optimal field size (without scaling) - 10 lines of 30 characters each:
 - +++++ ---- +++++ ----- +++++ -----
  +++++ +++++ +++++     
  +++++ +++++ +++++     
  +++++                        
                              
  0                            
           +++++ +++++     
  +++++ +++++ +++++     
  +++++ +++++ +++++     
 - ++++ ---- +++++ ----- +++++ -----
 123

Zero - the position of the player (I chose the second from the left column), 123 at the bottom left - the points scored.
')

Field animation


The first task is to learn how to move the playing field, move the obstacles from right to left. You can try to store the field cells in a two-dimensional array, but we will go some other way. Let's look at one of the lines, replacing spaces with zeros in it, and the barrier with ones:
011111000011111000001111100000
It is not difficult to guess that this is a binary representation of a number (521110496). The simpler it is for us - you can move it to the left with a bitwise left shift operation. Remember about the limit of the length of integer values. To maintain the 30 byte limit, just mask it and rip it, cutting off all the excess after the shift:
 = 011111000011111000001111100000;  =  << 1;  =  & 2^30; //  = 111110000111110000011111000000 

And the second task - to keep the same width of the obstacles. Four possible cases (before the first false) look like this:
 ????010 - true -> 000011 ???0110 - true -> 000111 ??01110 - true -> 001111 ?011110 - true -> 011111 0111110 - false 

The logic is trivial: if the second bit = 1, then it is true if there is a bit = 0 in the next after the second bits up to the width of the obstacle.
Total for shifting all rows of the field we use
  <<= 1;  &= 2^30; if ( & 2) { for ( = 2;  <= _; ++) { if (!( & 1 << )) {  |= 1; break; } } } 


Scoring


In this place we have enough conditions for scoring. Provided that two obstacles cannot go without an interval, we add a score for each empty column of the field in which the player is located, if in the previous column the obstacle was exactly (28 and 29 are indices of two adjacent columns in one of which, with a smaller index , - player):
  += (__ & 1 << 29) && !(__ & 1 << 28) ? 1 : 0; 


New obstacles


This is a little more complicated. I tried to withstand the conditions:

You can visualize it in this form:
 000011111 -> 0% (0 * 12.5) 0 000111110 -> 0% (0 * 12.5) 1 001111100 -> 12.5% (1 * 12.5) 2 011111000 -> 25.0% (2 * 12.5) 3 111110000 -> 37.5% (3 * 12.5) .. 111100000 -> 50.0% (4 * 12.5) 111000000 -> 62.5% (5 * 12.5) 110000000 -> 75.0% (6 * 12.5) 100000000 -> 87.5% (7 * 12.5) 000000000 -> 100.0% (8 * 12.5) 

The first column is the extreme bits of the field, followed by the percentages of the likelihood of a new obstacle. Eight - the maximum interval, which is optimal in the game, and convenient for calculations: 100 can be divided by 8 and get tangible values. The rightmost column is the amount of the bitwise left shift of the mask with which we will search and calculate the length of the current gap between the obstacles.
Things are easy: move the bit-unit mask to the left until we meet another unit. At this moment, knowing the current gap and probability, we are trying to create a new obstacle:
     0   { if (__ & 1 << ) { if ( > 1 && ( - 2) * 125 +  > (124..999)) { //   } break; } } 

I multiplied everything by 10 and thus got rid of non-integer values. In addition to 100%: I have taken one from one thousand (100% * 10), because one is a whole extra byte of the application! And, as we remember, bytes need to be saved.
Adding the obstacles themselves is not a difficult task, but to avoid creating impassable sections, I added a condition: each next obstacle must be one more / less than the previous one or equal to it, and not less than two or more than five. Plus we maintain a gap for flight - three lines. We get:
 //  -   ,   ,     = (   > 2 ?  - 1 : 2   < 5 ?  + 1 : 5 );    0   {  |= 1; }     + 3   {  |= 1; } 


Render


There are no tricks here. Just run, like an old lamp TV, in rows, and in them in columns and accumulate cells of the field:
  = '';    ( ) {    ( ) {  +=  == 28 &&  == _ ? "0" : (  & 1 <<  ? "+" : ( ! ||  == _ - 1 ? "-" : " " ) ); }  += "\n"; } _; 

We separately check and draw the player, as well as the upper and lower borders of the field itself.

Move


The game world is ready and working. It remains to revive the character. I simplified it to the maximum. No parabolic flights, gravity, acceleration and inertia (except a little). Let the "up" key sets the impulse - the stock of movement up. And let each iteration of the animation reduce this impulse, if it has not yet reached zero. On the tests, it looked quite shabby, and the bird was moving along a clearly triangular trajectory. Therefore, I slightly increased the initial impulse and added motion “by inertia”, if the impulse is equal to one:
 if (  ) {  ,   = 3; } //  if ( > 1) { --; --_ || ; //  } else if () { --; //  } else { _ < 9 ? _++ : ; } 

The impulse not only controls its value, but also the character's position. Plus checks if we hit the field boundaries.
Separately, we check the collision with an obstacle:
 if (_ & 1 << 28) { ; } 


We press


On this the main work is over. Add the layout, wrap the application in an anonymous function and check in several browsers.
Result before compression
 <script> (function(){ var run = 0, imp = 0; function up(){ run = 1; var pos = 2, rows = [1, 1, 1, 1, 0, 0, 0, 1, 1, 1], rowsLen = 10, fieldWidth = 30, fieldMask = Math.pow(2, fieldWidth) - 1, profit = 0, hTop = 4, row, col, timer = setInterval(function(){ /** * Move user */ if (imp > 1) { imp--; // up --pos || _stop(); } else if (imp) { imp--; } else { // down pos < 9 ? pos++ : _stop(); } /** * Move field * * 0111110 - false * ?011110 - true -> 011111 * ??01110 - true -> 001111 * ???0110 - true -> 000111 * ????010 - true -> 000011 */ for (row = rowsLen; row--;) { rows[row] <<= 1; rows[row] &= fieldMask; if (rows[row] & 2) { for (w = 2; w <= 5; w++) { if (!(rows[row] & 1 << w)) { rows[row] |= 1; break; } } } } /** * Add new objects * * * 000011111 -> 0% (0 * 12.5) 0 * 000111110 -> 0% (0 * 12.5) 1 * 001111100 -> 12.5% (1 * 12.5) 2 * 011111000 -> 25.0% (2 * 12.5) 3 * 111110000 -> 37.5% (3 * 12.5) .. * 111100000 -> 50.0% (4 * 12.5) * 111000000 -> 62.5% (5 * 12.5) * 110000000 -> 75.0% (6 * 12.5) * 100000000 -> 87.5% (7 * 12.5) * 000000000 -> 100.0% (8 * 12.5) */ for (var tryNum = 0; true; tryNum++) { if (rows[0] & 1 << tryNum) { if (tryNum > 1 && (tryNum - 2) * 125 + profit > _rnd(124, 999)) { hTop = _rnd(hTop > 2 ? hTop - 1 : 2, hTop < 5 ? hTop + 1 : 5); // 2..5, prev +/- 1 for (h = 0; h < hTop; h++) { rows[h] |= 1; } for (h = hTop + 3; h < rowsLen; h++) { rows[h] |= 1; } } break; } } /** * Render */ var text = ''; for (row = 0; row < rowsLen; row++) { for (col = 29; col >= 0; col--) { text += col == 28 && row == pos ? "0" : ( rows[row] & 1 << col ? "+" : ( !row || row == rowsLen - 1 ? "-" : " " ) ); } text += "\n"; } profit += (rows[0] & 1 << 29) && !(rows[0] & 1 << 28) ? 1 : 0; text += "\n"+profit; pre.innerHTML = text; if (rows[pos] & 1 << 28) { _stop(); } }, 250); var _rnd = function(min, max){ return Math.floor(Math.random() * (max - min + 1)) + min; } var _stop = function(){ clearInterval(timer); run && alert(':('); run = 0; } } onkeyup = function(e){ e.which == 38 && (run ? imp = 3 : up()); }; })() </script> <body onload=""><pre id="pre">press up! 


We run JS through any optimizer a la UglifyJS and then simply transfer it to:
 <body onload='..' 

Total: 785 bytes. I am sure this is not the limit!

Links


In addition to the above, will be interesting:

Source: https://habr.com/ru/post/220399/


All Articles