Severní Pól - Řídící Server
?
QR Kód s aktuální adresou
Expedice: Severní Pól
Hlavní řídící server a Lobby
Nastavení mise
Zvolte obtížnost:
Turista
Průzkumník
Amundsen
ZAHÁJIT HRU
Živá telemetrie
05:00
Integrity
100%
Altitude
100%
Fuel
100%
Position
0/100
Aktivní události (Krize)
Žádné probíhající krize...
Upravit obsah stránky
{% extends "templates/base.html" %} {% set game_name = "Severní Pól - Řídící Server" %} {% block content %} <div class="container py-4"> <div class="text-center mb-5"> <h1 class="text-primary"><i class="fa-solid fa-air-freshener"></i> Expedice: Severní Pól</h1> <p class="lead">Hlavní řídící server a Lobby</p> </div> <!-- Nastavení hry --> <div class="card mb-4" id="setupCard"> <div class="card-header bg-dark text-white"> <h4 class="mb-0"><i class="fa-solid fa-cogs"></i> Nastavení mise</h4> </div> <div class="card-body text-center"> <label class="form-label fw-bold">Zvolte obtížnost:</label> <div class="btn-group w-100 mb-4" role="group"> <input type="radio" class="btn-check" name="diff" id="diffEasy" value="easy" autocomplete="off" checked> <label class="btn btn-outline-success" for="diffEasy">Turista</label> <input type="radio" class="btn-check" name="diff" id="diffMed" value="medium" autocomplete="off"> <label class="btn btn-outline-warning" for="diffMed">Průzkumník</label> <input type="radio" class="btn-check" name="diff" id="diffHard" value="hard" autocomplete="off"> <label class="btn btn-outline-danger" for="diffHard">Amundsen</label> </div> <button class="btn btn-primary btn-lg w-100" onclick="startGame()"><i class="fa-solid fa-play"></i> ZAHÁJIT HRU</button> </div> </div> <!-- Dashboard běžící hry --> <div class="card border-primary" id="gameDashboard" style="display: none;"> <div class="card-header bg-primary text-white d-flex justify-content-between"> <h4 class="mb-0"><i class="fa-solid fa-satellite-dish"></i> Živá telemetrie</h4> <h4 class="mb-0" id="uiTimeLeft">05:00</h4> </div> <div class="card-body"> <div class="row text-center mb-3"> <div class="col"> <h5>Integrity</h5> <h2 id="uiIntegrity" class="text-success">100%</h2> </div> <div class="col"> <h5>Altitude</h5> <h2 id="uiAltitude" class="text-info">100%</h2> </div> <div class="col"> <h5>Fuel</h5> <h2 id="uiFuel" class="text-warning">100%</h2> </div> <div class="col"> <h5>Position</h5> <h2 id="uiPosition" class="text-primary">0/100</h2> </div> </div> <hr> <h5>Aktivní události (Krize)</h5> <ul id="uiEvents" class="list-group"> <li class="list-group-item text-muted">Žádné probíhající krize...</li> </ul> </div> <div class="card-footer text-center"> <button class="btn btn-danger" onclick="stopGame()"><i class="fa-solid fa-stop"></i> NOUZOVĚ UKONČIT</button> </div> </div> </div> <script> const forrestHubLib = ForrestHubLib.getInstance(false); // GM rozhraní nevyžaduje overlay samotné hry // Konfigurace obtížností z GDD const DIFFICULTY_CONFIG = { easy: { fuelDropSec: 10/60, eventInterval: 60, timeout: 25 }, medium: { fuelDropSec: 20/60, eventInterval: 40, timeout: 15 }, hard: { fuelDropSec: 30/60, eventInterval: 25, timeout: 10 } }; let gameLoopInterval = null; let eventTimer = 0; // Lokální kopie stavu pro server tick (minimalizace čtení z DB každou vteřinu) let state = { phase: 'lobby', timeLeft: 300, integrity: 100, altitude: 100, fuel: 100, position: 0, difficulty: 'easy' }; async function startGame() { // Inicializace proměnných state.difficulty = document.querySelector('input[name="diff"]:checked').value; state.phase = 'running'; state.timeLeft = 300; state.integrity = 100; state.altitude = 100; state.fuel = 100; state.position = 0; eventTimer = 0; // Uložení do DB await forrestHubLib.dbVarSetKey('gamePhase', state.phase); await forrestHubLib.dbVarSetKey('difficulty', state.difficulty); await forrestHubLib.dbVarSetKey('timeLeft', state.timeLeft); await forrestHubLib.dbVarSetKey('integrity', state.integrity); await forrestHubLib.dbVarSetKey('altitude', state.altitude); await forrestHubLib.dbVarSetKey('fuel', state.fuel); await forrestHubLib.dbVarSetKey('position', state.position); await forrestHubLib.dbArrayClearRecords('activeEvents'); // UI update document.getElementById('setupCard').style.display = 'none'; document.getElementById('gameDashboard').style.display = 'block'; forrestHubLib.uiShowAlert('success', 'Mise odstartována!'); // Spuštění serverové smyčky (1 tick za vteřinu) if(gameLoopInterval) clearInterval(gameLoopInterval); gameLoopInterval = setInterval(serverTick, 1000); } async function serverTick() { if (state.phase !== 'running' && state.phase !== 'endgame') return; const config = DIFFICULTY_CONFIG[state.difficulty]; // --- 1. Aplikace fyziky a času --- state.timeLeft -= 1; state.fuel -= config.fuelDropSec; eventTimer += 1; // --- 2. Kontrola Krizí (Timeouty a Generování) --- let eventsDict = await forrestHubLib.dbArrayFetchAllRecords('activeEvents') || {}; let activeEvents = Object.entries(eventsDict); // Kontrola selhání událostí (Timeouts) for (const [key, event] of activeEvents) { let elapsed = Math.floor(Date.now() / 1000) - event.startTime; if (elapsed >= event.timeout) { // Krize nevyřešena včas! Trest: -20% Integrity state.integrity -= 20; await forrestHubLib.dbArrayRemoveRecord('activeEvents', key); forrestHubLib.uiShowAlert('danger', `Krize ${event.id} nevyřešena! Integrity padá!`); } } // Generování nové krize if (state.phase === 'running' && eventTimer >= config.eventInterval) { generateRandomEvent(config.timeout); eventTimer = 0; } // --- 3. Posun lodi k cíli (zjednodušená automatika prozatím, ovlivní Kapitán) --- // V plné hře bude Position růst jen pokud Kapitán drží kurz a Strojník má tlak. // Prozatím přidáme simulovaný malý posun, aby šla hra testovat. if(state.altitude > 0 && state.fuel > 0) { state.position += 0.33; // Cca za 300s dosáhne 100. } // --- 4. Kontrola Win/Lose podmínek --- if (state.integrity <= 0 || state.altitude <= 0 || state.fuel <= 0 || state.timeLeft <= 0) { triggerGameEnd('lose'); return; } // Endgame fáze (Landing Sequence) if (state.phase === 'running' && state.timeLeft <= 30 && state.position >= 90) { triggerEndgame(); } // --- 5. Sync do DB a UI --- await syncStateToDB(); updateDashboardUI(eventsDict); } async function generateRandomEvent(timeoutSec) { const eventsList = [ { id: "Požár motoru", qr: "QR-01" }, { id: "Mlha na radaru", qr: "QR-02" }, { id: "Porucha kompasu", qr: "QR-03" } ]; const randomEvent = eventsList[Math.floor(Math.random() * eventsList.length)]; await forrestHubLib.dbArrayAddRecord('activeEvents', { id: randomEvent.id, qr: randomEvent.qr, timeout: timeoutSec, startTime: Math.floor(Date.now() / 1000) }); forrestHubLib.uiShowAlert('warning', `Nová událost: ${randomEvent.id}`); } async function triggerEndgame() { state.phase = 'endgame'; await forrestHubLib.dbArrayClearRecords('activeEvents'); // Zrušíme stávající krize await forrestHubLib.dbArrayAddRecord('activeEvents', { id: "CÍL NA DOHLED! Naskenujte všichni vlajku!", qr: "QR-FLAG", timeout: state.timeLeft, // Mají na to zbytek času startTime: Math.floor(Date.now() / 1000) }); await forrestHubLib.dbVarSetKey('gamePhase', 'endgame'); forrestHubLib.uiShowOverlay("CÍL NA DOHLED! Naskenujte QR-FLAG!", null, "info"); } async function triggerGameEnd(result) { clearInterval(gameLoopInterval); state.phase = result; await syncStateToDB(); await forrestHubLib.dbVarSetKey('gamePhase', result); let msg = result === 'win' ? "ÚSPĚCH! Severní pól dobyt!" : "PROHRA! Expedice selhala."; let alertType = result === 'win' ? "success" : "danger"; forrestHubLib.uiShowOverlay(msg, null, alertType, true); document.getElementById('setupCard').style.display = 'block'; document.getElementById('gameDashboard').style.display = 'none'; } async function stopGame() { triggerGameEnd('lose'); } async function syncStateToDB() { // Minimalizujeme desetinná místa pro ukládání a zobrazení await forrestHubLib.dbVarSetKey('timeLeft', Math.ceil(state.timeLeft)); await forrestHubLib.dbVarSetKey('integrity', Math.max(0, state.integrity).toFixed(1)); await forrestHubLib.dbVarSetKey('altitude', Math.max(0, state.altitude).toFixed(1)); await forrestHubLib.dbVarSetKey('fuel', Math.max(0, state.fuel).toFixed(1)); await forrestHubLib.dbVarSetKey('position', Math.min(100, state.position).toFixed(1)); } function updateDashboardUI(eventsDict) { let mins = Math.floor(state.timeLeft / 60); let secs = Math.ceil(state.timeLeft % 60); document.getElementById('uiTimeLeft').innerText = `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; document.getElementById('uiIntegrity').innerText = `${state.integrity.toFixed(0)}%`; document.getElementById('uiAltitude').innerText = `${state.altitude.toFixed(0)}%`; document.getElementById('uiFuel').innerText = `${state.fuel.toFixed(0)}%`; document.getElementById('uiPosition').innerText = `${state.position.toFixed(0)}/100`; const uiEvents = document.getElementById('uiEvents'); uiEvents.innerHTML = ''; const entries = Object.entries(eventsDict || {}); if (entries.length === 0) { uiEvents.innerHTML = '<li class="list-group-item text-muted">Žádné probíhající krize...</li>'; } else { entries.forEach(([key, ev]) => { let timeLeft = ev.timeout - (Math.floor(Date.now() / 1000) - ev.startTime); uiEvents.innerHTML += `<li class="list-group-item list-group-item-danger d-flex justify-content-between align-items-center"> ${ev.id} (Potřebuje: ${ev.qr}) <span class="badge bg-danger rounded-pill">${timeLeft}s</span> </li>`; }); } } </script> {% endblock %}