Skip to content

Instantly share code, notes, and snippets.

@dmage
Created July 13, 2018 15:28
Show Gist options
  • Save dmage/08eec0d9b97dd754f5390a7067c44402 to your computer and use it in GitHub Desktop.
Save dmage/08eec0d9b97dd754f5390a7067c44402 to your computer and use it in GitHub Desktop.
<!doctype html>
<html>
<head>
<script src="https://d3js.org/d3.v5.min.js"></script>
</head>
<body>
<svg width="600" height="400" style="background: #ccc" font-family="sans-serif" font-size="10" text-anchor="middle"></svg>
<script>
const width = 600, height = 400;
let animals = [1,2,3,4,5,6,7,8,9,10].map(() => { return {
x: 200,
y: 200,
r: 5,
dir: 0,
ttl: 5000,
speed: 3,
vision: 10,
rotationDelta: 50,
predator: 0,
forkP: 0.02,
forkN: 2,
initTTL: 5000,
}}).concat([1,2,3,4].map(() => { return {
x: 50,
y: 50,
r: 5,
dir: 0,
ttl: 5000,
speed: 2,
vision: 10,
rotationDelta: 50,
predator: 1,
forkP: 0.02,
forkN: 2,
initTTL: 5000,
}}));
function tick() {
let nodes = d3.select("svg")
.selectAll("g")
.data(animals)
.attr("transform", d => `translate(${d.x} ${d.y}) rotate(${d.dir*180/3.1415} 0 0)`);
let g = nodes.enter()
.append("g")
.attr("transform", d => `translate(${d.x} ${d.y}) rotate(${d.dir*180/3.1415} 0 0)`);
g.append("circle")
.attr("class", "vision")
.attr("stroke", "rgba(0,0,0,0.1)")
.attr("stroke-width", 1)
.attr("cx", 0)
.attr("cy", 0);
g.append("circle")
.attr("class", "body")
.attr("stroke", "#333")
.attr("stroke-width", 1)
.attr("cx", 0)
.attr("cy", 0);
g.append("line")
.attr("stroke", "#333")
.attr("stroke-width", 1)
.attr("x1", d => d.r - 1)
.attr("y1", 0)
.attr("x2", 0)
.attr("y2", 0);
nodes.merge(g).select(".vision")
.attr("r", d => d.vision - 1)
.attr("fill", d => d.predator ? "rgba(200,0,0,0.05)" : "rgba(0,200,0,0.05)");
nodes.merge(g).select(".body")
.attr("r", d => d.r - 1)
.attr("fill", d => d.predator ? "rgba(200,0,0,0.5)" : "rgba(0,200,0,0.5)");
nodes.exit()
.remove();
};
tick();
function distance(a, b) {
const dx = a.x - b.x, dy = a.y - b.y;
return Math.sqrt(dx*dx + dy*dy);
}
function angle(a, b) {
const d = Math.atan((a.y - b.y)/(a.x - b.x));
return a.x > b.x ? d + Math.PI : d;
}
function step() {
let eaten = [];
animals.forEach((d, i) => {
if (d.forkP/50 > Math.random()) {
const n = d.forkN*Math.random();
for (let j = 0; j < n; j++) {
let n = Object.assign({}, d);
n.vision += 30 * (Math.random() - 0.5);
n.speed += 1 * (Math.random() - 0.5);
if (n.speed < 1) n.speed = 1;
n.ttl = Math.round(Math.random()*(n.initTTL - 1) + 1);
if (n.predator) {
n.forkP = 0;
}
animals.push(n);
}
}
});
animals.forEach((d, i) => {
d.dir += d.rotationDelta*Math.PI/180 * 2*(Math.random() - 0.5);
d.x += d.speed*Math.cos(d.dir);
d.y += d.speed*Math.sin(d.dir);
if (d.predator) {
animals.forEach((alien, j) => {
if (j != i && alien.predator != d.predator && distance(alien, d) < d.r) {
eaten.push(j);
alien.eaten = true;
d.ttl += 500;
d.forkP += 0.01;
if (d.ttl > d.initTTL) d.ttl = d.initTTL;
}
});
}
if (d.ttl == 0) {
eaten.push(i);
}
d.ttl -= 1;
const aliens = animals.filter((alien, j) => !alien.eaten && j != i && alien.predator != d.predator && distance(alien, d) < d.vision);
if (aliens.length > 0) {
if (d.predator) {
let minDist = Infinity, alien;
aliens.forEach((a, j) => {
const dist = distance(a, d);
if (dist < minDist) {
minDist = dist;
alien = a;
}
});
d.dir = angle(d, alien);
} else {
let x = 0, y = 0;
aliens.forEach(a => {
let g = angle(d, a) + Math.PI;
x += Math.cos(g);
y += Math.sin(g);
});
console.log(i, x, y, angle({x: 0, y: 0}, {x: x, y: y}));
if (Math.abs(x) < 0.01 && Math.abs(y) < 0.01) {
d.dir = 2*Math.PI*Math.random();
} else {
d.dir = angle({x: 0, y: 0}, {x: x, y: y});
}
}
}
if (d.x < d.r) d.x = d.r;
if (d.y < d.r) d.y = d.r;
if (d.x > width - d.r) d.x = width - d.r;
if (d.y > height - d.r) d.y = height - d.r;
});
eaten.sort((a, b) => b - a);
for (let j in eaten) {
console.log("DELETE", eaten[j]);
animals.splice(eaten[j], 1);
}
tick();
requestAnimationFrame(step);
}
requestAnimationFrame(step);
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment