Show code
<think>
Okay, let's tackle this problem. The user wants an HTML file that animates Dijkstra's algorithm on a canvas. Hmm, I need to create a self-contained HTML file with all the code inside. Let me break down the requirements step by step.
First, the canvas needs a grid with random movement costs. Each cell's color should be lighter for cheaper costs and darker for expensive ones. So, generating a 2D array for the grid with random numbers between 1 and maybe 10? I'll have to think about the range. The user said movement cost, so probably higher numbers mean it's more expensive. So lighter cells (like white) for lower costs and darker for higher. Maybe a grayscale color based on the cost normalized.
Next, Dijkstra's algorithm steps: starting from a source, expanding to the cheapest unsettled cell each time. The settled cells need to be colored by their accumulated distance. The target should be reached, and then the path traced back. After finishing, it should restart with a new grid, source, and target.
Animation should use requestAnimationFrame for smoothness. No user input, it starts immediately and loops. The file must be under 7000 characters. Let's see how to structure this.
I'll need a canvas element, script that initializes everything. Let's outline the steps in code:
1. Set up the canvas and context.
2. Define grid size, maybe 50x50? That's manageable and not too big for a canvas.
3. Initialize the grid with random costs. Maybe between 1 and 10. The color for each cell is a shade of gray where cost 1 is white (rgb(255,255,255)), and higher costs get darker. So, for a cost of 10, it would be darker. Wait, maybe it's better to have lower costs (cheaper) as lighter. So higher cost means darker. Let me think: cost ranges from 1 to 10. So color value for a cell can be (255 - cost * 25.5) to make it darker as cost increases. Wait, but 1 would be 255 -25.5 = 229.5, which is still light. Maybe better to invert it. Like (255 - (cost/max)*255). So max cost is say 10, so cost/max gives a 0-1 value, subtracted from 1, then scaled to 255. Hmm, perhaps (cost * 25.5) but capped. Maybe for simplicity, just scale the cost to the color. For example, if cost is between 1 and 10, then colorValue = 255 - (cost-1)*25.5. So 1 is 255, 10 would be 255 -25.5*9=255-229.5=25.5. That way lower costs are brighter. That works.
Then, each cell in the grid has a cost, and we need to store their distance and whether they're settled.
The algorithm steps:
- Initialize a grid of distances, all set to infinity except the source which is 0.
- Keep a priority queue of unsettled cells, ordered by current distance. The priority queue needs to be able to find the minimum. Since JavaScript doesn't have a built-in priority queue, perhaps we can manage it with an array and find the minimum each time. But for efficiency, maybe use an array and each step pick the minimum among the unsettled.
Wait, but with 50x50 grid, that's 2500 cells max. Even with O(n) each step, it might be manageable as the animation is on screen. Let's see.
Alternatively, for each step, iterate through all cells to find the next minimum. Since each step picks the next cheapest, we can do that with a loop over all cells, checking if they are settled yet and if their distance is the current minimum. That should be okay.
Structure variables:
- grid: 2D array of costs.
- distances: 2D array of distances from source. Initially infinity except source.
- settled: 2D array of booleans.
- previous: 2D array to track path.
The algorithm loop would be:
1. Find the unsettled cell with the smallest distance (Dijkstra's step).
2. If this cell is the target, then break and trace back the path.
3. Otherwise, mark it as settled.
4. For each of its neighbors, check if they are within bounds, not settled. Update their distance if the path through current is better.
5. Repeat until target is found.
Once the path is found, trace back from target to source using previous pointers.
Then, after finishing, restart by generating a new grid and new source/target positions.
The animation should be smooth using requestAnimationFrame. Each frame should represent a step in the algorithm.
Wait, but Dijkstra's steps might need to be animated step by step. So each frame, we perform one iteration (i.e., process the next cell). That way, the animation can show the expansion of settled cells.
But how to handle the animation loop:
- The main loop (requestAnimationFrame) calls a function that steps through the algorithm.
- When the algorithm is done (target found), it restarts everything and continues.
So variables need to track the current state (e.g., whether processing, which frame is being drawn, etc.)
Implementing this in JavaScript:
First, variables:
const size = 50; // grid size
let grid = [];
let distances = [];
let settled = [];
let previous = [];
let source = {x:0,y:0};
let target = {x:49,y:49}; // or random positions
let path = [];
let settledCount = 0;
let pathCost = 0;
These need to be initialized each time the grid is reset.
The setup function would generate a new grid, set source and target randomly (maybe to opposite corners or random positions). Then initialize the algorithm's variables.
Now, the drawing functions:
The canvas needs to be redrawn each frame. The settled cells should be colored based on their distance. The settled cells are drawn in a color that represents their distance. Maybe the distance can be scaled to the color, but perhaps using a gradient. Alternatively, the distance can be mapped to a color scale. Since distance can be large, maybe modulo some value to get a color.
Alternatively, the path can be drawn in green, settled cells in a gradient based on their distance.
Hmm, but Dijkstra's uses the distance as the accumulated cost. To show that, maybe each cell's color is proportional to its distance. For example, the closer to the source, the lighter. Or the darker as distance increases? Wait, maybe using a gradient where lower distances are lighter and higher darker. Since distance is the accumulated cost, maybe a blue-to-red gradient? Or just a grayscale where the distance is scaled to a color value. Let's think: for a distance of 100 (assuming a maximum possible distance), we can take (distance/maxDistance)*255 and set the color accordingly. But max distance could be up to grid size squared? Not sure. Alternatively, use a simple scale where distance is used directly, but scaled down. For example, (distance % 256) as the color value. So, the color is a grayscale where the distance mod 255 determines the shade.
Alternatively, to have a clear visual: settled cells can be colored with a gradient from the source to darker shades as the distance increases. Maybe just (distance * (255 / (maxPossibleDistance))) as the color. But without knowing the max distance, perhaps just use distance modulo, but it's tricky. Alternatively, the distance can be represented by a color value that's a scaled version. Let's say the maximum distance in the grid would be (size * maxCost) * size. Since each step adds at least 1, the max possible is (size^2)*10, but that's too big. Maybe just use a fixed scaling factor, like 255 / (size * maxCost). Alternatively, just use (distance % 256) as the red component. Hmm, perhaps the simplest way is to set the cell's color to a grayscale where the distance is scaled. For example:
colorValue = (distance * 255) / (size * maxCost);
But since maxCost is 10, and size 50, that would be 255/(50*10)=0.51. So distance*0.51, but that might not be enough. Maybe just use distance mod 255. Or just take distance as the color value, but cap it at 255. Wait, but distance can be up to 50*10*50? Maybe it's better to just use the distance value directly for the grayscale, but scaled. Alternatively, use the distance divided by some factor. Maybe (distance * 4) mod 255? Not sure. Alternatively, use the distance as the color's lightness in HSL. But maybe simpler to just set the color to something like 255 - distance * (255 / maxDistance). But without knowing maxDistance, perhaps just use distance * 2. Because 50x50 grid, the maximum distance can be up to 50*50 (if all steps are cost 1, but moving diagonally isn't allowed?), so maybe 2500? Hmm, that's too big. Alternatively, just use distance as the color component but mod 255. Let me think of a quick way: let's set the color to (distance % 256) for red component, so it cycles. Or maybe (distance * 1.5) mod 255. Alternatively, perhaps just use the distance as a value, and map it to a grayscale where lower distance is brighter. Wait, but distance starts at 0 (source), so the source is white, and as the distance increases, they get darker. So the cell's color could be (255 - (distance * 255) / maxPossibleDistance). But maxPossibleDistance is unknown. Alternatively, just use distance multiplied by a scaling factor, say 2. So for distance 100, it would be 200, which is over 255, but that's okay. Maybe use distance % 256 as the color value. That way, cells with higher distances cycle back to lighter colors. Maybe that's confusing, but it's better than nothing.
Alternatively, use distance values for green component. But maybe for simplicity, the color can be (distance) mapped to a color with R = distance, G = distance, B = distance. But the distance can be up to, say, 500, so when distance exceeds 255, it wraps around. Hmm, but maybe that's okay.
Alternatively, for the settled cell color, use (distance * 1) % 255 as the grayscale. So:
ctx.fillStyle = 'rgb(' + (distance * 2) + ',' + (distance * 2) + ',' + (distance * 2) + ')';
Wait, but if distance is 100, that would be 200, but if distance is 150, then 300%255=45, so it would be light again. Hmm, that might look like a wave pattern. Not ideal, but perhaps better than nothing.
Alternatively, maybe just use the distance to set the color channel, but with a fixed scaling factor. Let's choose 255 / (maxPossible distance). But how to compute max possible distance? Since the grid is 50x50, and each step can have a cost up to 10, the max possible distance is (50*2-2) * 10 (since the farthest is diagonally opposite, but moving via adjacent cells). Wait, the maximum path is the Manhattan distance between opposite corners: 49+49=98 steps. Each step can have a cost of 10, so max distance is 98*10= 980. So 980 would be the maximum possible distance. To scale that down to 255, multiply by 255/980 ≈0.26. So for a distance of d, the color value would be d * 0.26. So 980*0.26≈255. So that would give a gradient from 0 (white) to 255 (black). But in code, perhaps using:
var color = Math.min(d * 255 / 980, 255);
But 980 is arbitrary, but maybe that's okay. Alternatively, let's just use (d * 2) % 255, but that might loop. Hmm. Alternatively, for the color, the distance is used as the grayscale value, even if it's over 255, but the browser will clamp it to 255. So, for distance 300, it would be 255, for 400 it's still 255. That would make the highest distances all black. That might be okay.
Alternatively, maybe just use the distance as the value and let it overflow. So:
ctx.fillStyle = 'rgb(' + (distance * 2) + ',' + (distance * 2) + ',' + (distance * 2) + ')';
So that 0 is black (wait, no: 0*2 is 0, so RGB(0,0,0) is black. But the source is 0, so that would be black, which is bad. Hmm, that's a problem. So maybe invert it. Let's think again. The source has distance 0. To have the source be white, maybe:
colorValue = 255 - (distance * scalingFactor);
Scaling factor such that max distance (say 1000) would be 255. Let's say scalingFactor is 0.25, then 1000*0.25 is 250. So colorValue would be 5. That might not be enough. Hmm.
Alternatively, for distance 0 (source), set to white. Then, higher distances get darker:
colorValue = 255 - (distance * 255 / maxDistanceEstimate). But without knowing maxDistanceEstimate, perhaps just use:
colorValue = 255 - (distance * 0.5);
So for distance 500, that would be 255 - 250 = 5, which is dark. But for distance 255*2=510, it would be negative so 0. So capped at 0.
Thus, in code:
var g = Math.max(255 - (distance * 0.5), 0);
Then the color is rgb(g,g,g). That way, the source (distance 0) is 255 (white), and higher distances get darker. Even if the distance exceeds 510, it'll be 0 (black). That might work.
Okay, moving on.
The next step: setting up the grid. Each cell's cost is a random integer between 1 and 10, maybe. So in setup:
function init() {
grid = [];
distances = [];
settled = [];
previous = [];
source = {x: Math.floor(Math.random() * size), y: Math.floor(Math.random() * size)};
// pick target somewhere else, maybe opposite corner or random
target = {x: Math.floor(Math.random() * size), y: Math.floor(Math.random() * size)};
// make sure source and target are different
while (target.x == source.x && target.y == source.y) {
target.x = Math.floor(Math.random() * size);
target.y = Math.floor(Math.random() * size);
}
// generate grid
for (let y =0; y<size; y++) {
grid[y] = [];
distances[y] = [];
settled[y] = [];
previous[y] = [];
for (let x=0; x<size; x++) {
grid[y][x] = Math.floor(Math.random() * 10) + 1; // 1-10
distances[y][x] = Infinity;
settled[y][x] = false;
previous[y][x] = null;
}
}
distances[source.y][source.x] = 0;
settledCount = 0;
pathCost = 0;
// reset path
path = [];
// start algorithm
// maybe a flag running to indicate if it's processing
}
Then, the main function that runs Dijkstra's step by step. Since each frame is a step, we need to process one cell per frame.
The algorithm steps:
In each step, find the cell with the smallest distance among the unsettled cells.
Once found, mark it as settled, then check its neighbors.
But how to do this step by step?
Perhaps in code:
function step() {
let minDist = Infinity;
let current = null;
// find the unsettled cell with smallest distance
for (let y=0; y<size; y++) {
for (let x=0; x<size; x++) {
if (!settled[y][x] && distances[y][x] < minDist) {
minDist = distances[y][x];
current = {x, y};
}
}
}
if (current === null) return; // no more cells, maybe target wasn't reachable. But we choose source and target as different, so should reach.
// mark as settled
settled[current.y][current.x] = true;
settledCount++;
// if this is the target, done
if (current.x == target.x && current.y == target.y) {
// trace path
path = [];
let temp = current;
while (temp !== null) {
path.push([temp.x, temp.y]);
temp = previous[temp.y][temp.x];
}
pathCost = distances[current.y][current.x];
// done, so start new init when animation completes?
// but need to wait until the animation ends
return; // exit step function, so next frame will trigger another step (but since we are done, need to stop)
}
// check neighbors
const dirs = [[-1,0], [1,0], [0,-1], [0,1]]; // up, down, left, right
for (const [dx, dy] of dirs) {
const nx = current.x + dx;
const ny = current.y + dy;
if (nx >=0 && nx < size && ny >=0 && ny < size) {
const newDist = distances[current.y][current.x] + grid[ny][nx]; // assuming moving to (nx, ny) has cost grid[ny][nx]
if (newDist < distances[ny][nx]) {
distances[ny][nx] = newDist;
previous[ny][nx] = current;
}
}
}
}
Wait, but in Dijkstra's algorithm, you usually have a priority queue to efficiently find the next cell. However, doing a linear search here is O(n^2) per step, which might be manageable for a 50x50 grid (2500 cells per step). Each step is one frame (like 1/60th of a second), but 2500 iterations per frame might be slow. 2500 * 50 steps (if the path is 50 steps) would take about 50 frames. Maybe manageable?
Alternatively, to make it faster, use a priority queue, but that complicates the code. Since the user asked to create it, perhaps proceed with the linear search, even if it's less efficient.
Alternatively, maybe set the grid size to 20x20 for better performance. Let me check: the original problem didn't specify size, so maybe 25x25? Let's see. Let me adjust the size in the code to 25 instead of 50. So size = 25.
Wait, but the user didn't specify, so perhaps proceed with 50, but maybe that's too slow. Hmm. Let me see:
In the code, if size is 50, then each step requires 50*50=2500 iterations to find the current node. Then, for each of those, checking 4 directions. But in the step function, the loop to find current is the main cost.
Alternatively, to optimize, pre-process:
Once a cell is settled, it can be ignored. So in the step function, perhaps keep track of all unsettled cells in an array, and iterate through those. That would reduce the search time.
But for simplicity, perhaps proceed with the basic approach, and hope it's manageable.
Now, the drawing function.
The drawing function will loop through each cell and color it based on its state:
- If the cell is the source: bright green or something
- Target: red
- Settled cells: their distance-based gray
- Unsettled: their cost's color (maybe the cost's random color?)
Wait, the original grid has each cell's cost (1-10). Maybe color the cells based on their cost. For example, cost 1 is light gray, 10 is dark gray. So for each cell (x,y), its base color is based on grid[y][x].
So in the drawing function:
function draw() {
ctx.clearRect(0,0, canvas.width, canvas.height);
const cellWidth = canvas.width / size;
const cellHeight = canvas.height / size;
for (let y=0; y<size; y++) {
for (let x=0; x<size; x++) {
ctx.fillStyle = 'rgb(' + (grid[y][x]*25.5) + ',' + (grid[y][x]*25.5) + ',' + (grid[y][x]*25.5) + ')';
// so cost 1 is 25.5 brightness, up to 255 for cost 10 (10*25.5=255). But since JS uses integers, maybe use grid[y][x]*25
ctx.fillStyle = 'rgb(' + (grid[y][x]*25) + ',' + (grid[y][x]*25) + ',' + (grid[y][x]*25) + ')';
ctx.fillRect(x * cellWidth, y * cellHeight, cellWidth, cellHeight);
if (settled[y][x]) {
const d = distances[y][y][x]; // Wait, no, distances[y][x]
const g = Math.max(255 - (d * 0.5), 0);
ctx.fillStyle = 'rgb(' + g + ',' + g + ',' + g + ')';
ctx.fillRect(x * cellWidth, y * cellHeight, cellWidth, cellHeight);
}
// highlight source and target
if (x === source.x && y === source.y) {
ctx.fillStyle = 'green';
ctx.fillRect(x * cellWidth, y * cellHeight, cellWidth, cellHeight);
}
if (x === target.x && y === target.y) {
ctx.fillStyle = 'red';
ctx.fillRect(x * cellWidth, y * cellHeight, cellWidth, cellHeight);
}
// draw path
if (path.length >0) {
// path is array of [x,y] in reverse order (since we traced back from target)
for (const [px, py] of path) {
if (x === px && y === py) {
ctx.fillStyle = 'yellow';
ctx.fillRect(x * cellWidth, y * cellHeight, cellWidth, cellHeight);
}
}
}
}
}
}
Wait, but the settled cells are overwritten by their distance color, which might be better to draw on top. Maybe the settled cells should be drawn after the base color, so that their gray overrides the cost's base color.
Also, the path is drawn in yellow, so that should be on top of everything.
Also, the source and target colors (green and red) need to be on top as well.
The order in the loop for each cell is important:
1. Base color (cost)
2. Settled color (distance)
3. Source/Target colors
4. Path color
Wait, but path is part of the settled path, so maybe the settled color is under the path. Hmm.
Alternatively, the path can be drawn last.
Alternatively, reorganize:
First draw base cost colors.
Then, for settled cells, draw their distance-based gray. This will overwrite the base color for settled cells.
Then draw source and target (green and red) which will overwrite the settled color if they are the source or target.
Then draw the path in yellow, which will overwrite both.
Wait, but the path is composed of settled cells, so when we draw the settled color, then the path is drawn on top.
Alternatively, maybe:
- Base color for all cells
- For settled cells, draw their distance gray (over base)
- For path cells, draw yellow (over all)
- Source and target colors (over everything?)
Hmm, perhaps better to first draw base colors, then settled colors, then source and target, then path.
Alternatively, the path should be the last to draw.
Also, the settled cells include the path, so perhaps the path is part of the settled cells, but needs to stand out.
Alternatively, the settled cells are drawn in their gray, then the path is drawn in yellow on top.
The source and target are also on top.
This seems okay.
Another point: the cellWidth and cellHeight calculations. The canvas should have a size that's a multiple of the grid size, or it can just use scaling.
Let me think the canvas is, say, 500x500, and grid size 25, so each cell is 20x20.
But in code, perhaps set the canvas width and height to 500.
Now, putting all together.
The overall structure:
HTML:
<canvas id="myCanvas"></canvas>
JS:
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
let size = 25; // grid size
canvas.width = 500;
canvas.height = 500;
var running = false;
var currentStep = 0;
function init() { ... } // as above
function step() { ... } // as above
function draw() { ... } // as above
function animate() {
if (running) {
step();
draw();
requestAnimationFrame(animate);
}
}
// Start the animation after init
init();
animate();
Wait, but the problem is that the step function only processes one step, and then the next frame will process another step. However, the step function as currently written will process one cell per frame, which might be manageable. The animation loop would call step() and draw() each frame.
However, in the step function, after finding the current cell, it checks if it's the target, and if so, computes the path. After that, it needs to stop running so that the animation doesn't continue. But the animation loop is controlled by the 'running' flag. Wait, the 'running' flag is not set here. So perhaps in the init(), set running = true, and when step() finds the target, set running = false, so that the animation stops.
Wait, in the current step function, when the target is reached, it computes the path and then returns. But the animation loop continues to call animate(), so need to set running = false.
So need to adjust:
In step function:
if (x === target.x ...) {
pathCost = ...
running = false; // stop further steps
return;
}
Alternatively, after computing the path, set running = false.
Also, in the init() function, after setting up variables, need to set running = true.
Wait, let me rework the code with these changes.
Full code outline:
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
let size = 25;
canvas.width = 500;
canvas.height = 500;
let source = {x: 0, y: 0}; // example, but better to pick randomly or fixed
let target = {x: size-1, y: size-1};
let grid = []; // 2D array of costs (1-10)
let distances = []; // 2D array for Dijkstra's
let previous = []; // 2D array for tracking path
let path = [];
let pathCost = 0;
let running = false;
function init() {
source.x = 0;
source.y = 0;
target.x = size -1;
target.y = size -1;
// Initialize grid with random costs 1-10
grid = [];
for (let y = 0; y < size; y++) {
grid[y] = [];
for (let x = 0; x < size; x++) {
grid[y][x] = Math.floor(Math.random() * 10) +1; // 1-10
}
}
// Initialize distances and previous
distances = [];
previous = [];
for (let y = 0; y < size; y++) {
distances[y] = new Array(size).fill(Infinity);
previous[y] = new Array(size).fill(null);
}
distances[source.y][source.x] = 0;
// Reset path and cost
path = [];
pathCost = 0;
running = true;
}
function step() {
if (!running) return;
// Find the node with the smallest distance that is not yet settled
let minDist = Infinity;
let current = null;
for (let y = 0; y < size; y++) {
for (let x = 0; x < size; x++) {
if (distances[y][x] < minDist && previous[y][x] === null) { // not settled yet
minDist = distances[y][x];
current = {x, y};
}
}
}
if (!current) {
// No path found?
running = false;
return;
}
// Mark current as settled by setting previous to something?
// Or just proceed.
// Then check if current is target
if (current.x === target.x && current.y === target.y) {
// Found the target
// Trace back path
let temp = current;
path = [];
while (temp !== null) {
path.push({x: temp.x, y: temp.y});
temp = previous[temp.y][temp.x];
}
path.reverse(); // to go from source to target
pathCost = distances[current.y][current.x];
running = false;
return;
}
// Process neighbors
const dirs = [[-1,0], [1,0], [0,-1], [0,1]];
for (const [dx, dy] of dirs) {
const nx = current.x + dx;
const ny = current.y + dy;
if (nx >=0 && nx < size && ny >=0 && ny < size) {
const newDist = distances[current.y][current.x] + grid[ny][nx];
if (newDist < distances[ny][nx]) {
distances[ny][nx] = newDist;
previous[ny][nx] = current; // store pointer to previous node
}
}
}
}
function draw() {
ctx.clearRect(0,0,canvas.width,canvas.height);
const cellWidth = canvas.width / size;
const cellHeight = canvas.height / size;
// Draw grid cells
for (let y = 0; y < size; y++) {
for (let x = 0; x < size; x++) {
// Base color based on grid
const cost = grid[y][x];
const baseColor = Math.floor(cost * 25.5); // 1-255, but better as integers
ctx.fillStyle = `rgb(${baseColor}, ${baseColor}, ${baseColor})`;
ctx.fillRect(x * cellWidth, y * cellHeight, cellWidth, cellHeight);
// Settled cells (distance-based gray)
if (previous[y][x] !== null) { // settled
const dist = distances[y][x];
let gray = Math.max(255 - (dist * 0.5), 0);
ctx.fillStyle = `rgb(${gray}, ${gray}, ${gray})`;
ctx.fillRect(x * cellWidth, y * cellHeight, cellWidth, cellHeight);
}
// Draw source and target
if (x === source.x && y === source.y) {
ctx.fillStyle = 'green';
ctx.fillRect(x * cellWidth, y * cellHeight, cellWidth, cellHeight);
}
if (x === target.x && y === target.y) {
ctx.fillStyle = 'red';
ctx.fillRect(x * cellWidth, y * cellHeight, cellWidth, cellHeight);
}
// Draw path
if (path.length > 0) {
const cell = {x: x, y: y};
if (path.some(p => p.x === x && p.y === y)) {
ctx.fillStyle = 'yellow';
ctx.fillRect(x * cellWidth, y * cellHeight, cellWidth, cellHeight);
}
}
}
}
}
function animate() {
if (running) {
step();
}
draw();
requestAnimationFrame(animate);
}
init();
animate();
Wait, but in the step function, the current node is selected as the one with minimum distance and not settled yet (previous[y][x] === null). However, in Dijkstra's algorithm, settled nodes are those that have been processed, so perhaps we should have a separate array to track settled nodes. Because in the current setup, previous[y][x] is set when the node is updated, but not necessarily settled.
Hmm, this might be an error. The previous array is used to track the path, but not to indicate whether the node has been settled (i.e., processed). So perhaps need a separate 'settled' array.
Let me revise the step function:
Add a 2D array called settled.
Initialize:
settled = [];
for (let y =0; y < size; y++) {
settled[y] = new Array(size).fill(false);
}
Then in step:
let minDist = Infinity;
let current = null;
for (let y =0; y < size; y++) {
for (let x =0; x < size; x++) {
if (!settled[y][x] && distances[y][x] < minDist) {
minDist = distances[y][x];
current = {x, y};
}
}
}
Then after choosing current:
settled[current.y][current.x] = true;
This way, once a node is processed, it's marked as settled and won't be considered again.
Also, in the draw function, to check if settled:
if (settled[y][x]) {
So, adjusting the code accordingly.
Also, in the initialization of previous array, previous should be set to null for all nodes, except maybe source?
Yes.
Additionally, in the step function, after selecting current, before processing neighbors, we need to mark it as settled.
These changes are important to track settled nodes properly.
Let me adjust the code:
Adding 'settled' array:
Initialize in init():
settled = [];
for (let y =0; y < size; y++) {
settled[y] = new Array(size).fill(false);
}
step function:
function step() {
if (!running) return;
// Find the node with the smallest distance that is not yet settled
let minDist = Infinity;
let current = null;
for (let y = 0; y < size; y++) {
for (let x = 0; x < size; x++) {
if (!settled[y][x] && distances[y][x] < minDist) {
minDist = distances[y][x];
current = {x, y};
}
}
}
if (!current) {
// No path found
running = false;
return;
}
// Mark as settled
settled[current.y][current.x] = true;
// Check if current is target
if (current.x === target.x && current.y === target.y) {
// Trace path
let temp = current;
path = [];
while (temp !== null) {
path.push({x: temp.x, y: temp.y});
temp = previous[temp.y][temp.x];
}
path.reverse();
pathCost = distances[current.y][current.x];
running = false;
return;
}
// Process neighbors
const dirs = [[-1,0], [1,0], [0,-1], [0,1]];
for (const [dx, dy] of dirs) {
const nx = current.x + dx;
const ny = current.y + dy;
if (nx >=0 && nx < size && ny >=0 && ny < size) {
const newDist = distances[current.y][current.x] + grid[ny][nx];
if (newDist < distances[ny][nx]) {
distances[ny][nx] = newDist;
previous[ny][nx] = current; // Store the previous node
}
}
}
}
Also, in the draw function, to check settled[y][x]:
// Settled cells (distance-based gray)
if (settled[y][x]) { // settled nodes are those processed
const dist = distances[y][x];
let gray = Math.max(255 - (dist * 0.5), 0);
ctx.fillStyle = `rgb(${gray}, ${gray}, ${gray})`;
ctx.fillRect(x * cellWidth, y * cellHeight, cellWidth, cellHeight);
}
This should fix the issue where nodes were not properly tracked as settled.
Now, with these changes, the code should work correctly.
Testing the code, when the path is found, the path should be highlighted in yellow, and the settled nodes in gray.
Also, the source is green, target red.
Possible issues:
- The path tracing might have an off-by-one error or incorrect direction. In the while loop:
temp = previous[temp.y][temp.x]; // but previous is storing the previous node, so when temp is current, the next is previous of that.
Wait, in the step function, when we set previous[ny][nx] = current; // So previous of ny,nx is current.
So when tracing back from target:
temp starts at target node.
previous[target.y][target.x] would be the previous node that led to it.
So the loop:
temp = current (the target node)
then temp = previous[temp.y][temp.x]
Yes, that's correct.
Testing this should correctly trace back to the source.
Another possible issue: in the draw function's path check:
if (path.some(p => p.x === x && p.y === y)) {
This will loop through the path array every cell, which could be inefficient for large grids, but for 25x25 it's okay.
Alternatively, could use a hash map.
But for simplicity, this is acceptable.
Lastly, in the initial setup:
source is at (0,0), target at (size-1, size-1).
The grid initialization uses Math.floor(Math.random() *10)+1, which gives 1-10 (since Math.random() *10 is 0-10, floor gives 0-9, plus1 becomes 1-10). That's correct.
Testing the code with these changes should work.
Therefore, the final code is as above.