# Angry Circles

For the next couple of weeks I though we might explore the rudiments of physics simulators and I was inspired by the excellent tutorials from Peter Collingridge on the topic. We have covered all the basic python tools we need already, so I think we can leap in with a fairly functional demo which will allow you to fling some 2D circular shapes (we’re calling them particles) around using the mouse. The particle implementation is also an excellent use of the concept of Classes we discussed a few weeks ago. In the code below you can see how we group together all the data and behaviour (the functions or methods) of a particle in one piece of code that defines a Particle class and then create lots of ‘instances’ of particles – each has the same structure (making all the management code much easier) but they all maintain own specific data values - positions, speeds and directions etc. This is perhaps the simplest (but still very powerful) benefit of using classes – we will come onto other benefits in due course.

Take a look at the code below. At the bottom you should by now
recognise the absolutely standard game loop – with the usual
input->update->draw cycle. I hope you will see it is surprisingly
short and very readable! This is a good indication of the well
structured code above. In the middle of the code, after we define a
couple of helper functions, is the definition of the class (make sure
you get your indenting right here – the class’ functions must be
indented **inside** the class level. The interesting
aspects of the maths I will cover in tonight’s session – but it all
comes down to some very simple *sin,* *cos, tan* usage and good ol’ Pythagoras.

**import **pygame**import **random**import **mathbackground_colour = (255,255,255)(width, height) = (400, 400)drag = 0.999elasticity = 0.75gravity = (math.pi, 0.2)**def **addVectors(vector1, vector2):angle1 = vector1[0]length1 = vector1[1]angle2 = vector2[0]length2 = vector2[1]x = math.sin(angle1) * length1 + math.sin(angle2) * length2y = math.cos(angle1) * length1 + math.cos(angle2) * length2angle = 0.5 * math.pi - math.atan2(y, x)length = math.hypot(x, y)**return **(angle, length)**def **findParticle(particles, x, y):**for **p **in **particles:**if **math.hypot(p.x-x, p.y-y) <= p.size:**return **p**return None****class **Particle():**def **__init__(self, x_y, size):self.x = x_y[0]self.y = x_y[1]self.size = sizeself.colour = (0, 0, 255)self.thickness = 1self.speed = 0self.angle = 0**def **display(self):pygame.draw.circle(screen, self.colour, (int(self.x), int(self.y)), self.size, self.thickness)**def **move(self):(self.angle, self.speed) = addVectors((self.angle, self.speed), gravity)self.x += math.sin(self.angle) * self.speedself.y -= math.cos(self.angle) * self.speedself.speed *= drag**def **bounce(self):**if **self.x > width - self.size:self.x = 2*(width - self.size) - self.xself.angle = - self.angleself.speed *= elasticity**elif **self.x < self.size:self.x = 2*self.size - self.xself.angle = - self.angleself.speed *= elasticity**if **self.y > height - self.size:self.y = 2*(height - self.size) - self.yself.angle = math.pi - self.angleself.speed *= elasticity**elif **self.y < self.size:self.y = 2*self.size - self.yself.angle = math.pi - self.angleself.speed *= elasticityscreen = pygame.display.set_mode((width, height))pygame.display.set_caption(**'Angry Circles'**)number_of_particles = 3my_particles = []**for **n **in **range(number_of_particles):size = random.randint(10, 20)x = random.randint(size, width-size)y = random.randint(size, height-size)particle = Particle((x, y), size)particle.speed = random.random()particle.angle = random.uniform(0, math.pi*2)my_particles.append(particle)clock = pygame.time.Clock()selected_particle = **None**running = **True****while **running:clock.tick(20)**for **event **in **pygame.event.get():**if **event.type == pygame.QUIT:running = **False****elif **event.type == pygame.MOUSEBUTTONDOWN:(mouseX, mouseY) = pygame.mouse.get_pos()selected_particle = findParticle(my_particles, mouseX, mouseY)**elif **event.type == pygame.MOUSEBUTTONUP:selected_particle = **None****if **selected_particle:(mouseX, mouseY) = pygame.mouse.get_pos()dx = mouseX - selected_particle.xdy = mouseY - selected_particle.yselected_particle.angle = 0.5*math.pi + math.atan2(dy, dx)selected_particle.speed = math.hypot(dx, dy) * 0.1screen.fill(background_colour)**for **particle **in **my_particles:particle.move()particle.bounce()particle.display()pygame.display.flip()