134 lines
3.9 KiB
TypeScript
134 lines
3.9 KiB
TypeScript
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<SpinnerListener>();
|
|
|
|
protected override draw(ctx: CanvasRenderingContext2D) {
|
|
ctx.scale(0.9, 0.9);
|
|
ctx.translate(0.05, 0.05);
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|