/** * Difficulty Configuration * * Configures dungeon difficulty based on crypto block timing. * The goal is to make the game challenging enough that the house wins * the majority of games (target: 60-70% house win rate). * * Key factors affecting difficulty: * 1. Dungeon size (larger = more moves needed = harder) * 2. Monster starting position (closer to player = harder) * 3. Monster move frequency (how often monster moves per player move) * 4. Monster AI aggressiveness * 5. Treasure placement (farther from exit = riskier) * 6. Number of monsters (future enhancement) * * Block times: * - Wownero (WOW): ~2 minutes (120 seconds) * - Monero (XMR): ~2 minutes (120 seconds) * - Bitcoin (for reference): ~10 minutes * * Note: Previously docs said WOW was 5 min but it's actually 2 min like XMR. * We keep the config flexible for future adjustments. */ const DIFFICULTY_PRESETS = { // Easy - for testing, high player win rate easy: { dungeon: { width: 30, height: 15, roomWidthRange: [4, 8], roomHeightRange: [3, 6], corridorLengthRange: [2, 5], dugPercentage: 0.25 // More open space = easier navigation }, monster: { startDistanceFromPlayer: 0.9, // 90% of max distance (far from player) movesPerPlayerMove: 0.5, // Monster moves every other player move chaseAggressiveness: 0.6, // 60% chance to chase, 40% random visionRange: 6, // Short vision - easy to evade respawnOnDeath: false }, treasure: { roomPositionRatio: 0.5, // Middle room distanceFromExitRatio: 0.3 // Close to exit }, targetHouseWinRate: 0.3 // 30% house wins (player-friendly) }, // Normal - balanced for Wownero (2 min blocks) normal: { dungeon: { width: 45, height: 22, roomWidthRange: [3, 7], roomHeightRange: [3, 6], corridorLengthRange: [3, 7], dugPercentage: 0.2 }, monster: { startDistanceFromPlayer: 0.6, // 60% of max distance movesPerPlayerMove: 1.0, // Monster moves every player move chaseAggressiveness: 0.8, // 80% chase, 20% random visionRange: 10, // Medium vision respawnOnDeath: false }, treasure: { roomPositionRatio: 0.4, // Early-middle room distanceFromExitRatio: 0.5 // Moderate distance from exit }, targetHouseWinRate: 0.55 // 55% house wins }, // Hard - challenging, house advantage hard: { dungeon: { width: 55, height: 28, roomWidthRange: [3, 6], roomHeightRange: [3, 5], corridorLengthRange: [4, 10], dugPercentage: 0.15 // Tighter corridors }, monster: { startDistanceFromPlayer: 0.4, // Closer to player movesPerPlayerMove: 1.0, chaseAggressiveness: 0.9, // Very aggressive visionRange: 14, // Good vision respawnOnDeath: false }, treasure: { roomPositionRatio: 0.3, // Earlier room (farther from exit) distanceFromExitRatio: 0.7 // Farther from exit }, targetHouseWinRate: 0.65 // 65% house wins }, // Casino - high house edge for real money casino: { dungeon: { width: 70, // Large dungeon (was 60) height: 35, // (was 30) roomWidthRange: [3, 4], // Small rooms (was [3, 5]) roomHeightRange: [3, 4], corridorLengthRange: [6, 15], // Long corridors (was [5, 12]) dugPercentage: 0.10 // Very tight (was 0.12) }, monster: { startDistanceFromPlayer: 0.30, // Close to player (was 0.35) movesPerPlayerMove: 1.0, // Monster matches player speed chaseAggressiveness: 0.97, // Nearly always chases (was 0.95) visionRange: 22, // Excellent vision (was 18) respawnOnDeath: false }, treasure: { roomPositionRatio: 0.20, // Early room (was 0.25) distanceFromExitRatio: 0.85 // Very far from exit (was 0.8) }, targetHouseWinRate: 0.70 // 70% house wins } }; /** * Get difficulty preset based on crypto type and environment */ function getDifficultyPreset(cryptoType = 'WOW', overridePreset = null) { // Allow explicit override via environment const envPreset = process.env.DIFFICULTY_PRESET || overridePreset; if (envPreset && DIFFICULTY_PRESETS[envPreset]) { return { ...DIFFICULTY_PRESETS[envPreset], presetName: envPreset }; } // Default based on payment mode and crypto const gameMode = process.env.GAME_MODE || 'FREE'; const paymentsEnabled = process.env.PAYMENTS_ENABLED === 'true'; if (gameMode === 'FREE' && !paymentsEnabled) { // Free mode = easier for fun return { ...DIFFICULTY_PRESETS.normal, presetName: 'normal' }; } // Paid modes = casino difficulty return { ...DIFFICULTY_PRESETS.casino, presetName: 'casino' }; } /** * Merge difficulty config with custom overrides. * Environment variables override preset values when set. */ function getDifficultyConfig(cryptoType = 'WOW', customOverrides = {}) { const preset = getDifficultyPreset(cryptoType, customOverrides.preset); // Apply env var overrides (only when explicitly set) const envOverrides = { dungeon: {}, monster: {}, treasure: {} }; const env = process.env; if (env.DUNGEON_WIDTH) envOverrides.dungeon.width = parseInt(env.DUNGEON_WIDTH); if (env.DUNGEON_HEIGHT) envOverrides.dungeon.height = parseInt(env.DUNGEON_HEIGHT); if (env.DUNGEON_DUG_PERCENTAGE) envOverrides.dungeon.dugPercentage = parseFloat(env.DUNGEON_DUG_PERCENTAGE); if (env.DUNGEON_ROOM_WIDTH_MIN) envOverrides.dungeon.roomWidthRange = [parseInt(env.DUNGEON_ROOM_WIDTH_MIN), parseInt(env.DUNGEON_ROOM_WIDTH_MAX || env.DUNGEON_ROOM_WIDTH_MIN)]; if (env.DUNGEON_ROOM_HEIGHT_MIN) envOverrides.dungeon.roomHeightRange = [parseInt(env.DUNGEON_ROOM_HEIGHT_MIN), parseInt(env.DUNGEON_ROOM_HEIGHT_MAX || env.DUNGEON_ROOM_HEIGHT_MIN)]; if (env.DUNGEON_CORRIDOR_MIN) envOverrides.dungeon.corridorLengthRange = [parseInt(env.DUNGEON_CORRIDOR_MIN), parseInt(env.DUNGEON_CORRIDOR_MAX || env.DUNGEON_CORRIDOR_MIN)]; if (env.MONSTER_SPEED) envOverrides.monster.movesPerPlayerMove = parseFloat(env.MONSTER_SPEED); if (env.MONSTER_CHASE) envOverrides.monster.chaseAggressiveness = parseFloat(env.MONSTER_CHASE); if (env.MONSTER_VISION) envOverrides.monster.visionRange = parseInt(env.MONSTER_VISION); if (env.MONSTER_DISTANCE) envOverrides.monster.startDistanceFromPlayer = parseFloat(env.MONSTER_DISTANCE); if (env.TREASURE_ROOM_POSITION) envOverrides.treasure.roomPositionRatio = parseFloat(env.TREASURE_ROOM_POSITION); if (env.TREASURE_EXIT_DISTANCE) envOverrides.treasure.distanceFromExitRatio = parseFloat(env.TREASURE_EXIT_DISTANCE); return { ...preset, dungeon: { ...preset.dungeon, ...envOverrides.dungeon, ...customOverrides.dungeon }, monster: { ...preset.monster, ...envOverrides.monster, ...customOverrides.monster }, treasure: { ...preset.treasure, ...envOverrides.treasure, ...customOverrides.treasure } }; } /** * Calculate optimal monster starting room based on difficulty config * @param {Array} rooms - Array of dungeon rooms * @param {number} startDistanceRatio - 0.0 (same room as player) to 1.0 (farthest room) * @returns {number} Room index for monster spawn */ function getMonsterSpawnRoomIndex(rooms, startDistanceRatio = 0.6) { if (!rooms || rooms.length < 2) return 0; // Player spawns in room 0 (entrance) // startDistanceRatio of 1.0 = last room, 0.0 = first room const targetIndex = Math.floor((rooms.length - 1) * startDistanceRatio); // Don't spawn in first room (player's room) or last room (exit) return Math.max(1, Math.min(targetIndex, rooms.length - 2)); } /** * Calculate treasure room based on difficulty config * @param {Array} rooms - Array of dungeon rooms * @param {number} positionRatio - 0.0 (first room) to 1.0 (last room) * @returns {number} Room index for treasure */ function getTreasureRoomIndex(rooms, positionRatio = 0.5) { if (!rooms || rooms.length < 3) return Math.floor(rooms.length / 2); // Avoid first room (entrance) and last room (exit) const usableRooms = rooms.length - 2; const targetIndex = 1 + Math.floor(usableRooms * positionRatio); return Math.max(1, Math.min(targetIndex, rooms.length - 2)); } module.exports = { DIFFICULTY_PRESETS, getDifficultyPreset, getDifficultyConfig, getMonsterSpawnRoomIndex, getTreasureRoomIndex };