My First Raspberry Pi Game – Part 11 – Being less rude

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

Writing your first ever computer program on the Raspberry Pi.

We’ve nearly finished our game. Next on our list is to fix that bug where you can’t exit some of the time, and make our code a bit tidier in the process.

The first thing I want to do is make the Esc key quit the game. This is fairly normal behaviour, and will help if we run in full screen mode, where there is no close button to click.

Open up redgreen.py in LeafPad as usual, and find the function shape_wait and the line if evt.type == pygame.QUIT:. Replace the whole line with this:

        if is_quit( evt ):

We’ve replaced the code asking whether the event was a quit event (i.e. the user closed the window) with a call to a function. Let’s write that function. Just above shape_wait type in this function:

def is_quit( evt ):
    return evt.type == pygame.QUIT

You’ve just done another bit of refactoring. Instead of writing the code directly in the if line, we’ve added a call to the function, and the function does exactly the same thing as we did before: it returns True if the event is a quit event, and False otherwise. If you try the program now (by saving in LeafPad, opening LXTerminal and typing ./redgreen.py) you should see it behaves exactly as it did before.

You may well ask why we did it. The answer is because now we can change the is_quit function to do something extra. Replace it with this:

def is_quit( evt ):
    return (
        evt.type == pygame.QUIT or
        (
            evt.type == pygame.KEYDOWN and
            evt.key == pygame.K_ESCAPE
        )
    )

This is a more complicated bit of logic, saying that we will return True if either of two things is true: EITHER the event is a quit event (as before), OR the event is a KEYDOWN, and the specific key that was pressed was the Escape key (“Esc”) on the keyboard. Notice that the brackets around the “and” part help us know which bits go together – we don’t want to quit for any keypress event, only one where the key is Escape.

If you try your program again, you should find you can press Escape to exit when you’re looking at a red or green shape.

The shape_wait function is quite a useful one, and the next thing we want to do is use it in a few more places. Before we can do that, we need to refactor it to make it a bit more flexible.

Make a new function called timed_wait further up, just before start, and cut the entire body of shape_wait and paste it into timed_wait. So it looks like this:

def timed_wait():
    event_types_that_cancel = pygame.KEYDOWN, pygame.MOUSEBUTTONDOWN
    ... all the rest of shape_wait here ...
    pygame.time.set_timer( finished_waiting_event_id, 0 )

Now change shape_wait to look like this:

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

As usual, we’ve just replaced some code with a call to a function that contains the exact same code, so hopefully our program will work exactly as before.

Now we’re going to make timed_wait a bit more general, while still preserving all the same behaviour. We do this by changing some of the variables we use in timed_wait into arguments we pass in. Change shape_wait to look like this:

def shape_wait():
    """
    Wait while we display a shape.  Return True if a key was pressed,
    or false otherwise.
    """
    press_events = pygame.KEYDOWN, pygame.MOUSEBUTTONDOWN 
    return timed_wait( 2000, press_events ) # 2 seconds

and modify timed_wait to accept those arguments (notice I also added a description of what it does):

def timed_wait( time_to_wait, event_types_that_cancel ):
    """
    Wait for time_to_wait, but cancel if a relevant event happens.
    Return True if cancelled, or False if we waited the full time.
    """

    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 is_quit( evt ):
            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

Again, after these changes our program should work exactly as before – we’re passing values in as arguments that are exactly what we used to make as variables. But now, timed_wait is a lot more flexible, and we’ll use that flexibility very soon.

But first, we need to make a change to cover the unexpected. Inside timed_wait we’ve made a timer using pygame.time.set_timer and at the end we’ve cancelled it by calling pygame.time.set_timer again. However, if something goes wrong in between where we create the timer, and where we cancel it, it’s possible that something called an “exception” will be “thrown”. When an exception is thrown, the program stops running normally, line by line, and jumps out to somewhere else.

I’m not going to explain any more about exceptions here, but I am going to show you how to make absolutely sure that something will happen, even if an exception is thrown. The way to do that is to use a try ... finally block. We want to make sure our timer is always cancelled, so as soon as we’ve made it, we start a try block, and at the end we say finally. Anything inside that finally block will be run, even if an exception was thrown in the code inside the try block. The changes look like this:

def timed_wait( time_to_wait, event_types_that_cancel ):
    """
    Wait for time_to_wait, but cancel if a relevant event happens.
    Return True if cancelled, or False if we waited the full time.
    """

    finished_waiting_event_id = pygame.USEREVENT + 1
    pygame.time.set_timer( finished_waiting_event_id, time_to_wait )

    try:
        pygame.event.clear()

        pressed = False
        waiting = True
        while waiting:
            evt = pygame.event.wait()
            if is_quit( evt ):
                quit()
            elif evt.type in event_types_that_cancel:
                waiting = False
                pressed = True
            elif evt.type == finished_waiting_event_id:
                waiting = False
    finally:
        pygame.time.set_timer( finished_waiting_event_id, 0 )

    return pressed

The lines in green are new, and the ones in blue are just indented by four more spaces to make them part of the try and finally blocks. Now, we know that even if something goes wrong while we’re waiting, we will always cancel the timer we set up. Yet more good manners!

Now, after all that work, we finally have a timed_wait function that is flexible enough to be used everywhere we want to wait for something. Let’s start with the wait function. Change it to look like this:

def wait():
    time_to_wait = random.randint( 1500, 3000 ) # Between 1.5 and 3 seconds
    timed_wait( time_to_wait, () )

By using our clever timed_wait function instead of the built-in pygame.time.wait we gain some extra politeness: we can now quit the program on the “Ready?” screen by closing the window or pressing the Escape key. Try it!

Notice that we passed in () as the second argument to timed_wait. This argument is called event_types_that_cancel and is normally a list of types of event that will stop us waiting. () is Python’s way of saying an empty list, so we’re saying we don’t want to stop for any normal events (such as key presses) – only for quit events, or when the time is up.

Before we change lots more code to use timed_wait, we are going to make a new variable that we can use in lots of places in the code. Quite a few times, we want to wait for either a key press or a mouse click. We want this when we’re showing a red or green square, and when we’re at the end saying goodbye, and ideally we also want it when we’re telling the user how they did, so they can skip it if they’re impatient. So, right near the top, add a new line just below where we create screen_size:

screen_width = 640
screen_height = 480
screen_size = screen_width, screen_height
press_events = pygame.KEYDOWN, pygame.MOUSEBUTTONDOWN

This variable press_events will be our list of normal event types that we consider to be a “press” – essentially, the player doing something. Now that we’ve defined this at the top, we can take out the variable with the same name from shape_wait – it will use the global one instead:

def shape_wait():
    """
    Wait while we display a shape.  Return True if a key was pressed,
    or false otherwise.
    """
    return timed_wait( 2000, press_events ) # 2 seconds

We can also re-use press_events in the end function, and at the same time call our new timed_wait function:

def end():
    screen.fill( pygame.Color( "black" ) )
    white = pygame.Color( "white" )
    write_text( screen, "Thanks for playing!", white, True )
    write_text( screen, "Press a key to exit", white, False )
    pygame.display.flip()
    pygame.event.clear()
    timed_wait( 0, press_events )

Notice that this time we pass zero as the time to wait – this just means we will never time out on this screen – the zero gets passed in and used in the pygame.time.set_timer call, but passing in zero for the time there means “cancel this event”, and is harmless if the event doesn’t actually exist, so no timer will be set up – we will only stop waiting when the player presses something, which is what we want here.

Now we can make our success and failure functions more polite. Change them all to look like this:

def green_success():
    tick()
    green = pygame.Color( "green" )
    white = pygame.Color( "white" )
    write_text( screen, "Well done!", green, True )
    write_text( screen, "You pressed on green!", white, False )
    pygame.display.flip()
    timed_wait( 2000, press_events ) # 2 seconds

def green_failure():
    cross()
    red   = pygame.Color( "red" )
    white = pygame.Color( "white" )
    write_text( screen, "Bad Luck!", red, True )
    write_text( screen, "Green means press something!", white, False )
    pygame.display.flip()
    timed_wait( 2000, press_events ) # 2 seconds

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()
    timed_wait( 2000, press_events ) # 2 seconds

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()
    timed_wait( 2000, press_events ) # 2 seconds

They all call timed_wait saying wait for 2 seconds, but skip if a key is pressed because the player is impatient to get on to the next round. This change means not only can you skip past these success and failure screens, but also you can quit while they are visible, and the last vestige of rudeness has been wiped out from our game.

Well done – just one job left, which is to allow several rounds, and count the player’s score as they play. We’ll do that next time.

In the meantime, you can fix a bug I made – I typed get_width instead of get_height, which made my circles too big. Change the line inside green_shape that looks like radius = screen.get_width() / 3 to look like this:

    radius = screen.get_height() / 3

There we are – much better

You can check your verson against mine here: redgreen.py

See you next time, when hopefully we’ll finish the game!

My First Raspberry Pi Game – Part 10 – Red square

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

Writing your first ever computer program on the Raspberry Pi.

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.

My First Raspberry Pi Game – Part 09 – Lots more words

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

Writing your first ever computer program on the Raspberry Pi.

We’re writing a really simple game – you have to press a key when you see green.

This time we’re going to add lots of instructions on each page so the player knows what to do.

Up until now, we’ve only had one piece of writing – the word “Ready?” when the game starts up. To make the game easy to use we want to write instructions on each page. We’ll take the code we wrote to say “Ready?” and turn it into a function that we can re-use for lots of different writing.

The first step we’re going to do will be “refactoring”, which just means changing our program without changing what it does.

Have a look at this code, that we’ve already got:

def start():
    global screen, ready_text
    pygame.init()
    screen = pygame.display.set_mode( screen_size )
    font = pygame.font.Font( None, screen_height / 5 )
    ready_text = font.render( "Ready?", 1, pygame.Color( "white" ) )

def quit():
    pygame.quit()
    sys.exit()

def ready_screen():
    textpos = ready_text.get_rect(
        centerx = screen.get_width() / 2,
        centery = screen.get_height() / 2
    )

    screen.blit( ready_text, textpos )
    pygame.display.flip()

The blue code here works together to write something on the screen. The top part inside start creates an image called ready_text, and the bottom part in ready_screen works out where to draw it, then draws it.

We’re going to extract these two bits out from where they are, and put them into a function we can re-use whenever we want to write something.

Make a function above start and call it write_text. Cut the lines in blue, and paste them into write_text. Rename the variable ready_text to rend (in three places) because it’s a rendered image of our writing. You should end up with something like this:

def write_text():
    font = pygame.font.Font( None, screen_height / 5 )
    rend = font.render( "Ready?", 1, pygame.Color( "white" ) )
    textpos = rend.get_rect(
        centerx = screen.get_width() / 2,
        centery = screen.get_height() / 2
    )
    screen.blit( rend, textpos )

Now make the code work almost exactly as before by making a call to this function from within ready_screen:

def ready_screen():
    write_text()
    pygame.display.flip()

You can try your program now and you should find it works exactly as it did before.

So far we haven’t make a very useful function, because it always writes “Ready?”. We can change that by adding arguments to the write_text function for the screen to write on, the text to write, and the colour of the writing. Now write_text looks like this:

def write_text( screen, text, color ):
    font = pygame.font.Font( None, screen.get_height() / 5 )
    rend = font.render( text, 1, color )
    pos = rend.get_rect(
        centerx = screen.get_width() / 2,
        centery = screen.get_height() / 2
    )
    screen.blit( rend, pos )

and ready_screen looks like this:

def ready_screen():
    white = pygame.Color( "white" )
    write_text( screen, "Ready?", white )
    pygame.display.flip()

(Notice that I made a variable called white and then passed it in.)

At this point, you can also delete the line above write_text that says ready_text = None because we don’t need that variable any more (we are just using the variable rend inside write_text) and you can remove ready_text from the first line of the start function, so it just reads global screen.

Again, you can try your program now and you should find it works exactly as it did before.

By working in gradual steps like this, we give ourselves small enough chunks of things to think about that we’re relatively unlikely to get confused and screw things up. As you work with longer programs I hope you’ll see how useful this technique can be.

The next step we want to take is to allow two different types of writing: either in the middle, big or at the bottom, smaller. Let’s add another argument, called big and make it control how big the writing is, and where it goes.

def write_text( screen, text, color, big ):
    if big:
        height = screen.get_height() / 5
        up = screen.get_height() / 2
    font = pygame.font.Font( None, height )
    rend = font.render( text, 1, color )
    pos = rend.get_rect(
        centerx = screen.get_width() / 2,
        centery = up
    )
    screen.blit( rend, up )

And let’s pass in True so we behave just as we did before:

def ready_screen():
    white = pygame.Color( "white" )
    write_text( screen, "Ready?", white, True )
    pygame.display.flip()

So far we’ve only said what happens when we pass True for big – if we pass False, height and pos will not be set, and the program will go wrong. Let’s fix that now:

def write_text( screen, text, color, big ):
    if big:
        height = screen.get_height() / 5
        up = screen.get_height() / 2
    else:
        height = screen_height / 12
        up = screen.get_height() - ( screen_height / 24 )
    font = pygame.font.Font( None, height )
    rend = font.render( text, 1, color )
    pos = rend.get_rect(
        centerx = screen.get_width() / 2,
        centery = up
    )
    screen.blit( rend, pos )

So now we can write small text at the bottom of the sreen. Let’s do that on the green_shape page, so the player knows to press a key. Change green_shape to look like:

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 )

    write_text( screen, "Press something!", pygame.Color( "black" ), False )

    pygame.display.flip()

    pressed = shape_wait()

    if pressed:
        green_success()
    else:
        green_failure()

Now we can add some more text to green_success and green_failure saying well done when you won and bad luck when you lost:

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

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

Notice that we have added pygame.display.flip() to the end of each of these sections, so we can remove that line from the end of tick and cross. Also, we need to move our tick and cross pictures up so they’re not on top of the writing, so we’ll divide by 4 instead of 2 to get the middle of the picture:

def tick():
    colour = pygame.Color( "green" )
    w = screen.get_width() / 2
    h = screen.get_height() / 4
    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() / 4
    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()

And finally, let’s say goodbye at the end. Change end to look like this:

def end():
    screen.fill( pygame.Color( "black" ) )
    white = pygame.Color( "white" )
    write_text( screen, "Thanks for playing!", white, True )
    write_text( screen, "Press a key to exit", white, False )
    pygame.display.flip()

    pygame.event.clear()
    event_types_that_cancel = pygame.KEYDOWN, pygame.MOUSEBUTTONDOWN
    waiting = True
    while waiting:
        evt = pygame.event.wait()
        if evt.type == pygame.QUIT:
            quit()
        elif evt.type in event_types_that_cancel:
            waiting = False

Now we’ve got much more enlightened players of our game!

We’ve made lots of changes in lots of different places today. If something doesn’t work, check your version again mine: redgreen.py.

Next time we’ll show red squares as well as green circles, making the game significantly more interesting.

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

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

Writing your first ever computer program on the Raspberry Pi.

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.

My First Raspberry Pi Game – Part 07 – A green circle

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

Writing your first ever computer program on the Raspberry Pi.

We’re going to write a game that tests your reactions – press a key when you see green, but don’t when you see red.

Today we see some of what we have been waiting for – a genuine bona-fide green circle, made by you!

We’re going to need some random numbers, so edit your program in LeafPad, and add a line, just before import pygame near the top:

import random

This makes the “random” module available to us so we can make some numbers later.

Remember we had a function called “wait”, but it never did anything? It was supposed to wait for a random amount of time before we showed our green or red shape. Let’s write it now. Find the empty wait function and replace it with:

def wait():
    time_to_wait = random.randint( 1500, 3000 ) # Between 1.5 and 3 seconds
    pygame.time.wait( time_to_wait ) # Note bug: can't quit during this time

The first line makes a variable time_to_wait and puts a random number into it. The random.randint function gives us a random number between the two numbers we supplied, so here between 1,500 and 3,000. time_to_wait is a time in milliseconds, so this means between one and half and three seconds.

After the closing bracket, we have a hash symbol #, and then some writing. This is a “comment”, and it is completely ignored by Python. It’s just for us.

[As time goes on, I hope you will begin to see programming more and more as talking to other people, not just to the computer. It’s fairly easy to write a computer program, but much harder to understand one written by someone else. Most programs live a long time, and people need to understand them to keep them up-to-date, so making them as easy to understand as possible is very important. Comments are one way to help people understand, but in a way they are a last resort – if possible, the code itself should be so easy to understand that you don’t need many comments. Here, I thought that the translation between seconds and milliseconds might be helpful to someone looking at this later.]

The next line uses a function inside PyGame’s time module to wait for the amount of time we give it (in milliseconds, stored in time_to_wait). Note that this is not the same wait function we have seen before, pygame.event.wait. That one waits forever for an event to happen, but this one waits (and can’t be interrupted) for the amount of time we say.

I’ve added another comment to this line saying that there’s a bug in our program: if we write it like this, you can’t actually quit the game by closing the window while we’re waiting. The pygame.time.wait function won’t be interrupted by the window being closed, so we’ll ignore it. This is almost unbearably rude, but don’t worry – we’ll fix it soon (ish).

And now for the really exciting part: we’re going to draw a green shape on the screen. Let’s make a function, just above the shape function, called green_shape:

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()

This code makes a variable green holding onto the colour green, one called centre holding the co-ordinates of the centre of the screen, and one called radius holding the size of the circle we want to draw.

Then it uses the fill function on screen to colour in the screen white, and then draws our circle with a call to pygame.draw.circle, using the variables we have prepared as arguments, telling it where to draw the circle, in what colour, and what size.

Finally it uses flip as before to tell PyGame we have finished.

The last piece of today’s jigsaw is just to call the function we created above. Find the empty shape function, and make it look like this:

def shape():
    green_shape()

This literally just means run the green_shape function.

Take a deep breath, prepare to be excited, open LXTerminal and run our new program in the usual way:

./redgreen.py

If all has gone well, the ready screen will appear for a couple of seconds, before a white screen with a big green circle on it appears. This will then go away when you press a key.

If something goes wrong, check back what you typed, and compare your version against mine: redgreen.py.

Next time, we’ll find out whether you pressed a key or were too slow!