'use strict'; // Browser: use window globals; Node: use require const _C = typeof require !== 'undefined' ? require('../core/constants') : { TILE_TYPES, ROWS, COLS }; function findFloorPosition(room, rng, exclude) { const candidates = []; for (let y = 1; y < _C.ROWS - 1; y++) { for (let x = 1; x < _C.COLS - 1; x++) { if (room.tiles[y][x] === _C.TILE_TYPES.FLOOR) { if (!exclude || !exclude.some(p => p.x === x && p.y === y)) { candidates.push({ x, y }); } } } } if (candidates.length === 0) return null; const idx = Math.floor(rng() * candidates.length); return candidates[idx]; } function findWallPosition(room, rng, edge, exclude) { const candidates = []; if (edge === 'bottom') { for (let x = 1; x < _C.COLS - 1; x++) { const y = _C.ROWS - 1; if (room.tiles[y][x] === _C.TILE_TYPES.WALL) { if (!exclude || !exclude.some(p => p.x === x && p.y === y)) { candidates.push({ x, y }); } } } } else if (edge === 'top') { for (let x = 1; x < _C.COLS - 1; x++) { const y = 0; if (room.tiles[y][x] === _C.TILE_TYPES.WALL) { if (!exclude || !exclude.some(p => p.x === x && p.y === y)) { candidates.push({ x, y }); } } } } else if (edge === 'left') { for (let y = 1; y < _C.ROWS - 1; y++) { const x = 0; if (room.tiles[y][x] === _C.TILE_TYPES.WALL) { if (!exclude || !exclude.some(p => p.x === x && p.y === y)) { candidates.push({ x, y }); } } } } else if (edge === 'right') { for (let y = 1; y < _C.ROWS - 1; y++) { const x = _C.COLS - 1; if (room.tiles[y][x] === _C.TILE_TYPES.WALL) { if (!exclude || !exclude.some(p => p.x === x && p.y === y)) { candidates.push({ x, y }); } } } } if (candidates.length === 0) return null; const idx = Math.floor(rng() * candidates.length); return candidates[idx]; } function generateRockPushPuzzle(room, rng) { const difficulty = room.difficulty || 1; const rockCount = Math.min(3, difficulty); const elements = []; const occupied = []; let solutions = []; for (let i = 0; i < rockCount; i++) { let attempts = 0; let rockPos, targetPos; while (attempts < 50) { rockPos = findFloorPosition(room, rng, occupied); if (!rockPos) break; targetPos = findFloorPosition(room, rng, [...occupied, rockPos]); if (!targetPos) { attempts++; continue; } const dx = targetPos.x - rockPos.x; const dy = targetPos.y - rockPos.y; const pushDirX = -Math.sign(dx); const pushDirY = -Math.sign(dy); const behindX = rockPos.x + pushDirX; const behindY = rockPos.y + pushDirY; if (behindX < 1 || behindX >= _C.COLS - 1 || behindY < 1 || behindY >= _C.ROWS - 1 || occupied.some(p => p.x === behindX && p.y === behindY)) { attempts++; continue; } if (room.tiles[behindY][behindX] !== _C.TILE_TYPES.FLOOR) { attempts++; continue; } room.tiles[rockPos.y][rockPos.x] = _C.TILE_TYPES.ROCK; room.tiles[targetPos.y][targetPos.x] = _C.TILE_TYPES.TARGET; const rock = { type: 'rock', x: rockPos.x, y: rockPos.y, pushable: true, requiredPosition: { x: targetPos.x, y: targetPos.y }, }; const target = { type: 'target', x: targetPos.x, y: targetPos.y, active: false, }; elements.push(rock); elements.push(target); occupied.push(rockPos); occupied.push(targetPos); occupied.push({ x: behindX, y: behindY }); solutions.push(`Push rock at (${rockPos.x}, ${rockPos.y}) to (${targetPos.x}, ${targetPos.y})`); break; } } return { type: 'rock_push', elements, solution: solutions.join('. '), solved: false, reward: elements.length >= 4 ? 'heart_piece' : 'small_key', }; } function generateLeverPuzzle(room, rng) { const elements = []; const occupied = []; const leverPos = findFloorPosition(room, rng, occupied); if (!leverPos) return null; occupied.push(leverPos); room.tiles[leverPos.y][leverPos.x] = _C.TILE_TYPES.LEVER; elements.push({ type: 'lever', x: leverPos.x, y: leverPos.y, activated: false, }); const targetPos = findFloorPosition(room, rng, occupied); if (!targetPos) return null; occupied.push(targetPos); room.tiles[targetPos.y][targetPos.x] = _C.TILE_TYPES.TARGET; const targetEffect = rng() < 0.6 ? 'softwall' : 'door'; elements.push({ type: 'target', x: targetPos.x, y: targetPos.y, active: false, effect: targetEffect, }); if (targetEffect === 'softwall') { let softwallPos = null; for (let attempts = 0; attempts < 30; attempts++) { softwallPos = findFloorPosition(room, rng, occupied); if (!softwallPos) break; if (room.tiles[softwallPos.y][softwallPos.x] === _C.TILE_TYPES.FLOOR) { break; } } if (softwallPos) { room.tiles[softwallPos.y][softwallPos.x] = _C.TILE_TYPES.SOFTWALL; occupied.push(softwallPos); elements.push({ type: 'softwall', x: softwallPos.x, y: softwallPos.y, breakable: true, linkedToLever: true, }); } } else if (targetEffect === 'door') { const edges = ['top', 'bottom', 'left', 'right']; const edge = edges[Math.floor(rng() * edges.length)]; const doorPos = findWallPosition(room, rng, edge, occupied); if (doorPos) { room.tiles[doorPos.y][doorPos.x] = _C.TILE_TYPES.DOOR; occupied.push(doorPos); elements.push({ type: 'door', x: doorPos.x, y: doorPos.y, locked: true, keyRequired: 'small_key', linkedToLever: true, }); } } return { type: 'lever', elements, solution: `Activate lever at (${leverPos.x}, ${leverPos.y}) to ${targetEffect === 'softwall' ? 'remove softwall' : 'unlock door'}`, solved: false, reward: 'small_key', }; } function generateKeyDoorPuzzle(room, rng) { const elements = []; const occupied = []; const edges = ['top', 'bottom', 'left', 'right']; const edge = edges[Math.floor(rng() * edges.length)]; const doorPos = findWallPosition(room, rng, edge, occupied); if (!doorPos) return null; room.tiles[doorPos.y][doorPos.x] = _C.TILE_TYPES.DOOR; occupied.push(doorPos); elements.push({ type: 'door', x: doorPos.x, y: doorPos.y, locked: true, keyRequired: 'small_key', }); const keyRoom = { type: 'key', x: -1, y: -1, keyType: 'small_key', collected: false, roomId: -1, }; elements.push(keyRoom); return { type: 'key_door', elements, solution: `Find small key in another room, then unlock door at (${doorPos.x}, ${doorPos.y})`, solved: false, reward: 'chest_unlock', keyRoomId: keyRoom.roomId, }; } function generateSwitchPuzzle(room, rng) { const elements = []; const occupied = []; const switchPos = findFloorPosition(room, rng, occupied); if (!switchPos) return null; occupied.push(switchPos); room.tiles[switchPos.y][switchPos.x] = _C.TILE_TYPES.SWITCH; elements.push({ type: 'switch', x: switchPos.x, y: switchPos.y, activated: false, }); const puzzleVariant = rng() < 0.7 ? 'softwall' : 'door'; if (puzzleVariant === 'softwall') { let softwallPos = null; for (let attempts = 0; attempts < 30; attempts++) { softwallPos = findFloorPosition(room, rng, occupied); if (!softwallPos) break; if (room.tiles[softwallPos.y][softwallPos.x] === _C.TILE_TYPES.FLOOR) { break; } } if (softwallPos) { room.tiles[softwallPos.y][softwallPos.x] = _C.TILE_TYPES.SOFTWALL; occupied.push(softwallPos); elements.push({ type: 'softwall', x: softwallPos.x, y: softwallPos.y, breakable: true, linkedToSwitch: true, }); } } else { const edges = ['top', 'bottom', 'left', 'right']; const edge = edges[Math.floor(rng() * edges.length)]; const doorPos = findWallPosition(room, rng, edge, occupied); if (doorPos) { room.tiles[doorPos.y][doorPos.x] = _C.TILE_TYPES.DOOR; occupied.push(doorPos); elements.push({ type: 'door', x: doorPos.x, y: doorPos.y, locked: true, keyRequired: null, linkedToSwitch: true, }); } } return { type: 'switch', elements, solution: `Step on switch at (${switchPos.x}, ${switchPos.y}) to ${puzzleVariant === 'softwall' ? 'break softwall' : 'unlock door'}`, solved: false, reward: 'small_key', }; } function generatePuzzles(rooms, dungeon, rng) { const difficulty = dungeon.difficulty || 1; const puzzleRooms = rooms.filter(r => r.type === 'puzzle'); for (const room of puzzleRooms) { room.difficulty = difficulty; const puzzleTypes = ['rock_push', 'lever', 'switch']; if (room.width >= 6 && room.height >= 6) { puzzleTypes.push('key_door'); } const chosenType = puzzleTypes[Math.floor(rng() * puzzleTypes.length)]; let puzzle; switch (chosenType) { case 'rock_push': puzzle = generateRockPushPuzzle(room, rng); break; case 'lever': puzzle = generateLeverPuzzle(room, rng); break; case 'switch': puzzle = generateSwitchPuzzle(room, rng); break; case 'key_door': puzzle = generateKeyDoorPuzzle(room, rng); break; default: puzzle = generateSwitchPuzzle(room, rng); break; } if (puzzle) { room.puzzles.push(puzzle); if (puzzle.type === 'key_door' && puzzle.keyRoomId !== undefined) { const keyRoom = rooms.find(r => r.id === puzzle.keyRoomId); if (keyRoom) { const keyPos = findFloorPosition(keyRoom, rng, null); if (keyPos) { const keyElement = puzzle.elements.find(e => e.type === 'key'); if (keyElement) { keyElement.x = keyPos.x; keyElement.y = keyPos.y; keyElement.roomId = keyRoom.id; room.tiles[keyPos.y][keyPos.x] = _C.TILE_TYPES.ITEM; } } } } } } for (const room of puzzleRooms) { for (const puzzle of room.puzzles) { if (puzzle.type === 'key_door' && puzzle.keyRoomId === -1) { const otherRooms = rooms.filter(r => r.id !== room.id && r.type !== 'boss'); if (otherRooms.length > 0) { const keyRoom = otherRooms[Math.floor(rng() * otherRooms.length)]; puzzle.keyRoomId = keyRoom.id; const keyPos = findFloorPosition(keyRoom, rng, null); if (keyPos) { const keyElement = puzzle.elements.find(e => e.type === 'key'); if (keyElement) { keyElement.x = keyPos.x; keyElement.y = keyPos.y; keyElement.roomId = keyRoom.id; keyRoom.tiles[keyPos.y][keyPos.x] = _C.TILE_TYPES.ITEM; keyRoom.items.push({ type: 'key', keyType: 'small_key', x: keyPos.x, y: keyPos.y, collected: false, }); } } } } } } } if (typeof module !== 'undefined' && module.exports) { module.exports = { generatePuzzles }; }