My First Raspberry Pi Game " Part 08 " Success and failure

December 04, 2012 [Games, 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.

Today we're going to wait for a key press. If we get one, we'll tell the player they did well. If not, we'll tell them they are a bad person.

We're going to change the green_shape function first, to make it wait for a key press (or give up waiting) and then tell the player what happened.

Find the green_shape function and add the new bit that I've highlighted in green, at the end:

def green_shape():
    green = pygame.Color( "green" )
    centre = ( screen.get_width() / 2, screen.get_height() / 2 )
    radius = screen.get_width() / 3

    screen.fill( pygame.Color( "white" ) )
    pygame.draw.circle( screen, green, centre, radius, 0 )

    pygame.display.flip()

    pressed = shape_wait()

    if pressed:
        green_success()
    else:
        green_failure()

green_shape is the function that shows a green shape to the player.

This new code does 2 things. First, it calls a function shape_wait (that we haven't written yet) that waits for a key press. We are expecting this function to give us back an answer, which we will store inside a new variable, pressed.

Second, it checks the value of pressed, and calls a different function in each case. If a key was pressed, this is good (because we're showing a green shape, so you're supposed to press a key) so we call the green_success function (which we haven't written yet either). If no key was pressed because we gave up waiting, we call the green_failure function (which we haven't written yet!).

That covers everything we want to do today - all we have to do is write those 3 missing functions.

Let's start with the hardest one - shape_wait. Go up to just above the green_shape function, and type this:

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 )

There are a few things to explain here. First, the writing at the top just below the def line. This is the way we explain in Python what a function does and what it's for. It's optional, and we haven't done it before, but I thought this function was interesting enough for us to provide some explanation. Notice the triple-quotes """ at the beginning and end. That is a way Python allows us to write longer strings of text that cover more than one line. The string starts at the first triple-quote, and ends at the last.

After our documentation string, we create a familiar variable event_types_that_cancel that holds on to all the types of event we are interested in - key presses and mouse clicks. Next we remember how long we are going to wait before giving up in another variable time_to_wait.

After that we do something a bit interesting. Up to now we have been dealing with "events" - things that happen such as mouse clicks, key presses and mouse movements, but we have only been responding to them, not creating them. The next 2 lines are how we create our own event, that we want to respond to later.

What we want to do is make an event happen in 2 seconds' time, so that we can give up waiting when it comes. The way we do that is first create an "ID" for it. This is just a numeric "name" that we can use to talk about the same type of event later. In PyGame the right ID to choose for an event you created yourself is pygame.USEREVENT + 1 (and higher numbers if you need more than one). We don't know what number PyGame has stored inside its own variable pygame.USEREVENT, and we don't care - all we care about is that PyGame says if we use numbers bigger than that, we'll be fine. If we use smaller numbers, we are going to clash with the built-in events like pygame.KEYDOWN that we have already seen.

Once we have an appropriate ID stored inside finished_waiting_event_id we are ready to ask PyGame to create an event that will happen in 2 seconds' time. We do that by calling the set_timer function inside pygame.time.

Now continue the function by typing all this:

    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

This is the code that waits for something to happen. It's quite similar to the loop we saw in part 6, where we were also waiting for something to happen, but it's slightly more complicated because we have to handle more possibilities.

This function will provide an answer to the code that called it, and the answer is going to be whether or not the player pressed a key. Providing an answer like this is called "returning a value" and we do it by writing a line like the last one here, using the return statement. The first line above creates a variable called pressed, which starts off set to False, meaning they haven't pressed anything, and somewhere in between it might get set to True, and then the last line returns this answer - True or False for whether or not a key was pressed.

In between we have a loop similar to part 6 - we create a variable called waiting which tells us whether to keep looping, and then we loop using the while line through all the lines indented below it. The inside of the loop (the part that gets repeated) waits for an event to happen with pygame.event.wait and then has a series of if and elif sections, that do different things depending what type of event happened.

First (the if part), we check whether the player closed the window. If so, we call our function quit, that stops everything immediately.

Next (the first elif), we check whether a key or mouse button was pressed. If so, we make sure the value we will return inside pressed is updated to say a key was pressed (i.e. we make it True), and then we set waiting to False so that we will stop looping at the end of this repeat.

Now (the second elif), we check whether what happened was the special event we created earlier when we called set_timer. If so, we need to end the loop (so we set waiting to False), but no key was pressed, so we leave pressed as it was.

Finally, if the event that happened didn't fit any of our categories (for example it might have been a mouse movement event), we do absolutely nothing because none of the if or elif sections was triggered. We jump straight back to the start of the loop and start waiting for the next event to happen.

So, eventually, either an interesting event happens, or the "we've been waiting too long" event we created happens, and we come out of the while loop. The last thing we have to do is cancel the "we've been waiting too long" event, just in case it hasn't happened yet - we don't want it confusing us later. We do that by calling set_timer again, with the same ID as before, but with 0 for the amount of time to wait - this tells PyGame we're not interested in that event any more.

Once we've done that we return the answer about whether a key was pressed, and we're done with shape_wait.

Next up are green_success and green_failure. These tell the player whether they succeeded or failed - did they manage to press when they saw green?

They're both quite simple. Type these just above green_shape:

def green_success():
    tick()
    pygame.time.wait( 2000 ) # Can't quit or skip!

def green_failure():
    cross()
    pygame.time.wait( 2000 ) # Can't quit or skip!

If a key was pressed on green, we want to draw a "tick" mark on the screen, so we call a function tick that we'll write in a moment. Similarly, if a key wasn't pressed, we will draw a cross.

Drawing shapes is fairly straightforward, but a bit verbose. Just above green_success type these 2 functions:

def tick():
    colour = pygame.Color( "green" )
    w = screen.get_width() / 2
    h = screen.get_height() / 2
    points = (
        ( w - w/5, h - h/9 ),
        ( w,       h + h/5 ),
        ( w + w/3, h - h/3 ),
    )

    screen.fill( pygame.Color( "black" ) )
    pygame.draw.lines( screen, colour, False, points, 20 )
    pygame.display.flip()

def cross():
    colour = pygame.Color( "red" )
    w = screen.get_width() / 2
    h = screen.get_height() / 2
    left   = w - w/3
    right  = w + w/3
    top    = h - h/3
    bottom = h + h/3

    start1 = left, top
    end1   = right, bottom

    start2 = left, bottom
    end2   = right, top

    screen.fill( pygame.Color( "black" ) )
    pygame.draw.line( screen, colour, start1, end1, 20 )
    pygame.draw.line( screen, colour, start2, end2, 20 )
    pygame.display.flip()

Both of these functions get some variables ready, do some maths on them to decide where on the screen to start and end the lines they are drawing, and then draw the lines (after making a black background with screen.fill).

The tick is drawn by passing in a list of 3 points on the screen to the pygame.draw.lines function, and the cross is drawn using two separate calls to pygame.draw.line, one for each line. After we've drawn our lines, we call pygame.display.flip as normal to show them on the screen.

With those two functions in place, we're ready to try it out. Open LXTerminal in the usual way, and type our usual incantation:

./redgreen.py

If all has gone well, you should see the green shape as before, but when you press a key a tick should appear. If you don't press a key, after a while a red cross should appear.

If that doesn't happen, check your typing really carefully, and compare your version with mine: redgreen.py.

Next time, we'll add some writing explaining what you should do at each step.