feat: der letzte Auftrag - Spielapp mit API backend

- HTML/CSS frontend mitQuest-System und Spieler-Management
- Node.js/Express backend mit SQLite database
- REST API endpoints für game state, spieler, notes
- AI instructions documentation
- Würfelfunktionen und Tracker-UI
This commit is contained in:
Clawdia
2026-05-08 10:30:47 +02:00
commit a16758c7b5
991 changed files with 382116 additions and 0 deletions
+188
View File
@@ -0,0 +1,188 @@
const express = require('express');
const Database = require('better-sqlite3');
const path = require('path');
const app = express();
app.use(express.json());
app.use(express.static(path.join(__dirname, '../')));
const DB_PATH = path.join(__dirname, 'data.db');
function getDb() {
const db = new Database(DB_PATH);
db.pragma('journal_mode = WAL');
db.exec(`
CREATE TABLE IF NOT EXISTS state (
key TEXT PRIMARY KEY, value TEXT
);
CREATE TABLE IF NOT EXISTS spieler (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT, rolle TEXT, quest_id INTEGER,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS notes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
body TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS drinks_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
amount INTEGER, note TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
`);
return db;
}
let db = getDb();
function initState() {
const row = db.prepare("SELECT value FROM state WHERE key = 'game'").get();
if (!row) {
const s = {
gameName: '', gameStarted: false, drinks: 0,
items: [false,false,false,false,false,false,false,false],
quests: [false,false,false,false,false,false,false,false],
timestamp: Date.now()
};
db.prepare('INSERT OR REPLACE INTO state (key,value) VALUES (?,?)').run('game', JSON.stringify(s));
}
}
initState();
// GET state
app.get('/api/game', (req, res) => {
initState();
const sv = db.prepare("SELECT value FROM state WHERE key='game'").get();
res.json({
state: JSON.parse(sv.value),
spieler: db.prepare('SELECT * FROM spieler ORDER BY id').all(),
notes: db.prepare('SELECT * FROM notes ORDER BY created_at DESC').all(),
stats: db.prepare('SELECT SUM(amount) as total, COUNT(*) as entries FROM drinks_log').get() || {total:0,entries:0}
});
});
// Start
app.post('/api/game/start', (req, res) => {
initState();
const {name} = req.body;
const sv = db.prepare("SELECT value FROM state WHERE key='game'").get();
const s = JSON.parse(sv.value);
s.gameName = name || 'Der letzte Auftrag';
s.gameStarted = true; s.timestamp = Date.now();
db.prepare("UPDATE state SET value=? WHERE key='game'").run(JSON.stringify(s));
res.json({success:true, state:s});
});
// Drink
app.post('/api/game/drink', (req, res) => {
initState();
const amt = req.body.amount || 1;
const sv = db.prepare("SELECT value FROM state WHERE key='game'").get();
const s = JSON.parse(sv.value);
s.drinks = Math.max(0, s.drinks + amt);
s.timestamp = Date.now();
db.prepare("UPDATE state SET value=? WHERE key='game'").run(JSON.stringify(s));
db.prepare('INSERT INTO drinks_log (amount) VALUES (?)').run(amt);
res.json({success:true, drinks:s.drinks});
});
// Reset drink
app.post('/api/game/drink/reset', (req, res) => {
initState();
const sv = db.prepare("SELECT value FROM state WHERE key='game'").get();
const s = JSON.parse(sv.value);
s.drinks = 0; s.timestamp = Date.now();
db.prepare("UPDATE state SET value=? WHERE key='game'").run(JSON.stringify(s));
res.json({success:true, drinks:0});
});
// Toggle item
app.post('/api/game/toggle-item', (req, res) => {
initState();
const idx = parseInt(req.body.index);
if (idx<0||idx>7) return res.status(400).json({error:'Index 0-7'});
const sv = db.prepare("SELECT value FROM state WHERE key='game'").get();
const s = JSON.parse(sv.value);
s.items[idx] = !s.items[idx];
s.timestamp = Date.now();
db.prepare("UPDATE state SET value=? WHERE key='game'").run(JSON.stringify(s));
res.json({success:true, state:s, collected:s.items.filter(Boolean).length});
});
// Toggle quest
app.post('/api/game/toggle-quest', (req, res) => {
initState();
const idx = parseInt(req.body.index);
if (idx<0||idx>7) return res.status(400).json({error:'Index 0-7'});
const sv = db.prepare("SELECT value FROM state WHERE key='game'").get();
const s = JSON.parse(sv.value);
s.quests[idx] = !s.quests[idx];
s.timestamp = Date.now();
db.prepare("UPDATE state SET value=? WHERE key='game'").run(JSON.stringify(s));
res.json({success:true, state:s});
});
// Save full state
app.post('/api/game/save-state', (req, res) => {
initState();
const s = req.body; s.timestamp = Date.now();
db.prepare("UPDATE state SET value=? WHERE key='game'").run(JSON.stringify(s));
res.json({success:true, state:s});
});
// Reset all
app.post('/api/game/reset', (req, res) => {
initState();
const sv = db.prepare("SELECT value FROM state WHERE key='game'").get();
const s = JSON.parse(sv.value);
s.drinks=0; s.items=[false,false,false,false,false,false,false,false];
s.quests=[false,false,false,false,false,false,false,false];
s.gameStarted=false; s.gameName=''; s.timestamp=Date.now();
db.prepare("UPDATE state SET value=? WHERE key='game'").run(JSON.stringify(s));
db.prepare('DELETE FROM notes').run();
db.prepare('DELETE FROM drinks_log').run();
res.json({success:true, state:s});
});
// Spieler CRUD
app.get('/api/spieler', (req, res) => res.json(db.prepare('SELECT * FROM spieler ORDER BY id').all()));
app.post('/api/spieler', (req, res) => {
const {name, rolle, quest_id} = req.body;
const r = db.prepare('INSERT INTO spieler (name,rolle,quest_id) VALUES (?,?,?)').run(name, rolle, quest_id||null);
res.json({success:true, spieler:db.prepare('SELECT * FROM spieler WHERE id=?').get(r.lastInsertRowid)});
});
app.put('/api/spieler/:id', (req, res) => {
const {name, rolle, quest_id} = req.body;
db.prepare('UPDATE spieler SET name=COALESCE(?,name),rolle=COALESCE(?,rolle),quest_id=COALESCE(?,quest_id) WHERE id=?')
.run(name||null, rolle||null, quest_id||null, req.params.id);
res.json({success:true, spieler:db.prepare('SELECT * FROM spieler WHERE id=?').get(req.params.id)});
});
app.delete('/api/spieler/:id', (req, res) => {
db.prepare('DELETE FROM spieler WHERE id=?').run(req.params.id);
res.json({success:true});
});
// Notes
app.post('/api/notes', (req, res) => {
const {body} = req.body;
const r = db.prepare('INSERT INTO notes (body) VALUES (?)').run(body);
res.json({success:true, note:db.prepare('SELECT * FROM notes WHERE id=?').get(r.lastInsertRowid)});
});
// Stats
app.get('/api/game/stats', (req, res) => {
initState();
const sv = db.prepare("SELECT value FROM state WHERE key='game'").get();
const s = JSON.parse(sv.value);
res.json({
collected: s.items.filter(Boolean).length, total:8,
questsDone: s.quests.filter(Boolean).length, questsTotal:8,
drinks: s.drinks, gameName: s.gameName,
spielerCount: db.prepare('SELECT COUNT(*) as c FROM spieler').get().c,
spieler: db.prepare('SELECT * FROM spieler').all()
});
});
const PORT = process.env.PORT || 8082;
app.listen(PORT, '0.0.0.0', () => {
console.log(`🏰 Der letzte Auftrag server on port ${PORT}`);
});