- Vue 3 + Vite + Tailwind 4 frontend with synthwave aesthetic - Hono backend on port 9100 with SQLite/Drizzle - Procedural pixel-art sprite generator (48x48, 8 animation states) - Kaplay fight scene with punch/kick/special/knockback/KO animations - 12 mock bots across 6 tiers with Elo rating system - 9 challenge types, 10 fight arenas with modifiers - Fight replay with staggered battle log and ~1 min timing - Sprite preview page at /sprites Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
42 lines
1005 B
TypeScript
42 lines
1005 B
TypeScript
type Listener = (event: FightEvent) => void
|
|
|
|
export interface FightEvent {
|
|
fightId: string
|
|
type: string
|
|
data: Record<string, unknown>
|
|
timestamp: string
|
|
}
|
|
|
|
class EventBus {
|
|
private listeners = new Map<string, Set<Listener>>()
|
|
private globalListeners = new Set<Listener>()
|
|
|
|
on(fightId: string, listener: Listener) {
|
|
if (!this.listeners.has(fightId)) {
|
|
this.listeners.set(fightId, new Set())
|
|
}
|
|
this.listeners.get(fightId)!.add(listener)
|
|
return () => this.off(fightId, listener)
|
|
}
|
|
|
|
onAll(listener: Listener) {
|
|
this.globalListeners.add(listener)
|
|
return () => this.globalListeners.delete(listener)
|
|
}
|
|
|
|
off(fightId: string, listener: Listener) {
|
|
this.listeners.get(fightId)?.delete(listener)
|
|
}
|
|
|
|
emit(event: FightEvent) {
|
|
this.listeners.get(event.fightId)?.forEach(fn => fn(event))
|
|
this.globalListeners.forEach(fn => fn(event))
|
|
}
|
|
|
|
cleanup(fightId: string) {
|
|
this.listeners.delete(fightId)
|
|
}
|
|
}
|
|
|
|
export const fightEvents = new EventBus()
|