Squirrel Eat Squirrel
Attribution
From Making Games with Python & Pygame chapter 8.
Original Python code released under a Simplified BSD license.
Images licensed under Creative Commons BY-NC-SA.
Original Python code
# 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()