66
77import numpy as np
88
9- from mesa import Agent
9+ from mesa . experimental . continuous_space import ContinuousSpaceAgent
1010
1111
12- class Boid (Agent ):
12+ class Boid (ContinuousSpaceAgent ):
1313 """A Boid-style flocker agent.
1414
1515 The agent follows three behaviors to flock:
@@ -26,10 +26,12 @@ class Boid(Agent):
2626 def __init__ (
2727 self ,
2828 model ,
29- speed ,
30- direction ,
31- vision ,
32- separation ,
29+ space ,
30+ position = (0 , 0 ),
31+ speed = 1 ,
32+ direction = (1 , 1 ),
33+ vision = 1 ,
34+ separation = 1 ,
3335 cohere = 0.03 ,
3436 separate = 0.015 ,
3537 match = 0.05 ,
@@ -46,7 +48,8 @@ def __init__(
4648 separate: Relative importance of avoiding close neighbors (default: 0.015)
4749 match: Relative importance of matching neighbors' directions (default: 0.05)
4850 """
49- super ().__init__ (model )
51+ super ().__init__ (space , model )
52+ self .position = position
5053 self .speed = speed
5154 self .direction = direction
5255 self .vision = vision
@@ -58,47 +61,31 @@ def __init__(
5861
5962 def step (self ):
6063 """Get the Boid's neighbors, compute the new vector, and move accordingly."""
61- neighbors = self .model . space . get_neighbors ( self .pos , self . vision , True )
64+ neighbors , distances = self .get_neighbors_in_radius ( radius = self .vision )
6265 self .neighbors = [n for n in neighbors if n is not self ]
6366
6467 # If no neighbors, maintain current direction
65- if not self .neighbors :
66- new_pos = self .pos + self .direction * self .speed
67- self .model .space .move_agent (self , new_pos )
68+ if not neighbors :
69+ self .position += self .direction * self .speed
6870 return
6971
70- # Initialize vectors for the three flocking behaviors
71- cohere = np .zeros (2 ) # Cohesion vector
72- match_vector = np .zeros (2 ) # Alignment vector
73- separation_vector = np .zeros (2 ) # Separation vector
72+ delta = self .space .calculate_difference_vector (self .position , agents = neighbors )
7473
75- # Calculate the contribution of each neighbor to the three behaviors
76- for neighbor in self .neighbors :
77- heading = self .model .space .get_heading (self .pos , neighbor .pos )
78- distance = self .model .space .get_distance (self .pos , neighbor .pos )
79-
80- # Cohesion - steer towards the average position of neighbors
81- cohere += heading
82-
83- # Separation - avoid getting too close
84- if distance < self .separation :
85- separation_vector -= heading
86-
87- # Alignment - match neighbors' flying direction
88- match_vector += neighbor .direction
89-
90- # Weight each behavior by its factor and normalize by number of neighbors
91- n = len (self .neighbors )
92- cohere = cohere * self .cohere_factor
93- separation_vector = separation_vector * self .separate_factor
94- match_vector = match_vector * self .match_factor
74+ cohere_vector = delta .sum (axis = 0 ) * self .cohere_factor
75+ separation_vector = (
76+ - 1 * delta [distances < self .separation ].sum (axis = 0 ) * self .separate_factor
77+ )
78+ match_vector = (
79+ np .asarray ([n .direction for n in neighbors ]).sum (axis = 0 ) * self .match_factor
80+ )
9581
9682 # Update direction based on the three behaviors
97- self .direction += (cohere + separation_vector + match_vector ) / n
83+ self .direction += (cohere_vector + separation_vector + match_vector ) / len (
84+ neighbors
85+ )
9886
9987 # Normalize direction vector
10088 self .direction /= np .linalg .norm (self .direction )
10189
10290 # Move boid
103- new_pos = self .pos + self .direction * self .speed
104- self .model .space .move_agent (self , new_pos )
91+ self .position += self .direction * self .speed
0 commit comments