Flocking simulation in Python
Table of Contents
Flocking simulation is a type of computer simulation that models the behavior of a group of autonomous agents, such as birds, fish, or insects, as they move and interact with one another in a flock or swarm. The simulation is based on the principles of emergent behavior, where the overall behavior of the group emerges from the individual interactions between each agent.
Flocking simulations can be used in a variety of applications, including robotics, computer graphics, and game development, to simulate the behavior of crowds, traffic, and other complex systems.
![]()
Imports
import tkinter
import random
import math
import BoidWe import the required libraries. tkinter is a standard Python library for creating graphical user interfaces. math provides mathematical functions and constants. Boid is a custom module defined in a separate Python file that contains all functions needed to control individual boids.
Canvas initialisation
def initialise_canvas(window, screen_size):
canvas = tkinter.(, width=, height=)
canvas.()
window.(True, True)
return canvasinitialise_canvas takes two parameters:
window— a tkinter window object.screen_size— an integer representing the desired canvas size in pixels.
The function creates a new canvas using tkinter.Canvas() with the specified width and height, adds it to the window via pack(), marks the window as resizable in both directions, and returns the canvas object.
Creating boids
def create_boids(canvas, no_of_boids):
list_of_boids = []
for n in range():
boid = Boid.("boid" + str())
list_of_boids.()
boid.()
return list_of_boidscreate_boids takes two parameters:
canvas— the canvas object on which boids will be drawn.no_of_boids— the number of boid objects to create.
The function initialises an empty list_of_boids, loops no_of_boids times, creates a new Boid with a unique name on each iteration, appends it to the list, draws it on the canvas, and returns the completed list.
Separation
def separation(nearest_neighbour, boid):
if nearest_neighbour is not None and boid.() < 35:
if nearest_neighbour.x - boid.x == 0.0:
angle = math.((. -.) / 0.0001)
else:
angle = math.((. -.) / (. -.))
boid.angle -= angleseparation takes nearest_neighbour and boid as arguments. It calculates the angle between the boid and its nearest neighbour, then steers the boid in the opposite direction.
The condition checks whether a nearest neighbour exists and whether the distance to it is less than 35 units. If met, the angle is computed from the positional difference.
The Euclidean distance between two points in Euclidean space is the length of the line segment connecting them. It is derived from the Cartesian coordinates of the points using the Pythagorean theorem, which is why it is sometimes called the Pythagorean distance.
If the nearest neighbour is directly above or below the boid, the function uses math.atan((nearest_neighbour.y - boid.y) / 0.0001) to avoid division by zero. The resulting angle is then subtracted from the boid’s current angle, steering it away from the neighbour.
When a number is divided by zero, the result is mathematically infinite and cannot be represented physically. Python raises a ZeroDivisionError: division by zero exception in this case.
Alignment
def alignment(neighbours, boid):
average_neighbours_angle = 0.0
if neighbours:
for neighbour_boid in neighbours:
average_neighbours_angle += neighbour_boid.angle
average_neighbours_angle /= len()
boid.angle -= (average_neighbours_angle - boid.angle) / 100.0
boid.angle = average_neighbours_anglealignment steers the boid to match the heading of its neighbours by nudging its angle toward their average. The average angle is computed by summing all neighbour angles and dividing by the neighbour count. The boid’s angle is then adjusted by (average_neighbours_angle - boid.angle) / 100.0 and finally set to the average.
Cohesion
def cohesion(neighbours, boid):
if neighbours:
avg_x = 0.0
avg_y = 0.0
for neighbour_boid in neighbours:
avg_x += neighbour_boid.x
avg_y += neighbour_boid.y
avg_x /= len()
avg_y /= len()
if avg_x - boid.x == 0.0:
angle = math.(( -.) / 0.00001)
else:
angle = math.(( -.) / ( -.))
boid.angle -= angle / 20.0cohesion moves the boid toward the average position of its neighbours by adjusting its heading. The average x and y coordinates of all neighbours are computed, and the angle toward that centroid is calculated — again guarding against division by zero when the boid is directly above or below the average position.
When a number is divided by zero, the result is mathematically infinite and cannot be represented physically. Python raises a ZeroDivisionError: division by zero exception in this case.
The angle is divided by 20 before being subtracted from the boid’s current angle, ensuring the boid moves toward the group centre gradually rather than snapping to it.
Applying behaviours
def boid_behaviours(canvas, list_of_boids, screen_size):
for boid in list_of_boids:
neighbours = []
for b in list_of_boids:
if boid.() < 75 and (not boid.() == 0):
neighbours.()
nearest_neighbour = None
if neighbours:
shortest_distance = 999999999
for neighbour_boid in neighbours:
d = boid.()
if d < shortest_distance:
shortest_distance = d
nearest_neighbour = neighbour_boid
(,)
(,)
(,)
for boid in list_of_boids:
boid.(,)
canvas.(100,,,,)boid_behaviours applies separation, alignment, and cohesion to every boid in the list and updates their positions. A neighbour is defined as any other boid within 75 distance units. After all three behaviours are applied, the flock method updates each boid’s position and redraws it. The function schedules itself to run again after 100 milliseconds via canvas.after.
Entry point
def main():
screen_size = 800
no_of_boids = 100
window = tkinter.()
canvas =(,)
list_of_boids =(,)
(,,)
window.()
()main sets the canvas to 800×800 pixels, spawns 100 boids, kicks off the behaviour loop, and enters the tkinter event loop. The call at the bottom executes the program immediately.
The Boid class
class Boid:
def __init__(self, label):
self.x = random.(100, 900)
self.y = random.(100, 900)
self.angle = random.(0.0, 2.0 *.)
self.label = label
self.color = ["blue", "red", "green"]
def draw_boid(self, canvas):
size = 10
x1 = self.x + size * math.(self.)
x2 = self.y + size * math.(self.)
canvas.(self., self.,,, fill='black', arrow='last',
arrowshape=(12.8, 16, 4.8), width=2, tags=self.)
def flock(self, canvas, screen_size):
distance = 3
self.x += distance * math.(self.)
self.y += distance * math.(self.)
self.x = self.x % screen_size
self.y = self.y % screen_size
canvas.(self.)
self.()
def euclidean_distance(self, neighbour_boid):
return math.(
(self. -.) ** 2 +
(self. -.) ** 2
)The Boid class is the custom module imported by the main script.
Attributes:
| Attribute | Description |
|---|---|
x | Random starting x-coordinate (100–900) |
y | Random starting y-coordinate (100–900) |
angle | Random initial heading in radians (0 to 2π) |
label | Unique string identifier for canvas tagging |
color | Available colours (unused in drawing, reserved for extension) |
Methods:
__init__— Initialises the boid with random position and angle.draw_boid— Renders the boid as an arrow line on the canvas.flock— Advances the boid by 3 units along its current heading, wraps it within the screen using modulo arithmetic, and redraws it.euclidean_distance— Returns the straight-line distance to another boid.
Summary
Each boid in the simulation has three possible movements:
- Separation — move away from the nearest neighbour to avoid collision.
- Alignment — orient toward the average heading of nearby neighbours.
- Cohesion — steer toward the average position of the group.
Together, these three simple rules produce the emergent flocking behaviour seen in the simulation.
For queries or suggestions, feel free to write to subkamble@gmail.com.