feat: move to typescript, miniplex ecs

This commit is contained in:
2026-09-05 12:07:22 +05:00
parent 63f9885e46
commit f6596f765e
69 changed files with 3932 additions and 1838 deletions
@@ -0,0 +1,75 @@
import { gameConfig } from '../../config/gameConfig'
import type { Player } from '../../core/types'
import type { InputCommand, LocalBufferSource } from '../InputSource'
import { firstToken } from '../InputSource'
/** Тестовый источник без Twitch: локальная клавиатура, как в оригинале. */
export class LocalKeyboardInputSource implements LocalBufferSource {
readonly origin = 'local' as const
private buffer = ''
private attached = false
private readonly commandHandlers = new Set<(command: InputCommand) => void>()
private readonly bufferHandlers = new Set<(text: string) => void>()
constructor(private readonly channelName: string) {}
start(): void {
if (this.attached) return
window.addEventListener('keydown', this.onKeyDown)
this.attached = true
}
stop(): void {
if (!this.attached) return
window.removeEventListener('keydown', this.onKeyDown)
this.attached = false
}
onCommand(handler: (command: InputCommand) => void): () => void {
this.commandHandlers.add(handler)
return () => this.commandHandlers.delete(handler)
}
onBuffer(handler: (text: string) => void): () => void {
this.bufferHandlers.add(handler)
return () => this.bufferHandlers.delete(handler)
}
private onKeyDown = (event: KeyboardEvent): void => {
if (event.key === 'Backspace') {
event.preventDefault()
this.buffer = this.buffer.slice(0, -1)
this.emitBuffer()
} else if (event.key === 'Enter') {
event.preventDefault()
const line = this.buffer
this.buffer = ''
this.submit(line)
} else if (event.key === 'Escape') {
this.buffer = ''
this.emitCommand({ kind: 'clear' })
} else if (event.key.length === 1 && !event.ctrlKey && !event.altKey && !event.metaKey) {
this.buffer += event.key
this.emitBuffer()
}
}
private submit(line: string): void {
const word = firstToken(line)
if (!word) return
if (word === gameConfig.commands.play) {
this.emitCommand({ kind: 'start' })
return
}
const player: Player = { id: 'local', name: this.channelName }
this.emitCommand({ kind: 'word', word, player })
}
private emitCommand(command: InputCommand): void {
for (const handler of [...this.commandHandlers]) handler(command)
}
private emitBuffer(): void {
for (const handler of [...this.bufferHandlers]) handler(this.buffer)
}
}