Skip to main content
Engineering LibreTexts

8.03: Source Code to Squirrel Eat Squirrel

  • Page ID
    13613
  • This source code can be downloaded from http://invpy.com/squirrel.py. If you get any error messages, look at the line number that is mentioned in the error message and check your code for any typos. You can also copy and paste your code into the web form at http://invpy.com/diff/squirrel to see if the differences between your code and the code in the book.

    You will also need to download the following image files:

    # Squirrel Eat Squirrel (a 2D Katamari Damacy clone)
    # By Al Sweigart al@inventwithpython.com
    # http://inventwithpython.com/pygame
    # Released under a "Simplified BSD" license
    
    import random, sys, time, math, pygame
    from pygame.locals import *
    
    FPS = 30 # frames per second to update the screen
    WINWIDTH = 640 # width of the program's window, in pixels
    WINHEIGHT = 480 # height in pixels
    HALF_WINWIDTH = int(WINWIDTH / 2)
    HALF_WINHEIGHT = int(WINHEIGHT / 2)
    
    GRASSCOLOR = (24, 255, 0)
    WHITE = (255, 255, 255)
    RED = (255, 0, 0)
    
    CAMERASLACK = 90     # how far from the center the squirrel moves before moving the camera
    MOVERATE = 9         # how fast the player moves
    BOUNCERATE = 6       # how fast the player bounces (large is slower)
    BOUNCEHEIGHT = 30    # how high the player bounces
    STARTSIZE = 25       # how big the player starts off
    WINSIZE = 300        # how big the player needs to be to win
    INVULNTIME = 2       # how long the player is invulnerable after being hit in seconds
    GAMEOVERTIME = 4     # how long the "game over" text stays on the screen in seconds
    MAXHEALTH = 3        # how much health the player starts with
    
    NUMGRASS = 80        # number of grass objects in the active area
    NUMSQUIRRELS = 30    # number of squirrels in the active area
    SQUIRRELMINSPEED = 3 # slowest squirrel speed
    SQUIRRELMAXSPEED = 7 # fastest squirrel speed
    DIRCHANGEFREQ = 2    # % chance of direction change per frame
    LEFT = 'left'
    RIGHT = 'right'
    
    """
    This program has three data structures to represent the player, enemy squirrels, and grass background objects. The data structures are dictionaries with the following keys:
    
    Keys used by all three data structures:
        'x' - the left edge coordinate of the object in the game world (not a pixel coordinate on the screen)
        'y' - the top edge coordinate of the object in the game world (not a pixel coordinate on the screen)
        'rect' - the pygame.Rect object representing where on the screen the object is located.
    Player data structure keys:
        'surface' - the pygame.Surface object that stores the image of the squirrel which will be drawn to the screen.
        'facing' - either set to LEFT or RIGHT, stores which direction the player is facing.
        'size' - the width and height of the player in pixels. (The width & height are always the same.)
        'bounce' - represents at what point in a bounce the player is in. 0 means standing (no bounce), up to BOUNCERATE (the completion of the bounce)
        'health' - an integer showing how many more times the player can be hit by a larger squirrel before dying.
    Enemy Squirrel data structure keys:
        'surface' - the pygame.Surface object that stores the image of the squirrel which will be drawn to the screen.
        'movex' - how many pixels per frame the squirrel moves horizontally. A negative integer is moving to the left, a positive to the right.
        'movey' - how many pixels per frame the squirrel moves vertically. A negative integer is moving up, a positive moving down.
        'width' - the width of the squirrel's image, in pixels
        'height' - the height of the squirrel's image, in pixels
        'bounce' - represents at what point in a bounce the player is in. 0 means standing (no bounce), up to BOUNCERATE (the completion of the bounce)
        'bouncerate' - how quickly the squirrel bounces. A lower number means a quicker bounce.
        'bounceheight' - how high (in pixels) the squirrel bounces
    Grass data structure keys:
        'grassImage' - an integer that refers to the index of the pygame.Surface object in GRASSIMAGES used for this grass object
    """
    
    def main():
        global FPSCLOCK, DISPLAYSURF, BASICFONT, L_SQUIR_IMG, R_SQUIR_IMG, GRASSIMAGES
    
        pygame.init()
        FPSCLOCK = pygame.time.Clock()
        pygame.display.set_icon(pygame.image.load('gameicon.png'))
        DISPLAYSURF = pygame.display.set_mode((WINWIDTH, WINHEIGHT))
        pygame.display.set_caption('Squirrel Eat Squirrel')
        BASICFONT = pygame.font.Font('freesansbold.ttf', 32)
    
        # load the image files
        L_SQUIR_IMG = pygame.image.load('squirrel.png')
        R_SQUIR_IMG = pygame.transform.flip(L_SQUIR_IMG, True, False)
        GRASSIMAGES = []
        for i in range(1, 5):
            GRASSIMAGES.append(pygame.image.load('grass%s.png' % i))
    
        while True:
            runGame()
    
    
    def runGame():
        # set up variables for the start of a new game
        invulnerableMode = False  # if the player is invulnerable
        invulnerableStartTime = 0 # time the player became invulnerable
        gameOverMode = False      # if the player has lost
        gameOverStartTime = 0     # time the player lost
        winMode = False           # if the player has won
    
        # create the surfaces to hold game text
        gameOverSurf = BASICFONT.render('Game Over', True, WHITE)
        gameOverRect = gameOverSurf.get_rect()
        gameOverRect.center = (HALF_WINWIDTH, HALF_WINHEIGHT)
    
        winSurf = BASICFONT.render('You have achieved OMEGA SQUIRREL!', True, WHITE)
        winRect = winSurf.get_rect()
        winRect.center = (HALF_WINWIDTH, HALF_WINHEIGHT)
    
        winSurf2 = BASICFONT.render('(Press "r" to restart.)', True, WHITE)
        winRect2 = winSurf2.get_rect()
        winRect2.center = (HALF_WINWIDTH, HALF_WINHEIGHT + 30)
    
        # camerax and cameray are the top left of where the camera view is
        camerax = 0
        cameray = 0
    
        grassObjs = []    # stores all the grass objects in the game
        squirrelObjs = [] # stores all the non-player squirrel objects
        # stores the player object:
        playerObj = {'surface': pygame.transform.scale(L_SQUIR_IMG, (STARTSIZE, STARTSIZE)),
                     'facing': LEFT,
                     'size': STARTSIZE,
                     'x': HALF_WINWIDTH,
                     'y': HALF_WINHEIGHT,
                     'bounce':0,
                     'health': MAXHEALTH}
    
        moveLeft  = False
        moveRight = False
        moveUp    = False
        moveDown  = False
    
        # start off with some random grass images on the screen
        for i in range(10):
            grassObjs.append(makeNewGrass(camerax, cameray))
            grassObjs[i]['x'] = random.randint(0, WINWIDTH)
            grassObjs[i]['y'] = random.randint(0, WINHEIGHT)
    
        while True: # main game loop
            # Check if we should turn off invulnerability
            if invulnerableMode and time.time() - invulnerableStartTime > INVULNTIME:
                invulnerableMode = False
    
            # move all the squirrels
            for sObj in squirrelObjs:
                # move the squirrel, and adjust for their bounce
                sObj['x'] += sObj['movex']
                sObj['y'] += sObj['movey']
                sObj['bounce'] += 1
                if sObj['bounce'] > sObj['bouncerate']:
                    sObj['bounce'] = 0 # reset bounce amount
    
                # random chance they change direction
                if random.randint(0, 99) < DIRCHANGEFREQ:
                    sObj['movex'] = getRandomVelocity()
                    sObj['movey'] = getRandomVelocity()
                    if sObj['movex'] > 0: # faces right
                        sObj['surface'] = pygame.transform.scale(R_SQUIR_IMG, (sObj['width'], sObj['height']))
                    else: # faces left
                        sObj['surface'] = pygame.transform.scale(L_SQUIR_IMG, (sObj['width'], sObj['height']))
    
    
            # go through all the objects and see if any need to be deleted.
            for i in range(len(grassObjs) - 1, -1, -1):
                if isOutsideActiveArea(camerax, cameray, grassObjs[i]):
                    del grassObjs[i]
            for i in range(len(squirrelObjs) - 1, -1, -1):
                if isOutsideActiveArea(camerax, cameray, squirrelObjs[i]):
                    del squirrelObjs[i]
    
            # add more grass & squirrels if we don't have enough.
            while len(grassObjs) < NUMGRASS:
                grassObjs.append(makeNewGrass(camerax, cameray))
            while len(squirrelObjs) < NUMSQUIRRELS:
                squirrelObjs.append(makeNewSquirrel(camerax, cameray))
    
            # adjust camerax and cameray if beyond the "camera slack"
            playerCenterx = playerObj['x'] + int(playerObj['size'] / 2)
            playerCentery = playerObj['y'] + int(playerObj['size'] / 2)
            if (camerax + HALF_WINWIDTH) - playerCenterx > CAMERASLACK:
                camerax = playerCenterx + CAMERASLACK - HALF_WINWIDTH
            elif playerCenterx - (camerax + HALF_WINWIDTH) > CAMERASLACK:
                camerax = playerCenterx - CAMERASLACK - HALF_WINWIDTH
            if (cameray + HALF_WINHEIGHT) - playerCentery > CAMERASLACK:
                cameray = playerCentery + CAMERASLACK - HALF_WINHEIGHT
            elif playerCentery - (cameray + HALF_WINHEIGHT) > CAMERASLACK:
                cameray = playerCentery - CAMERASLACK - HALF_WINHEIGHT
    
            # draw the green background
            DISPLAYSURF.fill(GRASSCOLOR)
    
            # draw all the grass objects on the screen
            for gObj in grassObjs:
                gRect = pygame.Rect( (gObj['x'] - camerax,
                                      gObj['y'] - cameray,
                                      gObj['width'],
                                      gObj['height']) )
                DISPLAYSURF.blit(GRASSIMAGES[gObj['grassImage']], gRect)
    
    
            # draw the other squirrels
            for sObj in squirrelObjs:
                sObj['rect'] = pygame.Rect( (sObj['x'] - camerax,
                                             sObj['y'] - cameray - getBounceAmount(sObj['bounce'], sObj['bouncerate'], sObj['bounceheight']),
                                             sObj['width'],
                                             sObj['height']) )
                DISPLAYSURF.blit(sObj['surface'], sObj['rect'])
    
    
            # draw the player squirrel
            flashIsOn = round(time.time(), 1) * 10 % 2 == 1
            if not gameOverMode and not (invulnerableMode and flashIsOn):
                playerObj['rect'] = pygame.Rect( (playerObj['x'] - camerax,
                                                  playerObj['y'] - cameray - getBounceAmount(playerObj['bounce'], BOUNCERATE, BOUNCEHEIGHT),
                                                  playerObj['size'],
                                                  playerObj['size']) )
                DISPLAYSURF.blit(playerObj['surface'], playerObj['rect'])
    
    
            # draw the health meter
            drawHealthMeter(playerObj['health'])
    
            for event in pygame.event.get(): # event handling loop
                if event.type == QUIT:
                    terminate()
    
                elif event.type == KEYDOWN:
                    if event.key in (K_UP, K_w):
                        moveDown = False
                        moveUp = True
                    elif event.key in (K_DOWN, K_s):
                        moveUp = False
                        moveDown = True
                    elif event.key in (K_LEFT, K_a):
                        moveRight = False
                        moveLeft = True
                        if playerObj['facing'] != LEFT: # change player image
                            playerObj['surface'] = pygame.transform.scale(L_SQUIR_IMG, (playerObj['size'], playerObj['size']))
                        playerObj['facing'] = LEFT
                    elif event.key in (K_RIGHT, K_d):
                        moveLeft = False
                        moveRight = True
                        if playerObj['facing'] != RIGHT: # change player image
                            playerObj['surface'] = pygame.transform.scale(R_SQUIR_IMG, (playerObj['size'], playerObj['size']))
                        playerObj['facing'] = RIGHT
                    elif winMode and event.key == K_r:
                        return
    
                elif event.type == KEYUP:
                    # stop moving the player's squirrel
                    if event.key in (K_LEFT, K_a):
                        moveLeft = False
                    elif event.key in (K_RIGHT, K_d):
                        moveRight = False
                    elif event.key in (K_UP, K_w):
                        moveUp = False
                    elif event.key in (K_DOWN, K_s):
                        moveDown = False
    
                    elif event.key == K_ESCAPE:
                        terminate()
    
            if not gameOverMode:
                # actually move the player
                if moveLeft:
                    playerObj['x'] -= MOVERATE
                if moveRight:
                    playerObj['x'] += MOVERATE
                if moveUp:
                    playerObj['y'] -= MOVERATE
                if moveDown:
                    playerObj['y'] += MOVERATE
    
                if (moveLeft or moveRight or moveUp or moveDown) or playerObj['bounce'] != 0:
                    playerObj['bounce'] += 1
    
                if playerObj['bounce'] > BOUNCERATE:
                    playerObj['bounce'] = 0 # reset bounce amount
    
                # check if the player has collided with any squirrels
                for i in range(len(squirrelObjs)-1, -1, -1):
                    sqObj = squirrelObjs[i]
                    if 'rect' in sqObj and playerObj['rect'].colliderect(sqObj['rect']):
                        # a player/squirrel collision has occurred
    
                        if sqObj['width'] * sqObj['height'] <= playerObj['size']**2:
                            # player is larger and eats the squirrel
                            playerObj['size'] += int( (sqObj['width'] * sqObj['height'])**0.2 ) + 1
                            del squirrelObjs[i]
    
                            if playerObj['facing'] == LEFT:
                                playerObj['surface'] = pygame.transform.scale(L_SQUIR_IMG, (playerObj['size'], playerObj['size']))
                            if playerObj['facing'] == RIGHT:
                                playerObj['surface'] = pygame.transform.scale(R_SQUIR_IMG, (playerObj['size'], playerObj['size']))
    
                            if playerObj['size'] > WINSIZE:
                                winMode = True # turn on "win mode"
    
                        elif not invulnerableMode:
                            # player is smaller and takes damage
                            invulnerableMode = True
                            invulnerableStartTime = time.time()
                            playerObj['health'] -= 1
                            if playerObj['health'] == 0:
                                gameOverMode = True # turn on "game over mode"
                                gameOverStartTime = time.time()
            else:
                # game is over, show "game over" text
                DISPLAYSURF.blit(gameOverSurf, gameOverRect)
                if time.time() - gameOverStartTime > GAMEOVERTIME:
                    return # end the current game
    
            # check if the player has won.
            if winMode:
                DISPLAYSURF.blit(winSurf, winRect)
                DISPLAYSURF.blit(winSurf2, winRect2)
    
            pygame.display.update()
            FPSCLOCK.tick(FPS)
    
    
    
    
    def drawHealthMeter(currentHealth):
        for i in range(currentHealth): # draw red health bars
            pygame.draw.rect(DISPLAYSURF, RED,   (15, 5 + (10 * MAXHEALTH) - i * 10, 20, 10))
        for i in range(MAXHEALTH): # draw the white outlines
            pygame.draw.rect(DISPLAYSURF, WHITE, (15, 5 + (10 * MAXHEALTH) - i * 10, 20, 10), 1)
    
    
    def terminate():
        pygame.quit()
        sys.exit()
    
    
    def getBounceAmount(currentBounce, bounceRate, bounceHeight):
        # Returns the number of pixels to offset based on the bounce.
        # Larger bounceRate means a slower bounce.
        # Larger bounceHeight means a higher bounce.
        # currentBounce will always be less than bounceRate
        return int(math.sin( (math.pi / float(bounceRate)) * currentBounce ) * bounceHeight)
    
    def getRandomVelocity():
        speed = random.randint(SQUIRRELMINSPEED, SQUIRRELMAXSPEED)
        if random.randint(0, 1) == 0:
            return speed
        else:
            return -speed
    
    
    def getRandomOffCameraPos(camerax, cameray, objWidth, objHeight):
        # create a Rect of the camera view
        cameraRect = pygame.Rect(camerax, cameray, WINWIDTH, WINHEIGHT)
        while True:
            x = random.randint(camerax - WINWIDTH, camerax + (2 * WINWIDTH))
            y = random.randint(cameray - WINHEIGHT, cameray + (2 * WINHEIGHT))
            # create a Rect object with the random coordinates and use colliderect()
            # to make sure the right edge isn't in the camera view.
            objRect = pygame.Rect(x, y, objWidth, objHeight)
            if not objRect.colliderect(cameraRect):
                return x, y
    
    
    def makeNewSquirrel(camerax, cameray):
        sq = {}
        generalSize = random.randint(5, 25)
        multiplier = random.randint(1, 3)
        sq['width']  = (generalSize + random.randint(0, 10)) * multiplier
        sq['height'] = (generalSize + random.randint(0, 10)) * multiplier
        sq['x'], sq['y'] = getRandomOffCameraPos(camerax, cameray, sq['width'], sq['height'])
        sq['movex'] = getRandomVelocity()
        sq['movey'] = getRandomVelocity()
        if sq['movex'] < 0: # squirrel is facing left
            sq['surface'] = pygame.transform.scale(L_SQUIR_IMG, (sq['width'], sq['height']))
        else: # squirrel is facing right
            sq['surface'] = pygame.transform.scale(R_SQUIR_IMG, (sq['width'], sq['height']))
        sq['bounce'] = 0
        sq['bouncerate'] = random.randint(10, 18)
        sq['bounceheight'] = random.randint(10, 50)
        return sq
    
    
    def makeNewGrass(camerax, cameray):
        gr = {}
        gr['grassImage'] = random.randint(0, len(GRASSIMAGES) - 1)
        gr['width']  = GRASSIMAGES[0].get_width()
        gr['height'] = GRASSIMAGES[0].get_height()
        gr['x'], gr['y'] = getRandomOffCameraPos(camerax, cameray, gr['width'], gr['height'])
        gr['rect'] = pygame.Rect( (gr['x'], gr['y'], gr['width'], gr['height']) )
        return gr
    
    
    def isOutsideActiveArea(camerax, cameray, obj):
        # Return False if camerax and cameray are more than
        # a half-window length beyond the edge of the window.
        boundsLeftEdge = camerax - WINWIDTH
        boundsTopEdge = cameray - WINHEIGHT
        boundsRect = pygame.Rect(boundsLeftEdge, boundsTopEdge, WINWIDTH * 3, WINHEIGHT * 3)
        objRect = pygame.Rect(obj['x'], obj['y'], obj['width'], obj['height'])
        return not boundsRect.colliderect(objRect)
    
    
    if __name__ == '__main__':
        main()