diff --git a/agar.html b/agar.html
new file mode 100644
index 0000000..65362d7
--- /dev/null
+++ b/agar.html
@@ -0,0 +1,40 @@
+
+
+
+
+
+ Test
+
+
+
+
+
+
+
+
+
diff --git a/agar.ts b/agar.ts
new file mode 100644
index 0000000..15891a9
--- /dev/null
+++ b/agar.ts
@@ -0,0 +1,80 @@
+import { drawCircle, fillCircle, resizeCanvas } from "./common.js";
+import Vec2d from "./vec.js";
+
+const INITIAL_RADIUS = 100;
+const MAX_SPEED = 300;
+
+let lastTime: number | undefined = undefined;
+
+// state
+let velocity = Vec2d.fromPoint({ x: 0, y: 0 });
+let pos = Vec2d.fromPoint({ x: 0, y: 0 });
+let radius = INITIAL_RADIUS;
+let blobs: Vec2d[] = [];
+
+function update(ctx: CanvasRenderingContext2D, timestamp: number) {
+ if (lastTime == null) lastTime = timestamp;
+ const dt = (timestamp - lastTime) / 1000; // s
+ lastTime = timestamp;
+
+ // update
+ pos = pos.add(velocity.scale(dt));
+ // check collisions
+ for (let i = blobs.length - 1; i >= 0; i--) {
+ const blob = blobs[i];
+ if (blob.distance(pos) < radius) {
+ // merge blobs areas
+ const new_area = Math.PI * radius ** 2 + Math.PI * 10 ** 2;
+ // compute radius from new area
+ radius = Math.sqrt(new_area / Math.PI);
+ blobs.splice(i, 1);
+ }
+ }
+
+ // drawing
+ ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
+ // world space to screen space: always put (pos.x, pos.y) at the center of the screen
+ ctx.translate(ctx.canvas.width / 2, ctx.canvas.height / 2);
+ ctx.scale(INITIAL_RADIUS / radius, INITIAL_RADIUS / radius);
+ ctx.translate(-pos.x, -pos.y)
+ for (const pos of blobs) {
+ fillCircle(ctx, pos, 10, 0x00ff00);
+ }
+ fillCircle(ctx, pos, radius, 0xff00ff);
+ // Reset current transformation matrix to the identity matrix
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
+ window.requestAnimationFrame((t) => update(ctx, t));
+}
+
+window.onload = () => {
+ const canvas = document.getElementById("canvas") as HTMLCanvasElement;
+ if (canvas == null) throw new Error("Canvas not found")
+ const ctx = canvas.getContext("2d") as CanvasRenderingContext2D;
+ if (ctx == null) throw new Error("Canvas context not found")
+ resizeCanvas(ctx);
+
+ canvas.onmousemove = (e) => {
+ const rect = canvas.getBoundingClientRect();
+ // mouse coordinates relative to canvas
+ const mouse = Vec2d.fromPoint({ x: e.clientX - rect.left, y: e.clientY - rect.top, });
+ const center = Vec2d.fromPoint({ x: canvas.width / 2, y: canvas.height / 2, });
+ const max = new Vec2d(MAX_SPEED, MAX_SPEED);
+ velocity = mouse.sub(center).clamp(max.neg(), max);
+ };
+
+ for (let i = 0; i < 10000; ++i) {
+ const x = (Math.random() - 0.5) * 10000;
+ const y = (Math.random() - 0.5) * 10000;
+ blobs.push(new Vec2d(x, y));
+ }
+
+ window.requestAnimationFrame((t) => update(ctx, t));
+};
+
+window.onresize = () => {
+ const canvas = document.getElementById("canvas") as HTMLCanvasElement;
+ if (canvas == null) throw new Error("Canvas not found")
+ const ctx = canvas.getContext("2d") as CanvasRenderingContext2D;
+ if (ctx == null) throw new Error("Canvas context not found")
+ resizeCanvas(ctx);
+};