middle step
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
// engine_ui.js - Reines DOM-Handling und visuelle Logik
|
||||
const EngineUI = {
|
||||
audioCtx: null,
|
||||
|
||||
init: function() {
|
||||
this.audioCtx = new (window.AudioContext || window.webkitAudioContext)();
|
||||
|
||||
const rawData = document.getElementById("story-data").textContent.trim();
|
||||
EngineLogic.init(rawData);
|
||||
|
||||
this.attachEvents();
|
||||
this.renderNode(EngineLogic.state.node);
|
||||
},
|
||||
|
||||
playBeep: function(freq = 440, type = "square", duration = 0.05) {
|
||||
try {
|
||||
if(!this.audioCtx) return;
|
||||
const osc = this.audioCtx.createOscillator();
|
||||
const gain = this.audioCtx.createGain();
|
||||
osc.type = type;
|
||||
osc.frequency.setValueAtTime(freq, this.audioCtx.currentTime);
|
||||
gain.gain.setValueAtTime(0.05, this.audioCtx.currentTime);
|
||||
gain.gain.exponentialRampToValueAtTime(0.001, this.audioCtx.currentTime + duration);
|
||||
osc.connect(gain); gain.connect(this.audioCtx.destination);
|
||||
osc.start(); osc.stop(this.audioCtx.currentTime + duration);
|
||||
} catch(e) {}
|
||||
},
|
||||
|
||||
renderHUD: function() {
|
||||
const hud = document.getElementById("hud-dynamic");
|
||||
hud.innerHTML = "";
|
||||
|
||||
hud.innerHTML += `<div class="stat-item">REBOOTS: <span style="color:var(--term-cyan)">#${String(EngineLogic.state.reboots).padStart(3, "0")}</span></div>`;
|
||||
hud.innerHTML += `<div class="stat-item">KNOTEN: <span style="color:var(--term-amber)">${EngineLogic.state.node}</span></div>`;
|
||||
|
||||
Object.keys(EngineLogic.state).forEach(key => {
|
||||
if(key !== "reboots" && key !== "node") {
|
||||
hud.innerHTML += `<div class="stat-item">${key.toUpperCase()}: <span style="color:var(--term-green)">${EngineLogic.state[key]}</span></div>`;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
renderNode: function(id) {
|
||||
const node = EngineLogic.processNodeEntry(id);
|
||||
if (!node) return;
|
||||
|
||||
this.playBeep(600, "sine", 0.04);
|
||||
if (node.isReboot) this.playBeep(200, "sawtooth", 0.3);
|
||||
|
||||
this.renderHUD();
|
||||
|
||||
document.getElementById("node-title").innerText = "=== " + node.title + " ===";
|
||||
document.getElementById("node-body").innerHTML = node.content;
|
||||
|
||||
const choicesBox = document.getElementById("choices-box");
|
||||
choicesBox.innerHTML = "";
|
||||
|
||||
if (node.choices && node.choices.length > 0) {
|
||||
let visibleIndex = 1;
|
||||
node.choices.forEach((choice) => {
|
||||
const parsed = EngineLogic.parseRequirement(choice.text);
|
||||
const btn = document.createElement("button");
|
||||
btn.className = "choice-btn";
|
||||
|
||||
if (parsed.meetsCondition) {
|
||||
btn.innerHTML = `<span class="choice-key">[${visibleIndex}]</span> ${parsed.displayText}`;
|
||||
btn.dataset.target = choice.targetId;
|
||||
btn.dataset.index = visibleIndex;
|
||||
btn.onclick = () => this.renderNode(choice.targetId);
|
||||
visibleIndex++;
|
||||
} else {
|
||||
btn.innerHTML = `<span class="choice-key">[LOCKED]</span> ${parsed.displayText} <span style="color:var(--term-warn); font-size:0.8rem; margin-left:auto;">${parsed.missingReq}</span>`;
|
||||
btn.disabled = true;
|
||||
}
|
||||
choicesBox.appendChild(btn);
|
||||
});
|
||||
} else {
|
||||
const btn = document.createElement("button");
|
||||
btn.className = "choice-btn";
|
||||
btn.style.borderColor = "var(--term-amber)";
|
||||
btn.style.color = "var(--term-amber)";
|
||||
btn.innerHTML = "<span class=\"choice-key\">[R]</span> Speicherabbild sichern und rebooten...";
|
||||
btn.onclick = () => this.triggerReboot();
|
||||
choicesBox.appendChild(btn);
|
||||
}
|
||||
document.getElementById("terminal-viewport").scrollTop = 0;
|
||||
},
|
||||
|
||||
triggerReboot: function() {
|
||||
const startId = EngineLogic.triggerReboot();
|
||||
if (startId) this.renderNode(startId);
|
||||
},
|
||||
|
||||
toggleGraph: function() {
|
||||
const modal = document.getElementById("graph-modal");
|
||||
if (modal.style.display === "flex") {
|
||||
modal.style.display = "none";
|
||||
} else {
|
||||
modal.style.display = "flex";
|
||||
this.drawGraphCanvas();
|
||||
}
|
||||
},
|
||||
|
||||
drawGraphCanvas: function() {
|
||||
const canvas = document.getElementById("graph-canvas");
|
||||
canvas.width = canvas.clientWidth; canvas.height = canvas.clientHeight;
|
||||
const ctx = canvas.getContext("2d");
|
||||
ctx.fillStyle = "#000"; ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
const keys = Object.keys(EngineLogic.storyData.nodes || {});
|
||||
if (keys.length === 0) return;
|
||||
|
||||
const total = keys.length; const radius = Math.min(canvas.width, canvas.height) * 0.38;
|
||||
const centerX = canvas.width / 2; const centerY = canvas.height / 2;
|
||||
const nodeRadius = 8; const positions = {};
|
||||
|
||||
keys.forEach((key, idx) => {
|
||||
const angle = (idx / total) * 2 * Math.PI;
|
||||
positions[key] = { x: centerX + radius * Math.cos(angle), y: centerY + radius * Math.sin(angle) };
|
||||
});
|
||||
|
||||
ctx.lineWidth = 1;
|
||||
keys.forEach(key => {
|
||||
const node = EngineLogic.storyData.nodes[key];
|
||||
if (node && node.choices) {
|
||||
node.choices.forEach(c => {
|
||||
if (positions[c.targetId]) {
|
||||
const start = positions[key], end = positions[c.targetId];
|
||||
ctx.beginPath(); ctx.strokeStyle = "rgba(51, 255, 102, 0.4)";
|
||||
ctx.moveTo(start.x, start.y); ctx.lineTo(end.x, end.y); ctx.stroke();
|
||||
const dx = end.x - start.x, dy = end.y - start.y, angle = Math.atan2(dy, dx), headlen = 12;
|
||||
const tipX = end.x - Math.cos(angle) * (nodeRadius + 2), tipY = end.y - Math.sin(angle) * (nodeRadius + 2);
|
||||
ctx.beginPath(); ctx.moveTo(tipX, tipY);
|
||||
ctx.lineTo(tipX - headlen * Math.cos(angle - Math.PI / 7), tipY - headlen * Math.sin(angle - Math.PI / 7));
|
||||
ctx.lineTo(tipX - headlen * Math.cos(angle + Math.PI / 7), tipY - headlen * Math.sin(angle + Math.PI / 7));
|
||||
ctx.closePath(); ctx.fillStyle = "rgba(51, 255, 102, 0.9)"; ctx.fill();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
keys.forEach(key => {
|
||||
const pos = positions[key];
|
||||
ctx.beginPath(); ctx.arc(pos.x, pos.y, nodeRadius, 0, 2 * Math.PI);
|
||||
if (key === EngineLogic.state.node) ctx.fillStyle = "#00ffff";
|
||||
else if (key === EngineLogic.storyData.startNodeId) ctx.fillStyle = "#ffb000";
|
||||
else ctx.fillStyle = "#116633";
|
||||
ctx.fill(); ctx.strokeStyle = "#33ff66"; ctx.stroke();
|
||||
ctx.fillStyle = "#fff"; ctx.font = "11px monospace";
|
||||
const textAngle = Math.atan2(pos.y - centerY, pos.x - centerX);
|
||||
ctx.fillText(key, pos.x + Math.cos(textAngle) * 15 - 10, pos.y + Math.sin(textAngle) * 15 + 4);
|
||||
});
|
||||
},
|
||||
|
||||
attachEvents: function() {
|
||||
window.addEventListener("keydown", (e) => {
|
||||
if (document.getElementById("graph-modal").style.display === "flex") {
|
||||
if (e.key.toLowerCase() === "g" || e.key === "Escape") this.toggleGraph();
|
||||
return;
|
||||
}
|
||||
if (e.key >= "1" && e.key <= "9") {
|
||||
const btn = document.querySelector(`.choice-btn[data-index="${e.key}"]`);
|
||||
if (btn && !btn.disabled) this.renderNode(btn.dataset.target);
|
||||
} else if (e.key.toLowerCase() === "r") {
|
||||
this.triggerReboot();
|
||||
} else if (e.key.toLowerCase() === "g") {
|
||||
this.toggleGraph();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
window.onload = () => EngineUI.init();
|
||||
Reference in New Issue
Block a user