Friday, 5 August 2016

Non-Flappy Bird

Here we present our first micro:bit game - Non-Flappy Bird. We wanted to write a version of Flappy Bird, but on a 5x5 display there didn't feel like there was enough room for flapping. So instead of flapping, we made the A and B buttons move you left and right. Rows of lit pixels drop down the screen towards you and you'll need to dodge them. The game gets faster the more barriers you avoid.


The code for this game is fairly simple, but there are is one interesting technique - we use an image as buffer for the horizontal barriers. Barriers are drawn directly to this buffer which is then copied directly to the screen and then the player overlaid at the bottom. The buffer is shifted down every few frames to give the illusion of movement. This buffer technique is great because we don't need to keep track of each barrier, we just draw it the buffer and forget about it. The alternative solution would be to remember each barrier's position and re-draw it every frame, which would be much more work.

Here's the code:

"""
 Non-Flappy Bird

 Steer though the gaps in the barriers using the A and B buttons. The game gets faster the longer 
 you play.

 This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 
 International License. https://creativecommons.org/licenses/by-nc-sa/4.0/
"""


from microbit import *
import random


def show_titles():
    """
    Show titles and wait for the A button to be pressed
    """
    display.scroll('Non-Flappy Bird - Press A To Start', wait = False, loop = True)
    while not button_a.was_pressed():
        pass


def play_game():
    """
    Play a single round of the game and return the score achieved
    """
    # Create a screen sized image to use as a screen buffer. We draw the barriers to this screen 
    # and then copy it to the main screen every frame
    barrier_screen_buffer = Image('00000:00000:00000:00000:00000:')

    # Player score and position
    player_x_position = 2
    score = 0

    # Variable to keep track of scrolling
    scroll_counter = 0

    # Variables to keep track of barriers
    barrier_counter = 0
    total_barriers_created = 0

    while True:
        # Deal with scrolling. We update scrolling once every 4 updates, so the player gets four 
        # updates for each scroll
        scroll_counter = scroll_counter + 1
        if scroll_counter % 4 == 0:
            # Scroll the barriers towards the player
            barrier_screen_buffer = barrier_screen_buffer.shift_down(1)

            # Deal with creating barriers. We create one barrier every 4 scrolls
            barrier_counter = barrier_counter + 1
            if barrier_counter % 4 == 0:
                # Draw a new barrier at the top of the barrier buffer
                gap_position = random.randint(0, 4)
                for i in range(5):
                    if i == gap_position:
                        barrier_screen_buffer.set_pixel(i, 0, 0)
                    else:
                        barrier_screen_buffer.set_pixel(i, 0, 7)
                
                # Increase score for every barrier
                score = score + 1

                # One more barrier created
                total_barriers_created = total_barriers_created + 1
                                        
        # Player movement
        if button_a.was_pressed():
            player_x_position = max(player_x_position - 1, 0)
        if button_b.was_pressed():
            player_x_position = min(player_x_position + 1, 4)
                        
        # Draw the barriers to the screen
        display.show(barrier_screen_buffer)
            
        # Collision detection - See if the pixel at the position on the screen where the player is 
        # right now is turned on, if it is then there must be a barrier there
        if display.get_pixel(player_x_position, 4) != 0:
            # Exit from the while loop
            break
            
        # Draw the player to the screen
        display.set_pixel(player_x_position, 4, 9)

        # Slow down the game. We use a short delay here. The delay gets slightly shorter with 
        # every barrier created
        sleep(max(100 - total_barriers_created, 0))
    
    # Finished. Return the score
    return score

    
def show_score(score):
    """
    Show score and wait for the A button to be pressed
    """
    display.scroll('Score: ' + str(score), wait = False, loop = True)
    while not button_a.was_pressed():
        pass

        
# Main loop
while True:
    show_titles()
    score = play_game()
    show_score(score)
    

And here are the source and hex files:
non_flappy_bird.py
non_flappy_bird.hex

No comments:

Post a Comment