Serpent Ski Mulligan

2026-10-19

Gotta go fast

The approach I used last time isn’t very efficient, since it repeatedly appends new SVG elements. Here’s a speedier approach, via drawing on a canvas 2D context:

Sierpiński Triangle via the Chaos Game

The code is shorter, too:

const CTX = document.getElementById("chaos").getContext("2d");
CTX.fillStyle = "#027f8d";
const [XS, YS] = ((b) => {
	const w = CTX.canvas.width;
	const s = 0.75 * w;
	const h = (s * Math.sqrt(3)) / 2;
	return [
		[b + 0, b + s / 2, b + s],
		[b + h, b + 0, b + h],
	];
})(10);
let [X, Y, ON] = [0, 0, null];
const new_xy = () => {
	const [u, v] = [Math.sqrt(Math.random()), Math.random()];
	X = (1 - u) * XS[0] + u * (1 - v) * XS[1] + u * v * XS[2];
	Y = (1 - u) * YS[0] + u * (1 - v) * YS[1] + u * v * YS[2];
};
const draw = (x, y, r = 2) => {
	CTX.beginPath();
	CTX.arc(x, y, r, 0, 2 * Math.PI, false);
	CTX.fill();
};
XS.forEach((x, i) => draw(x, YS[i], 4));
document.getElementById("reset").addEventListener("click", () => {
	ON = clearInterval(ON);
	CTX.clearRect(0, 0, CTX.canvas.width, CTX.canvas.height);
	XS.forEach((x, i) => draw(x, YS[i], 4));
});
document.getElementById("start_stop").addEventListener("click", () => {
	if (!ON) {
		new_xy();
		ON = setInterval(() => {
			const i = Math.floor(XS.length * Math.random());
			X += 0.5 * (XS[i] - X);
			Y += 0.5 * (YS[i] - Y);
			draw(X, Y);
		});
	} else {
		ON = clearInterval(ON);
	}
});