export class UI { constructor(ctx, game) { this.ctx = ctx; this.game = game; this.messageLog = document.getElementById('message-log'); this.messageLog = document.getElementById('message-log'); this.inventoryList = document.getElementById('inventory-list'); this.spellsList = document.getElementById('spells-list'); this.popup = null; this.popupTimeout = null; } log(message) { if (!this.messageLog) return; const div = document.createElement('div'); div.textContent = message; this.messageLog.appendChild(div); this.messageLog.scrollTop = this.messageLog.scrollHeight; } renderMap(map, player, monsters, items, projectiles, tileSize) { // Fill background this.ctx.fillStyle = '#000000'; this.ctx.fillRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height); this.ctx.font = `${tileSize}px monospace`; this.ctx.textBaseline = 'top'; // Calculate viewport (center on player) const viewWidth = Math.ceil(this.ctx.canvas.width / tileSize); const viewHeight = Math.ceil(this.ctx.canvas.height / tileSize); const startX = player.x - Math.floor(viewWidth / 2); const startY = player.y - Math.floor(viewHeight / 2); for (let y = 0; y < viewHeight; y++) { for (let x = 0; x < viewWidth; x++) { const mapX = startX + x; const mapY = startY + y; if (mapX >= 0 && mapX < map.width && mapY >= 0 && mapY < map.height) { const tile = map.tiles[mapY][mapX]; this.drawTile(x * tileSize, y * tileSize, tile, tileSize); } } } // Draw Items if (items) { for (const item of items) { const screenX = (item.x - startX) * tileSize; const screenY = (item.y - startY) * tileSize; if (screenX >= -tileSize && screenX < this.ctx.canvas.width && screenY >= -tileSize && screenY < this.ctx.canvas.height) { this.drawEntity(screenX, screenY, item, tileSize); } } } // Draw Monsters for (const monster of monsters) { const screenX = (monster.x - startX) * tileSize; const screenY = (monster.y - startY) * tileSize; // Only draw if visible if (screenX >= -tileSize && screenX < this.ctx.canvas.width && screenY >= -tileSize && screenY < this.ctx.canvas.height) { this.drawEntity(screenX, screenY, monster, tileSize); } } // Draw Projectiles if (projectiles) { for (const proj of projectiles) { const screenX = (proj.x - startX) * tileSize; const screenY = (proj.y - startY) * tileSize; if (screenX >= -tileSize && screenX < this.ctx.canvas.width && screenY >= -tileSize && screenY < this.ctx.canvas.height) { this.drawEntity(screenX, screenY, proj, tileSize); } } } // Draw Player const playerScreenX = (player.x - startX) * tileSize; const playerScreenY = (player.y - startY) * tileSize; this.drawEntity(playerScreenX, playerScreenY, player, tileSize); this.drawPopup(); } updateStats(player, depth) { document.getElementById('stat-hp').textContent = player.hp; document.getElementById('stat-max-hp').textContent = player.maxHp; document.getElementById('stat-str').textContent = player.stats.str; document.getElementById('stat-level').textContent = player.level; document.getElementById('stat-xp').textContent = player.xp; document.getElementById('stat-max-xp').textContent = player.maxXp; document.getElementById('stat-ac').textContent = player.getDefense(); document.getElementById('stat-floor').textContent = depth; document.getElementById('stat-gold').textContent = player.gold; document.getElementById('stat-gold').textContent = player.gold; document.getElementById('stat-mana').textContent = `${player.mana}/${player.maxMana}`; document.getElementById('stat-int').textContent = player.stats.int; } updateInventory(player) { this.inventoryList.innerHTML = ''; player.inventory.forEach((item, index) => { const li = document.createElement('li'); li.textContent = item.name; if (player.equipment.weapon === item || player.equipment.armor === item || player.equipment.shield === item) { li.textContent += " (E)"; } li.style.cursor = 'pointer'; li.onclick = () => { this.game.useItem(index); }; this.inventoryList.appendChild(li); }); } updateSpells(player) { if (!this.spellsList) return; this.spellsList.innerHTML = ''; player.spells.forEach((spell, index) => { const li = document.createElement('li'); li.textContent = `${index + 1}. ${spell}`; this.spellsList.appendChild(li); }); } drawTile(screenX, screenY, tile, size) { if (tile === '#') { this.ctx.fillStyle = '#808080'; // Gray wall this.ctx.fillRect(screenX, screenY, size, size); // Add a bevel effect for walls this.ctx.strokeStyle = '#ffffff'; this.ctx.lineWidth = 2; this.ctx.beginPath(); this.ctx.moveTo(screenX, screenY + size); this.ctx.lineTo(screenX, screenY); this.ctx.lineTo(screenX + size, screenY); this.ctx.stroke(); this.ctx.strokeStyle = '#404040'; this.ctx.beginPath(); this.ctx.moveTo(screenX + size, screenY); this.ctx.lineTo(screenX + size, screenY + size); this.ctx.lineTo(screenX, screenY + size); this.ctx.stroke(); } else if (tile === '>') { // Stairs Down this.ctx.fillStyle = '#202020'; this.ctx.fillRect(screenX, screenY, size, size); this.ctx.fillStyle = '#ffffff'; this.ctx.fillText('>', screenX + size / 4, screenY); } else if (tile === '<') { // Stairs Up this.ctx.fillStyle = '#202020'; this.ctx.fillRect(screenX, screenY, size, size); this.ctx.fillStyle = '#ffffff'; this.ctx.fillText('<', screenX + size / 4, screenY); } else { // Floor this.ctx.fillStyle = '#202020'; this.ctx.fillRect(screenX, screenY, size, size); this.ctx.fillStyle = '#404040'; this.ctx.fillText('.', screenX + size / 4, screenY); } } showPopup(text, duration) { this.popup = text; if (this.popupTimeout) clearTimeout(this.popupTimeout); this.game.render(); this.popupTimeout = setTimeout(() => { this.popup = null; this.game.render(); }, duration); } drawPopup() { if (!this.popup) return; const ctx = this.ctx; ctx.font = '16px "Courier New", monospace'; const textMetrics = ctx.measureText(this.popup); const textWidth = textMetrics.width; const width = textWidth + 40; // Add padding const height = 50; const x = (this.ctx.canvas.width - width) / 2; const y = (this.ctx.canvas.height - height) / 2 - 50; // Slightly above center // Background ctx.fillStyle = '#ffffff'; ctx.fillRect(x, y, width, height); // Border ctx.strokeStyle = '#000000'; ctx.lineWidth = 2; ctx.strokeRect(x, y, width, height); // Text ctx.fillStyle = '#000000'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText(this.popup, x + width / 2, y + height / 2); // Reset text align ctx.textAlign = 'start'; ctx.textBaseline = 'alphabetic'; } drawEntity(screenX, screenY, entity, size) { this.ctx.fillStyle = entity.color; this.ctx.fillText(entity.symbol, screenX + size / 4, screenY); } drawShop(shopkeeper, player, selectedIndex) { const ctx = this.ctx; const width = 400; const height = 300; const x = (ctx.canvas.width - width) / 2; const y = (ctx.canvas.height - height) / 2; // Background ctx.fillStyle = '#000000'; ctx.fillRect(x, y, width, height); ctx.strokeStyle = '#ffffff'; ctx.lineWidth = 2; ctx.strokeRect(x, y, width, height); // Title ctx.fillStyle = '#ffffff'; ctx.font = '20px monospace'; ctx.textAlign = 'center'; ctx.fillText(shopkeeper.shopName, x + width / 2, y + 30); // Items ctx.font = '16px monospace'; ctx.textAlign = 'left'; let itemY = y + 70; shopkeeper.inventory.forEach((entry, index) => { const item = entry.item; const price = entry.price; const isSelected = index === selectedIndex; if (isSelected) { ctx.fillStyle = '#333333'; ctx.fillRect(x + 20, itemY - 5, width - 40, 25); ctx.fillStyle = '#ffff00'; ctx.fillText('>', x + 25, itemY + 10); } else { ctx.fillStyle = '#aaaaaa'; } ctx.fillText(`${item.name}`, x + 50, itemY + 10); ctx.textAlign = 'right'; ctx.fillText(`${price}g`, x + width - 50, itemY + 10); ctx.textAlign = 'left'; itemY += 30; }); // Player Gold ctx.fillStyle = '#ffd700'; ctx.textAlign = 'center'; ctx.fillText(`Gold: ${player.gold}`, x + width / 2, y + height - 30); // Instructions ctx.fillStyle = '#888888'; ctx.font = '12px monospace'; ctx.fillText('Up/Down: Select | Enter: Buy | Esc: Exit', x + width / 2, y + height - 10); // Reset ctx.textAlign = 'start'; } }