## Detecting Collisions in our Game Python Tutorial

Welcome to part 20 of the intermediate Python programming tutorial series. In the previous tutorial, we covered how we can use special methods to do operator overloading, in order to write our own logic for how to handle the `+` operation. In this tutorial, we're going to cover how to actually detect that a collision has taken place.

How will we know logically when two blobs are touching? Well, we know their center (x,y) coordinates, and we know their radius. How do we calculate distances between two points on a plane? Euclidean Distance, of course! See the linked tutorial there for more information if you would like to learn more about calculating Euclidean distance, otherwise, you can rest easy knowing Numpy has your back with `np.linalg.norm`. For our purposes, the norm is the same as the Euclidean Distance. Let's say we have two blobs, `b1` and `b2`. How would we calculate the distance, and determine if they're touching? We merely need to calcuate the Euclidean distance between their two centers. Then we can add both blob radius attributes together. If the combined radius value is greater than the Euclidean distance, then we're touching! Something like:

```def is_touching(b1,b2):
if np.linalg.norm(np.array([b1.x,b1.y])-np.array([b2.x,b2.y])) < (b1.size + b2.size):
return True
else:
return False```

Note: You need to `import numpy as np` at the top of the script now. We can actually further simplify this with:

```def is_touching(b1,b2):
return np.linalg.norm(np.array([b1.x,b1.y])-np.array([b2.x,b2.y])) < (b1.size + b2.size)```

This is one of those times when I think a function that is purely a return statement is actually useful, since the return statement is a very long, and somewhat confusing-at-a-quick-glance, line.

Okay, great, now we're checking if blobs are touching, now what? We need to check every blue blob, and see if it's touching any other blob. Let's say we're being fed a list containing the colored-blob dictionaries, called `blob_list`:

```def handle_collisions(blob_list):
blues, reds, greens = blob_list
for blue_id, blue_blob in blues.copy().items():
for other_blobs in blues, reds, greens:
for other_blob_id, other_blob in other_blobs.copy().items():
if blue_blob == other_blob:
pass
else:
if is_touching(blue_blob, other_blob):
blue_blob + other_blob
if other_blob.size <= 0:
del other_blobs[other_blob_id]
if blue_blob.size <= 0:
del blues[blue_id]

return blues, reds, greens```

Above, note that, as we iterate through the dictionaries, we are using `.copy()`. Why are we doing this? We do this because we're actually modifying the main dictionaries, and you never want to modify something while you iterate through it. All sorts of nasty things can happen, and not necessarily every time (so even if you are testing your code, you might not discover it). At the end, after we've discovered any collisions, done the `+` operation, and deleted any blobs that are of size 0 or less, we return the modified dictionaries. Now, in our `draw_environment` function, we need to add these changes:

```def draw_environment(blob_list):
game_display.fill(WHITE)
blues, reds, greens = handle_collisions(blob_list)
for blob_dict in blob_list:
for blob_id in blob_dict:
blob = blob_dict[blob_id]
pygame.draw.circle(game_display, blob.color, [blob.x, blob.y], blob.size)
blob.move()
blob.check_bounds()

pygame.display.update()
return blues, reds, greens```

Notice there here we're now actually returning something (along with also doing `blues, reds, greens = handle_collisions(blob_list)`). This is because we eventually need to pass these new, modified, dictionaries to that main `while` loop. On that note, let's modify the `main` function now:

```def main():
blue_blobs = dict(enumerate([BlueBlob(WIDTH,HEIGHT) for i in range(STARTING_BLUE_BLOBS)]))
red_blobs = dict(enumerate([RedBlob(WIDTH,HEIGHT) for i in range(STARTING_RED_BLOBS)]))
green_blobs = dict(enumerate([GreenBlob(WIDTH,HEIGHT) for i in range(STARTING_GREEN_BLOBS)]))

while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
blue_blobs, red_blobs, green_blobs = draw_environment([blue_blobs,red_blobs,green_blobs])
clock.tick(60)```

Noting the `blue_blobs, red_blobs, green_blobs = draw_environment([blue_blobs,red_blobs,green_blobs])`. It as at this point that our `draw_environment` function really should be re-named. I'll save that for later, but you should note that this function is actually spending more code doing things other than drawing stuff, so we should probably break it into two functions, or give it a more fitting name.

Full code up to this point:

```import pygame
import random
from blob import Blob
import numpy as np

STARTING_BLUE_BLOBS = 15
STARTING_RED_BLOBS = 15
STARTING_GREEN_BLOBS = 15

WIDTH = 800
HEIGHT = 600
WHITE = (255, 255, 255)
BLUE = (0, 0, 255)
RED = (255, 0, 0)

game_display = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Blob World")
clock = pygame.time.Clock()

class BlueBlob(Blob):

def __init__(self, x_boundary, y_boundary):
Blob.__init__(self, (0, 0, 255), x_boundary, y_boundary)

if other_blob.color == (255, 0, 0):
self.size -= other_blob.size
other_blob.size -= self.size

elif other_blob.color == (0, 255, 0):
self.size += other_blob.size
other_blob.size = 0

elif other_blob.color == (0, 0, 255):
pass
else:
raise Exception('Tried to combine one or multiple blobs of unsupported colors!')

class RedBlob(Blob):
def __init__(self, x_boundary, y_boundary):
Blob.__init__(self, (255, 0, 0), x_boundary, y_boundary)

class GreenBlob(Blob):
def __init__(self, x_boundary, y_boundary):
Blob.__init__(self, (0, 255, 0), x_boundary, y_boundary)

def is_touching(b1,b2):
return np.linalg.norm(np.array([b1.x,b1.y])-np.array([b2.x,b2.y])) < (b1.size + b2.size)

def handle_collisions(blob_list):
blues, reds, greens = blob_list
for blue_id, blue_blob in blues.copy().items():
for other_blobs in blues, reds, greens:
for other_blob_id, other_blob in other_blobs.copy().items():
if blue_blob == other_blob:
pass
else:
if is_touching(blue_blob, other_blob):
blue_blob + other_blob
if other_blob.size <= 0:
del other_blobs[other_blob_id]
if blue_blob.size <= 0:
del blues[blue_id]

return blues, reds, greens

def draw_environment(blob_list):
game_display.fill(WHITE)
blues, reds, greens = handle_collisions(blob_list)
for blob_dict in blob_list:
for blob_id in blob_dict:
blob = blob_dict[blob_id]
pygame.draw.circle(game_display, blob.color, [blob.x, blob.y], blob.size)
blob.move()
blob.check_bounds()

pygame.display.update()
return blues, reds, greens

def main():
blue_blobs = dict(enumerate([BlueBlob(WIDTH,HEIGHT) for i in range(STARTING_BLUE_BLOBS)]))
red_blobs = dict(enumerate([RedBlob(WIDTH,HEIGHT) for i in range(STARTING_RED_BLOBS)]))
green_blobs = dict(enumerate([GreenBlob(WIDTH,HEIGHT) for i in range(STARTING_GREEN_BLOBS)]))

while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
blue_blobs, red_blobs, green_blobs = draw_environment([blue_blobs,red_blobs,green_blobs])
clock.tick(60)

if __name__ == '__main__':
main()```

The next tutorial: • Intermediate Python Programming introduction

• String Concatenation and Formatting Intermediate Python Tutorial part 2

• Argparse for CLI Intermediate Python Tutorial part 3

• List comprehension and generator expressions Intermediate Python Tutorial part 4

• More on list comprehension and generators Intermediate Python Tutorial part 5

• Timeit Module Intermediate Python Tutorial part 6

• Enumerate Intermediate Python Tutorial part 7

• Python's Zip function

• More on Generators with Python

• Multiprocessing with Python intro

• Getting Values from Multiprocessing Processes

• Multiprocessing Spider Example

• Introduction to Object Oriented Programming

• Creating Environment for our Object with PyGame

• Many Blobs - Object Oriented Programming

• Blob Class and Modularity - Object Oriented Programming

• Inheritance - Object Oriented Programming

• Decorators in Python Tutorial