Skip to content

Commit

Permalink
A* & Greedy path finder impl
Browse files Browse the repository at this point in the history
  • Loading branch information
sadanandpai committed Mar 15, 2024
1 parent e624b95 commit ce46d0d
Show file tree
Hide file tree
Showing 8 changed files with 264 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ interface DrawWallConfig {
end: number;
}

function getRandomEvenNumber(min: number, max: number) {
return Math.floor((Math.random() * (max - min + 1)) / 2) * 2 + min;
export function getRandomEvenNumber(min: number, max: number) {
const random = Math.floor(Math.random() * (max - min)) + min;
return random % 2 === 0 ? random : random + 1;
}

function getRandomOddNumber(min: number, max: number) {
return Math.floor((Math.random() * (max - min)) / 2) * 2 + 1 + min;
export function getRandomOddNumber(min: number, max: number) {
const random = Math.floor(Math.random() * (max - min)) + min;
return random % 2 === 1 ? random : random + 1;
}

async function drawHorizontalWall(
Expand Down
136 changes: 136 additions & 0 deletions src/apps/path-finder/algorithms/path-finder/a-star.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import { generateGrid } from '../../helpers/grid';
import { SearchAlgoProps, Cell, CellType } from '../../models/interfaces';

interface CostCell extends Cell {
f: number;
g: number;
h: number;
}

function getCostGrid(rows: number, cols: number): CostCell[][] {
return Array.from({ length: rows }, (_, row) =>
Array.from({ length: cols }, (_, col) => ({ row, col, f: 0, g: 0, h: 0 }))
);
}

function getManhattanDistance(cell: Cell, exit: Cell) {
return Math.abs(cell.row - exit.row) + Math.abs(cell.col - exit.col);
}

function getMinimumCostCell(costCells: CostCell[]) {
let idx = 0;
let minCostCell = costCells[idx];
for (let i = 1; i < costCells.length; i++) {
if (
costCells[i].f < minCostCell.f ||
(costCells[i].f === minCostCell.f && costCells[i].h < minCostCell.h)
) {
minCostCell = costCells[i];
idx = i;
}
}

return { minCostCell, idx };
}

function updateCostCell( // update cost cell
costCell: CostCell,
newCostCell: CostCell,
open: CostCell[]
) {
if (open.includes(costCell)) {
if (newCostCell.g >= costCell.g) {
return false;
}
}

costCell.f = newCostCell.f;
costCell.g = newCostCell.g;
costCell.h = newCostCell.h;
return true;
}

function exploreNeighbors(
grid: CellType[][],
costGrid: CostCell[][],
open: CostCell[],
closed: Set<CostCell>,
parents: Cell[][],
current: CostCell,
exit: Cell
) {
const rows = costGrid.length;
const cols = costGrid[0].length;
const neighbors = [
{ row: current.row - 1, col: current.col },
{ row: current.row, col: current.col + 1 },
{ row: current.row + 1, col: current.col },
{ row: current.row, col: current.col - 1 },
];

for (const neighbor of neighbors) {
const { row, col } = neighbor;
if (row < 0 || row >= rows || col < 0 || col >= cols) {
continue;
}

const cellType = grid[row][col];
const costCell = costGrid[row][col];
if (cellType === CellType.wall || closed.has(costCell)) {
continue;
}

const g = current.g + 1;
const h = getManhattanDistance(neighbor, exit);
const f = g + h;
const isUpdated = updateCostCell(costCell, { row, col, g, h, f }, open);

if (isUpdated) {
parents[row][col] = { row: current.row, col: current.col };

if (!open.includes(costCell)) {
open.push(costCell);
}
}
}
}

// A* algoirthm
export async function aStar({
grid: stateGrid,
entry,
exit,
updateCells,
}: SearchAlgoProps) {
const grid = stateGrid.map((row) => row.slice());
const rows = grid.length;
const cols = grid[0].length;
const costGrid = getCostGrid(rows, cols);
const open: CostCell[] = [];
const closed = new Set([costGrid[entry.row][entry.col]]);
const parents = generateGrid<Cell>(rows, cols, null);

exploreNeighbors(
grid,
costGrid,
open,
closed,
parents,
costGrid[entry.row][entry.col],
exit
);

while (open.length > 0) {
const { minCostCell, idx } = getMinimumCostCell(open);
if (minCostCell.row === exit.row && minCostCell.col === exit.col) {
return { grid, parents };
}

open.splice(idx, 1);
closed.add(minCostCell);
await updateCells(grid, minCostCell, CellType.fill);
exploreNeighbors(grid, costGrid, open, closed, parents, minCostCell, exit);
}

return { grid, parents: null };
}
2 changes: 1 addition & 1 deletion src/apps/path-finder/algorithms/path-finder/bfs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ function getAddToQueueIfAllowedFunction(
}

// The Breadth First Search Algorithm
export async function startBFSAlgo({
export async function breadthFirstSearch({
grid: stateGrid,
entry,
exit,
Expand Down
2 changes: 1 addition & 1 deletion src/apps/path-finder/algorithms/path-finder/dfs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { SearchAlgoProps, Cell, CellType } from '../../models/interfaces';
import { generateGrid } from '../../helpers/grid';

// The Depth First Search Algorithm
export async function startDFSAlgo({
export async function depthFirstSearch({
grid: stateGrid,
entry,
exit,
Expand Down
109 changes: 109 additions & 0 deletions src/apps/path-finder/algorithms/path-finder/greedy.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { generateGrid } from '../../helpers/grid';
import { SearchAlgoProps, Cell, CellType } from '../../models/interfaces';

interface CostCell extends Cell {
h: number;
}

function getCostGrid(rows: number, cols: number): CostCell[][] {
return Array.from({ length: rows }, (_, row) =>
Array.from({ length: cols }, (_, col) => ({ row, col, h: 0 }))
);
}

function getManhattanDistance(cell: Cell, exit: Cell) {
return Math.abs(cell.row - exit.row) + Math.abs(cell.col - exit.col);
}

function getMinimumCostCell(costCells: CostCell[]) {
let idx = 0;
let minCostCell = costCells[idx];
for (let i = 1; i < costCells.length; i++) {
if (costCells[i].h < minCostCell.h) {
minCostCell = costCells[i];
idx = i;
}
}

return { minCostCell, idx };
}

function exploreNeighbors(
grid: CellType[][],
costGrid: CostCell[][],
open: CostCell[],
closed: Set<CostCell>,
parents: Cell[][],
current: CostCell,
exit: Cell
) {
const rows = costGrid.length;
const cols = costGrid[0].length;
const neighbors = [
{ row: current.row - 1, col: current.col },
{ row: current.row, col: current.col + 1 },
{ row: current.row + 1, col: current.col },
{ row: current.row, col: current.col - 1 },
];

for (const neighbor of neighbors) {
const { row, col } = neighbor;
if (row < 0 || row >= rows || col < 0 || col >= cols) {
continue;
}

const cellType = grid[row][col];
const costCell = costGrid[row][col];
if (
cellType === CellType.wall ||
closed.has(costCell) ||
open.includes(costCell)
) {
continue;
}

costCell.h = getManhattanDistance(neighbor, exit);
parents[row][col] = { row: current.row, col: current.col };
open.push(costCell);
}
}

// A* algoirthm
export async function greedy({
grid: stateGrid,
entry,
exit,
updateCells,
}: SearchAlgoProps) {
const grid = stateGrid.map((row) => row.slice());
const rows = grid.length;
const cols = grid[0].length;
const costGrid = getCostGrid(rows, cols);
const open: CostCell[] = [];
const closed = new Set([costGrid[entry.row][entry.col]]);
const parents = generateGrid<Cell>(rows, cols, null);

exploreNeighbors(
grid,
costGrid,
open,
closed,
parents,
costGrid[entry.row][entry.col],
exit
);

while (open.length > 0) {
const { minCostCell, idx } = getMinimumCostCell(open);
if (minCostCell.row === exit.row && minCostCell.col === exit.col) {
return { grid, parents };
}

open.splice(idx, 1);
closed.add(minCostCell);
await updateCells(grid, minCostCell, CellType.fill);
exploreNeighbors(grid, costGrid, open, closed, parents, minCostCell, exit);
}

return { grid, parents: null };
}
12 changes: 8 additions & 4 deletions src/apps/path-finder/algorithms/path-finder/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { startBFSAlgo } from './bfs';
import { startDFSAlgo } from './dfs';
import { aStar } from './a-star';
import { breadthFirstSearch } from './bfs';
import { depthFirstSearch } from './dfs';
import { greedy } from './greedy';

export const pathFinders = new Map([
['bfs', { name: 'BFS', fn: startBFSAlgo }],
['dfs', { name: 'DFS', fn: startDFSAlgo }],
['bfs', { name: 'BFS', fn: breadthFirstSearch }],
['dfs', { name: 'DFS', fn: depthFirstSearch }],
['a-star', { name: 'A*', fn: aStar }],
['greedy', { name: 'Greedy', fn: greedy }],
]);
4 changes: 2 additions & 2 deletions src/apps/path-finder/components/grid/grid.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,12 @@
@keyframes wallAnimation {
0% {
transform: scale(0.3);
opacity: 0.5;
opacity: 0.75;
}

50% {
transform: scale(1.2);
opacity: 0.75;
opacity: 0.9;
}

100% {
Expand Down
2 changes: 1 addition & 1 deletion src/apps/path-finder/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export const cellColors = {
fillStart: 'blue',
fillMid: 'deepskyblue',
fill: 'rgb(225 208 254)',
path: 'deepskyblue',
path: 'blue',
};

const root = document.querySelector(':root') as HTMLElement;
Expand Down

0 comments on commit ce46d0d

Please sign in to comment.