Squirrel Eat Squirrel is loosely based on the game “Katamari
Damacy”. The player controls a small squirrel that must hop around the screen
eating smaller squirrels and avoiding larger squirrels. Each time the player’s
squirrel eats a squirrel that is smaller than it, it grows larger. If the
player’s squirrel gets hit by a larger squirrel larger than it, it loses a life
point. The player wins when the squirrel becomes a monstrously large squirrel
called the Omega Squirrel. The player loses if their squirrel gets hit three
times.

I’m not really sure where I got the idea for a video game where
squirrels eat each other. I’m a little strange sometimes.

There are three types of data structures in this game, which
are represented as dictionary values. The types are player squirrels, enemy
squirrels, and grass objects. There is only one player squirrel object at a
time in the game.

Note: Technically, “object” means something specific in
Object-Oriented Programming. Python does have OOP features, but they aren’t
covered in this book. Technically the Pygame objects such as “Rect object” or
“Surface object” are objects. But I’m going to use the term “object” in this
book to refer to “things that exist in the game world”. But really, the player
squirrel, enemy squirrels, and grass “objects” are just dictionary values.

All the objects have the following keys in their dictionary
value: 'x', 'y', and 'rect'. The 'x' and 'y' key’s value give the coordinates of the top left of
the object in game world coordinates. These are different from pixel
coordinates (which is what the 'rect' key’s value
tracks). The difference between game world and pixel coordinates will be
explained when you learn about the concept of cameras.

In addition, the player squirrel, enemy squirrel, and grass
objects have other keys which are explained in a large comment at the start of
the source code.

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.

The start of the program assigns several constant variables.
This program frequently makes use of the half length of the width and height of
the window so much that the HALF_WINWIDTH and HALF_WINHEIGHT variables store these numbers.

19. CAMERASLACK
= 90 # how far from the center the squirrel
moves before moving the camera

The “camera slack” is described later. Basically, it means
that the camera will begin following the player squirrel when it moves 90
pixels away from the center of the window.

20. MOVERATE
= 9 # how fast the player moves

21. BOUNCERATE
= 6 # how fast the player bounces (large is
slower)

22. BOUNCEHEIGHT
= 30 # how high the player bounces

23. STARTSIZE
= 25 # how big the player starts off

24. WINSIZE
= 300 # how big the player needs to be to
win

25. INVULNTIME
= 2 # how long the player is invulnerable
after being hit in seconds

26. GAMEOVERTIME
= 4 # how long the "game over" text
stays on the screen in seconds

27. MAXHEALTH
= 3 # how much health the player starts
with

28.

29. NUMGRASS
= 80 # number of grass objects in the active
area

30. NUMSQUIRRELS
= 30 # number of squirrels in the active area

31. SQUIRRELMINSPEED
= 3 # slowest squirrel speed

32. SQUIRRELMAXSPEED
= 7 # fastest squirrel speed

33. DIRCHANGEFREQ
= 2 # % chance of direction change per frame

34. LEFT
= 'left'

35. RIGHT
= 'right'

The comments next to these constants explains what the
constant variable is used for.

38. 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:

39.

40. Keys used by all three data structures:

41. 'x' - the left edge coordinate of the object in the
game world (not a pixel coordinate on the screen)

42. 'y' - the top edge coordinate of the object in the game
world (not a pixel coordinate on the screen)

43. 'rect' - the pygame.Rect object representing where on
the screen the object is located.

44. Player data structure keys:

45. 'surface' - the pygame.Surface object that stores the
image of the squirrel which will be drawn to the screen.

46. 'facing' - either set to LEFT or RIGHT, stores which
direction the player is facing.

47. 'size' - the width and height of the player in pixels.
(The width & height are always the same.)

48. '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)

49. 'health' - an integer showing how many more times the
player can be hit by a larger squirrel before dying.

50. Enemy Squirrel data structure keys:

51. 'surface' - the pygame.Surface object that stores the
image of the squirrel which will be drawn to the screen.

52. 'movex' - how many pixels per frame the squirrel moves
horizontally. A negative integer is moving to the left, a positive to the
right.

53. 'movey' - how many pixels per frame the squirrel moves
vertically. A negative integer is moving up, a positive moving down.

54. 'width' - the width of the squirrel's image, in pixels

55. 'height' - the height of the squirrel's image, in
pixels

56. '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)

57. 'bouncerate' - how quickly the squirrel bounces. A
lower number means a quicker bounce.

58. 'bounceheight' - how high (in pixels) the squirrel
bounces

59. Grass data structure keys:

60. 'grassImage' - an integer that refers to the index of
the pygame.Surface object in GRASSIMAGES used for this grass object

61. """

The comments from lines 37 to 61 are in one large,
multi-line string. They describe the keys in the player squirrel, enemy
squirrel, and grass objects. In Python, a multi-line string value by itself
works as a multi-line comment.

The first several lines of the main()
function are the same setup code that we’ve seen in our previous game programs.
The pygame.display.set_icon() is a Pygame function
that sets the icon in the window’s title bar (just like pygame.display.set_caption()
sets the caption text in the title bar). The single argument to pygame.display.set_icon() is a Surface object of a small
image. The ideal image size is 32 x 32 pixels, although you can use other sized
images. The image will just be compressed into a smaller size to be used as the
window’s icon.

The image for the player and enemy squirrels is loaded from squirrel.png
on line 74. Make sure that this PNG file is in the same folder as squirrel.py,
otherwise you will get the error pygame.error: Couldn't open
squirrel.png.

The image in squirrel.png (which you can download
from http://invpy.com/squirrel.png) is of a
squirrel facing to the left. We also need a Surface object that contains a
picture of the squirrel facing to the right. Instead of creating a second PNG
image file, we can call the pygame.transform.flip()
function. This function has three parameters: the Surface object with the image
to flip, a Boolean value to do a horizontal flip, and a Boolean value to do a
vertical flip. By passing True for the second
parameter and False for the third parameter, the
Surface object that returns has the image of the squirrel facing to the right.
The original Surface object in L_SQUIR_IMG that we
passed in is unchanged.

Here are examples of images being horizontally and
vertically flipped:

Original

Horizontal Flip

Vertical Flip

Horizontal and
Vertical Flip

80. while
True:

81. runGame()

After the setup in main() is
complete, the game begins with runGame() being
called.

These variables contain Surface objects with the “Game
Over”, “You have achieved OMEGA SQUIRREL!”, and “(Press "r" to
restart.)” text that appears on the screen after the game ends (with either the
player losing or winning).

The camerax and cameray variables track the game coordinates of the
“camera”. Imagine the game world as an infinite 2D space. This could, of
course, never fit on any screen. We can only draw a portion of the infinite 2D
space on the screen. We call the area of this portion a camera,
because it is as though our screen is just the area of the game world in front what
a camera would see. Here’s a picture of the game world (an infinite green
field) and the area that the camera can view:

As you can see, the game world XY coordinates keep getting
bigger and smaller forever. The game world origin is where the (0, 0) game
world coordinates are. You can see that the three squirrels are located (in
game world coordinates) at (-384, -84), (384, 306), and (585, -234).

But we can only display 640 x 480 pixel area on the screen
(though this can change if we pass different numbers to the pygame.display.set_mode() function), so we need to track
where the camera’s origin is located in game world coordinates. In the picture above,
the camera is placed at (-486, -330) in game world coordinates.

The picture below shows the same field and squirrels, except
everything is given in camera coordinates:

The area that the camera can see (called the camera view)
has it’s center (that is, its origin) at the game world coordinates (-486, -330).
Since what the camera sees is displayed on the player’s screen, the “camera”
coordinates are the same as the “pixel” coordinates. To find out the pixel
coordinates of the squirrels (that is, where on the screen they appear), take
the game coordinates of the squirrel and subtract the game coordinates of the
camera’s origin.

So the squirrel on the left has game world coordinates of (-384,
-84) but appears at (102, 246) on the screen in pixel coordinates. (For the X
coordinate, -384 - -486 = 102 and for the Y coordinate, -84 - -330 = 246.)

When we do the same calculation to find the pixel
coordinates of the other two squirrels, we find that they exist outside of the
range of the screen. This is why they don’t appear in the camera’s view.

The “active area” is just a name I came up with to describe
the area of the game world that the camera views plus an area around it the
size of the camera area:

Calculating if something is in the active area or not is
explained in the isOutsideActiveArea() function’s
explanation later in this chapter. When we create new enemy squirrel or grass
objects, we don’t want them to be created inside the view of the camera, since
it’ll appear that they just pop out of nowhere.

But we also don’t want to create them too far away from the
camera, because then they may never wander into the camera’s view. Inside the
active area but outside the camera is where squirrel and grass objects can safely
be created.

Also, when squirrel and grass objects are beyond the border
of the active area then they are far away enough to delete so that they don’t
take up memory any more. Objects that far away aren’t needed since it is much
less likely that they’ll come back into view of the camera.

If you have ever played Super Mario World on the Super
Nintendo, there is a good YouTube video explaining how Super Mario World’s
camera system works. You can find this video at http://invpy.com/mariocamera.

The grassObjs variable holds a
list of all the grass objects in the game. As new grass objects are created,
they are added to this list. As grass objects are deleted, they are removed
from this list. The same goes for the squirrelObjs
variable and the enemy squirrel objects.

The playerObj variable is not a
list, but just the dictionary value itself.

The move variables on lines 120 to 123 track which of arrow
keys (or WASD keys) are being held down, just like in a few of the previous
game programs.

The active area should start off with a few grass objects
visible on the screen. The makeNewGrass() function
will create and return a grass object that is randomly located somewhere in the
active area but outside the camera view. This is what we normally want when we
call makeNewGrass(), but since we want to make sure
the first few grass objects are on the screen, the X and Y coordinates are
overwritten.

When the player gets hit by an enemy squirrel and does not
die, we make the player invulnerable for a couple seconds (since the INVULNTIME constant is set to 2).
During this time, the player’s squirrel flashes and the won’t take any damage
from other squirrels. If the “invulnerability mode” time is over, line 134 will
set invulnerableMode to False.

The enemy squirrels all move according to the values in
their 'movex' and 'movey'
keys. If these values are positive, the squirrels move right or down. If these
values are negative, they move left or up. The larger the value, the farther
they move on each iteration through the game loop (which means they move
faster).

The for loop on line 137 will
apply this moving code to each of the enemy squirrel objects in the squirrelObjs list. First, line 139 and 140 will adjust
their 'x' and 'y' keys’
values.

141. sObj['bounce']
+= 1

142. if
sObj['bounce'] > sObj['bouncerate']:

143. sObj['bounce']
= 0 # reset bounce amount

The value in sObj['bounce'] is
incremented on each iteration of the game loop for each squirrel. When this
value is 0, the squirrel is at the very beginning of
its bounce. When this value is equal to the value in sObj['bouncerate']
the value is at its end. (This is why a smaller sObj['bouncerate']
value makes for a faster bounce. If sObj['bouncerate']
is 3, then it only takes three iterations through
the game loop for the squirrel to do a full bounce. If sObj['bouncerate']
were 10, then it would take ten iterations.)

When sObj['bounce'] gets larger
than sObj['bouncerate'], then it needs to be reset
to 0. This is what lines 142 and 143 do.

There is a 2% chance on each iteration through the game loop
that the squirrel will randomly change speed and direction. On line 146 the random.randint(0, 99) call randomly selects an integer out
of 100 possible integers. If this number is less than DIRCHANGEFREQ
(which we set to 2 on line 33) then a new value will
be set for sObj['movex'] and sObj['movey'].

Because this means the squirrel might have changed
direction, the Surface object in sObj['surface']
should be replaced by a new one that is properly facing left or right and
scaled to the squirrel’s size. This is what lines 149 to 152 determine. Note
that line 150 gets a Surface object scaled from R_SQUIR_IMG
and line 152 gets one scaled from L_SQUIR_IMG.

During each iteration of the game loop, the code will check
all of the grass and enemy squirrel objects to see if they are outside the
“active area”. The isOutsideActiveArea() function
takes the current coordinates of the camera (which are stored in camerax and cameray) and the
grass/enemy squirrel object, and returns True if the
object is not located in the active area.

If this is the case, this object is deleted on line 158 (for
grass objects) or line 161 (for squirrel objects). This is how squirrel and
grass objects get deleted when the player moves far enough away from them (or
when the enemy squirrels move away far enough from the player). This ensures
that there is always a number of squirrels and grass objects near the player.

Deleting squirrel and grass objects is done with the del operator. However, notice that the for loop on line 156 and 159 pass arguments to the range() function so that the numbering starts at the index
of the last item and then decrements by -1 (unlike
incrementing by 1 as it normally does) until it
reaches the number -1. We are iterating backwards
over the list’s indexes compared to how it is normally done. This is done
because we are iterating over the list that we are also deleting items from.

To see why this reverse order is needed, say we had the
following list value:

animals = ['cat', 'mouse',
'dog', 'horse']

So we wanted to write code to delete any instances of the
string 'dog' from this list. We might think to write
out code like this:

for i in
range(len(animals)):

if
animals[i] == 'dog':

del
animals[i]

But if we ran this code, we would get an IndexError error that looks like this:

Traceback (most
recent call last):

File
"<stdin>", line 2, in <module>

IndexError: list
index out of range

To see why this error happens, let’s walk through the code.
First, the animals list would be set to ['cat', 'mouse', 'dog', 'horse'] and len(animals)
would return 4. This means that the call to range(4) would cause the for
loop to iterate with the values 0, 1, 2, and 3.

When the for loop iterates with i set to 2, the if statement’s condition will be True
and the del animals[i] statement will delete animals[2]. This means that afterwards the animals list
will be ['cat', 'mouse', 'horse']. The indexes of
all the items after 'dog' are all shifted down by
one because the 'dog' value was removed.

But on the next iteration through the for
loop, i is set to 3. But animals[3] is out of bounds because the valid indexes of
the animals list is no longer 0 to 3 but 0 to 2.
The original call to range() was for a list with 4
items in it. The list changed in length, but the for
loop is set up for the original length.

However, if we iterate from the last index of the list to 0, we don’t run into this problem. The following program
deletes the 'dog' string from the animals list without causing an IndexError
error:

animals = ['cat',
'mouse', 'dog', 'horse']

for i in
range(len(animals) - 1, -1, -1):

if
animals[i] == 'dog':

del
animals[i]

The reason this code doesn’t cause an error is because the for loop iterates over 3, 2, 1, and 0.
On the first iteration, the code checks if animals[3]
is equal to 'dog'. It isn’t (animals[3]
is 'horse') so the code moves on to the next
iteration. Then animals[2] is checked if it equals 'dog'. It does, so animals[2]
is deleted.

After animals[2] is deleted, the animals list is set to ['cat',
'mouse', 'horse']. On the next iteration, i
is set to 1. There is a value at animals[1] (the 'mouse' value),
so no error is caused. It doesn’t matter that all the items in the list after 'dog' have shifted down by one, because since we started
at the end of the list and are going towards the front, all of those items have
already been checked.

Similarly, we can delete grass and squirrel objects from the
grassObjs and squirrelObjs
lists without error because the for loop on lines
156 and 159 iterate in reverse order.

Remember that the NUMGRASS
constant was set to 80 and the NUMSQUIRRELS
constant was set to 30 at the beginning of the
program? These variables are set so that we can be sure there are always plenty
of grass and squirrel objects in the active area at all times. If the length of
the grassObjs or squirrelObjs
drops below NUMGRASS or NUMSQUIRRELS
respectively, then new grass and squirrel objects are created. The makeNewGrass() and makeNewSquirrel()
functions that create these objects are explained later in this chapter.

The camera’s position (which is stored as integers in the camerax and cameray variables) needs
to be updated when the player moves over. I’ve called the number of pixels the
player can move before the camera gets updated the “camera slack”. Line 19 set
the CAMERASLACK constant to 90,
which our program will take to mean that the player squirrel can move 90 pixels
from the center before the camera position gets updated to follow the squirrel.

In order to understand the equations used in the if statements on lines 172, 174, 176, and 178, you should
note that (camerax + HALF_WINWIDTH) and (cameray + HALF_WINHEIGHT) are the XY game world coordinates
currently at the center of the screen. The playerCenterx
and playerCentery is set to the middle of the player’s
squirrel’s position, also in game world coordinates.

For line 172, if the center X coordinate minus the player’s
center X coordinate is greater than the CAMERASLACK
value, that means the player is more pixels to the right of the center of the
camera than the camera slack should allow. The camerax
value needs to be updated so that the player squirrel is just at the edge of
the camera slack. This is why line 173 sets camerax
to playerCenterx + CAMERASLACK – HALF_WINWIDTH. Note
that the camerax variable is changed, not the playerObj['x'] value. We want to move the camera, not the
player.

The other three if statements
follow similar logic for the left, up and down sides.

Line 182 begins the code that starts drawing the contents of
the display Surface object. First, line 182 draws a green color for the
background. This will paint over all of the previous contents of the Surface so
that we can start drawing the frame from scratch.

184. # draw all the grass objects on the screen

185. for
gObj in grassObjs:

186. gRect
= pygame.Rect( (gObj['x'] - camerax,

187. gObj['y']
- cameray,

188. gObj['width'],

189. gObj['height'])
)

190. DISPLAYSURF.blit(GRASSIMAGES[gObj['grassImage']],
gRect)

The for loop on line 185 goes
through all the grass objects in the grassObjs list
and creates a Rect object from the x, y, width, and height information stored
in it. This Rect object is stored in a variable named gRect.
On line 190, gRect is used in the blit() method call to draw the grass image on the display
Surface. Note that gObj['grassImage'] only contains
an integer that is an index to GRASSIMAGES. GRASSIMAGES is a list of Surface objects that contain all
the grass images. Surface objects take up much more memory than just a single
integer, and all the grass objects with similar gObj['grassImage']
values look identical. So it makes sense to only have each grass image stored
once in GRASSIMAGES and simply store integers in the
grass objects themselves.

The for loop that draws all the
enemy squirrel game objects is similar to the previous for
loop, except that the Rect object it creates is saved in the 'rect' key’s value of the squirrel dictionary. The reason
the code does this is because we will use this Rect object later to check if
the enemy squirrels have collided with the player squirrel.

Note that the top parameter for the Rect constructor is not
just sObj['y'] - cameray but sObj['y']
- cameray - getBounceAmount(sObj['bounce'], sObj['bouncerate'],
sObj['bounceheight']). The getBounceAmount()
function will return the number of pixels that the top value should be raised.

Also, there is no common list of Surface objects of the squirrel
images, like there was with grass game objects and GRASSIMAGES.
Each enemy squirrel game object has its own Surface object stored in the 'surface' key. This is because the squirrel images can be scaled
to different sizes.

202. # draw the player squirrel

203. flashIsOn
= round(time.time(), 1) * 10 % 2 == 1

After drawing the grass and enemy squirrels, the code will
draw the player’s squirrel. However, there is one case where we would skip
drawing the player’s squirrel. When the player collides with a larger enemy
squirrel, the player takes damage and flashes for a little bit to indicate that
the player is temporarily invulnerable. This flashing effect is done by drawing
the player squirrel on some iterations through the game loop but not on others.

The player squirrel will be drawn on game loop iterations
for a tenth of a second, and then not drawn on the game loop iterations for a
tenth of second. This repeats over and over again as long as the player is
invulnerable (which, in the code, means that the invulnerableMode
variable is set to True). Our code will make the
flashing last for two seconds, since 2 was stored in
the INVULNTIME constant variable on line 25.

To determine if the flash is on or not, line 202 grabs the
current time from time.time(). Let’s use the example
where this function call returns 1323926893.622.
This value is passed to round(), which rounds it to
one digit past the decimal point (since 1 is passed
as round()’s second parameter). This means round() will return the value 1323926893.6.

This value is then multiplied by 10,
to become 13239268936. Once we have it as an
integer, we can do the “mod two” trick first discussed in the Memory Puzzle
chapter to see if it is even or odd. 13239268936 % 2
evaluates to 0, which means that flashIsOn will be set to False,
since 0 == 1 is False.

In fact, time.time() will keep
returning values that will end up putting False into
flashIsOn until 1323926893.700,
which is the next tenth second. This is why the flashIsOn
variable will constantly have False for one tenth of
a second, and then True for the next one tenth of a
second (no matter how many iterations happen in that tenth of a second).

There are three things that must be True
before we draw the player’s squirrel. The game must currently be going on
(which happens while gameOverMode is False) and the player is not invulnerable and not flashing
(which happens while invulnerableMode and flashIsOn are False).

The code for drawing the player’s squirrel is almost
identical to the code for drawing the enemy squirrels.

212. # draw the health meter

213. drawHealthMeter(playerObj['health'])

The drawHealthMeter() function
draws the indicator at the top left corner of the screen that tells the player
how many times the player squirrel can be hit before dying. This function will
be explained later in this chapter.

The first thing that is checked in the event handling loop
is if the QUIT event has been generated. If so, then
the program should be terminated.

219. elif
event.type == KEYDOWN:

220. if
event.key in (K_UP, K_w):

221. moveDown
= False

222. moveUp
= True

223. elif
event.key in (K_DOWN, K_s):

224. moveUp
= False

225. moveDown
= True

If the up or down arrow keys have been pressed (or their
WASD equivalents), then the move variable (moveRight,
moveDown, etc.) for that direction should be set to True and the move variable for the opposite direction
should be set to False.

The moveLeft and moveRight variables should also be set when the left or
right arrow keys are pressed. Also, the value in playerObj['facing']
should be updated to either LEFT or RIGHT. If the player squirrel is now facing a new
direction, the playerObj['surface'] value should be
replaced with a correctly scaled image of the squirrel facing the new
direction.

Line 229 is run if the left arrow key was pressed and checks
if the player squirrel was facing right. If that was so, then a new scaled
Surface object of the player squirrel image is stored in playerObj['surface'].
The code in line 232’s elif statement handles the
opposite case.

238. elif
winMode and event.key == K_r:

239. return

If the player has won the game by growing large enough (in
which case, winMode will be set to True) and the R key has been pressed, then runGame()should return. This will end the current game,
and a new game will start the next time that runGame()
gets called.

241. elif
event.type == KEYUP:

242. # stop moving the player's squirrel

243. if
event.key in (K_LEFT, K_a):

244. moveLeft
= False

245. elif
event.key in (K_RIGHT, K_d):

246. moveRight
= False

247. elif
event.key in (K_UP, K_w):

248. moveUp
= False

249. elif
event.key in (K_DOWN, K_s):

250. moveDown
= False

If the player lets up on any of the arrow or WASD keys, then
the code should set the move variable for that direction to False. This will stop the squirrel from moving in that
direction any more.

252. elif
event.key == K_ESCAPE:

253. terminate()

If the key that was pressed was the Esc key, then terminate
the program.

The code inside the if statement
on line 255 will move the player’s squirrel around only if the game is not
over. (This is why pressing on the arrow keys after the player’s squirrel dies
will have no effect.) Depending on which of the move variables is set to True, the playerObj dictionary
should have its playerObj['x'] and playerObj['y'] values changed by MOVERATE.
(This is why a larger value in MOVERATE makes the
squirrel move faster.)

266. if
(moveLeft or moveRight or moveUp or moveDown) or playerObj['bounce'] != 0:

267. playerObj['bounce']
+= 1

268.

269. if
playerObj['bounce'] > BOUNCERATE:

270. playerObj['bounce']
= 0 # reset bounce amount

The value in playerObj['bounce']
keeps track of at what point in bouncing the player is at. This variable stores
an integer value from 0 to BOUNCERATE.
Just like the bounce value for the enemy squirrels, a playerObj['bounce']
value of 0 means the player squirrel is at the start
of a bounce and a value of BOUNCERATE means the
player squirrel is at the end of the bounce.

The player squirrel will bounce whenever the player is
moving, or if the player has stopped moving but the squirrel hasn’t finished
its current bounce. This condition is captured in the if
statement on line 266. If any of the move variables is set to True or the current playerObj['bounce']
is not 0 (which means the player is currently in a
bounce), then the variable should be incremented on line 267.

Because the playerObj['bounce']
variable should only be in the range of 0 to BOUNCERATE, if incrementing it makes it larger than BOUNCERATE, it should be reset back to 0.

The for loop on 273 will go run
code on each of the enemy squirrel game objects in squirrelObjs.
Notice that the parameters to range() on line 273
start at the last index of squirrelObjs and
decrement. This is because the code inside this for
loop may end up deleting some of these enemy squirrel game objects (if the
player’s squirrel ends up eating them), so it is important to iterate from the
end down to the front. The reason why was explained previously in the “When
Deleting Items in a List, Iterate Over the List in Reverse” section.

275. if
'rect' in sqObj and playerObj['rect'].colliderect(sqObj['rect']):

If the player’s squirrel is equal or larger than the size of
the enemy squirrel it has collided with, then the player’s squirrel will eat
that squirrel and grow. The number that is added to the 'size'
key in the player object (that is, the growth) is calculated based on the enemy
squirrel’s size on line 280. Here’s a graph showing the growth from different
sized squirrels. Notice that larger squirrels cause more growth:

So, according to the chart, eating a squirrel that has a
width and height of 45 (that is, an area of 1600 pixels) would cause the player
to grow 5 pixels wider and taller.

Line 281 deletes the eaten squirrel object from the squirrelObjs list so that it will no longer appear on the
screen or have its position updated.

The player’s squirrel image needs to be updated now that the
squirrel is larger. This can be done by passing the original squirrel image in L_SQUIR_IMG or R_SQUIR_IMG to
the pygame.transform.scale() function, which will
return an enlarged version of the image. Depending on whether playerObj['facing'] is equal to LEFT
or RIGHT determines which original squirrel image we
pass to the function.

288. if
playerObj['size'] > WINSIZE:

289. winMode
= True # turn on "win mode"

The way the player wins the game is by getting the squirrel
to have a size larger than the integer stored in the WINSIZE
constant variable. If this is true, then the winMode
variable is set to True. Code in the other parts of
this function will handle displaying the congratulations text and checking for
the player to press the R key to restart the game.

291. elif
not invulnerableMode:

292. # player is smaller and takes damage

293. invulnerableMode
= True

294. invulnerableStartTime
= time.time()

295. playerObj['health']
-= 1

296. if
playerObj['health'] == 0:

297. gameOverMode
= True # turn on "game over mode"

298. gameOverStartTime
= time.time()

If the player’s area was not equal to or larger than the area
of the enemy squirrel, and invulnerableMode was not
set to True, then the player will take damage from
colliding with this larger squirrel.

To prevent the player from being damaged several times by
the same squirrel immediately, we will briefly make the player invulnerable to
further squirrel attacks by setting invulnerableMode
to True on line 293. Line 294 will set invulnerableStartTime to the current time (which is
returned by time.time()) so that lines 133 and 134
can know when to set invulnerableMode to False.

Line 295 decrements the player’s health by 1. Because there is a chance that the player’s health is
now at 0, line 296 checks for this and, if so, sets gameOverMode to True and gameOverStartTime to the current time.

When the player has died, the “Game Over” text (which is on
the Surface object in the gameOverSurf variable)
will be shown on the screen for the number of seconds that is in the GAMEOVERTIME constant. Once this amount of time has
elapsed, then the runGame() function will return.

This lets the enemy squirrels continue to be animated and
moving around for a few seconds after the player dies and before the next game
starts. The “game over screen” in Squirrel Eat Squirrel does not wait until the
player presses a key before a new game starts.

The winMode variable is set to True on line 289 if the player has reached a certain size
(which is dictated by the WINSIZE constant). All
that happens when the player has won is that the “You have achieved OMEGA
SQUIRREL!” text (which is on the Surface object stored in the winSurf variable) and the “(Press “r” to restart.)” text
(which is on the Surface object stored in the winSurf2
variable) appears on the screen. The game continues until the user presses the
R key, at which point the program execution will return from runGame(). The event handling code for the R key is done
on lines 238 and 239.

To draw the health meter, first the for
loop on line 317 draws the filled-in red rectangle for the amount of health the
player has. Then the for loop on line 319 draws an unfilled
white rectangle for all of the possible health the player could have (which is
the integer value stored in the MAXHEALTH constant).
Note that the pygame.display.update() function is
not called in drawHealthMeter().

There is a mathematical function (which is similar to
functions in programming in that they both “return” or “evaluate” to a number
based on their parameters) called sine
(pronounced like “sign” and often abbreviated as “sin”). You may have learned
about it in math class, but if you haven’t it will be explained here. Python
has this mathematic function as a Python function in the math
module. You can pass an int or float value to math.sin(),
and it will return a float value that is called the “sine value”

In the interactive shell, let’s see what math.sin() returns for some values:

>>>
import math

>>>
math.sin(1)

0.8414709848078965

>>>
math.sin(2)

0.90929742682568171

>>>
math.sin(3)

0.14112000805986721

>>>
math.sin(4)

-0.7568024953079282

>>>
math.sin(5)

-0.95892427466313845

It seems really hard to predict what value math.sin() is going to return based on what value we pass
it (which might make you wonder what math.sin() is
useful for). But if we graph the sine values of the integers 1 through 10 on a graph, we
would get this:

You can kind of see a wavy pattern in the values returned by
math.sin(). If you figure out the sine values for
more numbers besides integers (for example, 1.5 and 2.5 and so on) and then connect the dots with lines, you
can see this wavy pattern more easily:

In fact, if you kept adding more and more data points to
this graph, you would see that the sine wave looks like this:

Notice that math.sin(0) returns 0, then gradually increases until math.sin(3.14
/ 2) returns 1, then it begins to decrease
until math.sin(3.14) returns 0.
The number 3.14 is a special number in mathematics
called pi (pronounced the same as delicious
“pie”). This value is also stored in the constant variable pi
in the math module (which is why line 333 uses the
variable, math.pi), which is technically the float value
3.1415926535897931. Since we want a wavy-looking
bounce for our squirrel, we’ll only pay attention to the return values of math.sin() for the arguments 0
to 3.14:

Let’s take a look at the return value of getBounceAmount() and figure out what it does exactly.

Remember that on line 21 we set the BOUNCERATE
constant to 6. This means that our code will only
increment playerObj['bounce'] from 0 to 6 and that we want to
split up the range of floating-point values from 0
to 3.14 into 6 parts,
which we can do with simple division: 3.14 / 6 = 0.5235.
Each of the 6 equal parts of the 3.14 length on the graph for the “sine wave bounce” is 0.5235.

You can see that when playerObj['bounce']
is at 3 (halfway between 0
and 6), the value passed to the math.sin()
call is math.pi / 6 * 3, which is 1.5707 (halfway between 0 and 3.1415). Then math.sin(1.5707)
will return 1.0, which is the highest part of the
sine wave (and the highest part of the sine wave happens half way through the
wave).

As playerObj['bounce'] gets its
value incremented, the getBounceAmount() function
will return values that have the same bounce shape that the sine wave has from 0 to 3.14. If you want to make
the bounce higher, than increase the BOUNCEHEIGHT
constant. If you want to make the bounce slower, than increase the BOUNCERATE constant.

The sine function is a concept from trigonometry
mathematics. If you’d like to learn more about the sine wave, the Wikipedia
page has detailed information: http://en.wikipedia.org/wiki/Sine

The reason we call float() to
convert bounceRate to a floating point number is
simply so that this program will work in Python version 2. In Python version 3,
the division operator will evaluate to a floating point value even if both of
the operands are integers, like this:

>>> #
Python version 3

...

>>> 10
/ 5

2.0

>>> 10
/ 4

2.5

>>>

However, in Python version 2, the /
division operator will only evaluate to a floating point value if one of the
operands is also a floating point value. If both operands are integers, then
Python 2’s division operator will evaluate to an integer value (rounding down
if needed), like this:

>>> #
Python version 2

...

>>> 10
/ 5

2

>>> 10
/ 4

2

>>> 10
/ 4.0

2.5

>>>
10.0 / 4

2.5

>>>
10.0 / 4.0

2.5

But if we always convert one of the values to a floating
point value with the float() function, then the
division operator will evaluate to a float value no matter which version of
Python runs this source code. Making these changes so that our code works with
older versions of software is called backwards
compatibility. It is important to maintain backwards compatibility,
because not everyone will always be running the latest version of software and
you want to ensure that the code you write works with as many computers as
possible.

You can’t always make your Python 3 code backwards
compatible with Python 2, but if it’s possible then you should do it.
Otherwise, when people with Python 2 try to run your games will get error
messages and think that your program is buggy.

The getRandomVelocity() function
is used to randomly determine how fast an enemy squirrel will move. The range
of this velocity is set in the SQUIRRELMINSPEED and SQUIRRELMAXSPEED constants, but on top of that, the speed
is either negative (indicating the squirrel goes to the left or up) or positive
(indicating the squirrel goes to the right or down). There is a fifty-fifty
chance for the random speed to be positive or negative.

349.
# create a Rect object with the random
coordinates and use colliderect()

350. # to make sure the right edge isn't in the
camera view.

351. objRect
= pygame.Rect(x, y, objWidth, objHeight)

352. if
not objRect.colliderect(cameraRect):

353. return
x, y

When a new squirrel or grass object is created in the game
world, we want it to be within the active area (so that it is near the player’s
squirrel) but not within the view of the camera (so that it doesn’t just
suddenly pop into existence on the screen). To do this, we create a Rect object
that represents the area of the camera (using camerax,
cameray, WINWIDTH, and WINHEIGHT constants).

Next, we randomly generate numbers for the XY coordinates
that would be within the active area. The active area’s left and top edge are WINWIDTH and WINHEIGHT pixels
to the left and up of camerax and cameray. So the active area’s left and top edge are at camerax - WINWIDTH and cameray -
WINHEIGHT. The active area’s width and height are also three times the
size of the WINWIDTH and WINHEIGHT,
as you can see in this image (where WINWIDTH is set
to 640 pixels and WINHEIGHT set to 480 pixels):

This means the right and bottom edges will be at camerax + (2 * WINWIDTH) and cameray +
(2 * WINHEIGHT). Line 352 will check if the random XY coordinates would
collide with the camera view’s Rect object. If not, then those coordinates are
returned. If so, then the while loop on line 346
will keep generating new coordinates until it finds acceptable ones.

Creating enemy squirrel game objects is similar to making
the grass game objects. The data for each enemy squirrel is also stored in a
dictionary. The width and height are set to random sizes on line 360 and 361. The
generalSize variable is used so that the width and
height of each squirrel aren’t too different from each other. Otherwise, using
completely random numbers for width and height could give us very tall and
skinny squirrels or very short and wide squirrels. The width and height of the
squirrel are this general size with a random number from 0
to 10 added to it (for slight variation), and then
multiplied by the multiplier variable.

The original XY coordinate position of the squirrel will be
a random location that the camera cannot see, to prevent the squirrels from
just “popping” into existence on the screen.

The speed and direction are also randomly selected by the getRandomVelocity() function.

The L_SQUIR_IMG and R_SQUIR_IMG constants contain Surface objects with
left-facing and right-facing squirrel images on them. New Surface objects will
be made using the pygame.transform.scale() function
to match the squirrel’s width and height (stored in sq['width']
and sq['height'] respectively).

After that, the three bounce-related values are randomly
generated (except for sq['bounce'] which is 0 because the squirrel always starts at the beginning of
the bounce) and the dictionary is returned on line 372.

The grass game objects are dictionaries with the usual 'x', 'y', 'width',
'height', and 'rect' keys
but also a 'grassImage' key which is a number from 0 to one less than the length of the GRASSIMAGES
list. This number will determine what image the grass game object has. For
example, if the value of the grass object’s 'grassImage'
key is 3, then it will use the Surface object stored
at GRASSIMAGES[3] for its image.

The isOutsideActiveArea() will
return True if the object you pass it is outside of
the “active area” that is dictated by the camerax
and cameray parameters. Remember that the active
area is an area around the camera view the size of the camera view (which has a
width and height set by WINWIDTH and WINHEIGHT), like this:

We can create a Rect object that represents the active area
by passing camerax - WINWIDTH for the left edge
value and cameray - WINHEIGHT for the top edge
value, and then WINWIDTH * 3 and WINHEIGHT * 3 for the width and height. Once we have the
active area represented as a Rect object, we can use the colliderect()
method to determine if the object in the obj
parameter is collides with (that is, is inside of) the active area Rect object.

Since the player squirrel, enemy squirrel and grass objects
all have 'x', 'y', 'width' and 'height' keys, the isOutsideActiveArea() code can work with any type of those
game objects.

395. if
__name__ == '__main__':

396. main()

Finally, after all the functions have been defined, the
program will run the main() function and start the
game.

Squirrel Eat Squirrel was our first game to have multiple
enemies moving around the board at once. The key to having several enemies was
using a dictionary value with identical keys for each enemy squirrel, so that
the same code could be run on each of them during an iteration through the game
loop.

The concept of the camera was also introduced. Cameras
weren’t needed for our previous games because the entire game world fit onto
one screen. However, when you make your own games that involve a player moving
around a large game world, you will need code to handle converting between the
game world’s coordinate system and the screen’s pixel coordinate system.

Finally, the mathematical sine function was introduced to
give realistic squirrel hops (no matter how tall or long each hop was). You
don’t need to know a lot of math to do programming. In most cases, just knowing
addition, multiplication, and negative numbers is fine. However, if you study
mathematics, you’ll often find several uses for math to make your games cooler.

For additional programming practice, you can download buggy
versions of Squirrel Eat Squirrel from http://invpy.com/buggy/squirrel
and try to figure out how to fix the bugs.