/** * Path Generation Module * * This module demonstrates several advanced game development concepts: * 1. Procedural Content Generation (PCG) * 2. Pathfinding algorithms * 3. Constraint-based generation * * @module path */ /** * Generates a valid path through the game grid using a modified depth-first search. * This algorithm ensures: * - Path always moves from left to right * - No diagonal movements * - No path segments touch each other (except at turns) * - Path is always completable * * @param {Array>} grid - 2D array representing the game grid * @returns {Promise>} Promise resolving to array of path coordinates * * Implementation uses: * - Backtracking algorithm pattern * - Constraint satisfaction * - Random selection for variety */ function generatePath(grid) { const width = grid[0].length; const height = grid.length; // Initialize with random start point on left edge const startY = Math.floor(Math.random() * height); let currentPos = { x: 0, y: startY }; const path = [currentPos]; grid[startY][0] = 'path'; /** * Determines valid moves from current position based on game rules * Uses constraint checking to ensure path validity * * @param {Object} pos - Current position {x, y} * @returns {Array<{x: number, y: number}>} Array of valid next positions */ function getValidMoves(pos) { const moves = []; // Prioritize right movement for path progression const directions = [ { x: 1, y: 0 }, // right { x: 0, y: -1 }, // up { x: 0, y: 1 } // down ]; for (const dir of directions) { const newX = pos.x + dir.x; const newY = pos.y + dir.y; // Enforce boundary constraints if (newX < 0 || newX >= width || newY < 0 || newY >= height) { continue; } // Check path isolation constraint if (grid[newY][newX] === 'empty' && !hasAdjacentPath(newX, newY, grid)) { moves.push({ x: newX, y: newY }); } } return moves; } /** * Checks if a position has adjacent path tiles (excluding previous path tile) * Implements path isolation constraint * * @param {number} x - X coordinate to check * @param {number} y - Y coordinate to check * @param {Array>} grid - Current grid state * @returns {boolean} True if position has adjacent path tiles */ function hasAdjacentPath(x, y, grid) { const adjacentCells = [ { x: x, y: y - 1 }, // up { x: x, y: y + 1 }, // down { x: x - 1, y: y }, // left { x: x + 1, y: y }, // right ]; return adjacentCells.some(cell => { if (cell.x < 0 || cell.x >= width || cell.y < 0 || cell.y >= height) { return false; } return grid[cell.y][cell.x] === 'path' && !path.some(p => p.x === cell.x && p.y === cell.y); }); } // Main path generation loop with backtracking while (currentPos.x < width - 1) { const moves = getValidMoves(currentPos); if (moves.length === 0) { // Backtrack when no valid moves exist if (path.length <= 1) { // Restart if backtracking fails return generatePath(grid); } path.pop(); const lastPos = path[path.length - 1]; grid[currentPos.y][currentPos.x] = 'empty'; currentPos = lastPos; continue; } // Random selection for path variety const nextMove = moves[Math.floor(Math.random() * moves.length)]; currentPos = nextMove; path.push(currentPos); grid[currentPos.y][currentPos.x] = 'path'; } return Promise.resolve(path); } /** * Calculates a position along the path based on a progress value * Implements smooth entity movement along path segments * * @param {number} progress - Progress along path (0-1) * @param {Array<{x: number, y: number}>} path - Array of path coordinates * @returns {{x: number, y: number}} Interpolated position along path * * Uses: * - Linear interpolation (lerp) * - Path segment traversal * - Normalized progress tracking */ function getPathPosition(progress, path) { // Normalize progress to valid range progress = Math.max(0, Math.min(1, progress)); // Calculate total path length for normalization let totalLength = 0; for (let i = 1; i < path.length; i++) { const dx = path[i].x - path[i-1].x; const dy = path[i].y - path[i-1].y; totalLength += Math.sqrt(dx * dx + dy * dy); } // Convert progress to distance along path const targetDistance = progress * totalLength; // Find appropriate path segment let currentDistance = 0; for (let i = 1; i < path.length; i++) { const dx = path[i].x - path[i-1].x; const dy = path[i].y - path[i-1].y; const segmentLength = Math.sqrt(dx * dx + dy * dy); if (currentDistance + segmentLength >= targetDistance) { // Linear interpolation within segment const segmentProgress = (targetDistance - currentDistance) / segmentLength; return { x: path[i-1].x + dx * segmentProgress, y: path[i-1].y + dy * segmentProgress }; } currentDistance += segmentLength; } // Fallback to end of path return { ...path[path.length - 1] }; }