for(_='/1E3;Z...Ye(Xg)WMath.V=Vmin(U&&Tq/2T(CRd.QQpOQgN),L1.5KLc.J1.1,-KJH=Vsqrt(G[v]Qf.8,[1][0]Qb+=[2]0,en/g*u*2500Qh);u=Vmax(1,f.b=>{e-=n/g*u*4,*=.55**lfillStyle=`,v=v++, U2E4,+[1E-8](-)/(+Lvar .map(O=(O+*l+m)%m,e(n+f.f-)*u,;f/f*l*(110-)/110*pfor(i in c)c[i+[i[6]]]=c[i];b=[YArray(90)]()=>({g:0})Lh=r=u,v;onmouseup=()=>r=!r;A=xl=(x-h)Zh=x;m=[a.width+1,a.height+1],p=mZc.rgb(49${.7*!r})`;c.fc(Ymb(d,$)e=[,,];0>=N?(O=mf=>f*Vrandom()LQf=[0],=N=1,=0):(bfif(d!=f){g=n=m(q,C)C=f.p[C]-O[C];C<-RqC>R-=qgC*C;return C}gGW/p;t=.2<y=.2<-E=!tT!y;ET(u=.1+(5>W e2*uif(ETg<4*||yT2>W2-W eu;tT(3-W* eutTg<1.1*T(LNU1,N+/Lf.g=0)}}Lefv=0;v++}), $/5*lLN-=l/5G/VPIJsavXJta(O-5O-50JscalX*p,*pJrotatX-Vatan2(YQf)Jba(Jm(-HbC(K,-K,KJbC(-K,-K,-Hhsl(${80-},1%,${50*N}%)`,c.fill(JrX))}requestAnimationFramXA)};A(0';G=/[^ -FIMPS[-}]/.exec(_);)with(_.split(G))_=join(shift());eval(_)
// ==ClosureCompiler==
// @language_out ECMASCRIPT_2017
// @compilation_level ADVANCED_OPTIMIZATIONS
// @js_externs var w = {};
// @js_externs var a = {};
// @js_externs var c = {};
// ==/ClosureCompiler==
/*
The rules for fishy are:
Fish have mass.
Fish gain a small amount of mass over time
Fish have 0 to 1 energy.
Fish lose energy over time independent of mass.
Fish die at 0 energy.
Fish eat prey and add the prey mass to their own.
Fish gain energy from eating prey equal to the prey mass / new own mass.
Much smaller fish are prey.
Much larger fish are predators.
The rest of fish are kin.
The velocity of the fish is determined using a boids-like model. All rules are distance weighted:
1. Fish tries to move closer to kin.
2. Fish tries to move in the same direction as kin.
3. Fish chase prey.
4. Fish move away from very close kin and nearby predators.
*/
var fishCount = 90;
var maxMass = 2e4;
var padding = 50;
var fish = [...Array(fishCount)].map(() => ({ energy: 0 }));
var lastTime = 0;
var trail = 0;
var weight;
var i;
// Switch on and off trail mode
onmouseup = () => trail = !trail;
// Main loop
var update = time => {
var elapsedSeconds = (time - lastTime) / 1000;
lastTime = time;
var size = [a.width + padding * 2, a.height + padding * 2];
// Scale the logic based on screen height
var scale = size[1] / 1000;
// Clear (blur) the canvas
c.fillStyle = `rgb(0,40,90,${!trail * .7})`;
c.fillRect(0, 0, ...size);
// Update fishes
fish.map((f, fi) => {
var r = [[0, 0, 1e-8], [0, 0, 1e-8], [0, 0, 1e-8]];
// Die if there is no energy
if (f.energy <= 0) {
f.p = size.map((s, i) => s * Math.random());
f.v = [0, 0];
f.mass = f.energy = 1;
f.radius = 0;
return;
}
// React to other fishes using a boids model
fish.map(f2 => {
// Not itself
if (f == f2) {
return;
}
// Wrapping delta position and distance
var dist = 0;
var d = size.map((s, i) => {
var d = f2.p[i] - f.p[i];
if (d < -s / 2) {
d += s;
}
if (d > s / 2) {
d -= s;
}
dist += d * d;
return d;
});
dist = Math.sqrt(dist) / scale;
// Relationship type
var isPredator = (f.mass - f2.mass) / (f.mass + f2.mass) > .2;
var isPrey = -(f.mass - f2.mass) / (f.mass + f2.mass) > .2;
var isKin = !isPredator && !isPrey;
// Cohesion and alignment
if (isKin) {
weight = .1 + (dist < 500);
i = 0;
r[0][i] += (d[i] + f2.v[i] - f.v[i]) * weight;
i++;
r[0][i] += (d[i] + f2.v[i] - f.v[i]) * weight;
r[0][2] += weight * 2;
}
// Separation and fleeing
if (isKin && dist < f.radius * 4 || isPrey && dist < 200) {
weight = Math.max(1, 200 - dist);
i = 0;
r[1][i] -= d[i] / dist * weight * 400;
i++;
r[1][i] -= d[i] / dist * weight * 400;
r[1][2] += weight;
}
// Chasing
if (isPredator) {
weight = Math.max(1, 300 - dist) * f2.mass;
i = 0;
r[2][i] += d[i] / dist * weight * 250;
i++;
r[2][i] += d[i] / dist * weight * 250;
r[2][2] += weight;
}
// Eat other fish
if (isPredator && dist < f.radius * 1.1) {
f.mass = Math.min(maxMass, f.mass + f2.mass);
f.energy = Math.min(1, f.energy + f2.mass / f.mass);
f2.energy = 0;
}
});
// Apply boid rules to fish velocity
r.map(rr => {
i = 0;
f.v[i] += (rr[i] / rr[2]) * elapsedSeconds * (110 - f.radius) / 110 * scale;
i++;
f.v[i] += (rr[i] / rr[2]) * elapsedSeconds * (110 - f.radius) / 110 * scale;
});
// Apply drag
i = 0;
f.v[i] *= .55 ** elapsedSeconds;
i++;
f.v[i] *= .55 ** elapsedSeconds;
// Update position
i = 0;
f.p[i] = (f.p[i] + f.v[i] * elapsedSeconds + size[i]) % size[i];
i++;
f.p[i] = (f.p[i] + f.v[i] * elapsedSeconds + size[i]) % size[i];
// Grow and consume energy
f.mass = Math.min(maxMass, f.mass + elapsedSeconds * (fi / 5));
f.energy -= elapsedSeconds / 50;
f.radius = Math.sqrt(f.mass / Math.PI);
// Draw fish
c.save();
c.translate(f.p[0] - padding, f.p[1] - padding);
c.scale(f.radius * scale, f.radius * scale);
c.rotate(-Math.atan2(...f.v));
c.beginPath();
c.moveTo(-1.1, -1.5);
c.bezierCurveTo(1.5, -.8, 1.5, .8, 0, 1.5);
c.bezierCurveTo(-1.5, .8, -1.5, -.8, 1.1, -1.5);
c.fillStyle = `hsl(${80 - f.radius},100%,${f.energy * 50}%)`;
c.fill();
c.restore();
});
requestAnimationFrame(update);
};
update(0);