From efba557e96443e09097620d556c6a25143b48f83 Mon Sep 17 00:00:00 2001 From: Pabloader Date: Wed, 25 Jun 2025 13:39:32 +0000 Subject: [PATCH] Start creating zombies --- src/games/zombies/index.ts | 49 +++++++++++++++ src/games/zombies/spinner.ts | 116 +++++++++++++++++++++++++++++++++++ 2 files changed, 165 insertions(+) create mode 100644 src/games/zombies/index.ts create mode 100644 src/games/zombies/spinner.ts diff --git a/src/games/zombies/index.ts b/src/games/zombies/index.ts new file mode 100644 index 0000000..d64ef7d --- /dev/null +++ b/src/games/zombies/index.ts @@ -0,0 +1,49 @@ +import { createCanvas } from "@common/display/canvas"; +import Spinner from "./spinner"; + +const canvas = createCanvas(1024, 1024); +const spinner = new Spinner(); + +async function update(dt: number) { + spinner.update(dt); +} + +async function render(ctx: CanvasRenderingContext2D) { + ctx.fillStyle = 'green'; + ctx.fillRect(0, 0, canvas.width, canvas.height); + + ctx.save(); + + ctx.translate(100, 100); + ctx.scale(200, 200); + + spinner.render(ctx); + + ctx.restore(); +} + +export default async function main() { + document.body.style.background = 'green'; + canvas.style.imageRendering = 'auto'; + + const ctx = canvas.getContext('2d'); + + spinner.addListener(console.log); + canvas.addEventListener('click', () => { + spinner.spin(); + }) + + if (ctx) { + let prevFrame = performance.now(); + const loop = () => { + const now = performance.now() + const dt = (now - prevFrame) / 1000; + prevFrame = now; + + update(dt); + render(ctx); + requestAnimationFrame(loop); + }; + loop(); + } +} \ No newline at end of file diff --git a/src/games/zombies/spinner.ts b/src/games/zombies/spinner.ts new file mode 100644 index 0000000..2938792 --- /dev/null +++ b/src/games/zombies/spinner.ts @@ -0,0 +1,116 @@ +export type SpinnerListener = (angle: number) => void; + +export default class Spinner { + private readonly probabilities = [0.3, 0.3, 0.2, 0.2]; + private readonly colors = ['yellow', 'green', 'blue', 'red']; + private readonly startAngle = -Math.PI / 2 - this.probabilities[0] * 2 * Math.PI; + + private angle = this.startAngle; + private speed = 0; + private friction = 0.3; + private fired = true; + private listeners = new Set(); + + public render(ctx: CanvasRenderingContext2D) { + ctx.fillStyle = 'white'; + + ctx.beginPath(); + ctx.arc(0.5, 0.5, 0.5, 0, Math.PI * 2); + ctx.fill(); + + + ctx.lineCap = 'butt'; + ctx.fillStyle = 'black'; + ctx.font = '0.1px Arial'; + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + ctx.lineWidth = 0.2; + + let angle = this.startAngle; + for (let i = 0; i < this.probabilities.length; i++) { + ctx.strokeStyle = this.colors[i]; + ctx.beginPath(); + const nextAngle = angle + this.probabilities[i] * 2 * Math.PI; + ctx.arc(0.5, 0.5, 0.4, angle, nextAngle); + ctx.stroke(); + + const center = (angle + nextAngle) / 2; + ctx.fillText(`${i + 1}`, 0.5 + Math.cos(center) * 0.4, 0.5 + Math.sin(center) * 0.4); + + angle = nextAngle; + } + + ctx.lineCap = 'round'; + ctx.strokeStyle = 'black'; + ctx.lineWidth = 0.01; + + angle = this.startAngle; + for (let i = 0; i < this.probabilities.length; i++) { + const nextAngle = angle + this.probabilities[i] * 2 * Math.PI; + + ctx.beginPath(); + ctx.moveTo(0.5, 0.5); + ctx.lineTo(0.5 + Math.cos(angle) * 0.5, 0.5 + Math.sin(angle) * 0.5) + ctx.stroke(); + + angle = nextAngle; + } + + ctx.lineWidth = 0.02; + ctx.beginPath(); + ctx.arc(0.5, 0.5, 0.5, 0, Math.PI * 2); + ctx.moveTo(0.5, 0.5); + ctx.lineTo(0.5 + Math.cos(this.angle) * 0.4, 0.5 + Math.sin(this.angle) * 0.4); + ctx.stroke(); + } + + public update(dt: number) { + if (this.fired) return; + if (this.speed < 0.1) { + this.fire(); + return; + } + + this.angle += this.speed * dt; + this.speed *= 1.0 - this.friction * dt; + this.friction += 0.7 * dt; + } + + public spin() { + if (!this.fired) return; + + this.fired = false; + this.speed = 25 + Math.random() * 25; + this.friction = 0.3 + Math.random() * 0.3; + } + + public addListener(listener: SpinnerListener) { + this.listeners.add(listener); + } + + public removeListener(listener: SpinnerListener) { + this.listeners.delete(listener); + } + + private fire() { + if (this.fired) return; + this.fired = true; + + let checkAngle = this.angle % (Math.PI * 2); + + for (let a = 0; a < 2; a++) { + let angle = (this.startAngle + Math.PI * 2) % (Math.PI * 2); + for (let i = 0; i < this.probabilities.length; i++) { + const nextAngle = angle + this.probabilities[i] * 2 * Math.PI; + + if (angle <= checkAngle && checkAngle <= nextAngle) { + this.listeners.forEach(l => l(i + 1)); + return; + } + + angle = nextAngle; + } + checkAngle += Math.PI * 2; + } + } +}