An Interactive Maze

A couple of weeks ago we wrote code to create mazes. Let's add code so that people can try to solve them. We'll modify maze_6.py so that our users can try to solve it by moving an icon (perhaps our standby Aqua-Ball-icon.png: Aqua-Ball-icon.png) through it. They should be able to control the icon with either the mouse (by clicking in the cell they want it to move to) or the arrow keys (to try and move it up, down, left or right). The program should prevent them from moving the icon through walls, and should be able to tell when they have won and maybe play a triumphant sound — or at least beep (cheerfully rather than gratingly!).

The pseudocode is fairly straightforward:

clock = pygame.time.Clock() # See Note 1.
set the initial maze dimensions
while the user is not finished playing
    generate a maze
    while the current cell is not the exit cell
        # Wait for an event: quit, mouse click or keypress.
        event = pygame.event.wait(): # See Note 2.
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()
        elif event.type == pygame.MOUSEBUTTONUP:
            Get mouse coords.
            Convert mouse coords to cell coords.
            (Hint: as in earlier Life GUI program).
        elif event.type == KEYUP:
            N.B. event contains keypress information
            Figure out what requested destination cell is.
        if the destination cell neighbours the current cell
           and if there is a door connecting them
            change the current cell location to be the destination cell
            move the player icon from the old cell to the new one
        else:
            beep chidingly # See Note 3.
        clock.tick(20) # Adjust parameter to suit.

    play the happy sound # See Note 3.
    make the maze fade/dissolve/crumble # See Note 4.
    increase the maze dimensions

Notes:

  1. pygame.time.Clock() is used because this is not a fast action game where we want every CPU resource available going to monitor events from the user. Instead we'd like this program to get along with the other programs running on our computer and give them a chance to get things done as well. We'll use the .tick() method to tell pygame to take timeouts during its processing. You'll want to adjust the parameter to tick() so that Pygame resource usage does not spike, while keeping the game responsive.

  2. Similarly we'll use pygame.event.wait() both so that our program will get along well with others, and so that our program will only ever handle one event per main event loop. According to the pygame documentation it: "Returns a single event from the queue. If the queue is empty this function will wait until one is created. The event is removed from the queue once it has been returned. While the program is waiting it will sleep in an idle state. This is important for programs that want to share the system with other applications."

  3. You play a sound by loading it which creates a sound object and then invoking the object's play method,

    sound = pygame.mixer.Sound(filename)
    sound.play()

    This bit of code assumes filename exists, and can be opened, and interpreted as a sound file. You might be interested to see how the code changes if we add typical safety checks to it using Python exceptions,

    def load_sound(name):
        # Define a dummy class that behaves enough like a Sound object
        # that the rest of the game will still run.
        class NoneSound:
            def play(self): pass
        # Check that pygame.mixer was imported successfully.
        if not pygame.mixer:
            return NoneSound()
        # Build OS independent pathname to requested sound file.
        fullname = os.path.join('data', name)
        # Now try loading the sound file.
        try:
            sound = pygame.mixer.Sound(fullname)
        except pygame.error, message:
            print 'Cannot load sound:', wav
            raise SystemExit, message
        return sound
    
    sound = load_sound(filename)

    There are lots of sources for .wav files on the internet, but a small one with a decent selection for small programming projects for the undiscriminating user is http://www.mediacollege.com/downloads/sound-effects/. I use it because it is small, and the sound effects are free (as in speech, and less importantly, beer) to download.

  4. You can fade the maze away by redrawing it in increasingly dim colours. You could "dissolve" it by randomly removing walls until there are none left. Crumbling the maze is left as a challenge.

Enhancements are possible and will be tested with great relish. For example,