diff --git a/src/game/__tests__/generator.test.ts b/src/game/__tests__/generator.test.ts new file mode 100644 index 0000000..7317b14 --- /dev/null +++ b/src/game/__tests__/generator.test.ts @@ -0,0 +1,136 @@ +import { describe, it, expect } from 'vitest'; +import { generateWorld } from '../generator'; +import { isWall, inBounds } from '../world'; + +describe('World Generator', () => { + describe('generateWorld', () => { + it('should generate a world with correct dimensions', () => { + const runState = { + stats: { maxHp: 20, hp: 20, attack: 5, defense: 2 }, + inventory: { gold: 0, items: [] } + }; + + const { world } = generateWorld(1, runState); + + expect(world.width).toBe(60); + expect(world.height).toBe(40); + expect(world.tiles.length).toBe(60 * 40); + }); + + it('should place player actor', () => { + const runState = { + stats: { maxHp: 20, hp: 20, attack: 5, defense: 2 }, + inventory: { gold: 0, items: [] } + }; + + const { world, playerId } = generateWorld(1, runState); + + expect(playerId).toBe(1); + const player = world.actors.get(playerId); + expect(player).toBeDefined(); + expect(player?.isPlayer).toBe(true); + expect(player?.stats).toEqual(runState.stats); + }); + + it('should create walkable rooms', () => { + const runState = { + stats: { maxHp: 20, hp: 20, attack: 5, defense: 2 }, + inventory: { gold: 0, items: [] } + }; + + const { world, playerId } = generateWorld(1, runState); + const player = world.actors.get(playerId)!; + + // Player should spawn in a walkable area + expect(isWall(world, player.pos.x, player.pos.y)).toBe(false); + }); + + it('should place exit in valid location', () => { + const runState = { + stats: { maxHp: 20, hp: 20, attack: 5, defense: 2 }, + inventory: { gold: 0, items: [] } + }; + + const { world } = generateWorld(1, runState); + + expect(inBounds(world, world.exit.x, world.exit.y)).toBe(true); + // Exit should be on a floor tile + expect(isWall(world, world.exit.x, world.exit.y)).toBe(false); + }); + + it('should create enemies', () => { + const runState = { + stats: { maxHp: 20, hp: 20, attack: 5, defense: 2 }, + inventory: { gold: 0, items: [] } + }; + + const { world, playerId } = generateWorld(1, runState); + + // Should have player + enemies + expect(world.actors.size).toBeGreaterThan(1); + + // All non-player actors should be enemies + const enemies = Array.from(world.actors.values()).filter(a => !a.isPlayer); + expect(enemies.length).toBeGreaterThan(0); + + // Enemies should have stats + enemies.forEach(enemy => { + expect(enemy.stats).toBeDefined(); + expect(enemy.stats!.hp).toBeGreaterThan(0); + expect(enemy.stats!.attack).toBeGreaterThan(0); + }); + }); + + it('should generate deterministic maps for same level', () => { + const runState = { + stats: { maxHp: 20, hp: 20, attack: 5, defense: 2 }, + inventory: { gold: 0, items: [] } + }; + + const { world: world1, playerId: player1 } = generateWorld(1, runState); + const { world: world2, playerId: player2 } = generateWorld(1, runState); + + // Same level should generate identical layouts + expect(world1.tiles).toEqual(world2.tiles); + expect(world1.exit).toEqual(world2.exit); + + const player1Pos = world1.actors.get(player1)!.pos; + const player2Pos = world2.actors.get(player2)!.pos; + expect(player1Pos).toEqual(player2Pos); + }); + + it('should generate different maps for different levels', () => { + const runState = { + stats: { maxHp: 20, hp: 20, attack: 5, defense: 2 }, + inventory: { gold: 0, items: [] } + }; + + const { world: world1 } = generateWorld(1, runState); + const { world: world2 } = generateWorld(2, runState); + + // Different levels should have different layouts + expect(world1.tiles).not.toEqual(world2.tiles); + }); + + it('should scale enemy difficulty with level', () => { + const runState = { + stats: { maxHp: 20, hp: 20, attack: 5, defense: 2 }, + inventory: { gold: 0, items: [] } + }; + + const { world: world1 } = generateWorld(1, runState); + const { world: world5 } = generateWorld(5, runState); + + const enemies1 = Array.from(world1.actors.values()).filter(a => !a.isPlayer); + const enemies5 = Array.from(world5.actors.values()).filter(a => !a.isPlayer); + + // Higher level should have more enemies + expect(enemies5.length).toBeGreaterThan(enemies1.length); + + // Higher level enemies should have higher stats + const avgHp1 = enemies1.reduce((sum, e) => sum + (e.stats?.hp || 0), 0) / enemies1.length; + const avgHp5 = enemies5.reduce((sum, e) => sum + (e.stats?.hp || 0), 0) / enemies5.length; + expect(avgHp5).toBeGreaterThan(avgHp1); + }); + }); +}); diff --git a/src/game/__tests__/simulation.test.ts b/src/game/__tests__/simulation.test.ts new file mode 100644 index 0000000..d0b7691 --- /dev/null +++ b/src/game/__tests__/simulation.test.ts @@ -0,0 +1,125 @@ +import { describe, it, expect } from 'vitest'; +import { applyAction } from '../simulation'; +import { type World, type Actor, type EntityId } from '../types'; + +describe('Combat Simulation', () => { + const createTestWorld = (actors: Map): World => ({ + width: 10, + height: 10, + tiles: new Array(100).fill(0), + actors, + exit: { x: 9, y: 9 } + }); + + describe('applyAction - attack', () => { + it('should deal damage when player attacks enemy', () => { + const actors = new Map(); + actors.set(1, { + id: 1, + isPlayer: true, + pos: { x: 3, y: 3 }, + speed: 100, + energy: 0, + stats: { maxHp: 20, hp: 20, attack: 5, defense: 2 } + }); + actors.set(2, { + id: 2, + isPlayer: false, + pos: { x: 4, y: 3 }, + speed: 100, + energy: 0, + stats: { maxHp: 10, hp: 10, attack: 3, defense: 1 } + }); + + const world = createTestWorld(actors); + const events = applyAction(world, 1, { type: 'attack', targetId: 2 }); + + const enemy = world.actors.get(2)!; + expect(enemy.hp).toBeLessThan(10); + + // Should have attack event + expect(events.some(e => e.type === 'attacked')).toBe(true); + }); + + it('should kill enemy when damage exceeds hp', () => { + const actors = new Map(); + actors.set(1, { + id: 1, + isPlayer: true, + pos: { x: 3, y: 3 }, + speed: 100, + energy: 0, + stats: { maxHp: 20, hp: 20, attack: 50, defense: 2 } + }); + actors.set(2, { + id: 2, + isPlayer: false, + pos: { x: 4, y: 3 }, + speed: 100, + energy: 0, + stats: { maxHp: 10, hp: 10, attack: 3, defense: 1 } + }); + + const world = createTestWorld(actors); + const events = applyAction(world, 1, { type: 'attack', targetId: 2 }); + + // Enemy should be removed from world + expect(world.actors.has(2)).toBe(false); + + // Should have killed event + expect(events.some(e => e.type === 'killed')).toBe(true); + }); + + it('should apply defense to reduce damage', () => { + const actors = new Map(); + actors.set(1, { + id: 1, + isPlayer: true, + pos: { x: 3, y: 3 }, + speed: 100, + energy: 0, + stats: { maxHp: 20, hp: 20, attack: 5, defense: 2 } + }); + actors.set(2, { + id: 2, + isPlayer: false, + pos: { x: 4, y: 3 }, + speed: 100, + energy: 0, + stats: { maxHp: 10, hp: 10, attack: 3, defense: 3 } + }); + + const world = createTestWorld(actors); + const events = applyAction(world, 1, { type: 'attack', targetId: 2 }); + + const enemy = world.actors.get(2)!; + const damage = 10 - enemy.stats!. hp; + + // Damage should be reduced by defense (5 attack - 3 defense = 2 damage) + expect(damage).toBe(2); + }); + }); + + describe('applyAction - move', () => { + it('should move actor to new position', () => { + const actors = new Map(); + actors.set(1, { + id: 1, + isPlayer: true, + pos: { x: 3, y: 3 }, + speed: 100, + energy: 0, + stats: { maxHp: 20, hp: 20, attack: 5, defense: 2 } + }); + + const world = createTestWorld(actors); + const events = applyAction(world, 1, { type: 'move', dx: 1, dy: 0 }); + + const player = world.actors.get(1)!; + expect(player.pos).toEqual({ x: 4, y: 3 }); + + // Should have moved event + expect(events.some(e => e.type === 'moved')).toBe(true); + }); + }); +}); diff --git a/src/game/__tests__/world.test.ts b/src/game/__tests__/world.test.ts new file mode 100644 index 0000000..c37c302 --- /dev/null +++ b/src/game/__tests__/world.test.ts @@ -0,0 +1,110 @@ +import { describe, it, expect } from 'vitest'; +import { idx, inBounds, isWall, isBlocked } from '../world'; +import { type World, type Tile } from '../types'; + +describe('World Utilities', () => { + const createTestWorld = (width: number, height: number, tiles: Tile[]): World => ({ + width, + height, + tiles, + actors: new Map(), + exit: { x: 0, y: 0 } + }); + + describe('idx', () => { + it('should calculate correct index for 2D coordinates', () => { + const world = createTestWorld(10, 10, []); + + expect(idx(world, 0, 0)).toBe(0); + expect(idx(world, 5, 0)).toBe(5); + expect(idx(world, 0, 1)).toBe(10); + expect(idx(world, 5, 3)).toBe(35); + }); + }); + + describe('inBounds', () => { + it('should return true for coordinates within bounds', () => { + const world = createTestWorld(10, 10, []); + + expect(inBounds(world, 0, 0)).toBe(true); + expect(inBounds(world, 5, 5)).toBe(true); + expect(inBounds(world, 9, 9)).toBe(true); + }); + + it('should return false for coordinates outside bounds', () => { + const world = createTestWorld(10, 10, []); + + expect(inBounds(world, -1, 0)).toBe(false); + expect(inBounds(world, 0, -1)).toBe(false); + expect(inBounds(world, 10, 0)).toBe(false); + expect(inBounds(world, 0, 10)).toBe(false); + expect(inBounds(world, 11, 11)).toBe(false); + }); + }); + + describe('isWall', () => { + it('should return true for wall tiles', () => { + const tiles: Tile[] = new Array(100).fill(0); + tiles[0] = 1; // wall at 0,0 + tiles[55] = 1; // wall at 5,5 + + const world = createTestWorld(10, 10, tiles); + + expect(isWall(world, 0, 0)).toBe(true); + expect(isWall(world, 5, 5)).toBe(true); + }); + + it('should return false for floor tiles', () => { + const tiles: Tile[] = new Array(100).fill(0); + const world = createTestWorld(10, 10, tiles); + + expect(isWall(world, 3, 3)).toBe(false); + expect(isWall(world, 7, 7)).toBe(false); + }); + + it('should return false for out of bounds coordinates', () => { + const world = createTestWorld(10, 10, new Array(100).fill(0)); + + expect(isWall(world, -1, 0)).toBe(false); + expect(isWall(world, 10, 10)).toBe(false); + }); + }); + + describe('isBlocked', () => { + it('should return true for walls', () => { + const tiles: Tile[] = new Array(100).fill(0); + tiles[55] = 1; // wall at 5,5 + + const world = createTestWorld(10, 10, tiles); + + expect(isBlocked(world, 5, 5)).toBe(true); + }); + + it('should return true for actor positions', () => { + const world = createTestWorld(10, 10, new Array(100).fill(0)); + world.actors.set(1, { + id: 1, + isPlayer: true, + pos: { x: 3, y: 3 }, + speed: 100, + energy: 0 + }); + + expect(isBlocked(world, 3, 3)).toBe(true); + }); + + it('should return false for empty floor tiles', () => { + const world = createTestWorld(10, 10, new Array(100).fill(0)); + + expect(isBlocked(world, 3, 3)).toBe(false); + expect(isBlocked(world, 7, 7)).toBe(false); + }); + + it('should return true for out of bounds', () => { + const world = createTestWorld(10, 10, new Array(100).fill(0)); + + expect(isBlocked(world, -1, 0)).toBe(true); + expect(isBlocked(world, 10, 10)).toBe(true); + }); + }); +}); diff --git a/src/game/config/GameConfig.ts b/src/game/config/GameConfig.ts new file mode 100644 index 0000000..700f41f --- /dev/null +++ b/src/game/config/GameConfig.ts @@ -0,0 +1,62 @@ +export const GAME_CONFIG = { + player: { + initialStats: { maxHp: 20, hp: 20, attack: 5, defense: 2 }, + speed: 100, + viewRadius: 8 + }, + + map: { + width: 60, + height: 40, + minRooms: 8, + maxRooms: 13, + roomMinWidth: 5, + roomMaxWidth: 12, + roomMinHeight: 4, + roomMaxHeight: 10 + }, + + enemy: { + baseHpPerLevel: 2, + baseHp: 8, + baseAttack: 3, + attackPerTwoLevels: 1, + minSpeed: 80, + maxSpeed: 130, + maxDefense: 2, + baseCountPerLevel: 1, + baseCount: 3, + randomBonus: 4 + }, + + rendering: { + tileSize: 24, + cameraZoom: 2, + wallColor: 0x2b2b2b, + floorColor: 0x161616, + exitColor: 0xffd166, + playerColor: 0x66ff66, + enemyColor: 0xff6666, + pathPreviewColor: 0x3355ff, + fogAlphaFloor: 0.15, + fogAlphaWall: 0.35, + visibleMinAlpha: 0.35, + visibleMaxAlpha: 1.0, + visibleStrengthFactor: 0.65 + }, + + ui: { + minimapPanelWidth: 340, + minimapPanelHeight: 220, + minimapPadding: 20, + menuPanelWidth: 340, + menuPanelHeight: 220 + }, + + gameplay: { + energyThreshold: 100, + actionCost: 100 + } +} as const; + +export type GameConfig = typeof GAME_CONFIG; diff --git a/src/game/generator.ts b/src/game/generator.ts index 25f040e..edd35a7 100644 --- a/src/game/generator.ts +++ b/src/game/generator.ts @@ -1,5 +1,6 @@ import { type World, type EntityId, type RunState, type Tile, type Actor, type Vec2 } from "./types"; import { idx } from "./world"; +import { GAME_CONFIG } from "./config/GameConfig"; interface Room { x: number; @@ -16,9 +17,15 @@ function seededRandom(seed: number): () => number { }; } -export function makeTestWorld(level: number, runState: RunState): { world: World; playerId: EntityId } { - const width = 60; - const height = 40; +/** + * Generates a procedural dungeon world with rooms and corridors + * @param level The level number (affects difficulty and randomness seed) + * @param runState Player's persistent state across levels + * @returns Generated world and player ID + */ +export function generateWorld(level: number, runState: RunState): { world: World; playerId: EntityId } { + const width = GAME_CONFIG.map.width; + const height = GAME_CONFIG.map.height; const tiles: Tile[] = new Array(width * height).fill(1); // Start with all walls const fakeWorldForIdx: World = { width, height, tiles, actors: new Map(), exit: { x: 0, y: 0 } }; @@ -26,11 +33,11 @@ export function makeTestWorld(level: number, runState: RunState): { world: World // Generate rooms const rooms: Room[] = []; - const numRooms = 8 + Math.floor(random() * 6); // 8-13 rooms + const numRooms = GAME_CONFIG.map.minRooms + Math.floor(random() * (GAME_CONFIG.map.maxRooms - GAME_CONFIG.map.minRooms + 1)); for (let i = 0; i < numRooms; i++) { - const roomWidth = 5 + Math.floor(random() * 8); // 5-12 - const roomHeight = 4 + Math.floor(random() * 7); // 4-10 + const roomWidth = GAME_CONFIG.map.roomMinWidth + Math.floor(random() * (GAME_CONFIG.map.roomMaxWidth - GAME_CONFIG.map.roomMinWidth + 1)); + const roomHeight = GAME_CONFIG.map.roomMinHeight + Math.floor(random() * (GAME_CONFIG.map.roomMaxHeight - GAME_CONFIG.map.roomMinHeight + 1)); const roomX = 1 + Math.floor(random() * (width - roomWidth - 2)); const roomY = 1 + Math.floor(random() * (height - roomHeight - 2)); @@ -108,7 +115,7 @@ export function makeTestWorld(level: number, runState: RunState): { world: World id: playerId, isPlayer: true, pos: { x: playerX, y: playerY }, - speed: 100, + speed: GAME_CONFIG.player.speed, energy: 0, stats: { ...runState.stats }, inventory: { gold: runState.inventory.gold, items: [...runState.inventory.items] } @@ -116,7 +123,7 @@ export function makeTestWorld(level: number, runState: RunState): { world: World // Place enemies in random rooms (skip first room with player) let enemyId = 2; - const numEnemies = 3 + level + Math.floor(random() * 4); // Increases with level + const numEnemies = GAME_CONFIG.enemy.baseCount + level * GAME_CONFIG.enemy.baseCountPerLevel + Math.floor(random() * GAME_CONFIG.enemy.randomBonus); for (let i = 0; i < numEnemies && i < rooms.length - 1; i++) { const roomIdx = 1 + Math.floor(random() * (rooms.length - 1)); @@ -126,20 +133,20 @@ export function makeTestWorld(level: number, runState: RunState): { world: World const enemyY = room.y + 1 + Math.floor(random() * (room.height - 2)); // Vary enemy stats by level - const baseHp = 8 + level * 2; - const baseAttack = 3 + Math.floor(level / 2); + const baseHp = GAME_CONFIG.enemy.baseHp + level * GAME_CONFIG.enemy.baseHpPerLevel; + const baseAttack = GAME_CONFIG.enemy.baseAttack + Math.floor(level / 2) * GAME_CONFIG.enemy.attackPerTwoLevels; actors.set(enemyId, { id: enemyId, isPlayer: false, pos: { x: enemyX, y: enemyY }, - speed: 80 + Math.floor(random() * 50), // 80-130 speed + speed: GAME_CONFIG.enemy.minSpeed + Math.floor(random() * (GAME_CONFIG.enemy.maxSpeed - GAME_CONFIG.enemy.minSpeed)), energy: 0, stats: { maxHp: baseHp + Math.floor(random() * 4), hp: baseHp + Math.floor(random() * 4), attack: baseAttack + Math.floor(random() * 2), - defense: Math.floor(random() * 3) + defense: Math.floor(random() * (GAME_CONFIG.enemy.maxDefense + 1)) } }); enemyId++; @@ -147,3 +154,8 @@ export function makeTestWorld(level: number, runState: RunState): { world: World return { world: { width, height, tiles, actors, exit }, playerId }; } + +// Backward compatibility - will be removed in Phase 2 +/** @deprecated Use generateWorld instead */ +export const makeTestWorld = generateWorld; + diff --git a/src/game/types.ts b/src/game/types.ts index baad038..2322dd5 100644 --- a/src/game/types.ts +++ b/src/game/types.ts @@ -53,6 +53,10 @@ export type World = { exit: Vec2; }; -export const TILE_SIZE = 24; -export const ENERGY_THRESHOLD = 100; -export const ACTION_COST = 100; +// Import constants from config +import { GAME_CONFIG } from "./config/GameConfig"; + +export const TILE_SIZE = GAME_CONFIG.rendering.tileSize; +export const ENERGY_THRESHOLD = GAME_CONFIG.gameplay.energyThreshold; +export const ACTION_COST = GAME_CONFIG.gameplay.actionCost; + diff --git a/src/scenes/DungeonRenderer.ts b/src/scenes/DungeonRenderer.ts index a9bf8ad..5ded101 100644 --- a/src/scenes/DungeonRenderer.ts +++ b/src/scenes/DungeonRenderer.ts @@ -2,6 +2,7 @@ import Phaser from "phaser"; import { FOV } from "rot-js"; import { type World, type EntityId, type Vec2, TILE_SIZE } from "../game/types"; import { idx, inBounds, isWall } from "../game/world"; +import { GAME_CONFIG } from "../game/config/GameConfig"; export class DungeonRenderer { private scene: Phaser.Scene; @@ -12,7 +13,6 @@ export class DungeonRenderer { private seen!: Uint8Array; private visible!: Uint8Array; private visibleStrength!: Float32Array; - private viewRadius = 8; // State refs private world!: World; @@ -21,8 +21,6 @@ export class DungeonRenderer { private minimapGfx!: Phaser.GameObjects.Graphics; private minimapContainer!: Phaser.GameObjects.Container; private minimapBg!: Phaser.GameObjects.Rectangle; - private minimapPanelWidth = 340; // Match menu size - private minimapPanelHeight = 220; // Match menu size private minimapVisible = false; // Off by default constructor(scene: Phaser.Scene) { @@ -40,7 +38,7 @@ export class DungeonRenderer { // Background panel (like menu) this.minimapBg = this.scene.add - .rectangle(0, 0, this.minimapPanelWidth, this.minimapPanelHeight, 0x000000, 0.8) + .rectangle(0, 0, GAME_CONFIG.ui.minimapPanelWidth, GAME_CONFIG.ui.minimapPanelHeight, 0x000000, 0.8) .setStrokeStyle(1, 0xffffff, 0.9) .setInteractive(); // Capture clicks @@ -94,7 +92,7 @@ export class DungeonRenderer { const ox = player.pos.x; const oy = player.pos.y; - this.fov.compute(ox, oy, this.viewRadius, (x: number, y: number, r: number, v: number) => { + this.fov.compute(ox, oy, GAME_CONFIG.player.viewRadius, (x: number, y: number, r: number, v: number) => { if (!inBounds(this.world, x, y)) return; const i = idx(this.world, x, y); @@ -102,7 +100,7 @@ export class DungeonRenderer { this.seen[i] = 1; // falloff: 1 at center, ~0.4 at radius edge - const radiusT = Phaser.Math.Clamp(r / this.viewRadius, 0, 1); + const radiusT = Phaser.Math.Clamp(r / GAME_CONFIG.player.viewRadius, 0, 1); const falloff = 1 - radiusT * 0.6; const strength = Phaser.Math.Clamp(v * falloff, 0, 1); @@ -139,14 +137,14 @@ export class DungeonRenderer { } const wall = isWall(this.world, x, y); - const base = wall ? 0x2b2b2b : 0x161616; + const base = wall ? GAME_CONFIG.rendering.wallColor : GAME_CONFIG.rendering.floorColor; let alpha: number; if (isVis) { const s = this.visibleStrength[i]; - alpha = Phaser.Math.Clamp(0.35 + s * 0.65, 0.35, 1.0); + alpha = Phaser.Math.Clamp(GAME_CONFIG.rendering.visibleMinAlpha + s * GAME_CONFIG.rendering.visibleStrengthFactor, GAME_CONFIG.rendering.visibleMinAlpha, GAME_CONFIG.rendering.visibleMaxAlpha); } else { - alpha = wall ? 0.35 : 0.15; + alpha = wall ? GAME_CONFIG.rendering.fogAlphaWall : GAME_CONFIG.rendering.fogAlphaFloor; } this.gfx.fillStyle(base, alpha); @@ -160,15 +158,15 @@ export class DungeonRenderer { const ey = this.world.exit.y; const i = idx(this.world, ex, ey); if (this.seen[i] === 1) { - const alpha = this.visible[i] === 1 ? 1.0 : 0.35; - this.gfx.fillStyle(0xffd166, alpha); + const alpha = this.visible[i] === 1 ? 1.0 : GAME_CONFIG.rendering.visibleMinAlpha; + this.gfx.fillStyle(GAME_CONFIG.rendering.exitColor, alpha); this.gfx.fillRect(ex * TILE_SIZE + 7, ey * TILE_SIZE + 7, TILE_SIZE - 14, TILE_SIZE - 14); } } // Path preview (seen only) if (playerPath.length >= 2) { - this.gfx.fillStyle(0x3355ff, 0.35); + this.gfx.fillStyle(GAME_CONFIG.rendering.pathPreviewColor, GAME_CONFIG.rendering.visibleMinAlpha); for (const p of playerPath) { // We can check isSeen via internal helper or just local array since we're inside const i = idx(this.world, p.x, p.y); @@ -183,7 +181,7 @@ export class DungeonRenderer { const isVis = this.visible[i] === 1; if (!a.isPlayer && !isVis) continue; - const color = a.isPlayer ? 0x66ff66 : 0xff6666; + const color = a.isPlayer ? GAME_CONFIG.rendering.playerColor : GAME_CONFIG.rendering.enemyColor; this.gfx.fillStyle(color, 1); this.gfx.fillRect(a.pos.x * TILE_SIZE + 4, a.pos.y * TILE_SIZE + 4, TILE_SIZE - 8, TILE_SIZE - 8); } @@ -198,9 +196,9 @@ export class DungeonRenderer { if (!this.world) return; // Calculate scale to fit map within panel - const padding = 20; - const availableWidth = this.minimapPanelWidth - padding * 2; - const availableHeight = this.minimapPanelHeight - padding * 2; + const padding = GAME_CONFIG.ui.minimapPadding; + const availableWidth = GAME_CONFIG.ui.minimapPanelWidth - padding * 2; + const availableHeight = GAME_CONFIG.ui.minimapPanelHeight - padding * 2; const scaleX = availableWidth / this.world.width; const scaleY = availableHeight / this.world.height; diff --git a/src/scenes/GameScene.ts b/src/scenes/GameScene.ts index 2c659a5..a724fad 100644 --- a/src/scenes/GameScene.ts +++ b/src/scenes/GameScene.ts @@ -12,6 +12,7 @@ import { findPathAStar } from "../game/pathfinding"; import { applyAction, stepUntilPlayerTurn } from "../game/simulation"; import { makeTestWorld } from "../game/generator"; import { DungeonRenderer } from "./DungeonRenderer"; +import { GAME_CONFIG } from "../game/config/GameConfig"; export class GameScene extends Phaser.Scene { private world!: World; @@ -20,7 +21,7 @@ export class GameScene extends Phaser.Scene { private levelIndex = 1; private runState: RunState = { - stats: { maxHp: 20, hp: 20, attack: 5, defense: 2 }, + stats: { ...GAME_CONFIG.player.initialStats }, inventory: { gold: 0, items: [] } }; @@ -41,7 +42,7 @@ export class GameScene extends Phaser.Scene { this.cursors = this.input.keyboard!.createCursorKeys(); // Camera - this.cameras.main.setZoom(2); + this.cameras.main.setZoom(GAME_CONFIG.rendering.cameraZoom); // Initialize Sub-systems this.dungeonRenderer = new DungeonRenderer(this); diff --git a/src/scenes/GameUI.ts b/src/scenes/GameUI.ts index 4c09c8c..b4bfee6 100644 --- a/src/scenes/GameUI.ts +++ b/src/scenes/GameUI.ts @@ -1,5 +1,6 @@ import Phaser from "phaser"; import { type World, type EntityId } from "../game/types"; +import { GAME_CONFIG } from "../game/config/GameConfig"; export default class GameUI extends Phaser.Scene { // HUD @@ -84,8 +85,8 @@ export default class GameUI extends Phaser.Scene { mapBtnBg.setInteractive({ useHandCursor: true }).on("pointerdown", () => this.toggleMap()); // Panel (center) - const panelW = 340; - const panelH = 220; + const panelW = GAME_CONFIG.ui.menuPanelWidth; + const panelH = GAME_CONFIG.ui.menuPanelHeight; this.menuBg = this.add .rectangle(0, 0, panelW, panelH, 0x000000, 0.8)