const BOARD_SIZE = 10; const state = { fleetDef: [], selectedShip: null, orientation: "h", status: "placing", }; const el = (id) => document.getElementById(id); const CAP_COLORS = ["#c0392b", "#2980b9", "#27ae60", "#8e44ad", "#d35400", "#16a085", "#f1c40f", "#c2185b"]; function capColorFor(row, col) { return CAP_COLORS[(row * 7 + col * 3) % CAP_COLORS.length]; } async function api(path, options) { const res = await fetch(path, { method: options?.method || "GET", headers: { "Content-Type": "application/json" }, body: options?.body ? JSON.stringify(options.body) : undefined, }); const data = await res.json(); if (!res.ok) { setMessage(data.error || "Fehler", true); throw new Error(data.error || "Request failed"); } return data; } function setMessage(text, isError = false) { const m = el("message"); m.textContent = text; m.style.color = isError ? "#ff8b8b" : ""; } function wapplerSprite(hat) { const wrap = document.createElement("div"); wrap.className = "wappler"; wrap.style.setProperty("--hat", hat); wrap.innerHTML = '
' + '
'; return wrap; } function buildBoard(container, size, onClick) { container.innerHTML = ""; const cells = []; for (let r = 0; r < size; r++) { const rowCells = []; for (let c = 0; c < size; c++) { const cell = document.createElement("div"); cell.className = "cell"; cell.dataset.row = r; cell.dataset.col = c; if (onClick) cell.addEventListener("click", () => onClick(r, c, cell)); container.appendChild(cell); rowCells.push(cell); } cells.push(rowCells); } return cells; } // ----- Placement screen ----- let placementCells = null; function renderFleetPicker(data) { const picker = el("fleet-picker"); picker.innerHTML = ""; const placedIds = new Set(); for (const row of data.player_board) { for (const cell of row) if (cell) placedIds.add(cell); } for (const ship of data.fleet_def) { const btn = document.createElement("button"); btn.type = "button"; const isPlaced = placedIds.has(ship.id); btn.textContent = `${ship.name} (${ship.size})`; btn.style.setProperty("--hat", ship.hat); btn.classList.toggle("placed", isPlaced); btn.classList.toggle("selected", state.selectedShip === ship.id); btn.disabled = isPlaced; btn.addEventListener("click", () => { state.selectedShip = ship.id; renderFleetPicker(data); }); picker.appendChild(btn); } } function renderPlacementBoard(data) { if (!placementCells) { placementCells = buildBoard(el("placement-board"), BOARD_SIZE, handlePlacementClick); } for (let r = 0; r < BOARD_SIZE; r++) { for (let c = 0; c < BOARD_SIZE; c++) { const cell = placementCells[r][c]; cell.className = "cell own"; cell.innerHTML = ""; const shipId = data.player_board[r][c]; if (shipId) { cell.appendChild(wapplerSprite(capColorFor(r, c))); } } } const placedCount = new Set(data.player_board.flat().filter(Boolean)).size; el("start-btn").disabled = placedCount !== data.fleet_def.length; } async function handlePlacementClick(r, c) { if (!state.selectedShip) { setMessage("Wähle zuerst einen Wappler aus.", true); return; } try { const data = await api("/api/place_ship", { method: "POST", body: { ship_id: state.selectedShip, row: r, col: c, orientation: state.orientation }, }); applyState(data); state.selectedShip = null; } catch (e) { /* message already shown */ } } // ----- Battle screen ----- let ownCells = null; let enemyCells = null; function renderBattle(data) { if (!ownCells) ownCells = buildBoard(el("own-board"), BOARD_SIZE, null); if (!enemyCells) enemyCells = buildBoard(el("enemy-board"), BOARD_SIZE, handleFireClick); for (let r = 0; r < BOARD_SIZE; r++) { for (let c = 0; c < BOARD_SIZE; c++) { const own = ownCells[r][c]; own.className = "cell own"; own.innerHTML = ""; const shipId = data.player_board[r][c]; const receivedShot = data.player_shots_received[r][c]; if (shipId) own.appendChild(wapplerSprite(capColorFor(r, c))); if (receivedShot) own.classList.add(receivedShot); const enemy = enemyCells[r][c]; enemy.className = "cell"; enemy.innerHTML = ""; const madeShot = data.player_shots_made[r][c]; if (madeShot) enemy.classList.add(madeShot); } } renderFleetStatus(el("own-fleet"), data.player_fleet); renderFleetStatus(el("enemy-fleet"), data.computer_fleet); } function renderFleetStatus(container, fleet) { container.innerHTML = ""; for (const ship of fleet) { const badge = document.createElement("span"); badge.className = "badge" + (ship.sunk ? " sunk" : ""); badge.textContent = `${ship.name}`; container.appendChild(badge); } } async function handleFireClick(r, c) { if (state.status !== "playing") return; try { const data = await api("/api/fire", { method: "POST", body: { row: r, col: c } }); applyState(data); } catch (e) { /* message already shown */ } } // ----- Shared ----- function applyState(data) { state.fleetDef = data.fleet_def; state.status = data.status; setMessage(data.message); const placementScreen = el("placement-screen"); const battleScreen = el("battle-screen"); if (data.status === "placing") { placementScreen.classList.remove("hidden"); battleScreen.classList.add("hidden"); renderFleetPicker(data); renderPlacementBoard(data); } else { placementScreen.classList.add("hidden"); battleScreen.classList.remove("hidden"); renderBattle(data); el("new-game-btn").classList.toggle( "hidden", !(data.status === "player_win" || data.status === "computer_win") ); } } function init() { el("rotate-btn").addEventListener("click", () => { state.orientation = state.orientation === "h" ? "v" : "h"; el("rotate-btn").textContent = "🔄 Ausrichtung: " + (state.orientation === "h" ? "horizontal" : "vertikal"); }); el("random-btn").addEventListener("click", async () => { const data = await api("/api/random_place", { method: "POST" }); applyState(data); }); el("reset-btn").addEventListener("click", async () => { const data = await api("/api/reset_placement", { method: "POST" }); applyState(data); }); el("start-btn").addEventListener("click", async () => { try { const data = await api("/api/start", { method: "POST" }); applyState(data); } catch (e) { /* message already shown */ } }); el("new-game-btn").addEventListener("click", async () => { const data = await api("/api/new_game", { method: "POST" }); applyState(data); }); api("/api/state").then(applyState); } init();