From 5a73afa22bf50c0c293ca6c7b65bb66cc22c4055 Mon Sep 17 00:00:00 2001 From: Jared Miller Date: Mon, 23 Feb 2026 17:40:16 -0500 Subject: [PATCH] Add render module for state-to-DOM projection --- src/render.js | 117 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 src/render.js diff --git a/src/render.js b/src/render.js new file mode 100644 index 0000000..b9ff079 --- /dev/null +++ b/src/render.js @@ -0,0 +1,117 @@ +import { getCard } from "./cards.js"; +import { resolveEnemyAction } from "./enemies.js"; + +export function render(state) { + renderEnemy(state); + renderInfoBar(state); + renderHand(state); + renderOverlay(state); +} + +function renderEnemy(state) { + const { enemy, combat } = state; + document.getElementById("enemy-name").textContent = enemy.name; + document.getElementById("enemy-hp").textContent = `${enemy.hp}/${enemy.maxHp}`; + document.getElementById("enemy-hp").style.width = + `${(enemy.hp / enemy.maxHp) * 100}%`; + document.getElementById("enemy-block").textContent = + enemy.block > 0 ? `block: ${enemy.block}` : ""; + + const statusEl = document.getElementById("enemy-status"); + const tokens = []; + if (enemy.vulnerable > 0) tokens.push(`vuln ${enemy.vulnerable}`); + if (enemy.weak > 0) tokens.push(`weak ${enemy.weak}`); + if (enemy.strength > 0) tokens.push(`str ${enemy.strength}`); + statusEl.textContent = tokens.join(" | "); + + const intentEl = document.getElementById("enemy-intent"); + if (combat.dieResult && combat.phase === "player_turn") { + const action = resolveEnemyAction( + enemy, + combat.dieResult, + enemy.trackPosition, + ); + if (action) { + intentEl.textContent = formatIntent(action, enemy); + } + } else { + intentEl.textContent = ""; + } +} + +function formatIntent(action, enemy) { + if (!action?.effects) return "?"; + const parts = action.effects.map((e) => { + if (e.type === "hit") { + const damage = e.value + (enemy.strength || 0); + return `attack ${damage}`; + } + if (e.type === "block") return `block ${e.value}`; + if (e.type === "strength") return `str +${e.value}`; + return e.type; + }); + return parts.join(", "); +} + +function renderInfoBar(state) { + const { player } = state; + document.getElementById("energy").textContent = + `energy: ${player.energy}/${player.maxEnergy}`; + document.getElementById("player-hp").textContent = + `${player.hp}/${player.maxHp}`; + document.getElementById("player-hp").style.width = + `${(player.hp / player.maxHp) * 100}%`; + document.getElementById("player-block").textContent = + player.block > 0 ? `block: ${player.block}` : ""; + document.getElementById("player-strength").textContent = + player.strength > 0 ? `str: ${player.strength}` : ""; + const playerStatus = document.getElementById("player-status"); + if (playerStatus) { + const tokens = []; + if (player.vulnerable > 0) tokens.push(`vuln ${player.vulnerable}`); + if (player.weak > 0) tokens.push(`weak ${player.weak}`); + playerStatus.textContent = tokens.join(" | "); + } + document.getElementById("draw-count").textContent = player.drawPile.length; + document.getElementById("discard-count").textContent = + player.discardPile.length; +} + +function renderHand(state) { + const handEl = document.getElementById("hand"); + const { player, combat } = state; + + handEl.innerHTML = ""; + player.hand.forEach((cardId, index) => { + const card = getCard(cardId); + const img = document.createElement("img"); + img.src = card.image || ""; + img.alt = `${card.name} (${card.cost})`; + img.title = card.description; + img.className = "card"; + img.dataset.index = index; + + if (index === combat.selectedCard) { + img.classList.add("selected"); + } + if (player.energy < card.cost) { + img.classList.add("no-energy"); + } + + handEl.appendChild(img); + }); +} + +function renderOverlay(state) { + const overlay = document.getElementById("overlay"); + const result = state.combat.phase === "ended" ? state.combat.result : null; + if (result === "victory") { + overlay.hidden = false; + overlay.textContent = "victory"; + } else if (result === "defeat") { + overlay.hidden = false; + overlay.textContent = "defeat"; + } else { + overlay.hidden = true; + } +}