Implement the Boids algorithm
Processing 3.3.6 (Java), 932 931 940 928 957 917 904 bytes
-1 byte from Jonathan Frech
+11 bytes to better match the spec
-2 bytes from Kevin Cruijssen
-12 bytes for changing args to t()
+29 bytes because I was doing ghosting wrong, see commented version below
-40 bytes for using for loops instead of separate calls for each ghost
-13 bytes for using default frameRate, 30
Well, it's a start, for someone who doesn't Java-golf. :)
int n=15,f=400,i,j,z=255,w=500;float d=200./n;PVector m;B[]a=new B[n];void setup(){size(500,500);fill(z,0,0);randomSeed(n);for(i=0;i<n;a[i++]=new B(new PVector(random(w),random(w)),m.fromAngle(random(TAU))));}void draw(){background(z);for(B b:a)b.u();if(frameCount%f<1)setup();}class B{PVector p,v,e,q,r;ArrayList<B>n;B(PVector m,PVector o){p=m;v=o;}void u(){e=v.copy();n=new ArrayList();for(B b:a){if(b!=this)for(i=-w;i<=w;i+=w)for(j=-w;j<=w;j+=w)t(i,j,b);}if(n.size()>0){q=new PVector();r=q.copy();for(B b:n){q.add(b.v);r.add(b.p);if(p.dist(b.p)<=d)e.add(p).sub(b.p);}e.add(q.div(n.size()).sub(v).div(10));e.add(r.div(n.size()).sub(p).div(50));}p.add(e.limit(d/10));v=e.mult(10);p.set((p.x+w)%w,(p.y+w)%w);noStroke();ellipse(p.x,p.y,d,d);stroke(0,0,z);line(p.x,p.y,p.x+v.x,p.y+v.y);}void t(int x,int y,B o){m=o.p.copy().add(x,y);if(2*d>=p.dist(m)&q.angleBetween(v,q.sub(m,p))<=5*PI/6)n.add(new B(m,o.v));}}
I don't know any reasonable way to do input in Processing, so the first two variables are the inputs (and I didn't count their values (5 bytes) toward the byte count). If this is a problem, I can try other things.
I also don't know of a good way to allow trying it online (the Processing.js project can't deal with this code style) without hosting things myself; and that is something I'm not eager to attempt. Let me know if there is anything clever I can do.
Formatted code, with comments
int n=15, // Number of boids
f=400, // Number of frames
i,j,z=255,w=500; // temp*2, and two constants
float d=200./n; // Boid diameter
PVector m; // temp
B[]a=new B[n];
void setup(){ // This is automatically called at startup
size(500,500); // Can't use variables for this without extra bytes for settings()
fill(z,0,0);
randomSeed(n); // seeded from number of Boids, so that n=19 is very different from n=20
for(i=0;i<n;a[i++]=new B(new PVector(random(w),random(w)),m.fromAngle(random(TAU))));
}
void draw(){ // This is automatically called each frame
background(z);
for(B b:a)
b.u();
if(frameCount%f<1) // When desired frames length is hit, reset everything.
setup(); // Could also use noLoop() instead of setup() to just stop instead.
// Or, remove this if statement altogether to go on to infinity.
}
class B{ // Boid
PVector p,v,e,q,r; // Position, Velocity, Next velocity, and two temp vectors
ArrayList<B>n; // List of neighbors
B(PVector m,PVector o){
p=m;
v=o;
}
void u(){ // Update function, does rules and redraw for this Boid
e=v.copy();
n=new ArrayList();
for(B b:a){ // Test a Boid and its eight ghosts for neighborship
if(b!=this) // Note: Assumes neighborhood diameter < min(width,height)
// The ghosts are to check if it'd be closer to measure by wrapping
// We need eight for wrapping north, east, south, west, northeast,
// northwest, southeast, and southwest. And also the non-wrapped one.
// The above assumption ensures that each ghost is further apart than
// the neighborhood diameter, meaning that only one neighbor might be
// found for each boid. To test this, place a boid in each corner, right
// to the edge, facing away from center. Each boid should find three
// neighbors, that are the three other boids.
for(i=-w;i<=w;i+=w)for(j=-w;j<=w;j+=w)t(i,j,b);
}
if(n.size()>0){
q=new PVector();
r=q.copy();
for(B b:n){
q.add(b.v); // Velocity matching, pt 1
r.add(b.p); // Flock centering, pt 1
if(p.dist(b.p)<=d)
e.add(p).sub(b.p); // Collision avoidance
}
e.add(q.div(n.size()).sub(v).div(10)); // Velocity matching, pt 2
e.add(r.div(n.size()).sub(p).div(50)); // Flock centering, pt 2
}
p.add(e.limit(d/10)); // Update vectors
v=e.mult(10);
p.set((p.x+w)%w,(p.y+w)%w); // Wrapping
noStroke();
ellipse(p.x,p.y,d,d); // Draw Boid, finally
stroke(0,0,z);
line(p.x,p.y,p.x+v.x,p.y+v.y);
}
void t(int x,int y,B o){ // Test if a Boid (or a ghost) is a neighbor
m=o.p.copy().add(x,y);
if(2*d>=p.dist(m)&q.angleBetween(v,q.sub(m,p))<=5*PI/6)
n.add(new B(m,o.v));
}
}
Sample output
n = 15, frames = 400:
Or, the same animation, but showing the neighborhood of each boid.
JavaScript (ES6) + HTML5, 1200 bytes
Here's my current solution using the Canvas API. The eval()
returns a curried function whose first input is the Boid
population, and second is the number of animation frames. You can use Infinity
for continuous animation.
The eval(...)
is 1187 bytes and <canvas id=c>
is 13 bytes, making a total of 1200. The CSS is unnecessary, but for convenience, it allows you to see the edges of the canvas.
eval("L7F7{function B8{t=this,t.a=o8*T,t.x=o8*S,t.y=o8*S}C=this.c,D=C.getContext`2d`,({abs:z,random:o,atan2:k,cos:u,sin:g,PI:P,T=2*P,G={c:_7A[r='filter'](b7b!=t)[i](9)79)),n:_7A[r](b7b!=t)[i](9)7({a,x,y:y-S})),s:_7A[r](b7b!=t)[i](9)7({a,x,y:y+S})),e:_7A[r](b7b!=t)[i](9)7({a,x:x-S,y})),w:_7A[r](b7b!=t)[i](9)7({a,x:x+S,y}))},M=I7[I,I+T,I-T][p]((a,x)7z(x)<z(a)?x:a)}=Math),B.prototype={d8{with(D)save8,translate(x,y),rotate(a),beginPath8,arc(0,0,5,0,T),fillStyle='red',fill8,beginPath8,moveTo(0,0),lineTo(10,0),strokeStyle='blue',stroke8,restore8},n:_7(({c,n,s,e,w}=G),c8.concat(n8,s8,e8,w8)[r](b7(d=b.x-x,f=b.y-y,400>d*d+f*f&&z(z(k(f,d)-a)/P-1)>1/6))),s8{q=(j=t.n8).length,v=t.v8||0,l=t.l8||0,f=t.f8||0,a=t.a=(t.a+v+l/10+f/50)%T,t.x=(x+u(a)+S)%S,t.y=(y+g(a)+S)%S},v:_7([d,f]=j[r](b7225>(b.x-x)**2+(b.y-y)**2)[p='reduce'](([d,f],b)7[x+d-b.x,y+f-b.y],[0,0]),d||f?M(k(f,d)-a):0),l:_7j[i](b7M(b.a-a))[p]((a,x)7a+x,0)/q,f:_7([d,f]=j[p](([d,f],b)7[d+b.x,f+b.y],[-x*q,-y*q]),d||f?M(k(f,d)-a):0)},S=C.width=C.height=50*L,A=Array(L).fill().map(_7new B),R=_7{D.clearRect(0,0,S,S),A[i='map'](b79=b).d8),A[i](b79=t=b).s8),F--&&setTimeout(R,10)},R8}".replace(/[789]/g,m=>['=>','()','({a,x,y}'][m-7]))
(10)(Infinity)
canvas{border:1px solid}
<canvas id=c>
Edit
As requested, another snippet with an input for Boid population:
b.onchange=()=>{eval("L7F7{function B8{t=this,t.a=o8*T,t.x=o8*S,t.y=o8*S}C=this.c,D=C.getContext`2d`,({abs:z,random:o,atan2:k,cos:u,sin:g,PI:P,T=2*P,G={c:_7A[r='filter'](b7b!=t)[i](9)79)),n:_7A[r](b7b!=t)[i](9)7({a,x,y:y-S})),s:_7A[r](b7b!=t)[i](9)7({a,x,y:y+S})),e:_7A[r](b7b!=t)[i](9)7({a,x:x-S,y})),w:_7A[r](b7b!=t)[i](9)7({a,x:x+S,y}))},M=I7[I,I+T,I-T][p]((a,x)7z(x)<z(a)?x:a)}=Math),B.prototype={d8{with(D)save8,translate(x,y),rotate(a),beginPath8,arc(0,0,5,0,T),fillStyle='red',fill8,beginPath8,moveTo(0,0),lineTo(10,0),strokeStyle='blue',stroke8,restore8},n:_7(({c,n,s,e,w}=G),c8.concat(n8,s8,e8,w8)[r](b7(d=b.x-x,f=b.y-y,400>d*d+f*f&&z(z(k(f,d)-a)/P-1)>1/6))),s8{q=(j=t.n8).length,v=t.v8||0,l=t.l8||0,f=t.f8||0,a=t.a=(t.a+v/3+l/10+f/50)%T,t.x=(x+u(a)+S)%S,t.y=(y+g(a)+S)%S},v:_7([d,f]=j[r](b7225>(b.x-x)**2+(b.y-y)**2)[p='reduce'](([d,f],b)7[x+d-b.x,y+f-b.y],[0,0]),d||f?M(k(f,d)-a):0),l:_7j[i](b7M(b.a-a))[p]((a,x)7a+x,0)/q,f:_7([d,f]=j[p](([d,f],b)7[d+b.x,f+b.y],[-x*q,-y*q]),d||f?M(k(f,d)-a):0)},S=C.width=C.height=50*L,A=Array(L).fill().map(_7new B),R=_7{D.clearRect(0,0,S,S),A[i='map'](b79=b).d8),A[i](b79=t=b).s8),F--&&setTimeout(R,10)},R8}".replace(/[789]/g,m=>['=>','()','({a,x,y}'][m-7]))(+b.value)(Infinity);b.remove()}
input{display:block}canvas{border:1px solid}
<input id=b><canvas id=c>