- crossOrigin von Asset-Images entfernt (haeufige Ursache fuer "tainted canvas", wodurch der Szenen-Export fehlschlug -> kaputtes Bild) - toBlob mit Fallback auf toDataURL - Ladefehler werden jetzt sichtbar im UI angezeigt (statt nur Konsole) - Frontend mit Cache-Control: no-cache -> kein veraltetes JS/CSS nach git pull Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
108 lines
3.4 KiB
JavaScript
108 lines
3.4 KiB
JavaScript
// fuesse.sexy — "Where's the Feet"
|
|
// Express-Server: liefert das Frontend aus, scannt die Szenen-Ordner beim Start,
|
|
// generiert auf Anfrage eine zufaellige Szene und prueft Klick-Treffer serverseitig.
|
|
|
|
import express from 'express';
|
|
import { dirname, join } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { existsSync } from 'node:fs';
|
|
|
|
import { scanLibrary, scanScenes } from './sceneScanner.js';
|
|
import { generateScene } from './sceneGenerator.js';
|
|
import { getScene } from './sceneStore.js';
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
const ROOT = join(__dirname, '..');
|
|
const SCENES_DIR = join(ROOT, 'scenes');
|
|
const LIBRARY_DIR = join(ROOT, 'library');
|
|
const PUBLIC_DIR = join(ROOT, 'public');
|
|
const PANZOOM_DIST = join(ROOT, 'node_modules', 'panzoom', 'dist');
|
|
|
|
const PORT = process.env.PORT || 3000;
|
|
|
|
// Wie oft die normale Library-Szene im Vergleich zu einem Spezial-Level drankommt.
|
|
const LIBRARY_WEIGHT = 3;
|
|
|
|
// Beim Start: Library (Normalfall) + Spezial-Level (Ausnahmen) einlesen und zu einem
|
|
// gewichteten Zufalls-Pool zusammenbauen.
|
|
const library = scanLibrary(LIBRARY_DIR);
|
|
const specials = scanScenes(SCENES_DIR);
|
|
|
|
const scenePool = [];
|
|
if (library) {
|
|
const libCategory = {
|
|
id: 'library',
|
|
name: '',
|
|
type: 'clipart',
|
|
isLibrary: true,
|
|
background: null, // pro Szene zufaellig
|
|
minSize: 68,
|
|
maxSize: 128,
|
|
cellFactor: 0.82,
|
|
props: library.props,
|
|
targets: library.feet,
|
|
};
|
|
for (let i = 0; i < LIBRARY_WEIGHT; i++) scenePool.push(libCategory);
|
|
}
|
|
for (const s of specials) scenePool.push(s);
|
|
|
|
if (scenePool.length === 0) {
|
|
console.warn('[server] Keine spielbaren Szenen — fuelle library/ oder scenes/.');
|
|
} else {
|
|
console.log(
|
|
`[server] bereit: Library=${library ? 'ja' : 'nein'}, Spezial-Level: ${specials.map((c) => c.id).join(', ') || '(keine)'}`
|
|
);
|
|
}
|
|
|
|
const app = express();
|
|
app.use(express.json());
|
|
|
|
// Statisches Frontend + Assets.
|
|
// no-cache fuers Frontend (HTML/JS/CSS): Browser/Proxy muss revalidieren -> nie altes
|
|
// Skript nach einem Update (haeufige Ursache fuer "geht gar nix" nach git pull).
|
|
app.use(
|
|
express.static(PUBLIC_DIR, {
|
|
setHeaders: (res) => res.setHeader('Cache-Control', 'no-cache'),
|
|
})
|
|
);
|
|
app.use('/scenes', express.static(SCENES_DIR));
|
|
app.use('/library', express.static(LIBRARY_DIR));
|
|
if (existsSync(PANZOOM_DIST)) {
|
|
app.use('/vendor', express.static(PANZOOM_DIST));
|
|
}
|
|
|
|
// Eine zufaellige, frisch gemischte Szene liefern (meist Library, selten Spezial-Level).
|
|
app.get('/api/scene', (req, res) => {
|
|
if (scenePool.length === 0) {
|
|
return res.status(503).json({ error: 'Noch keine Szenen vorhanden.' });
|
|
}
|
|
const cat = scenePool[Math.floor(Math.random() * scenePool.length)];
|
|
const payload = generateScene(cat);
|
|
res.json(payload);
|
|
});
|
|
|
|
// Klick pruefen: liegt (x, y) in einer der Ziel-Trefferzonen dieser Szene?
|
|
app.post('/api/scene/:sceneId/guess', (req, res) => {
|
|
const entry = getScene(req.params.sceneId);
|
|
if (!entry) {
|
|
return res.status(404).json({ error: 'Szene unbekannt oder abgelaufen.' });
|
|
}
|
|
const { x, y } = req.body || {};
|
|
if (typeof x !== 'number' || typeof y !== 'number') {
|
|
return res.status(400).json({ error: 'x und y muessen Zahlen sein.' });
|
|
}
|
|
|
|
let hit = null;
|
|
for (const t of entry.targets) {
|
|
if (Math.hypot(t.cx - x, t.cy - y) <= t.radius) {
|
|
hit = t.id;
|
|
break;
|
|
}
|
|
}
|
|
res.json({ hit });
|
|
});
|
|
|
|
app.listen(PORT, () => {
|
|
console.log(`[server] fuesse.sexy laeuft auf http://localhost:${PORT}`);
|
|
});
|