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
+94
View File
@@ -0,0 +1,94 @@
/**
* Formatierung und Eingabeprüfung für Anzeige und Highscore-Formular (ohne Phaser).
* @module logic/format
*/
import { DIRECTION_SYMBOLS, HIGHSCORE } from '../config.js';
const scoreFormatter = new Intl.NumberFormat('de-DE');
/**
* Punktestand mit Tausendertrennzeichen („12.345“).
* @param {number} score
* @returns {string}
*/
export function formatScore(score) {
return scoreFormatter.format(Math.max(0, Math.floor(score)));
}
/**
* Dauer als „m:ss“.
* @param {number} ms
* @returns {string}
*/
export function formatDuration(ms) {
const totalSeconds = Math.max(0, Math.floor(ms / 1000));
const minutes = Math.floor(totalSeconds / 60);
const seconds = String(totalSeconds % 60).padStart(2, '0');
return `${minutes}:${seconds}`;
}
/**
* Multiplikator mit deutschem Dezimalkomma („×1,5“).
* @param {number} multiplier
* @returns {string}
*/
export function formatMultiplier(multiplier) {
return `×${String(Math.round(multiplier * 10) / 10).replace('.', ',')}`;
}
/**
* Abklingzeit in Sekunden für das HUD („12“, „0,8“).
* @param {number} ms
* @returns {string}
*/
export function formatCooldown(ms) {
if (ms <= 0) return '';
const seconds = ms / 1000;
return seconds >= 1 ? String(Math.ceil(seconds)) : seconds.toFixed(1).replace('.', ',');
}
/**
* Richtungsfolge als Pfeile („↑ → ↓ ↓“).
* @param {readonly string[]} sequence
* @param {string} [separator]
* @returns {string}
*/
export function formatSequence(sequence, separator = ' ') {
return sequence.map((dir) => DIRECTION_SYMBOLS[dir] ?? '?').join(separator);
}
/** Wie auf dem Server (app/validation.py): alles außer Buchstaben, Ziffern und wenigen Zeichen. */
const DISALLOWED_NAME_CHARS = /[^\p{L}\p{N}_ .\-'!?]/gu;
/** Steuer-, Format-, Surrogat- und private Zeichen (Unicode-Kategorie C). */
const CONTROL_CHARS = /\p{C}/gu;
/**
* Bereinigt einen Namen genauso wie das Backend, damit der Spieler vor dem Absenden
* sieht, was gespeichert wird. Der Server prüft trotzdem selbst.
* @param {string} raw
* @returns {string}
*/
export function sanitizeName(raw) {
return String(raw)
.normalize('NFKC')
.replace(CONTROL_CHARS, '')
.replace(DISALLOWED_NAME_CHARS, '')
.split(/\s+/u)
.filter(Boolean)
.join(' ');
}
/**
* Prüft einen Namen für das Highscore-Formular.
* @param {string} raw
* @returns {{ok: true, name: string} | {ok: false, error: string}}
*/
export function validateName(raw) {
const name = sanitizeName(raw);
if (!name) return { ok: false, error: 'Bitte einen Namen mit Buchstaben oder Ziffern eingeben.' };
if ([...name].length > HIGHSCORE.nameMaxLength) {
return { ok: false, error: `Höchstens ${HIGHSCORE.nameMaxLength} Zeichen.` };
}
return { ok: true, name };
}