Flappy Bird: What I Did For The Pandora Version

flappy

Since nobody had released a Flappy Bird clone on the repo, I took some time a couple of days ago to look for some of them that may be working without much effort on the Pandora. Since Flappy Bird was a such popular and, honestly, not very hard game to program, there are a ton of clones out there for many programming languages. Python and Pygame are included by default in the Pandora firmware, so I was set to find something running with those on GitHub, and it did not take too long to identify the right candidate.

Yet that candidate (and very readable, I have to commend Sourabh Verma for the quality of his code), while perfectly functional, was made to recreate the Smartphone aspect ratio, i.e. a vertical screen – while this works well on Smartphones, it’s not ideal on horizontal screens.

flappyscreenshot1

The first modification I started with was the screen size, described as a global variable in the code. Easy enough.

SCREENWIDTH = 800
SCREENHEIGHT = 480

Now I had Flappy Bird working in 800*480, but an issue popped up following that change. The background graphics were too small in width for such a resolution, and he right part of the screen was therefore not blitted at every frame, resulting in pipes leaving a trace behind as they moved.

140817-204456

To fix that issue, I went to resize the graphic assets. I did this with ImageJ on the Pandora (I could have used Gimp, too, but ImageJ has a very minimal interface that I happen to like more) and a couple of minutes later, the assets were fixed and working on the full screen width. So, was my work done at this stage ?

01

Sort of. Flappy Bird was now working as expected, but only 2 pipes appeared at once, making the gameplay pretty boring since you had to wait for them to disappear until new ones set in. This was coming from the original vertical aspect ratio. In a vertical window, you would not see more than 2 pipes at once, therefore that number made sense. For the Pandora, I had to increase that number to 5 in order to have enough pipes shown on screen at all times.

02

Now we’re talking!

 newPipe1 = getRandomPipe()
 newPipe2 = getRandomPipe()
 newPipe3 = getRandomPipe()
 newPipe4 = getRandomPipe()
 newPipe5 = getRandomPipe()

Adding more pipes was just the beginning of the story, since I had to define the proper position for the new ones. I modified this piece of code to ensure they were all positioned correctly following the new size of the screen. I found that taking a fifth of the screen wdith worked quite well.

 # list of upper pipes
 upperPipes = [
 {'x': SCREENWIDTH + 200, 'y': newPipe1[0]['y']},
 {'x': SCREENWIDTH + 200 + (SCREENWIDTH / 5), 'y': newPipe2[0]['y']},
 {'x': SCREENWIDTH + 200 + (2*SCREENWIDTH / 5), 'y': newPipe3[0]['y']},
 {'x': SCREENWIDTH + 200+ (3*SCREENWIDTH/5), 'y': newPipe4[0]['y']}, 
 {'x': SCREENWIDTH + 200+ (4*SCREENWIDTH/5), 'y': newPipe4[0]['y']},
 ]

The same code was of course applied to the “lower pipes” array.

It was still running in a window at that stage, so I first used the SDL export flags in a run.sh script. But in the end I removed them and decided to implement the full screen in Pygame directly, with the appropriate flags:

SCREEN = pygame.display.set_mode((SCREENWIDTH, SCREENHEIGHT),pygame.FULLSCREEN|pygame.HWSURFACE|pygame.DOUBLEBUF)

The pygame.HWSURFACE creates a hardware surface instead of a software one so that the GPU can handle the display instead of the CPU alone. The last flag, pygame.DOUBLEBUF, implements double buffering, i.e. two hardware surfaces that you flip from and to every time you refresh the screen to make the display even more smooth. To make use of double buffering, I had to replace all pygame.display.update() statements by:

pygame.display.flip()

I did not do any benchmarks but it seems that the game ran a little better after these changes.

The default code I was working with did not have any track the highest score. That was an easy fix… I assigned a global variable called HIGHSCORE, that would be modified in case your current game’s points cross the current HIGHSCORE value. Displaying it was not big deal either, since I could reuse the code they had for displaying the current score as well.

The next step was to work on a C4A (Compo4All) implementation, and since there was no good article on the subject, let me describe here what is required to make it work. C4A makes it possible to synchronize highscores on a remote server so that you can compare with your friends how good you are doing. The first thing you need to do to make your game work with C4A is to register it on Skeezik’s server. You need to send him this kind of JSON info:

{
"active": true,
"plugin": "scoreonly",
"module": null,
"alltime": true,
"platforms": [ "pandora" ],
"league": "all",
"shortname": "flappybird",
"longname": "Flappy Bird",
"ordering": "ascending",
"dispunit": "points",
"type": "int",
"field": "indie",
"genre": "action",
"execinfo": {                                                                     
  "pandora": {                                                                    
    "type": "standalone",
    "pnd_unique_id": "flappybird",
    "last_known_filename_hint": "FlappyBird.pnd",
    "command_line_add": "",
    "last_known_appdata_hint": "flappybird",
    "file_dependancies_hint": ""
    }                                                                            
  }                                                                              
}

He will then activate your game in his C4A database, so that you will now be able to “push” high scores to the service. But how do you push them, you say ? Well, the easiest way to do it is to use the Spaghetti Client which is a little script that handles the communication with the C4A service. You can download it in a specific thread. I did and dropped the executable right in the root folder so that I could call it directly. A proper sc liner looks like this:

sc so push <nameofapplication> pandora <score>

Several games out there only do the upload of the score once you exit the game. I guess it’s OK, but I’d like to offer a more seamless experience – with the application uploading the score right when you get it, in the game. In python this should be relatively straightforward to do, with a subprocess one liner.

 p=subprocess.Popen(["./sc","so","push","flappybird","pandora",uploadedscore], stdout=subprocess.PIPE)

You can even detect if the score was properly transmitted – if the request returns “0”, then there’s no issue. If anything less than 0, it means there was an issue when uploading it. So you can even display an error message in case it did not upload properly. Anyway, this one liner worked very nicely. In order to avoid uploading each single score, I added a global variable called sessionHIGHSCORE which would monitor your best score during your game session (until you quit), and only upload  to C4A the scores that were superior to your sessionHIGHSCORE. For example, if you scored 5, 15 and 8, 5 and 15 would be uploaded since they both are your best sessionHIGHSCORE at the time when you play, but 8 being lower to 15 it would not be transmitted to C4A.

In order to make the game a little harder and less boring, I have included something that was not in the original code. Some randomization around the x position of the pipes. In the first place they are all equally spaced from one to the next. In order to give some element of surprise, I was giving from 0 to 50 pixels of random position increase or decrease between each pipe – that way you really have to pay attention continuously as you play instead of falling in the rhythm. This small changes results in some very difficult areas where the space between two pipes is narrow AND different in height. I later changed the randomization range a little to 0 to 25 only to limit the cases where it would be impossible to cross the space between two consecutive pipes. I made this range either negative or positive (that part is not shown here).

gapX = random.randrange(0,30)

This gapX variable is added to the new X position of a new pipe.

I wanted to give a little more polish to the game over screen as well. Well, actually fix it first, because the “game over” message did not appear in the original code (while the picture was in the assets). The issue was very simple to fix, it was simply a missing call in the game over sequence:

SCREEN.blit(IMAGES['gameover'], (gameoverx, gameovery))

where gameoverx and y are the coordinates where the game over surface is drawn. But a static “game over” is pretty boring, so I added a little bouncing effect to that message. It’s fairly easy to do, you simply add an acceleration vector to it (first directed down), the acceleration gives an increasing speed which drives the movement down. But you don’t want to “game over” message to get out of the screen, so once it reaches a certain downward speed that you define yourself, you invert the acceleration vector (to make it go up instead of down), so that it will progressively reduce the speed, make it go to zero, and then increase the speed progressively to the upward direction. Since you don’t want it to go off screen upward either, you apply the same restriction on speed in the negative values, so that effectively your “game over” message will stay constrained within a small area on screen. The effect is pretty smooth.

 velocity+=acceleration
 gameovery+=velocity
 
 if velocity>3:
    acceleration=-0.25
 else:
    if velocity< -3:
        acceleration=0.25

As you can see the code basically applies acceleration and velocity to the gameovery variable to make it bounce vertically.

140817-211013

The last major addition I did was to include an automatic fetching function for C4A scores. Since Flappy Bird is very much a high score based game, it’s better if you can always see, when you are online, if someone else has a better score than you. This is what the code looks like:

def getc4a():
    """fetches c4a highscores"""
    global highscoresc4a
    try:
        c4ahighscoredata=[]
        json_data = json.load(urllib2.urlopen(URL))
        scoreboard=json_data['scoreboard']
        currentname=[]
        differentnames=0
        i=0
        """filters by user name to avoid score spamming"""
        while differentnames < 10:
            if scoreboard[i]['shortname'] not in currentname:
                differentnames+=1
               c4ahighscoredata.append("{0} {1}".format(scoreboard[i]['shortname'],scoreboard[i]['score']))
              currentname.append(scoreboard[i]['shortname'])
              i+=1
        else:
           i+=1
           highscoresc4a=c4ahighscoredata
    except:
        pass

I won’t go in the detail line by line, because it’s quite explicit, but what this pieces of code does is to fetch the data from C4A as a JSON string (from the API URL), then getting the scoreboard piece of it into an array, and then extracting the values of “initials” and “score” for the top 5 players. It has a filtering function to avoid fetching the data from the same user over and over again, by focusing only on the best score of each user. I know the code could be simplified and probably shorter, but here again I went for something that worked without looking twice at it.

This is the API URL, by the way, that you can use to get the JSON data:

"""url for json c4a data to fetch"""
URL="http://skeezix.wallednetworks.com:13001/json_1/flappybird"

With that data available, I display it once you do game over (while online) so that you can see what are the latest scores on C4A without quitting the application. I wish more apps could do that as well.

140817-211025

By the way, I decided to fetch the c4a scores in a separate thread in order to avoid delay in the execution of the game since it can take 2-3 seconds (and freeze the game during the meantime). You need to do an “import thread” at the beginning of your code if you want to use that function.

thread.start_new_thread(getc4a, ())

Note that this is not the best way to do it in Python since you can not really know when the thread is finished with its task, but I went for the “good enough” solution at this stage. Seems to work fine.

Finally, a nice little touch I did was to create a darker background that is displayed instead of the normal backgrounds when you hit a pipe or the ground. It does not change anything in the game but gives more impact to the Game Over screen. That’s the kind of little details that, when they add up, can make a difference on how your game feels.

Before hitting the pipe…

140817-211002

and after – see the darker background:

140817-211013

I did many other small changes, and I am still in the process of implementing a couple of others too, but there was enough to write about and share. There’s nothing stellar in what I did, but it’s actually pretty fun to take someone else’s code and try to improve on it. Because you need to understand the logic to see where you should plan for a surgical operation to achieve the desired effect. And since it’s not as time consuming as creating a new game from scratch, you can learn a lot this way, by reading code and seeing what makes it break and what makes it work again.

I will post my latest code on Github so don’t hesitate to have a look if you want.

Leave a Reply

2 Comments on "Flappy Bird: What I Did For The Pandora Version"

avatar
  Subscribe  
newest oldest most voted
Notify of
Shenmue
Guest

Awesome! I hate AngryBird but like FlappyBird – it doesn’t destroy things 🙂

Dredd
Member

Nice work! 🙂 it’s crazy how this game became so successful that the developer took it down!
all thanks to PewDiePie’s review “FLAPPY BIRD – DONT PLAY THIS GAME!”
https://www.youtube.com/watch?v=lQz6xhlOt18