You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
165 lines
3.7 KiB
165 lines
3.7 KiB
console.clear();
|
|
|
|
/* Play with these values! */
|
|
const PARTICLE_COUNT = 100;
|
|
const SAFE_DISTANCE = 130;
|
|
const INFECTED_DISTANCE = 15;
|
|
const INFECTION_RATE = 0.25;
|
|
const RECOVERY_TIME = 14000;
|
|
const STAY_AT_HOME = 0.1;
|
|
|
|
/* ---------------------------------- */
|
|
|
|
let particles = [];
|
|
|
|
const STATUSES = {
|
|
HEALTHY: "HEALTHY",
|
|
INFECTED: "INFECTED",
|
|
RECOVERED: "RECOVERED"
|
|
};
|
|
|
|
const elBody = document.body;
|
|
const elCanvas = document.querySelector("#canvas");
|
|
const ctx = elCanvas.getContext("2d");
|
|
|
|
let width, height;
|
|
|
|
function resize() {
|
|
width = elCanvas.width = elBody.clientWidth;
|
|
height = elCanvas.height = elBody.clientHeight;
|
|
}
|
|
resize();
|
|
window.addEventListener("resize", resize);
|
|
|
|
/* ---------------------------------- */
|
|
|
|
class Particle {
|
|
constructor() {
|
|
this.x = Math.random() * width;
|
|
this.y = Math.random() * height;
|
|
this.radius = 3;
|
|
this.color = "white";
|
|
this.speed = Math.random() < STAY_AT_HOME ? 0 : 1;
|
|
this.directionAngle = Math.floor(Math.random() * 360);
|
|
this.vector = {
|
|
x: Math.cos(this.directionAngle) * this.speed,
|
|
y: Math.sin(this.directionAngle) * this.speed
|
|
};
|
|
this.status = STATUSES.HEALTHY;
|
|
|
|
if (Math.random() < INFECTION_RATE) {
|
|
this.infect();
|
|
}
|
|
}
|
|
infect() {
|
|
if (
|
|
this.status === STATUSES.INFECTED ||
|
|
this.status === STATUSES.RECOVERED
|
|
) {
|
|
return;
|
|
}
|
|
|
|
this.color = "green";
|
|
this.status = STATUSES.INFECTED;
|
|
|
|
setTimeout(() => {
|
|
this.recover();
|
|
}, RECOVERY_TIME);
|
|
}
|
|
recover() {
|
|
this.status = STATUSES.RECOVERED;
|
|
this.color = "hotpink";
|
|
}
|
|
draw(drawCtx) {
|
|
drawCtx.beginPath();
|
|
drawCtx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
|
|
drawCtx.closePath();
|
|
drawCtx.fillStyle = this.color;
|
|
drawCtx.fill();
|
|
}
|
|
update() {
|
|
this.checkBoundaries();
|
|
this.x += this.vector.x;
|
|
this.y += this.vector.y;
|
|
}
|
|
checkBoundaries() {
|
|
if (this.x > width || this.x < 0) {
|
|
this.vector.x *= -1;
|
|
/* Ensure the dots are pushed inside */
|
|
this.x = Math.max(0, Math.min(width, this.x));
|
|
}
|
|
if (this.y > height || this.y < 0) {
|
|
this.vector.y *= -1;
|
|
/* Ensure the dots are pushed inside */
|
|
this.y = Math.max(0, Math.min(height, this.y));
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ---------------------------------- */
|
|
|
|
function distance(x1, y1, x2, y2) {
|
|
var dx = x1 - x2;
|
|
var dy = y1 - y2;
|
|
return Math.sqrt(dx * dx + dy * dy);
|
|
}
|
|
|
|
function linkParticles(particle, otherParticles, drawCtx) {
|
|
for (const p of otherParticles) {
|
|
const d = distance(particle.x, particle.y, p.x, p.y);
|
|
|
|
if (d > SAFE_DISTANCE) {
|
|
continue;
|
|
}
|
|
|
|
// Infect other particle!
|
|
if (particle.status === STATUSES.INFECTED && d < INFECTED_DISTANCE) {
|
|
p.infect();
|
|
}
|
|
|
|
const opacity = 0.8 - (d / SAFE_DISTANCE) * 0.8;
|
|
drawCtx.lineWidth = 1;
|
|
drawCtx.strokeStyle = particle.color; //rgba(255,255,255,${opacity})`;
|
|
drawCtx.globalAlpha = opacity;
|
|
drawCtx.beginPath();
|
|
drawCtx.moveTo(particle.x, particle.y);
|
|
drawCtx.lineTo(p.x, p.y);
|
|
drawCtx.closePath();
|
|
drawCtx.stroke();
|
|
drawCtx.globalAlpha = 1;
|
|
}
|
|
}
|
|
|
|
/* ---------------------------------- */
|
|
|
|
function render() {
|
|
try {
|
|
requestAnimationFrame(render);
|
|
|
|
ctx.clearRect(0, 0, width, height);
|
|
|
|
particles.forEach(particle => {
|
|
particle.update();
|
|
if (particle.status === STATUSES.INFECTED) {
|
|
linkParticles(particle, particles, ctx);
|
|
}
|
|
particle.draw(ctx);
|
|
});
|
|
} catch (e) {
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
render();
|
|
|
|
/* ---------------------------------- */
|
|
|
|
function reset() {
|
|
particles = [];
|
|
for (var i = 0; i < PARTICLE_COUNT; i++) {
|
|
particles.push(new Particle());
|
|
}
|
|
}
|
|
reset();
|
|
|
|
document.addEventListener("click", reset);
|