// 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 app.use(express.static(PUBLIC_DIR)); 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}`); });