95 lines
2.7 KiB
JavaScript
95 lines
2.7 KiB
JavaScript
/**
|
||
* 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 };
|
||
}
|