import { WORLD, LEVELS, createLevelState, isBlocked, tryPickBottle, tryDropBottle, } from "./gameLogic.js"; import { AudioManager } from "./audio.js"; const canvas = document.getElementById("gameCanvas"); const ctx = canvas.getContext("2d"); const statusEl = document.getElementById("status"); const levelInfoEl = document.getElementById("levelInfo"); const progressInfoEl = document.getElementById("progressInfo"); const carryInfoEl = document.getElementById("carryInfo"); const restartBtn = document.getElementById("restartBtn"); const musicBtn = document.getElementById("musicBtn"); const tile = 28; const audio = new AudioManager(); let levelIndex = 0; let state = createLevelState(levelIndex); const moveState = { up: false, down: false, left: false, right: false, }; let moveCooldown = 0; function setStatus(text, type = "") { statusEl.textContent = text; statusEl.className = type; } function shapeLabel(shape) { return shape; } function getCarryingText() { if (!state.carrying) { return "Trage: nichts"; } const type = state.types.find((t) => t.id === state.carrying.typeId); return `Trage: ${type.label} (${shapeLabel(type.shape)})`; } function updateHud() { levelInfoEl.textContent = `Level ${levelIndex + 1}/4`; progressInfoEl.textContent = `${state.sortedCount}/${state.totalCount} sortiert`; carryInfoEl.textContent = getCarryingText(); } function restartLevel(message = "Level neu gestartet.") { state = createLevelState(levelIndex); setStatus(message, ""); updateHud(); } function nextLevel() { if (levelIndex >= LEVELS.length - 1) { setStatus("Geschafft! Alle 4 Level erfolgreich abgeschlossen.", "success"); return; } levelIndex += 1; state = createLevelState(levelIndex); setStatus(`Level ${levelIndex + 1} startet.`, "success"); updateHud(); } function handleAction() { audio.ensureContext(); if (!state.carrying) { const pick = tryPickBottle(state); if (pick.ok) { setStatus("Flasche aufgenommen."); audio.playPickup(); updateHud(); } return; } const drop = tryDropBottle(state); if (!drop.ok) { if (drop.reason === "falsch") { audio.playDropWrong(); restartLevel("Falsch einsortiert. Level wurde zurückgesetzt."); setStatus("Falsch einsortiert. Level wurde zurückgesetzt.", "error"); return; } return; } audio.playDropOk(); updateHud(); if (state.completed) { audio.playLevelDone(); setStatus(`Level ${levelIndex + 1} geschafft!`, "success"); setTimeout(nextLevel, 900); } else { setStatus("Korrekt einsortiert."); } } function tryMove(dx, dy) { const nx = state.player.x + dx; const ny = state.player.y + dy; if (isBlocked(nx, ny, state.walls)) { return; } state.player.x = nx; state.player.y = ny; } function processMovement(dt) { moveCooldown -= dt; if (moveCooldown > 0) { return; } if (moveState.up) { tryMove(0, -1); moveCooldown = 0.11; } else if (moveState.down) { tryMove(0, 1); moveCooldown = 0.11; } else if (moveState.left) { tryMove(-1, 0); moveCooldown = 0.11; } else if (moveState.right) { tryMove(1, 0); moveCooldown = 0.11; } } function drawBottle(x, y, type) { const px = x * tile; const py = y * tile; ctx.fillStyle = type.color; if (type.shape === "lang") { ctx.fillRect(px + 10, py + 4, 8, 18); ctx.fillRect(px + 11, py + 2, 6, 4); } else if (type.shape === "rund") { ctx.fillRect(px + 8, py + 6, 12, 14); ctx.fillRect(px + 10, py + 3, 8, 4); } else if (type.shape === "eckig") { ctx.fillRect(px + 7, py + 5, 14, 14); ctx.fillRect(px + 10, py + 3, 8, 4); } else { ctx.fillRect(px + 9, py + 8, 10, 11); ctx.fillRect(px + 11, py + 5, 6, 4); } } function drawCrate(crate) { const px = crate.x * tile; const py = crate.y * tile; ctx.fillStyle = "#8d5524"; ctx.fillRect(px + 2, py + 2, tile - 4, tile - 4); ctx.strokeStyle = "#4e2d12"; ctx.lineWidth = 2; ctx.strokeRect(px + 2, py + 2, tile - 4, tile - 4); const type = state.types.find((t) => t.id === crate.typeId); ctx.fillStyle = type.color; ctx.fillRect(px + 8, py + 8, 12, 12); } function render() { ctx.clearRect(0, 0, canvas.width, canvas.height); for (let y = 0; y < WORLD.rows; y += 1) { for (let x = 0; x < WORLD.cols; x += 1) { ctx.fillStyle = (x + y) % 2 === 0 ? "#355834" : "#2f4f2f"; ctx.fillRect(x * tile, y * tile, tile, tile); } } ctx.fillStyle = "#6f4e37"; state.walls.forEach((w) => { ctx.fillRect(w.x * tile, w.y * tile, tile, tile); }); state.crates.forEach(drawCrate); state.bottles.forEach((b) => { const type = state.types.find((t) => t.id === b.typeId); drawBottle(b.x, b.y, type); }); const p = state.player; const px = p.x * tile; const py = p.y * tile; ctx.fillStyle = "#4cc9f0"; ctx.fillRect(px + 7, py + 8, 14, 14); ctx.fillStyle = "#ffe0bd"; ctx.fillRect(px + 8, py + 3, 12, 8); if (state.carrying) { const type = state.types.find((t) => t.id === state.carrying.typeId); drawBottle(p.x, p.y - 1, type); } } let last = performance.now(); function loop(now) { const dt = Math.min(0.05, (now - last) / 1000); last = now; processMovement(dt); render(); requestAnimationFrame(loop); } function onKeyChange(event, isDown) { const key = event.key.toLowerCase(); if ( ["arrowup", "arrowdown", "arrowleft", "arrowright", "w", "a", "s", "d", " "].includes(key) ) { event.preventDefault(); } if (key === "arrowup" || key === "w") moveState.up = isDown; if (key === "arrowdown" || key === "s") moveState.down = isDown; if (key === "arrowleft" || key === "a") moveState.left = isDown; if (key === "arrowright" || key === "d") moveState.right = isDown; if (key === " " && isDown) handleAction(); } document.addEventListener("keydown", (e) => onKeyChange(e, true)); document.addEventListener("keyup", (e) => onKeyChange(e, false)); restartBtn.addEventListener("click", () => restartLevel("Level neu gestartet.")); musicBtn.addEventListener("click", () => { const on = audio.toggleMusic(); musicBtn.textContent = on ? "Musik: An" : "Musik: Aus"; setStatus(on ? "8-Bit-Musik aktiviert." : "Musik pausiert."); }); updateHud(); requestAnimationFrame(loop);