main
John Kenyon 2025-12-24 17:29:57 -08:00
parent fbcc3a91d9
commit e082b12cc7
6 changed files with 515 additions and 70 deletions

View File

@ -48,6 +48,12 @@
<!-- Items will go here -->
</ul>
</div>
<div id="spells-panel" class="group-box">
<div class="group-box-label">Spells</div>
<ul id="spells-list">
<!-- Spells will go here -->
</ul>
</div>
</div>
</div>
<div id="message-log" class="inset-border">

View File

@ -21,8 +21,12 @@ export class Player extends Entity {
};
this.stats = {
str: 10,
dex: 10
dex: 10,
int: 10
};
this.mana = 10;
this.maxMana = 10;
this.spells = ['Magic Arrow', 'Fireball'];
this.level = 1;
this.xp = 0;
this.maxXp = 10;
@ -37,7 +41,10 @@ export class Player extends Entity {
this.maxXp = Math.floor(this.maxXp * 1.5);
this.maxHp += 5;
this.hp = this.maxHp;
this.stats.str += 2;
this.maxMana += 5;
this.mana = this.maxMana;
this.stats.str += 1;
this.stats.int += 1;
return true; // Leveled up
}
return false;
@ -74,3 +81,11 @@ export class Monster extends Entity {
return Math.floor(this.level / 2); // Slight natural armor for higher level monsters
}
}
export class Shopkeeper extends Entity {
constructor(x, y, name, shopName, inventory) {
super(x, y, name, '$', '#ffd700');
this.shopName = shopName;
this.inventory = inventory; // Array of { item: Item, price: number }
}
}

View File

@ -2,7 +2,7 @@ import { Map } from './Map.js';
import { UI } from './UI.js';
import { Player } from './Entity.js';
import { Monster } from './Entity.js';
import { Monster, Shopkeeper } from './Entity.js';
import { Item } from './Item.js';
export class Game {
@ -23,32 +23,52 @@ export class Game {
this.tileSize = 32;
this.gameState = 'PLAY'; // 'PLAY', 'SHOP', 'TARGETING'
this.currentShopkeeper = null;
this.shopSelection = 0;
this.targetingSpell = null;
this.projectiles = [];
// Bind input
window.addEventListener('keydown', (e) => this.handleInput(e));
}
start() {
console.log("Game Started");
this.generateLevel();
this.generateLevel('down');
this.ui.log("Welcome to the Town of Oakhaven!");
}
generateLevel() {
generateLevel(direction = 'down') {
this.monsters = [];
this.items = [];
if (this.depth === 0) {
this.map.generateTown();
// Spawn player in center
// Spawn player in center (default) or at stairs if coming up
if (direction === 'up') {
// Coming up from dungeon, spawn at dungeon entrance
this.player.x = 43;
this.player.y = 8;
} else {
this.player.x = Math.floor(this.map.width / 2);
this.player.y = Math.floor(this.map.height / 2);
}
} else {
this.map.generate();
// Spawn Player in first room
// Spawn Player
if (this.map.rooms.length > 0) {
if (direction === 'down') {
// Start at first room (Stairs Up location)
const startRoom = this.map.rooms[0];
this.player.x = Math.floor(startRoom.x + startRoom.w / 2);
this.player.y = Math.floor(startRoom.y + startRoom.h / 2);
} else {
// Start at last room (Stairs Down location)
const endRoom = this.map.rooms[this.map.rooms.length - 1];
this.player.x = Math.floor(endRoom.x + endRoom.w / 2);
this.player.y = Math.floor(endRoom.y + endRoom.h / 2);
}
}
}
@ -59,6 +79,7 @@ export class Game {
this.render();
this.ui.updateStats(this.player, this.depth);
this.ui.updateInventory(this.player);
this.ui.updateSpells(this.player);
}
nextLevel() {
@ -70,27 +91,49 @@ export class Game {
this.depth++;
this.ui.log(`You descend to level ${this.depth}...`);
this.generateLevel();
this.generateLevel('down');
}
prevLevel() {
if (this.depth === 0) {
this.ui.log("You are already in town.");
return;
}
this.depth--;
if (this.depth === 0) {
this.ui.log("You return to the Town of Oakhaven.");
} else {
this.ui.log(`You ascend to level ${this.depth}...`);
}
this.generateLevel('up');
}
spawnStairs() {
if (this.depth === 0) {
// Stairs in a random building or just random spot
if (this.map.rooms.length > 0) {
const room = this.map.rooms[0];
const sx = Math.floor(Math.random() * room.w) + room.x;
const sy = Math.floor(Math.random() * room.h) + room.y;
// Town: Stairs Down in the Dungeon Entrance building
const sx = 43; // Center X
const sy = 8; // Center Y
this.map.tiles[sy][sx] = '>';
}
return;
}
// Spawn stairs in the last room
// Dungeon Levels
if (this.map.rooms.length > 0) {
const room = this.map.rooms[this.map.rooms.length - 1];
const sx = Math.floor(Math.random() * room.w) + room.x;
const sy = Math.floor(Math.random() * room.h) + room.y;
this.map.tiles[sy][sx] = '>';
// Stairs Up (<) in the first room (where you start when going down)
const startRoom = this.map.rooms[0];
const ux = Math.floor(startRoom.x + startRoom.w / 2);
const uy = Math.floor(startRoom.y + startRoom.h / 2);
// Ensure we don't overwrite player if they are standing there (visual only, logic handles it)
this.map.tiles[uy][ux] = '<';
// Stairs Down (>) in the last room (unless max depth)
if (this.depth < this.maxDepth) {
const endRoom = this.map.rooms[this.map.rooms.length - 1];
const dx = Math.floor(Math.random() * endRoom.w) + endRoom.x;
const dy = Math.floor(Math.random() * endRoom.h) + endRoom.y;
this.map.tiles[dy][dx] = '>';
}
}
}
@ -105,6 +148,25 @@ export class Game {
this.monsters.push(peasant);
}
}
// Spawn Shopkeepers
// Weapon Shop (East)
const weaponShopInventory = [
{ item: new Item("Dagger", "weapon", 1, { damage: 4 }), price: 50 },
{ item: new Item("Short Sword", "weapon", 2, { damage: 6 }), price: 100 },
{ item: new Item("Buckler", "shield", 1, { defense: 1 }), price: 40 },
{ item: new Item("Wooden Shield", "shield", 2, { defense: 2 }), price: 80 }
];
const weaponSmith = new Shopkeeper(41, 18, "Smith", "Weapon Shop", weaponShopInventory);
this.monsters.push(weaponSmith);
// General Store (West)
const generalStoreInventory = [
{ item: new Item("Potion", "potion", 1, { heal: 5 }), price: 20 }
];
const merchant = new Shopkeeper(9, 18, "Merchant", "General Store", generalStoreInventory);
this.monsters.push(merchant);
return;
}
@ -193,33 +255,61 @@ export class Game {
handleInput(e) {
if (this.gameOver) return;
if (this.gameState === 'SHOP') {
this.handleShopInput(e);
return;
}
if (this.gameState === 'TARGETING') {
this.handleTargetingInput(e);
return;
}
let dx = 0;
let dy = 0;
let handled = false;
switch (e.key) {
case 'ArrowUp': dy = -1; break;
case 'ArrowDown': dy = 1; break;
case 'ArrowLeft': dx = -1; break;
case 'ArrowRight': dx = 1; break;
case 'NumPad8': dy = -1; break;
case 'NumPad2': dy = 1; break;
case 'NumPad4': dx = -1; break;
case 'NumPad6': dx = 1; break;
case 'NumPad7': dx = -1; dy = -1; break;
case 'NumPad9': dx = 1; dy = -1; break;
case 'NumPad1': dx = -1; dy = 1; break;
case 'NumPad3': dx = 1; dy = 1; break;
case '.': case 'NumPad5': break; // Wait
case 'ArrowUp': dy = -1; handled = true; break;
case 'ArrowDown': dy = 1; handled = true; break;
case 'ArrowLeft': dx = -1; handled = true; break;
case 'ArrowRight': dx = 1; handled = true; break;
case 'NumPad8': dy = -1; handled = true; break;
case 'NumPad2': dy = 1; handled = true; break;
case 'NumPad4': dx = -1; handled = true; break;
case 'NumPad6': dx = 1; handled = true; break;
case 'NumPad7': dx = -1; dy = -1; handled = true; break;
case 'NumPad9': dx = 1; dy = -1; handled = true; break;
case 'NumPad1': dx = -1; dy = 1; handled = true; break;
case 'NumPad3': dx = 1; dy = 1; handled = true; break;
case '.': case 'NumPad5': handled = true; break; // Wait
case 'Enter':
case '>':
console.log(`Trying to descend. Player at ${this.player.x},${this.player.y}. Tile: '${this.map.tiles[this.player.y][this.player.x]}'`);
if (this.map.tiles[this.player.y][this.player.x] === '>') {
this.nextLevel();
} else if (this.map.tiles[this.player.y][this.player.x] === '<') {
this.prevLevel();
} else {
this.ui.log("There are no stairs here.");
}
handled = true;
break;
case 'g': this.pickupItem(); break;
case '<':
case ',':
if (this.map.tiles[this.player.y][this.player.x] === '<') {
this.prevLevel();
} else {
this.ui.log("There are no stairs up here.");
}
handled = true;
break;
case 'g': this.pickupItem(); handled = true; break;
case '1': this.startTargeting('Magic Arrow'); handled = true; break;
case '2': this.startTargeting('Fireball'); handled = true; break;
}
if (handled) {
e.preventDefault();
}
if (dx !== 0 || dy !== 0 || e.key === '.' || e.key === 'NumPad5') {
@ -228,6 +318,64 @@ export class Game {
}
}
handleShopInput(e) {
e.preventDefault();
const inventory = this.currentShopkeeper.inventory;
switch (e.key) {
case 'ArrowUp':
case 'NumPad8':
this.shopSelection--;
if (this.shopSelection < 0) this.shopSelection = inventory.length - 1;
break;
case 'ArrowDown':
case 'NumPad2':
this.shopSelection++;
if (this.shopSelection >= inventory.length) this.shopSelection = 0;
break;
case 'Enter':
case ' ':
this.buyItem(inventory[this.shopSelection]);
break;
case 'Escape':
this.closeShop();
break;
}
this.render();
}
openShop(shopkeeper) {
this.gameState = 'SHOP';
this.currentShopkeeper = shopkeeper;
this.shopSelection = 0;
this.ui.log(`You talk to ${shopkeeper.name}.`);
this.render();
}
closeShop() {
this.gameState = 'PLAY';
this.currentShopkeeper = null;
this.render();
}
buyItem(entry) {
if (this.player.gold >= entry.price) {
this.player.gold -= entry.price;
// Clone item to avoid reference issues if buying multiple
const newItem = new Item(entry.item.name, entry.item.type, entry.item.level, entry.item.stats);
// Copy visual props
newItem.symbol = entry.item.symbol;
newItem.color = entry.item.color;
this.player.inventory.push(newItem);
this.ui.log(`You bought ${entry.item.name} for ${entry.price} gold.`);
this.ui.updateInventory(this.player);
this.ui.updateStats(this.player, this.depth);
} else {
this.ui.log("You don't have enough gold!");
}
}
movePlayer(dx, dy) {
const newX = this.player.x + dx;
const newY = this.player.y + dy;
@ -235,7 +383,11 @@ export class Game {
// Check for monster
const targetMonster = this.monsters.find(m => m.x === newX && m.y === newY);
if (targetMonster) {
if (targetMonster instanceof Shopkeeper) {
this.openShop(targetMonster);
} else {
this.attack(this.player, targetMonster);
}
} else if (this.map.isWalkable(newX, newY)) {
this.player.x = newX;
this.player.y = newY;
@ -246,13 +398,174 @@ export class Game {
this.ui.log(`You see a ${item.name}. (Press 'g' to get)`);
}
} else {
console.log(`Blocked at ${newX},${newY}. Tile: '${this.map.tiles[newY][newX]}'`);
if (dx !== 0 || dy !== 0) this.ui.log("Blocked!");
}
// Temple Healing (Town only)
if (this.depth === 0 && this.player.x === 25 && this.player.y === 12) {
if (this.player.hp < this.player.maxHp) {
this.player.hp = this.player.maxHp;
this.ui.log("You enter the temple and feel refreshed. HP fully restored!");
this.ui.updateStats(this.player, this.depth);
} else {
this.ui.log("You enter the temple. It is peaceful here.");
}
}
// Monsters turn
this.updateMonsters();
}
handleTargetingInput(e) {
e.preventDefault();
let dx = 0;
let dy = 0;
switch (e.key) {
case 'ArrowUp': case 'NumPad8': dy = -1; break;
case 'ArrowDown': case 'NumPad2': dy = 1; break;
case 'ArrowLeft': case 'NumPad4': dx = -1; break;
case 'ArrowRight': case 'NumPad6': dx = 1; break;
case 'NumPad7': dx = -1; dy = -1; break;
case 'NumPad9': dx = 1; dy = -1; break;
case 'NumPad1': dx = -1; dy = 1; break;
case 'NumPad3': dx = 1; dy = 1; break;
case 'Escape':
this.gameState = 'PLAY';
this.ui.log("Canceled.");
this.render();
return;
}
if (dx !== 0 || dy !== 0) {
this.castSpell(this.targetingSpell, dx, dy);
this.gameState = 'PLAY';
this.targetingSpell = null;
}
}
startTargeting(spellName) {
this.targetingSpell = spellName;
this.gameState = 'TARGETING';
this.ui.log(`Select direction for ${spellName}...`);
}
castSpell(spellName, dx, dy) {
let manaCost = 0;
let damage = 0;
let symbol = '*';
let color = '#ffffff';
if (spellName === 'Magic Arrow') {
manaCost = 2;
damage = Math.floor(this.player.stats.int / 2) + Math.floor(Math.random() * 4) + 1;
symbol = '*';
color = '#aaaaff';
} else if (spellName === 'Fireball') {
manaCost = 5;
damage = this.player.stats.int + Math.floor(Math.random() * 6) + 1;
symbol = 'o';
color = '#ff4400';
}
if (this.player.mana < manaCost) {
this.ui.log("Not enough mana!");
return;
}
this.player.mana -= manaCost;
this.ui.updateStats(this.player, this.depth);
// Create Projectile
const projectile = {
x: this.player.x,
y: this.player.y,
dx: dx,
dy: dy,
symbol: symbol,
color: color,
damage: damage,
name: spellName
};
this.projectiles.push(projectile);
this.animateProjectile(projectile);
}
animateProjectile(projectile) {
const interval = setInterval(() => {
const newX = projectile.x + projectile.dx;
const newY = projectile.y + projectile.dy;
// Check collision
if (!this.map.isWalkable(newX, newY)) {
clearInterval(interval);
this.removeProjectile(projectile);
this.ui.log(`${projectile.name} hits the wall.`);
this.render();
return;
}
const monster = this.monsters.find(m => m.x === newX && m.y === newY);
if (monster) {
clearInterval(interval);
this.removeProjectile(projectile);
// Shopkeepers are immune
if (monster instanceof Shopkeeper) {
this.ui.log(`${projectile.name} hits ${monster.name} but does nothing.`);
} else {
// Deal damage
monster.hp -= projectile.damage;
this.ui.log(`${projectile.name} hits ${monster.name} for ${projectile.damage} damage.`);
if (monster.hp <= 0) {
this.killMonster(monster);
}
}
this.render();
return;
}
projectile.x = newX;
projectile.y = newY;
this.render();
// Range limit (optional, but good for safety)
if (Math.abs(projectile.x - this.player.x) > 20 || Math.abs(projectile.y - this.player.y) > 20) {
clearInterval(interval);
this.removeProjectile(projectile);
this.render();
}
}, 50); // Speed of projectile
}
removeProjectile(projectile) {
const index = this.projectiles.indexOf(projectile);
if (index > -1) {
this.projectiles.splice(index, 1);
}
}
killMonster(monster) {
this.ui.log(`${monster.name} dies!`);
this.monsters = this.monsters.filter(m => m !== monster);
const gold = monster.level * (Math.floor(Math.random() * 5) + 1);
this.player.gold += gold;
this.ui.log(`You kill the ${monster.name}! It drops ${gold} gold.`);
if (monster.name === "The Dungeon Lord") {
const map = new Item("Ancient Map", "map", 1, {});
map.x = monster.x;
map.y = monster.y;
this.items.push(map);
this.ui.log("The Dungeon Lord drops an Ancient Map!");
}
if (this.player.gainXp(monster.level * 5)) {
this.ui.log(`You reached level ${this.player.level}!`);
}
}
pickupItem() {
const itemIndex = this.items.findIndex(i => i.x === this.player.x && i.y === this.player.y);
if (itemIndex !== -1) {
@ -292,6 +605,9 @@ export class Game {
}
attack(attacker, defender) {
// Shopkeepers are invincible/pacifist
if (defender instanceof Shopkeeper) return;
// Peasants are invincible
if (defender.name === "Peasant") {
const quotes = [
@ -355,6 +671,8 @@ export class Game {
updateMonsters() {
for (const monster of this.monsters) {
if (monster instanceof Shopkeeper) continue; // Shopkeepers don't move
if (monster.name === "Peasant") {
// Random walk (Slow: 10% chance to move)
if (Math.random() < 0.1) {
@ -403,7 +721,12 @@ export class Game {
render() {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.ui.renderMap(this.map, this.player, this.monsters, this.items, this.tileSize);
this.ui.renderMap(this.map, this.player, this.monsters, this.items, this.projectiles, this.tileSize);
if (this.gameState === 'SHOP') {
this.ui.drawShop(this.currentShopkeeper, this.player, this.shopSelection);
}
this.ui.updateStats(this.player, this.depth);
}
}

View File

@ -70,18 +70,9 @@ export class Map {
}
}
// Create buildings
const numBuildings = 5;
for (let i = 0; i < numBuildings; i++) {
const w = Math.floor(Math.random() * 6) + 5;
const h = Math.floor(Math.random() * 6) + 5;
const x = Math.floor(Math.random() * (this.width - w - 2)) + 1;
const y = Math.floor(Math.random() * (this.height - h - 2)) + 1;
// Check overlap (simple check against other buildings)
// For now, just place them. If they overlap, they merge.
// Draw Walls
// Helper to draw a building
const drawBuilding = (x, y, w, h, doorSide) => {
// Walls
for (let by = y; by < y + h; by++) {
for (let bx = x; bx < x + w; bx++) {
if (by === y || by === y + h - 1 || bx === x || bx === x + w - 1) {
@ -92,22 +83,37 @@ export class Map {
}
}
// Add Door (randomly on one side)
if (Math.random() < 0.5) {
// Horizontal wall door
const doorX = Math.floor(Math.random() * (w - 2)) + x + 1;
const doorY = Math.random() < 0.5 ? y : y + h - 1;
this.tiles[doorY][doorX] = '.';
} else {
// Vertical wall door
const doorY = Math.floor(Math.random() * (h - 2)) + y + 1;
const doorX = Math.random() < 0.5 ? x : x + w - 1;
this.tiles[doorY][doorX] = '.';
// Door
if (doorSide === 'bottom') {
this.tiles[y + h - 1][Math.floor(x + w / 2)] = '.';
} else if (doorSide === 'top') {
this.tiles[y][Math.floor(x + w / 2)] = '.';
} else if (doorSide === 'left') {
this.tiles[Math.floor(y + h / 2)][x] = '.';
} else if (doorSide === 'right') {
this.tiles[Math.floor(y + h / 2)][x + w - 1] = '.';
}
// Add to rooms list so we can spawn things inside if we want
// Add to rooms for reference
this.rooms.push({ x, y, w, h });
}
};
// 1. Temple (North Center)
drawBuilding(20, 5, 10, 8, 'bottom');
// 2. General Store (West)
drawBuilding(5, 15, 8, 6, 'right');
// 3. Weapon Shop (East)
drawBuilding(37, 15, 8, 6, 'left');
// 4. Dungeon Entrance (North East)
drawBuilding(40, 5, 6, 6, 'bottom');
// 5. Houses (South)
drawBuilding(10, 30, 6, 5, 'top');
drawBuilding(22, 30, 6, 5, 'top');
drawBuilding(34, 30, 6, 5, 'top');
}
@ -133,6 +139,6 @@ export class Map {
isWalkable(x, y) {
if (x < 0 || x >= this.width || y < 0 || y >= this.height) return false;
return this.tiles[y][x] === '.' || this.tiles[y][x] === '>';
return this.tiles[y][x] === '.' || this.tiles[y][x] === '>' || this.tiles[y][x] === '<';
}
}

View File

@ -3,7 +3,9 @@ export class UI {
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;
}
@ -16,7 +18,7 @@ export class UI {
this.messageLog.scrollTop = this.messageLog.scrollHeight;
}
renderMap(map, player, monsters, items, tileSize) {
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);
@ -66,6 +68,18 @@ export class UI {
}
}
// 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;
@ -85,6 +99,9 @@ export class UI {
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) {
@ -103,6 +120,16 @@ export class UI {
});
}
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
@ -124,11 +151,17 @@ export class UI {
this.ctx.stroke();
} else if (tile === '>') {
// Stairs
// 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';
@ -186,4 +219,65 @@ export class UI {
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';
}
}

View File

@ -2,5 +2,6 @@ import { Game } from './Game.js';
window.addEventListener('DOMContentLoaded', () => {
const game = new Game();
window.game = game;
game.start();
});