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