/**
* 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<Array<string>>} grid - 2D array representing the game grid
* @returns {Promise<Array<{x: number, y: number}>>} 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<Array<string>>} 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] };
}