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
+127
View File
@@ -0,0 +1,127 @@
/**
* Punkte, Serien und Multiplikator (ohne Phaser).
*
* Regeln: Jeder Abschuss innerhalb von `streakWindowMs` nach dem letzten setzt die
* Serie fort. Alle `stepKills` Abschüsse steigt der Multiplikator um `stepValue`
* bis `maxMultiplier`. Ein Treffer am Präsidenten beendet die Serie – das belohnt
* sauberes Spielen statt blindem Draufhalten.
* @module logic/scoring
*/
import { SCORING } from '../config.js';
/**
* Multiplikator für eine Serienlänge.
* @param {number} streak Anzahl Abschüsse in Folge (inkl. des aktuellen).
* @param {typeof SCORING} [cfg]
* @returns {number}
*/
export function multiplierForStreak(streak, cfg = SCORING) {
if (streak <= 0) return 1;
return Math.min(cfg.maxMultiplier, 1 + Math.floor(streak / cfg.stepKills) * cfg.stepValue);
}
/**
* Bonus für eine überstandene Welle (ohne Multiplikator).
* @param {number} waveNumber
* @param {typeof SCORING} [cfg]
* @returns {number}
*/
export function waveBonus(waveNumber, cfg = SCORING) {
return waveNumber * cfg.waveBonusPerWave;
}
/**
* Führt Punktestand, Serie und Statistik einer Spielrunde.
*/
export class ScoreKeeper {
/** @param {typeof SCORING} [cfg] */
constructor(cfg = SCORING) {
this.cfg = cfg;
this.score = 0;
this.kills = 0;
this.streak = 0;
this.bestStreak = 0;
this.lastKillAt = -Infinity;
}
/**
* Läuft die Serie zum Zeitpunkt `now` noch?
* @param {number} now Spielzeit in ms.
* @returns {boolean}
*/
isStreakActive(now) {
return this.streak > 0 && now - this.lastKillAt <= this.cfg.streakWindowMs;
}
/**
* Aktueller Multiplikator (1, wenn die Serie abgelaufen ist).
* @param {number} now
* @returns {number}
*/
multiplierAt(now) {
return this.isStreakActive(now) ? multiplierForStreak(this.streak, this.cfg) : 1;
}
/**
* Verbleibender Anteil des Serienfensters (1 = gerade getroffen, 0 = abgelaufen).
* @param {number} now
* @returns {number}
*/
streakTimeLeft(now) {
if (!this.isStreakActive(now)) return 0;
return 1 - (now - this.lastKillAt) / this.cfg.streakWindowMs;
}
/**
* Verbucht einen Abschuss.
* @param {number} basePoints Punkte des Gegnertyps.
* @param {number} now Spielzeit in ms.
* @returns {{awarded: number, multiplier: number, streak: number}}
*/
registerKill(basePoints, now) {
if (!this.isStreakActive(now)) {
this.streak = 0;
}
this.streak += 1;
this.kills += 1;
this.bestStreak = Math.max(this.bestStreak, this.streak);
this.lastKillAt = now;
const multiplier = multiplierForStreak(this.streak, this.cfg);
const awarded = Math.round(basePoints * multiplier);
this.score += awarded;
return { awarded, multiplier, streak: this.streak };
}
/** Beendet die laufende Serie (z. B. wenn der Präsident getroffen wird). */
breakStreak() {
this.streak = 0;
}
/**
* Fügt Bonuspunkte ohne Multiplikator hinzu.
* @param {number} points
* @returns {number} Tatsächlich gutgeschriebene Punkte.
*/
addBonus(points) {
const awarded = Math.max(0, Math.round(points));
this.score += awarded;
return awarded;
}
/**
* Momentaufnahme für HUD und Game-Over-Bildschirm.
* @param {number} now
*/
snapshot(now) {
const active = this.isStreakActive(now);
return {
score: this.score,
kills: this.kills,
streak: active ? this.streak : 0,
multiplier: this.multiplierAt(now),
streakTimeLeft: this.streakTimeLeft(now),
bestStreak: this.bestStreak,
};
}
}