Files
Zauberwald/src/forest/scene.ts
T

132 lines
3.7 KiB
TypeScript
Raw Normal View History

import type { Progress, LevelStatus } from "../types";
import { levels } from "../game/levels";
import { getProgress, getForestElements } from "../storage/db";
let activeObjectUrls: string[] = [];
export async function renderForestScene(
db: IDBDatabase,
newElementId?: number,
): Promise<void> {
const grid = document.getElementById("forest-grid");
if (!grid) return;
// Revoke old object URLs to prevent memory leaks
for (const url of activeObjectUrls) {
URL.revokeObjectURL(url);
}
activeObjectUrls = [];
const elements = await getForestElements(db);
grid.innerHTML = "";
// Render up to 12 elements (4x3 grid)
const maxSlots = 12;
const toRender = elements.slice(0, maxSlots);
for (const element of toRender) {
const slot = document.createElement("div");
slot.className = "forest__element";
const img = document.createElement("img");
const objectUrl = URL.createObjectURL(element.imageBlob);
activeObjectUrls.push(objectUrl);
img.src = objectUrl;
img.alt = element.description;
// Random offset +-10-20px for natural placement
const offsetX =
(Math.random() > 0.5 ? 1 : -1) * (10 + Math.random() * 10);
const offsetY =
(Math.random() > 0.5 ? 1 : -1) * (10 + Math.random() * 10);
img.style.transform = `translate(${Math.round(offsetX)}px, ${Math.round(offsetY)}px)`;
// Animate newly added element
if (element.id === newElementId) {
slot.classList.add("forest__element--new");
}
slot.appendChild(img);
grid.appendChild(slot);
}
}
export function getLevelStatus(
levelNum: number,
progress: Progress,
): LevelStatus {
if (progress.completedLevels.includes(levelNum)) return "completed";
if (levelNum === progress.currentLevel) return "current";
return "locked";
}
export function renderLevelMap(
container: HTMLElement,
progress: Progress,
onLevelClick: (level: number) => void,
): void {
container.innerHTML = "";
levels.forEach((level) => {
const status = getLevelStatus(level.level, progress);
const card = document.createElement("button");
card.className = `level-card level-card--${status}`;
card.dataset.level = String(level.level);
const statusIcon =
status === "completed" ? "\u2713" : status === "current" ? "\u2192" : "\uD83D\uDD12";
const keysDisplay = level.newKeys
.filter((k) => k !== " ")
.map((k) => k.toUpperCase())
.join(" ");
card.innerHTML = `
<span class="level-card__number">Stufe ${level.level}</span>
<span class="level-card__keys">${keysDisplay}${level.newKeys.includes(" ") ? " + Leertaste" : ""}</span>
<span class="level-card__status">${statusIcon}</span>
`;
// Only current and completed levels are clickable
if (status !== "locked") {
card.addEventListener("click", () => onLevelClick(level.level));
} else {
card.disabled = true;
}
container.appendChild(card);
});
}
const MAX_LEVEL = 6;
export async function initForestScreen(
db: IDBDatabase,
onStartLesson: (level: number) => void,
): Promise<void> {
const mapContainer = document.getElementById("level-map")!;
const continueBtn = document.getElementById(
"continue-btn",
) as HTMLButtonElement;
const progress = await getProgress(db);
if (!progress) return;
renderLevelMap(mapContainer, progress, onStartLesson);
await renderForestScene(db);
const allComplete = levels.every((l) =>
progress.completedLevels.includes(l.level),
);
if (allComplete) {
continueBtn.textContent = "Alle Stufen geschafft!";
continueBtn.disabled = true;
continueBtn.classList.add("forest__continue-btn--done");
} else {
continueBtn.textContent = "Weiter ueben";
continueBtn.disabled = false;
continueBtn.classList.remove("forest__continue-btn--done");
continueBtn.onclick = () =>
onStartLesson(Math.min(progress.currentLevel, MAX_LEVEL));
}
}