This recipe demonstrates a 2D boids simulation. The code is configurable based on some constants defined near the top. The idea for the code shown here came from the following URL: http://www.vergenet.net/~conrad/boids/pseudocode.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 | import random # FOR RANDOM BEGINNINGS
from Tkinter import * # ALL VISUAL EQUIPMENT
WIDTH = 800 # OF SCREEN IN PIXELS
HEIGHT = 600 # OF SCREEN IN PIXELS
BOIDS = 20 # IN SIMULATION
WALL = 100 # FROM SIDE IN PIXELS
WALL_FORCE = 10 # ACCELERATION PER MOVE
SPEED_LIMIT = 500 # FOR BOID VELOCITY
BOID_RADIUS = 3 # FOR BOIDS IN PIXELS
OFFSET_START = 20 # FROM WALL IN PIXELS
################################################################################
def main():
# Start the program.
initialise()
mainloop()
def initialise():
# Setup simulation variables.
build_boids()
build_graph()
def build_graph():
# Build GUI environment.
global graph
root = Tk()
root.overrideredirect(True)
root.geometry('%dx%d+%d+%d' % (WIDTH, HEIGHT, (root.winfo_screenwidth() - WIDTH) / 2, (root.winfo_screenheight() - HEIGHT) / 2))
root.bind_all('<Escape>', lambda event: event.widget.quit())
graph = Canvas(root, width=WIDTH, height=HEIGHT, background='white')
graph.after(40, update)
graph.pack()
def update():
# Main simulation loop.
draw()
move()
graph.after(40, update)
def draw():
# Draw all boids.
graph.delete(ALL)
for boid in boids:
x1 = boid.position.x - BOID_RADIUS
y1 = boid.position.y - BOID_RADIUS
x2 = boid.position.x + BOID_RADIUS
y2 = boid.position.y + BOID_RADIUS
graph.create_oval((x1, y1, x2, y2), fill='red')
graph.update()
def move():
# Move all boids.
for boid in boids:
simulate_wall(boid)
for boid in boids:
boid.update_velocity(boids)
for boid in boids:
boid.move()
def simulate_wall(boid):
# Create viewing boundaries.
if boid.position.x < WALL:
boid.velocity.x += WALL_FORCE
elif boid.position.x > WIDTH - WALL:
boid.velocity.x -= WALL_FORCE
if boid.position.y < WALL:
boid.velocity.y += WALL_FORCE
elif boid.position.y > HEIGHT - WALL:
boid.velocity.y -= WALL_FORCE
def limit_speed(boid):
# Limit boid speed.
if boid.velocity.mag() > SPEED_LIMIT:
boid.velocity /= boid.velocity.mag() / SPEED_LIMIT
def build_boids():
# Create boids variable.
global boids
boids = tuple(Boid(WIDTH, HEIGHT, OFFSET_START) for boid in xrange(BOIDS))
################################################################################
# TWO DIMENTIONAL VECTOR CLASS
class TwoD:
def __init__(self, x, y):
self.x = float(x)
self.y = float(y)
def __repr__(self):
return 'TwoD(%s, %s)' % (self.x, self.y)
def __add__(self, other):
return TwoD(self.x + other.x, self.y + other.y)
def __sub__(self, other):
return TwoD(self.x - other.x, self.y - other.y)
def __mul__(self, other):
return TwoD(self.x * other, self.y * other)
def __div__(self, other):
return TwoD(self.x / other, self.y / other)
def __iadd__(self, other):
self.x += other.x
self.y += other.y
return self
def __isub__(self, other):
self.x -= other.x
self.y -= other.y
return self
def __idiv__(self, other):
if isinstance(other, TwoD):
self.x /= other.x if other.x else 1
self.y /= other.y if other.y else 1
else:
self.x /= other
self.y /= other
return self
def mag(self):
return ((self.x ** 2) + (self.y ** 2)) ** 0.5
################################################################################
# BOID RULE IMPLEMENTATION CLASS
class Boid:
def __init__(self, width, height, offset):
self.velocity = TwoD(0, 0)
self.position = TwoD(*self.random_start(width, height, offset))
def random_start(self, width, height, offset):
if random.randint(0, 1):
# along left and right
y = random.randint(1, height)
if random.randint(0, 1):
# along left
x = -offset
else:
# along right
x = width + offset
else:
# along top and bottom
x = random.randint(1, width)
if random.randint(0, 1):
# along top
y = -offset
else:
# along bottom
y = height + offset
return x, y
def update_velocity(self, boids):
v1 = self.rule1(boids)
v2 = self.rule2(boids)
v3 = self.rule2(boids)
self.__temp = v1 + v2 + v3
def move(self):
self.velocity += self.__temp
limit_speed(self)
self.position += self.velocity / 100
def rule1(self, boids):
# clumping
vector = TwoD(0, 0)
for boid in boids:
if boid is not self:
vector += boid.position
vector /= len(boids) - 1
return (vector - self.position) / 7.5
def rule2(self, boids):
# avoidance
vector = TwoD(0, 0)
for boid in boids:
if boid is not self:
if (self.position - boid.position).mag() < 25:
vector -= (boid.position - self.position)
return vector
def rule3(self, boids):
# schooling
vector = TwoD(0, 0)
for boid in boids:
if boid is not self:
vector += boid.velocity
vector /= len(boids) - 1
return (vector - self.velocity) / 2
################################################################################
# Execute the simulation.
if __name__ == '__main__':
main()
|
Boid.rule1 defines the pull towards the middle of the flock. Boid.rule2 defines collision avoidance that all boids have. Boid.rule3 defines the common desire for a single velocity.
Enjoy or adapt this recipe as you see fit!
Cool. That's fun. I added MyBoid that bounces around and keeps the others moving.
Thanks, Justin
Recipe 576959 is the new version of this program and was written to act as a screensaver.