Objekt-Anzahl stufenweise, Ban-CLI, Live-Abbruch bei privat
- Objekt-Anzahl waechst ab STEP_START zusaetzlich schrittweise (Deckel 100), Mindestgroesse leicht gesenkt fuer dichte Felder - Ban per CLI: ban/unban/bans (namensbasiert), gebannte Namen koennen keinen Score eintragen (/api/score -> 403) und werden beim Bannen aus der Rangliste entfernt - Privat-Modus bricht laufende Spiele ab: Client pollt /api/status und laedt neu, sobald Spielen auf dem Pfad nicht mehr erlaubt ist (geheimer Pfad wird nicht verraten) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -17,6 +17,7 @@ let PROPS = []; // [{id,name,url}]
|
||||
let state = null;
|
||||
let rafId = null;
|
||||
let lastT = 0;
|
||||
let modePollId = null;
|
||||
|
||||
// ---- Utils ----
|
||||
const rand = (a, b) => a + Math.random() * (b - a);
|
||||
@@ -31,9 +32,24 @@ function shuffle(a) {
|
||||
return a;
|
||||
}
|
||||
|
||||
// Objekt-Anzahl: erst linear, ab STEP_START zusaetzlich SCHRITTWEISE mehr (mit Deckel).
|
||||
// Alle Werte hier leicht justierbar.
|
||||
const COUNT_BASE = 10; // Startanzahl
|
||||
const COUNT_PER_SCORE = 1.5; // frühes lineares Wachstum
|
||||
const STEP_START = 15; // ab diesem Score kommen Stufen dazu
|
||||
const STEP_EVERY = 3; // alle N Punkte eine Stufe
|
||||
const STEP_ADD = 5; // +M Objekte pro Stufe
|
||||
const COUNT_CAP = 100; // harte Obergrenze (Performance/Spielbarkeit)
|
||||
|
||||
function objectCount(score) {
|
||||
let count = COUNT_BASE + Math.floor(score * COUNT_PER_SCORE);
|
||||
if (score >= STEP_START) count += Math.floor((score - STEP_START) / STEP_EVERY) * STEP_ADD;
|
||||
return Math.min(count, COUNT_CAP);
|
||||
}
|
||||
|
||||
// Schwierigkeit aus dem Score ableiten
|
||||
function difficulty(score) {
|
||||
const count = Math.min(10 + Math.floor(score * 1.5), 46);
|
||||
const count = objectCount(score);
|
||||
if (score < 3) return { count, stage: 'grid', groups: 0, speed: 0 };
|
||||
if (score < 6) return { count, stage: 'scatter', groups: 0, speed: 0 };
|
||||
if (score < 11) return { count, stage: 'groups', groups: score < 9 ? 2 : 3, speed: Math.min(45 + (score - 6) * 10, 150) };
|
||||
@@ -94,6 +110,7 @@ async function startGame() {
|
||||
lastT = performance.now();
|
||||
cancelAnimationFrame(rafId);
|
||||
rafId = requestAnimationFrame(loop);
|
||||
startModeWatch(); // laufendes Spiel abbrechen, falls live auf "privat" geschaltet wird
|
||||
|
||||
// Anticheat-Session holen (der Server zaehlt die Treffer selbst)
|
||||
state.sessionId = await startSession();
|
||||
@@ -117,6 +134,34 @@ function beaconHit() {
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
// Live-Pruefung des Betriebsmodus: darf auf DIESEM Pfad gerade gespielt werden?
|
||||
async function checkPlayAllowed() {
|
||||
try {
|
||||
const slug = location.pathname.replace(/^\/+/, '');
|
||||
const res = await fetch('/api/status?path=' + encodeURIComponent(slug));
|
||||
if (!res.ok) return true; // im Zweifel weiterspielen lassen
|
||||
return (await res.json()).allowed !== false;
|
||||
} catch {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
function startModeWatch() {
|
||||
stopModeWatch();
|
||||
modePollId = setInterval(async () => {
|
||||
if (!state || !state.running) return;
|
||||
if (!(await checkPlayAllowed())) {
|
||||
stopModeWatch();
|
||||
location.reload(); // laufendes Spiel abbrechen -> Server-Gate entscheidet neu
|
||||
}
|
||||
}, 4000);
|
||||
}
|
||||
function stopModeWatch() {
|
||||
if (modePollId) {
|
||||
clearInterval(modePollId);
|
||||
modePollId = null;
|
||||
}
|
||||
}
|
||||
|
||||
function fieldSize() {
|
||||
const r = $('field').getBoundingClientRect();
|
||||
return { w: r.width, h: r.height };
|
||||
@@ -138,7 +183,7 @@ function buildRound() {
|
||||
$('wanted-img').src = target.url;
|
||||
|
||||
// Item-Groessenwahl: schrumpft mit steigender Anzahl, bleibt tippfreundlich
|
||||
const size = clamp(Math.sqrt((w * h) / (diff.count * 3.4)), 40, 66);
|
||||
const size = clamp(Math.sqrt((w * h) / (diff.count * 3.4)), 34, 66);
|
||||
|
||||
// Typen fuer die Items zusammenstellen: 1 Ziel + Ablenker
|
||||
const otherFeet = FEET.filter((f) => f.id !== target.id);
|
||||
@@ -393,6 +438,7 @@ async function gameOver() {
|
||||
if (!state) return;
|
||||
state.running = false;
|
||||
cancelAnimationFrame(rafId);
|
||||
stopModeWatch();
|
||||
|
||||
$('final-score').textContent = state.score;
|
||||
$('gameover').classList.remove('hidden');
|
||||
@@ -425,6 +471,7 @@ async function submitScore(name) {
|
||||
|
||||
// ---- Setup / Events ----
|
||||
function goHome() {
|
||||
stopModeWatch();
|
||||
$('gameover').classList.add('hidden');
|
||||
$('game').classList.add('hidden');
|
||||
$('home').classList.remove('hidden');
|
||||
|
||||
Reference in New Issue
Block a user