slop game oder so

This commit is contained in:
2026-09-24 17:57:54 +02:00
commit 4c997eee21
166 changed files with 14976 additions and 0 deletions
+132
View File
@@ -0,0 +1,132 @@
/**
* Eingabe der Orbital-Befehle als Richtungsfolge (ohne Phaser).
*
* Solange die Halte-Taste gedrückt ist, sammelt `ComboInput` Richtungen. Nach jeder
* Eingabe wird geprüft, ob der Puffer noch Anfang mindestens eines Befehls ist:
* - passt nichts mehr → „fail“, Puffer wird geleert (man kann direkt neu ansetzen)
* - Folge vollständig → „match“ mit Befehls-ID
* - sonst → „progress“
* Ob ein Befehl gerade abklingt, entscheidet der Aufrufer – hier geht es nur um Folgen.
* @module logic/comboInput
*/
/** @typedef {'up'|'down'|'left'|'right'} Direction */
/** @typedef {{id: string, sequence: readonly Direction[]}} ComboCommand */
/** @type {readonly Direction[]} */
export const DIRECTIONS = Object.freeze(['up', 'down', 'left', 'right']);
/**
* Physische Tastencodes (KeyboardEvent.code) → Richtung. `code` statt `key`, damit
* WASD auch auf AZERTY- oder QWERTZ-Tastaturen an derselben Stelle liegt.
*/
const CODE_TO_DIRECTION = Object.freeze({
ArrowUp: 'up',
KeyW: 'up',
ArrowDown: 'down',
KeyS: 'down',
ArrowLeft: 'left',
KeyA: 'left',
ArrowRight: 'right',
KeyD: 'right',
});
/**
* @param {string} code KeyboardEvent.code
* @returns {Direction|null}
*/
export function codeToDirection(code) {
return CODE_TO_DIRECTION[code] ?? null;
}
/**
* @param {readonly string[]} prefix
* @param {readonly string[]} sequence
* @returns {boolean}
*/
export function isPrefix(prefix, sequence) {
if (prefix.length > sequence.length) return false;
return prefix.every((dir, i) => sequence[i] === dir);
}
/**
* Prüft die Befehlsliste: gültige Richtungen, eindeutige IDs und keine Folge, die
* Anfang einer anderen ist (sonst wäre die längere Folge nie erreichbar).
* @param {readonly ComboCommand[]} commands
* @throws {Error} bei ungültiger Konfiguration.
*/
export function validateCommands(commands) {
const ids = new Set();
for (const command of commands) {
if (ids.has(command.id)) throw new Error(`Doppelte Befehls-ID: ${command.id}`);
ids.add(command.id);
if (command.sequence.length === 0) throw new Error(`Leere Folge bei ${command.id}`);
for (const dir of command.sequence) {
if (!DIRECTIONS.includes(dir)) throw new Error(`Ungültige Richtung „${dir}“ bei ${command.id}`);
}
}
for (const a of commands) {
for (const b of commands) {
if (a !== b && isPrefix(a.sequence, b.sequence)) {
throw new Error(`Folge von ${a.id} ist Anfang der Folge von ${b.id}`);
}
}
}
}
export class ComboInput {
/** @param {readonly ComboCommand[]} commands */
constructor(commands) {
validateCommands(commands);
this.commands = commands;
/** @type {Direction[]} */
this.buffer = [];
this.active = false;
}
/** Halte-Taste gedrückt: Eingabe beginnt mit leerem Puffer. */
begin() {
this.active = true;
this.buffer = [];
}
/** Halte-Taste losgelassen: halbe Eingaben verfallen. */
end() {
this.active = false;
this.buffer = [];
}
/**
* Verarbeitet eine Richtung.
* @param {string} direction
* @returns {{status: 'inactive'|'ignored'|'fail'|'progress'|'match', commandId?: string, candidates?: string[]}}
*/
push(direction) {
if (!this.active) return { status: 'inactive' };
if (!DIRECTIONS.includes(/** @type {Direction} */ (direction))) return { status: 'ignored' };
this.buffer.push(/** @type {Direction} */ (direction));
const candidates = this.commands.filter((c) => isPrefix(this.buffer, c.sequence));
if (candidates.length === 0) {
this.buffer = [];
return { status: 'fail' };
}
const exact = candidates.find((c) => c.sequence.length === this.buffer.length);
if (exact) {
this.buffer = [];
return { status: 'match', commandId: exact.id };
}
return { status: 'progress', candidates: candidates.map((c) => c.id) };
}
/**
* Wie viele Schritte eines Befehls bereits korrekt eingegeben sind (für die HUD-Anzeige).
* @param {string} commandId
* @returns {number}
*/
progressFor(commandId) {
const command = this.commands.find((c) => c.id === commandId);
if (!command || !isPrefix(this.buffer, command.sequence)) return 0;
return this.buffer.length;
}
}