LyssaGame/src/Game.js

410 lines
15 KiB
JavaScript
Raw Normal View History

2025-11-30 01:33:30 +00:00
import { Map } from './Map.js';
import { UI } from './UI.js';
import { Player } from './Entity.js';
import { Monster } from './Entity.js';
import { Item } from './Item.js';
export class Game {
constructor() {
this.canvas = document.getElementById('game-canvas');
this.ctx = this.canvas.getContext('2d');
this.ui = new UI(this.ctx, this); // Pass game instance to UI for callbacks
this.map = new Map(50, 50);
this.player = new Player(10, 10);
this.monsters = [];
this.items = [];
this.gameOver = false;
this.monsters = [];
this.items = [];
this.gameOver = false;
this.depth = 0; // Start in Town
this.maxDepth = 3;
this.tileSize = 32;
// Bind input
window.addEventListener('keydown', (e) => this.handleInput(e));
}
start() {
console.log("Game Started");
this.generateLevel();
this.ui.log("Welcome to the Town of Oakhaven!");
}
generateLevel() {
this.monsters = [];
this.items = [];
if (this.depth === 0) {
this.map.generateTown();
// Spawn player in center
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
if (this.map.rooms.length > 0) {
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);
}
}
this.spawnMonsters();
this.spawnItems();
this.spawnStairs();
this.render();
this.ui.updateStats(this.player, this.depth);
this.ui.updateInventory(this.player);
}
nextLevel() {
if (this.depth >= this.maxDepth) {
this.ui.log("You have reached the bottom! You Win!");
setTimeout(() => alert("You Win!"), 100);
return;
}
this.depth++;
this.ui.log(`You descend to level ${this.depth}...`);
this.generateLevel();
}
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;
this.map.tiles[sy][sx] = '>';
}
return;
}
// Spawn stairs in the last room
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] = '>';
}
}
spawnMonsters() {
if (this.depth === 0) {
// Spawn Peasants in Town
for (let i = 0; i < 10; i++) {
const mx = Math.floor(Math.random() * this.map.width);
const my = Math.floor(Math.random() * this.map.height);
if (this.map.isWalkable(mx, my)) {
const peasant = new Monster(mx, my, "Peasant", "p", "#00ffff", 1);
this.monsters.push(peasant);
}
}
return;
}
// Skip first room
for (let i = 1; i < this.map.rooms.length; i++) {
const room = this.map.rooms[i];
// Spawn 1-3 monsters per room
const count = Math.floor(Math.random() * 3) + 1;
for (let j = 0; j < count; j++) {
const mx = Math.floor(Math.random() * room.w) + room.x;
const my = Math.floor(Math.random() * room.h) + room.y;
const type = Math.random();
let monster;
// Difficulty scaling
if (this.depth === 1) {
if (type < 0.7) monster = new Monster(mx, my, "Rat", "r", "#a0a0a0", 1);
else monster = new Monster(mx, my, "Kobold", "k", "#00ff00", 2);
} else if (this.depth === 2) {
if (type < 0.4) monster = new Monster(mx, my, "Rat", "r", "#a0a0a0", 1);
else if (type < 0.8) monster = new Monster(mx, my, "Kobold", "k", "#00ff00", 2);
else monster = new Monster(mx, my, "Orc", "o", "#008000", 3);
} else {
if (type < 0.3) monster = new Monster(mx, my, "Kobold", "k", "#00ff00", 2);
else if (type < 0.7) monster = new Monster(mx, my, "Orc", "o", "#008000", 3);
else monster = new Monster(mx, my, "Ogre", "O", "#ff0000", 4);
}
this.monsters.push(monster);
}
}
// Spawn Boss on final level
if (this.depth === this.maxDepth) {
const room = this.map.rooms[this.map.rooms.length - 1]; // Last room
const mx = Math.floor(Math.random() * room.w) + room.x;
const my = Math.floor(Math.random() * room.h) + room.y;
// Boss: Level 10, High HP
const boss = new Monster(mx, my, "The Dungeon Lord", "D", "#ff00ff", 10);
boss.hp = 100;
boss.maxHp = 100;
this.monsters.push(boss);
}
}
spawnItems() {
if (this.depth === 0) return; // No items in town
// Spawn items in random rooms
for (let i = 0; i < this.map.rooms.length; i++) {
const room = this.map.rooms[i];
if (Math.random() < 0.5) {
const ix = Math.floor(Math.random() * room.w) + room.x;
const iy = Math.floor(Math.random() * room.h) + room.y;
// Random item
const type = Math.random();
let item;
// Better loot deeper
let tier = this.depth;
if (Math.random() < 0.2) tier++; // Chance for higher tier
if (type < 0.3) item = new Item("Potion", "potion", 1, { heal: 5 });
else if (type < 0.5) {
if (tier <= 1) item = new Item("Dagger", "weapon", 1, { damage: 4 });
else if (tier === 2) item = new Item("Short Sword", "weapon", 2, { damage: 6 });
else item = new Item("Long Sword", "weapon", 3, { damage: 8 });
} else if (type < 0.7) {
if (tier <= 1) item = new Item("Buckler", "shield", 1, { defense: 1 });
else if (tier === 2) item = new Item("Wooden Shield", "shield", 2, { defense: 2 });
else item = new Item("Kite Shield", "shield", 3, { defense: 3 });
} else {
if (tier >= 3) item = new Item("Great Axe", "weapon", 4, { damage: 12 });
else item = new Item("Short Sword", "weapon", 2, { damage: 6 });
}
item.x = ix;
item.y = iy;
this.items.push(item);
}
}
}
handleInput(e) {
if (this.gameOver) return;
let dx = 0;
let dy = 0;
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 '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 {
this.ui.log("There are no stairs here.");
}
break;
case 'g': this.pickupItem(); break;
}
if (dx !== 0 || dy !== 0 || e.key === '.' || e.key === 'NumPad5') {
this.movePlayer(dx, dy);
this.render();
}
}
movePlayer(dx, dy) {
const newX = this.player.x + dx;
const newY = this.player.y + dy;
// Check for monster
const targetMonster = this.monsters.find(m => m.x === newX && m.y === newY);
if (targetMonster) {
this.attack(this.player, targetMonster);
} else if (this.map.isWalkable(newX, newY)) {
this.player.x = newX;
this.player.y = newY;
// Check for item
const item = this.items.find(i => i.x === newX && i.y === newY);
if (item) {
this.ui.log(`You see a ${item.name}. (Press 'g' to get)`);
}
} else {
if (dx !== 0 || dy !== 0) this.ui.log("Blocked!");
}
// Monsters turn
this.updateMonsters();
}
pickupItem() {
const itemIndex = this.items.findIndex(i => i.x === this.player.x && i.y === this.player.y);
if (itemIndex !== -1) {
const item = this.items[itemIndex];
this.items.splice(itemIndex, 1);
this.player.inventory.push(item);
this.ui.log(`You picked up ${item.name}.`);
this.ui.updateInventory(this.player);
this.render();
} else {
this.ui.log("There is nothing here to pick up.");
}
}
useItem(index) {
const item = this.player.inventory[index];
if (!item) return;
if (item.type === 'weapon') {
this.player.equipment.weapon = item;
this.ui.log(`You equipped ${item.name}.`);
} else if (item.type === 'shield') {
this.player.equipment.shield = item;
this.ui.log(`You equipped ${item.name}.`);
} else if (item.type === 'potion') {
if (item.stats.heal) {
this.player.hp = Math.min(this.player.hp + item.stats.heal, this.player.maxHp);
this.ui.log(`You drank ${item.name} and recovered ${item.stats.heal} HP.`);
// Remove potion
this.player.inventory.splice(index, 1);
}
}
this.ui.updateInventory(this.player);
this.ui.updateStats(this.player, this.depth);
this.render(); // Re-render to show equipped status if we add that
}
attack(attacker, defender) {
// Peasants are invincible
if (defender.name === "Peasant") {
const quotes = [
"This is our town.",
"The Dungeon Lord is oppressing us! Please help!",
"You can get healing at the temple.",
"There are things to buy in the shops.",
"It's unpleasant for peasants at present.",
"Hello, hero!"
];
const quote = quotes[Math.floor(Math.random() * quotes.length)];
this.ui.showPopup(quote, 1000);
return;
}
let damage = 0;
if (attacker === this.player) {
damage = this.player.getDamage() + Math.floor(Math.random() * 2);
} else {
damage = 1 + Math.floor(Math.random() * 2); // Monster damage
}
// Apply defense
const defense = defender.getDefense ? defender.getDefense() : 0;
damage = Math.max(1, damage - defense); // Always take at least 1 damage
defender.hp -= damage;
this.ui.log(`${attacker.name} hits ${defender.name} for ${damage} damage.`);
if (defender.hp <= 0) {
this.ui.log(`${defender.name} dies!`);
if (defender === this.player) {
this.ui.log("GAME OVER");
this.gameOver = true;
setTimeout(() => alert("Game Over! Refresh to restart."), 100);
} else {
// Remove monster
this.monsters = this.monsters.filter(m => m !== defender);
// Gold Drop
const gold = defender.level * (Math.floor(Math.random() * 5) + 1);
this.player.gold += gold;
this.ui.log(`You kill the ${defender.name}! It drops ${gold} gold.`);
// Boss Drop
if (defender.name === "The Dungeon Lord") {
const map = new Item("Ancient Map", "map", 1, {});
map.x = defender.x;
map.y = defender.y;
this.items.push(map);
this.ui.log("The Dungeon Lord drops an Ancient Map!");
}
// Award XP
if (this.player.gainXp(defender.level * 5)) {
this.ui.log(`You reached level ${this.player.level}!`);
}
}
}
}
updateMonsters() {
for (const monster of this.monsters) {
if (monster.name === "Peasant") {
// Random walk (Slow: 10% chance to move)
if (Math.random() < 0.1) {
const dx = Math.floor(Math.random() * 3) - 1;
const dy = Math.floor(Math.random() * 3) - 1;
const newX = monster.x + dx;
const newY = monster.y + dy;
if (this.map.isWalkable(newX, newY) && !this.monsters.some(m => m.x === newX && m.y === newY) && (newX !== this.player.x || newY !== this.player.y)) {
monster.x = newX;
monster.y = newY;
}
}
continue;
}
// Simple chase logic
const dx = this.player.x - monster.x;
const dy = this.player.y - monster.y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 8) { // Aggro range
let moveX = 0;
let moveY = 0;
if (Math.abs(dx) > Math.abs(dy)) {
moveX = dx > 0 ? 1 : -1;
} else {
moveY = dy > 0 ? 1 : -1;
}
const newX = monster.x + moveX;
const newY = monster.y + moveY;
if (newX === this.player.x && newY === this.player.y) {
this.attack(monster, this.player);
} else if (this.map.isWalkable(newX, newY)) {
// Check if occupied by another monster
if (!this.monsters.some(m => m.x === newX && m.y === newY)) {
monster.x = newX;
monster.y = newY;
}
}
}
}
}
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.updateStats(this.player, this.depth);
}
}