import Entity from "./entity"; export enum SpinnerAction { RUN = 1, BITE = 2, MELEE = 3, SHOOT = 4, }; export type SpinnerListener = (action: SpinnerAction) => void; export default class Spinner extends Entity { private readonly probabilities = [0.3, 0.3, 0.2, 0.2]; private readonly colors = ['yellow', 'green', 'blue', 'red']; private readonly symbols = ['🏃‍♂️', '🧟‍♂️', '🔪', '🎯']; 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(); protected override draw(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); ctx.fillText(this.symbols[i], 0.5 + Math.cos(center) * 0.18, 0.5 + Math.sin(center) * 0.18); 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.49, 0.5 + Math.sin(angle) * 0.49); ctx.stroke(); angle = nextAngle; } ctx.beginPath(); ctx.arc(0.5, 0.5, 0.3, 0, Math.PI * 2); ctx.stroke(); ctx.lineWidth = 0.03; ctx.beginPath(); ctx.arc(0.5, 0.5, 0.49, 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 override 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 override onClick() { 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; } } }