I found great utility from ChatGPT for coding learning and assistance. This seems to be quite a bit better in practice with GPT-4o.
I asked ChatGPT to create a swarm simulation using python, like the old ‘swarm’ screensaver. Then added a ‘hawk’ that catches them. When they are all caught, it restarts.
When it was done, I asked how to make it work in a browser. ChatGPT told me to use p5.js and rewrote the code for me. It took maybe 10 minutes to create the code… then far longer to get it sized and not ruining the formatting within WordPress.
Note that it should work okay on a newer cellphone, but will likely hide the sides and total number of birds left unless you run it in landscape more. It is really meant for a larger screen.
It is amusing to see the speed increase as the birds are consumed when running the python version.
Here is the python code for anyone interested. 'H' to turn on the hawk and 'C' to change from 'relocate' the bird when caught to 'consume'.
import pygame
import random
import numpy as np
# Constants
WIDTH, HEIGHT = 1200, 600 # Wider window
BIRD_SIZE = 3
HAWK_SIZE = 7
NUM_BIRDS = 150 # Increase number of birds initially
MAX_SPEED = 5
HISTORY_LENGTH = 20 # Length of the trail history
# More appealing colors - we will use a predefined palette
BIRD_COLORS = [
(255, 109, 194), # Pink
(109, 218, 255), # Light Blue
(109, 255, 132) # Light Green
]
# Different behaviors for different colors
BEHAVIORS = {
(255, 109, 194): {'cohesion': 0.005, 'separation': 0.1, 'alignment': 0.05},
(109, 218, 255): {'cohesion': 0.01, 'separation': 0.15, 'alignment': 0.1},
(109, 255, 132): {'cohesion': 0.002, 'separation': 0.05, 'alignment': 0.03},
}
HAWK_SPEED = 7 # Faster hawk
# Initialize Pygame
pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT), pygame.RESIZABLE) # Resizable screen
pygame.display.set_caption("Flock Simulation with Hawk")
font = pygame.font.SysFont(None, 36) # Font for bird count
# Select the current behavior when the hawk catches a bird
CATCH_BEHAVIOR = 'relocate' # Initial behavior
# Bird Class
class Bird:
def __init__(self, x, y, color):
self.position = np.array([x, y], dtype=float)
self.velocity = np.random.rand(2) * 2 - 1 # Random direction
self.velocity /= np.linalg.norm(self.velocity)
self.velocity *= MAX_SPEED / 2 # Adjusting initial speed
self.color = color
self.behavior = BEHAVIORS[color]
self.history = [] # To store position history for the trail
def update(self, birds, hawk):
self.flock(birds, hawk)
self.position += self.velocity
# Keep a copy of the previous position for boundary check
prev_position = self.position.copy()
# Screen looping
self.position[0] %= WIDTH
self.position[1] %= HEIGHT
# Add current position to history and maintain length
if len(self.history) >= HISTORY_LENGTH:
self.history.pop(0)
# Break the trail if the bird wraps around the screen
if np.linalg.norm(self.position - prev_position) > MAX_SPEED:
self.history.clear()
self.history.append(self.position.copy())
def flock(self, birds, hawk):
sep = np.zeros(2)
align = np.zeros(2)
coh = np.zeros(2)
count = 0
for bird in birds:
if bird != self:
diff = bird.position - self.position
distance = np.linalg.norm(diff)
if distance < 50:
coh += bird.position
align += bird.velocity
count += 1
if distance < 20:
sep -= diff / distance
if count > 0:
coh = (coh / count - self.position) * self.behavior['cohesion']
align = (align / count - self.velocity) * self.behavior['alignment']
if hawk:
diff = hawk.position - self.position
distance = np.linalg.norm(diff)
if distance < 10: # Catching distance
if CATCH_BEHAVIOR == 'relocate':
self.position = np.random.rand(2) * np.array([WIDTH, HEIGHT], dtype=float)
self.history.clear() # Clear the history when relocating
elif CATCH_BEHAVIOR == 'consume':
birds.remove(self)
return
elif distance < 100:
sep -= diff / distance * self.behavior['separation'] * 10
self.velocity += coh + align + sep
speed = np.linalg.norm(self.velocity)
if speed > MAX_SPEED:
self.velocity = self.velocity / speed * MAX_SPEED
# Hawk Class
class Hawk:
def __init__(self):
self.position = np.random.rand(2) * np.array([WIDTH, HEIGHT], dtype=float)
self.velocity = np.random.rand(2) * 2 - 1
self.velocity /= np.linalg.norm(self.velocity)
self.velocity *= HAWK_SPEED
def update(self, birds):
if birds:
closest_bird = min(birds, key=lambda bird: np.linalg.norm(bird.position - self.position))
direction = closest_bird.position - self.position
self.velocity = direction / np.linalg.norm(direction) * HAWK_SPEED
self.position += self.velocity
self.position[0] %= WIDTH
self.position[1] %= HEIGHT
def draw_trail(screen, bird):
for i in range(1, len(bird.history)):
start_pos = bird.history[i - 1]
end_pos = bird.history[i]
alpha = int((i / HISTORY_LENGTH) * 255)
color = (bird.color[0] * alpha // 255, bird.color[1] * alpha // 255, bird.color[2] * alpha // 255)
pygame.draw.line(screen, color, start_pos, end_pos, BIRD_SIZE)
def main():
global WIDTH, HEIGHT, screen, CATCH_BEHAVIOR # Declare global variables here
birds = [Bird(random.uniform(0, WIDTH), random.uniform(0, HEIGHT), random.choice(BIRD_COLORS)) for _ in range(NUM_BIRDS)]
hawk = None
running = True
clock = pygame.time.Clock()
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN and event.key == pygame.K_h:
if hawk is None:
hawk = Hawk()
else:
hawk = None
elif event.type == pygame.KEYDOWN and event.key == pygame.K_c:
CATCH_BEHAVIOR = 'consume' if CATCH_BEHAVIOR == 'relocate' else 'relocate'
elif event.type == pygame.VIDEORESIZE:
WIDTH, HEIGHT = event.size
screen = pygame.display.set_mode((WIDTH, HEIGHT), pygame.RESIZABLE) # Update the screen with new size
screen.fill((0, 0, 0))
if hawk:
hawk.update(birds)
pygame.draw.circle(screen, (255, 0, 0), hawk.position.astype(int), HAWK_SIZE)
for bird in birds:
bird.update(birds, hawk)
draw_trail(screen, bird) # Draw the trail
pygame.draw.circle(screen, bird.color, bird.position.astype(int), BIRD_SIZE)
# Display bird count
bird_count_text = font.render(f'Birds: {len(birds)}', True, (255, 255, 255))
screen.blit(bird_count_text, (10, 10))
pygame.display.flip()
clock.tick(60)
pygame.quit()
if __name__ == "__main__":
main()