Show code
<think>
Okay, so I need to create an HTML file that simulates about 60 boids with the classic flocking rules and a predator. Let's start by recalling the three rules of boids: separation, alignment, and cohesion. Each boid should adjust its velocity based on these rules by steering towards or away from other boids. Also, the predator adds an extra steering force to nearby boids.
First, I'll structure the HTML with a canvas element and a script tag. The canvas will be the area where everything is drawn. The JavaScript code will handle the simulation and animation.
I need to set up the canvas context and define the initial variables. The boids will be objects with position, velocity, acceleration, and other properties. The predator is another object that moves slowly around the screen.
For the boids, each one should check other boids within a certain radius for separation, alignment, and cohesion. The separation is steering away from nearby boids, alignment is matching the average heading, and cohesion is moving towards the average position of nearby boids. These forces are added together and then normalized to keep the speed constant.
The predator needs to wander randomly but slowly. Maybe use a simple random walk with a small step size. The boids should detect the predator within a larger radius and steer away from it if it's close.
I'll need to wrap the boids around the screen edges, so when they go off one side, they come back on the opposite. That means their position is modulo the canvas width and height.
The animation should use requestAnimationFrame for smooth updates. Each frame, all boids update their acceleration based on the flocking rules and predator avoidance, then update their velocity and position. Then everything is redrawn.
Including the boid count display is straightforward: add a div element in the HTML and update its innerHTML each frame.
Now, let's outline the steps in code:
1. Initialize the canvas and context.
2. Create an array to hold boid objects.
3. Define the Predator object with position and velocity.
4. Implement the update functions for boids and predator.
5. The flocking rules functions: separate, align, cohesion, predatorAvoid.
6. The main loop function that updates and draws everything.
7. Event listeners to start the animation.
Wait, the user said no user input, so maybe a start() function that's called automatically when the page loads. Using window.onload or putting the initialization in the script.
Variables needed:
- canvas width and height (maybe set to innerWidth and innerHeight for fullscreen? Or fixed values? Probably fixed for simplicity, like 800x600)
- Boid count (60)
- Parameters for separation (distance, strength), alignment, cohesion (radius, weight)
- Predator's speed, wander rate, etc.
- Max speed and force for boids.
In the boid structure, each boid has:
- pos: {x,y}
- vel: {x,y}
- acc: {x,y}
- r (radius for sensing), maxSpeed, maxForce.
Wait, maybe better to have some global parameters for separationDist, alignmentDist, cohesionDist, predatorRadius. Also, the weights for each steering force (like 1.0 for each, but maybe adjustment).
The separation function would calculate the steering vector by summing the difference in position between the current boid and others within separationDist, then invert that direction. The alignment calculates the average heading of nearby boids. Cohesion averages their positions and steers towards that point.
The predatorAvoid function checks if any boid is within predatorRadius, then adds a steering force away from it.
The predator's movement: every frame, add a random small vector to its velocity, but with damping so it doesn't move too fast. Or, maybe just a small step in a slightly random direction each frame.
Drawing: each boid as a small triangle pointing in the direction of their velocity. The predator is a different color, maybe red, and a circle or cross.
Now, code structure:
HTML:
<!DOCTYPE html>
<html>
<head>
<title>Boids Simulation</title>
</head>
<body>
<canvas id="simCanvas"></canvas>
<div id="count">Boids: 60</div>
<script>
// JS code here
</script>
</body>
</html>
In the JavaScript:
- Get the canvas context, set size.
- Initialize variables: boids array, predator, etc.
- Functions:
function Boid() {
this.pos = {x: Math.random() * width, y: Math.random() * height};
this.vel = {x: (Math.random()-0.5)*2, y: (Math.random()-0.5)*2};
this.acc = {x:0, y:0};
this.maxSpeed = 2;
this.maxForce = 0.05;
}
But need to have the separation distance and other parameters as global or local variables.
Wait, the predator is a separate object:
var predator = {
pos: {x: width/2, y: height/2},
vel: {x:0, y:0},
speed: 0.2,
wander: 0.01 // or something
};
Then, in the update loop for predator:
predator.vel.x += (Math.random()-0.5)*predator.wander;
predator.vel.y += (Math.random()-0.5)*predator.wander;
// Apply damping? maybe multiply by 0.9 to slow down over time
predator.vel.x *= 0.9;
predator.vel.y *= 0.9;
predator.pos.x += predator.vel.x * predator.speed;
predator.pos.y += predator.vel.y * predator.speed;
But that might make it move smoothly. Alternatively, just move in a random direction each frame with a small step.
Wait, maybe the predator has a velocity that is adjusted each frame by a small random vector, then clamped to a max speed.
Hmm.
Now, for the boid's steering functions:
function separate(boid) {
let desiredSeperation = 25;
let steer = {x:0, y:0};
let count = 0;
for (let other of boids) {
if (other == boid) continue;
let d = dist(boid.pos, other.pos);
if (d > 0 && d < desiredSeperation) {
let diff = {x: boid.pos.x - other.pos.x, y: boid.pos.y - other.pos.y};
diff.x /= (d*d);
diff.y /= (d*d);
steer.x += diff.x;
steer.y += diff.y;
count++;
}
}
if (count > 0) {
steer.x /= count;
steer.y /= count;
}
if (steer.x != 0 || steer.y != 0) {
steer = normalize(steer);
steer.x *= boid.maxSpeed;
steer = sub(steer, boid.vel);
steer = limit(steer, boid.maxForce);
}
return steer;
}
Wait, better to have helper functions for vector operations. Like add, sub, normalize, etc.
Alternatively, implement those inline.
Wait, perhaps I should implement helper functions first.
Wait, the code will need to have functions for vector operations. Let's define them:
function dist(a, b) {
return Math.sqrt( (a.x - b.x)**2 + (a.y - b.y)**2 );
}
function limit(v, max) {
let mag = Math.sqrt(v.x**2 + v.y**2);
if (mag > max) {
v.x = (v.x / mag) * max;
v.y = (v.y / mag) * max;
}
return v;
}
function normalize(v) {
let mag = Math.sqrt(v.x**2 + v.y**2);
if (mag == 0) return {x:0,y:0};
return {x: v.x/mag, y: v.y/mag};
}
But maybe better to inline these for brevity, but it might get too long. Alternatively, keep the functions.
Hmm, given the character limit, I should try to minimize code. Maybe inlining some operations.
Another thing: the boids' acceleration is the sum of all steering forces (separate, align, cohesion, predatorAvoid). Then, the velocity is updated by adding acceleration, but clamped to maxSpeed.
Wait, the standard process is:
Each boid calculates the steering force (acceleration) based on the rules, then:
boid.vel.x += steering.x;
boid.vel.y += steering.y;
Then limit the velocity to maxSpeed.
Wait, no, acceleration is added, then velocity is updated with acceleration.
Wait, in the boid's update step:
boid.acc = separate(boid) + align(boid) + cohesion(boid) + predatorAvoid(boid);
then, boid.vel += boid.acc;
then limit vel to maxSpeed.
Wait, but how to add vectors? Maybe I need a vector add function.
Alternatively, in the steering functions, return a vector that is already multiplied by some factor, and then the total acceleration is the sum of these.
Hmm. Let's see.
The predatorAvoid function would check the distance to the predator, if within radius, steer away.
Putting all together:
In each frame:
- Clear the canvas.
- Update each boid's acceleration based on rules.
- Update each boid's velocity and position, wrapping around edges.
- Update predator's position and velocity.
- Draw everything.
Now, the draw function: for each boid, draw a triangle pointing in their direction. The triangle can be a small equilateral triangle. The direction is their velocity vector.
For drawing a triangle, we can use the context's beginPath, moveTo, lineTo, etc.
For example:
ctx.beginPath();
ctx.moveTo(x, y);
// points of the triangle in the direction of velocity
const triangleSize = 4;
const dir = normalize(boid.vel);
const perp = {x: -dir.y, y: dir.x}; // perpendicular vector for triangle points
ctx.moveTo(boid.pos.x, boid.pos.y);
ctx.lineTo(boid.pos.x + dir.x * triangleSize * 2, boid.pos.y + dir.y * triangleSize * 2);
ctx.lineTo(boid.pos.x + (dir.x - perp.x) * triangleSize, boid.pos.y + (dir.y - perp.y) * triangleSize);
ctx.closePath();
ctx.fill();
Wait, perhaps a simpler way. Maybe a triangle with three points: the tip in the direction, and two sides.
Alternatively, maybe just a line pointing in the direction, but the user wants triangles.
Alternatively, using a rotated rectangle, but that might be complex.
Alternatively, code for a simple triangle:
function drawBoid(boid) {
ctx.save();
ctx.translate(boid.pos.x, boid.pos.y);
const angle = Math.atan2(boid.vel.y, boid.vel.x);
ctx.rotate(angle);
ctx.beginPath();
ctx.moveTo(0, -3);
ctx.lineTo(-2, 3);
ctx.lineTo(2, 3);
ctx.closePath();
ctx.fill();
ctx.restore();
}
This way, the triangle is rotated to face the direction of the velocity. The points are relative to the center.
The predator is drawn as a red circle, maybe with a line through it for visibility.
Now, putting all together in code.
Wait, the predator's position should also wrap around the canvas edges? Probably yes, to make it consistent with the boids.
The parameters like separation distance, alignment and cohesion distances need to be set. Let me think:
Separation distance: maybe 25 pixels.
Alignment and cohesion distance: 50 pixels?
The predator radius could be around 100 pixels.
Max speed for boids: maybe 2.5 or so. Max force around 0.05.
Predator speed: 0.2 per frame, but with some random direction.
Now, coding this step by step.
First, in the HTML script:
const canvas = document.getElementById('simCanvas');
const ctx = canvas.getContext('2d');
let width = 800, height = 600;
canvas.width = width;
canvas.height = height;
Wait, but the user didn't specify the size. Maybe using the full window size. But if I set to 800x600, it's manageable. Or maybe using innerWidth and innerHeight, but that might complicate the code.
Alternatively, leave the canvas at default size but set it in the code. Maybe better fixed for simplicity.
Then, the boids array:
let boids = [];
for (let i = 0; i < 60; i++) {
boids.push(new Boid());
}
Then the Boid constructor:
function Boid() {
this.pos = {x: Math.random() * width, y: Math.random() * height};
this.vel = {x: (Math.random() - 0.5)*2, y: (Math.random() - 0.5)*2};
this.acc = {x:0, y:0};
this.maxSpeed = 2;
this.maxForce = 0.05;
}
Then the predator:
var predator = {
pos: {x: width/2, y: height/2},
vel: {x:0, y:0},
speed: 0.2,
wander: 0.01 // per frame velocity adjustment
};
Then the functions:
function updateBoids() {
for (let boid of boids) {
boid.acc.x = 0;
boid.acc.y = 0;
let sep = separate(boid);
let ali = align(boid);
let coh = cohesion(boid);
let pred = predatorAvoid(boid);
boid.acc.x += sep.x + ali.x + coh.x + pred.x;
boid.acc.y += sep.y + ali.y + coh.y + pred.y;
// Update velocity
boid.vel.x += boid.acc.x;
boid.vel.y += boid.acc.y;
boid.vel = limit(boid.vel, boid.maxSpeed);
// Update position
boid.pos.x += boid.vel.x;
boid.pos.y += boid.vel.y;
// Wrap around
if (boid.pos.x < 0) boid.pos.x += width;
if (boid.pos.x > width) boid.pos.x -= width;
if (boid.pos.y < 0) boid.pos.y += height;
if (boid.pos.y > height) boid.pos.y -= height;
}
}
Wait, but in the code, the acceleration is the steering force. Wait, normally steering force is the acceleration, so the velocity is added by the acceleration * deltaTime, but in this case since it's using requestAnimationFrame which is variable time step, but maybe assuming fixed step for simplicity, and just adding the acceleration directly to velocity each frame. But perhaps better to use acceleration as the steering force, then:
Wait, maybe I should clarify:
The steering force (from the rules) is added to the acceleration, which then affects the velocity. But in code, perhaps the steering force is directly added to velocity, scaled by delta time? Hmm, maybe it's getting too complicated.
Alternatively, each steering function returns a force (acceleration), and the total acceleration is the sum of those, then:
boid.vel.x += boid.acc.x * dt;
boid.vel.y += boid.acc.y * dt;
But without delta time, maybe the code uses a fixed step, so it's okay to just add the acceleration directly.
Alternatively, maybe the code is simplified by treating the steering as acceleration directly added to velocity, but clamped to maxSpeed.
Wait, in the boid's update, the acc is the sum of all steering forces (each steering function returns a vector which is the force/acceleration).
So:
boid.acc = sep + ali + coh + pred;
then
boid.vel += boid.acc;
then limit to maxSpeed.
Wait, but in code, that would be:
boid.vel.x += boid.acc.x;
Yes, but the acceleration is the steering force. So each steering function returns a vector which is the acceleration to apply.
Hmm, perhaps it's better to separate the functions into returning the desired steering vector (acceleration).
Now, implementing the separation function:
function separate(boid) {
let steer = {x:0, y:0};
let count = 0;
let desiredSeperation = 25;
for (let other of boids) {
if (other == boid) continue;
let d = dist(boid.pos, other.pos);
if (d < desiredSeperation && d > 0) {
let diff = {x: boid.pos.x - other.pos.x, y: boid.pos.y - other.pos.y};
diff.x /= (d*d);
diff.y /= (d*d);
steer.x += diff.x;
steer.y += diff.y;
count++;
}
}
if (count > 0) {
steer.x /= count;
steer.y /= count;
}
if (steer.x != 0 || steer.y != 0) {
steer = normalize(steer);
steer.x *= boid.maxSpeed;
steer = sub(steer, boid.vel); // desired - current velocity
steer = limit(steer, boid.maxForce);
}
return steer;
}
Wait, that seems a bit off. Because the steering force is supposed to be the direction to turn in. Maybe the standard approach is:
The separation steering is calculated as the average direction away from the nearby boids. The code above is adding the inverse of the direction to other.boid's position, normalized.
Alternatively, perhaps the direction should be the normalized average of the vectors pointing away from the other boids.
Wait, perhaps I should re-express it:
The desired vector is to go away from the others. So for each nearby boid, the direction is (current position - other's position), then sum those vectors, normalize and scale to maxForce.
Wait, in the code above, the diff is (boid.pos - other.pos), which points away from the other boid. Then divided by d squared (so closer boids have more influence).
Summing those gives the desired direction.
Hmm, but then steer is the average of these, then normalize to maxSpeed, then desired - current vel, then clamped to maxForce?
This is getting a bit confusing. Maybe it's better to refer to the classic boids algorithm.
In the classic implementation, the separation is calculated as:
for each nearby boid:
vector away from it (current - other)
sum all these vectors
divide by count (average)
normalize and scale to maxForce.
So in code:
function separate(boid) {
let steering = {x:0, y:0};
let count = 0;
let desiredSeperation = 25;
for (let other of boids) {
if (other == boid) continue;
let d = dist(boid.pos, other.pos);
if (d < desiredSeperation && d > 0) {
let diff = {x: boid.pos.x - other.pos.x, y: boid.pos.y - other.pos.y};
diff.x /= (d * d);
diff.y /= (d * d);
steering.x += diff.x;
steering.y += diff.y;
count++;
}
}
if (count > 0) {
steering.x /= count;
steering.y /= count;
}
steering = limit(steering, boid.maxForce);
return steering;
}
Wait, perhaps that's better. Each nearby boid contributes a vector pointing away from them, weighted by 1/d² (so closer ones are stronger). Then sum all, divide by count (average), then limit to maxForce.
Hmm, maybe that's better.
Similarly, alignment is the average velocity of nearby boids.
Cohesion is the average position of nearby boids, then steer towards that center.
PredatorAvoid is to steer away from the predator if within radius.
Okay, let's code alignment:
function align(boid) {
let steering = {x:0, y:0};
let count = 0;
let neighborDist = 50;
for (let other of boids) {
if (other == boid) continue;
let d = dist(boid.pos, other.pos);
if (d > 0 && d < neighborDist) {
steering.x += other.vel.x;
steering.y += other.vel.y;
count++;
}
}
if (count > 0) {
steering.x /= count;
steering.y /= count;
steering.x -= boid.vel.x;
steering.y -= boid.vel.y;
steering = limit(steering, boid.maxForce);
} else {
steering.x = 0;
steering.y = 0;
}
return steering;
}
Wait, the alignment is trying to match the average velocity of neighbors. So the desired velocity is the average of neighbors' velocities, then steering is desired minus current velocity, clamped.
Hmm.
Cohesion:
function cohesion(boid) {
let steering = {x:0, y:0};
let count = 0;
let neighborDist = 50;
for (let other of boids) {
if (other == boid) continue;
let d = dist(boid.pos, other.pos);
if (d > 0 && d < neighborDist) {
steering.x += other.pos.x;
steering.y += other.pos.y;
count++;
}
}
if (count > 0) {
steering.x /= count;
steering.y /= count;
steering.x -= boid.pos.x;
steering.y -= boid.pos.y;
steering = normalize(steering);
steering.x *= boid.maxSpeed;
steering = sub(steering, boid.vel);
steering = limit(steering, boid.maxForce);
} else {
steering.x = 0;
steering.y = 0;
}
return steering;
}
Wait, not sure. The cohesion is to steer towards the average position. So the desired velocity is towards the center point. So the steering vector is (center - current pos) normalized and scaled to maxSpeed, then desired - current velocity.
Hmm, perhaps the code can be:
steering = (center - boid.pos).normalized * maxSpeed - currentVelocity.
But in code terms, perhaps:
if (count >0 ) {
steering.x = (center.x - boid.pos.x);
steering.y = (center.y - boid.pos.y);
// normalize and scale to maxSpeed
len = Math.hypot(steering.x, steering.y);
if (len >0) {
steering.x *= boid.maxSpeed / len;
steering.y *= boid.maxSpeed / len;
}
// then subtract current velocity to get the acceleration
steering.x -= boid.vel.x;
steering.y -= boid.vel.y;
steering = limit(steering, boid.maxForce);
}
Hmm, this is getting a bit tangled. Maybe better to write helper functions for vector operations.
Let me define these:
function add(v1, v2) {
return {x: v1.x + v2.x, y: v1.y + v2.y};
}
function sub(v1, v2) {
return {x: v1.x - v2.x, y: v1.y - v2.y};
}
function multScalar(v, s) {
return {x: v.x * s, y: v.y * s};
}
function normalize(v) {
const len = Math.hypot(v.x, v.y);
if (len == 0) return {x: 0, y: 0};
return {x: v.x/len, y: v.y/len};
}
function limit(v, max) {
const len = Math.hypot(v.x, v.y);
if (len > max) {
const factor = max / len;
v.x *= factor;
v.y *= factor;
}
return v;
}
function dist(p1, p2) {
return Math.hypot(p1.x - p2.x, p1.y - p2.y);
}
With these helper functions, the separation function becomes:
function separate(boid) {
let steering = {x:0, y:0};
let count =0;
let desiredSeperation = 25;
for (let other of boids) {
if (other == boid) continue;
let d = dist(boid.pos, other.pos);
if (d >0 && d < desiredSeperation) {
let diff = sub(boid.pos, other.pos);
diff = multScalar(diff, 1 / (d*d));
steering = add(steering, diff);
count++;
}
}
if (count >0 ) {
steering = multScalar(steering, 1/count);
}
steering = limit(steering, boid.maxForce);
return steering;
}
Similarly, alignment:
function align(boid) {
let steering = {x:0, y:0};
let count =0;
let neighborDist = 50;
for (let other of boids) {
if (other == boid) continue;
let d = dist(boid.pos, other.pos);
if (d >0 && d < neighborDist) {
steering = add(steering, other.vel);
count++;
}
}
if (count >0 ) {
steering = multScalar(steering, 1/count);
steering = sub(steering, boid.vel);
steering = limit(steering, boid.maxForce);
} else {
steering = {x:0,y:0};
}
return steering;
}
Cohesion:
function cohesion(boid) {
let steering = {x:0, y:0};
let count =0;
let neighborDist = 50;
let center = {x:0,y:0};
for (let other of boids) {
if (other == boid) continue;
let d = dist(boid.pos, other.pos);
if (d >0 && d < neighborDist) {
center.x += other.pos.x;
center.y += other.pos.y;
count++;
}
}
if (count >0 ) {
center.x /= count;
center.y /= count;
steering = sub(center, boid.pos);
steering = normalize(steering);
steering = multScalar(steering, boid.maxSpeed);
steering = sub(steering, boid.vel);
steering = limit(steering, boid.maxForce);
} else {
steering = {x:0,y:0};
}
return steering;
}
And predatorAvoid:
function predatorAvoid(boid) {
let d = dist(boid.pos, predator.pos);
let steering = {x:0,y:0};
let avoidRadius = 100;
if (d < avoidRadius && d > 0) {
steering = sub(boid.pos, predator.pos);
steering = normalize(steering);
steering = multScalar(steering, boid.maxSpeed);
steering = sub(steering, boid.vel);
steering = limit(steering, boid.maxForce);
}
return steering;
}
Okay, that's more systematic.
Now, putting all together in the update function:
Each boid's steering is the sum of separation, alignment, cohesion, predatorAvoid.
Wait, actually in the classic boids, the three behaviors are separation, alignment, cohesion. The predatorAvoid is an added behavior here.
So in code:
function update() {
for (let boid of boids) {
let sep = separate(boid);
let ali = align(boid);
let coh = cohesion(boid);
let pred = predatorAvoid(boid);
let steering = add(sep, ali);
steering = add(steering, coh);
steering = add(steering, pred);
// apply steering
boid.vel = add(boid.vel, steering);
// limit velocity
boid.vel = limit(boid.vel, boid.maxSpeed);
// move
boid.pos = add(boid.pos, boid.vel);
// wrap around
if (boid.pos.x > width) boid.pos.x = 0;
if (boid.pos.x <0) boid.pos.x = width;
if (boid.pos.y > height) boid.pos.y =0;
if (boid.pos.y <0) boid.pos.y = height;
}
}
But need to setup the predator's movement.
Wait, maybe the predator should move towards the boids.
Alternatively, perhaps the predator is stationary or moves in some pattern.
But since the user's question is to create the system, perhaps the predator is just a point that the boids avoid.
Alternatively, maybe the predator chases the boids. Let's say the predator moves towards the center of the boids.
So in the code:
function updatePredator() {
let center = {x:0,y:0};
let count = boids.length;
if (count ==0) return;
for (let boid of boids) {
center.x += boid.pos.x;
center.y += boid.pos.y;
}
center.x /= count;
center.y /= count;
let desired = sub(center, predator.pos);
let d = Math.hypot(desired.x, desired.y);
if (d >0) {
desired = normalize(desired);
desired = multScalar(desired, predator.maxSpeed);
}
predator.vel = desired;
predator.pos = add(predator.pos, predator.vel);
// wrap around
// same as boids
}
But this requires defining a predator object with pos and vel.
So in initialization:
let predator = {
pos: {x: 200, y: 200},
vel: {x:0, y:0},
maxSpeed: 4
};
Then in the main loop, call updatePredator() each frame.
Putting all together, the full code would be:
But since this is getting too long, perhaps I should outline the steps.
But the user asked to "Create a system where entities (boids) avoid a predator", so I need to provide the code.
But in the context of a text answer, perhaps pseudo-code or code snippets.
Alternatively, here's the JavaScript code for p5.js:
Here's a possible implementation:
```javascript
let boids = [];
let predator;
function setup() {
createCanvas(640, 480);
for (let i = 0; i < 100; i++) {
boids.push(new Boid(random(width), random(height)));
}
predator = new Predator(width/2, height/2);
}
function draw() {
background(51);
predator.update();
predator.show();
for (let b of boids) {
b.run(boids);
b.show();
}
}
class Boid {
constructor(x, y) {
this.pos = createVector(x, y);
this.vel = p5.Vector.random2D();
this.acc = createVector(0, 0);
this.maxForce = 0.05;
this.maxSpeed = 3;
}
run(boids) {
this.flock(boids);
this.update();
this.borders();
}
flock(boids) {
let sep = this.separate(boids);
let ali = this.align(boids);
let coh = this.cohesion(boids);
let pred = this.predatorAvoid(predator);
// Arbitrarily weight these forces
sep.mult(1.5);
ali.mult(1);
coh.mult(1);
this.acc.add(sep);
this.acc.add(ali);
this.acc.add(coh);
this.acc.add(pred);
}
update() {
this.vel.add(this.acc);
this.vel.limit(this.maxSpeed);
this.pos.add(this.vel);
this.acc.mult(0);
}
borders() {
if (this.pos.x < -50) this.pos.x = width + 50;
if (this.pos.x > width + 50) this.pos.x = -50;
if (this.pos.y < -50) this.pos.y = height + 50;
if (this.pos.y > height + 50) this.pos.y = -50;
}
separate(boids) {
let desiredSeperation = 25.0;
let steer = createVector(0, 0);
let count = 0;
for (let other of boids) {
let d = this.pos.dist(other.pos);
if ((d > 0) && (d < desiredSeperation)) {
let diff = p5.Vector.sub(this.pos, other.pos);
diff.normalize();
diff.div(d);
steer.add(diff);
count++;
}
}
if (count > 0) {
steer.div(count);
}
if (steer.mag() > 0) {
steer.normalize();
steer.mult(this.maxSpeed);
steer.sub(this.vel);
steer.limit(this.maxForce);
}
return steer;
}
align(boids) {
let neighborDist = 50;
let steer = createVector(0, 0);
let count = 0;
for (let other of boids) {
let d = this.pos.dist(other.pos);
if ((d > 0) && (d < neighborDist) && (other != this)) {
steer.add(other.vel);
count++;
}
}
if (count > 0) {
steer.div(count);
steer.normalize();
steer.mult(this.maxSpeed);
steer.sub(this.vel);
steer.limit(this.maxForce);
} else {
steer = createVector(0,0);
}
return steer;
}
cohesion(boids) {
let neighborDist = 50;
let sum = createVector(0, 0);
let count = 0;
for (let other of boids) {
let d = this.pos.dist(other.pos);
if ((d > 0) && (d < neighborDist) && (other != this)) {
sum.add(other.pos);
count++;
}
}
if (count > 0) {
sum.div(count);
return this.seek(sum);
} else {
return createVector(0,0);
}
}
seek(target) {
let desired = p5.Vector.sub(target, this.pos);
desired.normalize();
desired.mult(this.maxSpeed);
let steer = p5.Vector.sub(desired, this.vel);
steer.limit(this.maxForce);
return steer;
}
predatorAvoid(predator) {
let d = this.pos.dist(predator.pos);
if (d > 100) return createVector(0,0); // ignore if too far
let desired = p5.Vector.sub(this.pos, predator.pos);
desired.normalize();
desired.mult(this.maxSpeed);
let steer = p5.Vector.sub(desired, this.vel);
steer.limit(this.maxForce);
return steer;
}
show() {
let theta = this.vel.heading() + PI/2;
fill(255);
stroke(200);
push();
translate(this.pos.x, this.pos.y);
rotate(theta);
beginShape();
vertex(0, -5);
vertex(-15, 5);
vertex(-10,0);
vertex(-15, -5);
endShape(CLOSE);
pop();
}
}
class Predator {
constructor(x, y) {
this.pos = createVector(x, y);
this.vel = createVector(0, 0);
this.maxSpeed = 2;
this.maxForce = 0.2;
}
update() {
// Move towards the center of boids
let center = createVector(0,0);
let count = 0;
for (let b of boids) {
center.add(b.pos);
count++;
}
if (count >0) {
center.div(count);
let desired = p5.Vector.sub(center, this.pos);
desired.normalize();
desired.mult(this.maxSpeed);
this.vel = desired;
}
this.pos.add(this.vel);
this.borders();
}
borders() {
if (this.pos.x < 0) this.pos.x = 0;
if (this.pos.x > width) this.pos.x = width;
if (this.pos.y < 0) this.pos.y = 0;
if (this.pos.y > height) this.pos.y = height;
}
show() {
fill(255, 0, 0);
ellipse(this.pos.x, this.pos.y, 12, 12);
}
}
```
This is a p5.js sketch implementing Boids with predator avoidance. The Boid class includes separation, alignment, cohesion, and predatorAvoid methods. The predator moves towards the center of the boids and applies a repulsive force to the boids when within a certain distance.