My First Raspberry Pi Game " Part 10 " Red square

December 14, 2012 [My First Raspberry Pi Game, Python, Raspberry Pi, Tech, Videos]

Parts: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12.

Writing your first ever computer program on the Raspberry Pi.

Embedded YouTube video

We're writing a really simple game - you have to press a key when you see green, and not press a key when you see red.

I've been promising for a while that there will be a red square as well as a green circle, and this time we're going to make that dream a reality.

The code we've written so far has this overall structure:

start()

ready_screen()

wait()

shape()

end()

It gets started, tells you to get ready, waits a random time, shows you a shape and collects your keypresses (or not), and then it ends.

Previously, the shape function just showed a green shape every time, by calling a function called green_shape. Not any more - change it to look like this:

def shape():
    GREEN = 0
    RED   = 1
    shape = random.choice( [GREEN, RED] )

    if shape == GREEN:
        green_shape()
    else:
        red_shape()

The first 2 lines just make two variables for us called GREEN and RED. They can have any values, so long as they're not the same, so I've chosen 0 and 1.

The reason we've made these variables is so that we can make a random choice of one or the other. To do this, we call the choice function from the random module (which we already have listed as imported at the top). choice takes in a list of things to choose from, and returns the one it chose randomly.

So the shape variable will contain the value of either GREEN or RED. We do an if to decide what to do based on which it is.

If we chose GREEN, we do what we used to do, and call green_shape but if we chose RED we will end up in the else part of the if, and call a new function we will call red_shape.

So, what will red_shape look like? Quite a lot like green_shape actually. Just above the shape function, add this:

def red_shape():
    red = pygame.Color( "red" )
    height = 2 * ( screen.get_height() / 3 )
    left = ( screen.get_width() / 2 ) - ( height / 2 )
    top = screen.get_height() / 6

    screen.fill( pygame.Color( "white" ) )
    pygame.draw.rect( screen, red, ( left, top, height, height ), 0 )

    write_text( screen, "Don't press!", pygame.Color( "black" ), False )

    pygame.display.flip()

    pressed = shape_wait()

    if pressed:
        red_failure()
    else:
        red_success()

Most of this function is taken up with drawing a red rectangle, which we do by working out what size it should be, then calling pygame.draw.rect with the right dimensions and colour. After that we write some text encouraging the player to leave their keyboard alone, and then we do the normal pygame.display.flip to show this on the screen.

Once we've drawn the shape, we do something very similar to what we did inside green_shape - we wait to see what happens, by calling the already-existing shape_wait function, and get the answer back from it saying whether or not the player pressed something.

This time, if they pressed something they got it wrong, so we call a new function called red_failure, and if they did nothing they did the right thing, so we call another new function called red_success.

These two functions are also quite simple. Type them in above green_shape:

def red_success():
    tick()
    green = pygame.Color( "green" )
    white = pygame.Color( "white" )
    write_text( screen, "Well done!", green, True )
    write_text( screen, "You didn't press on red!", white, False )
    pygame.display.flip()
    pygame.time.wait( 2000 ) # Can't quit or skip!

def red_failure():
    cross()
    red   = pygame.Color( "red" )
    white = pygame.Color( "white" )
    write_text( screen, "Bad Luck!", red, True )
    write_text( screen, "Red means don't press anything!", white, False )
    pygame.display.flip()
    pygame.time.wait( 2000 ) # Can't quit or skip!

These functions re-use lots of existing code - they draw a tick or a cross and then use write_text to tell the player what happened, and then they do the normal flip and wait for a bit.

Try your program - it should now show you a red square about half of the time, instead of a green circle every time, and it should give you feedback about whether you did the right or the wrong thing. Feel free to try it a few times, and make sure you've run through all the combinations. If you made a mistake somewhere you may not see it until you actually run the relevant bit of code.

Once you're happy with that, let's fix a little bug while we're here. Somehow I missed a bit from the shape_wait function, so if you press a key on the ready screen, it will register as you pressing the key really quickly when the shape appears. Try running your program and hammering a key when it says "Ready?". You'll see it thinks you pressed immediately the shape appears (whether red or green). This is annoying, and could even allow cheating, but we can prevent it by adding a single line to shape_wait:

def shape_wait():
    """
    Wait while we display a shape.  Return True if a key was pressed,
    or false otherwise.
    """

    event_types_that_cancel = pygame.KEYDOWN, pygame.MOUSEBUTTONDOWN

    time_to_wait = 2000 # Display the shape for 2 seconds
    finished_waiting_event_id = pygame.USEREVENT + 1
    pygame.time.set_timer( finished_waiting_event_id, time_to_wait )

    pygame.event.clear()

    pressed = False
    waiting = True
    while waiting:
        evt = pygame.event.wait()
        if evt.type == pygame.QUIT:
            quit()
        elif evt.type in event_types_that_cancel:
            waiting = False
            pressed = True
        elif evt.type == finished_waiting_event_id:
            waiting = False

    pygame.time.set_timer( finished_waiting_event_id, 0 )

    return pressed

As we've seen before, pygame.event.clear tells PyGame to forget all the events that have happened recently, which prevents this problem.

If you want to check your version against mine, you can find it here: redgreen.py.

We've nearly built a fully-working game. We've got two main tasks ahead of us: fix the "can't exit" bug, and allow multiple rounds with a score at the end. We'll do them in that order, so next time it's bug-fixing, and some more refactoring to help us do it.