Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,27 @@
# my project
# A* implementations in multiple languages

This repository contains small, self-contained examples of the A* search
algorithm for 4-directional grid maps in Python, JavaScript, and Java.

## Running the examples

Each implementation prints a sample path across the same demo grid:

- **Python**

```bash
python python/astar.py
```

- **JavaScript (Node.js)**

```bash
node javascript/astar.js
```

- **Java**

```bash
javac java/AStar.java
java -cp java AStar
```
91 changes: 91 additions & 0 deletions java/AStar.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import java.util.*;

/** A* search for 4-directional grid maps. */
public class AStar {
public record Point(int row, int col) {
@Override
public String toString() {
return "(" + row + ", " + col + ")";
}
}

private static int manhattan(Point a, Point b) {
return Math.abs(a.row() - b.row()) + Math.abs(a.col() - b.col());
}

private static List<Point> neighbors(Point point, int[][] grid) {
int rows = grid.length;
int cols = grid[0].length;
int[][] deltas = new int[][]{{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
List<Point> results = new ArrayList<>();
for (int[] delta : deltas) {
int nr = point.row() + delta[0];
int nc = point.col() + delta[1];
if (nr >= 0 && nr < rows && nc >= 0 && nc < cols && grid[nr][nc] == 0) {
results.add(new Point(nr, nc));
}
}
return results;
}

private static List<Point> reconstruct(Map<Point, Point> cameFrom, Point current) {
List<Point> path = new ArrayList<>();
path.add(current);
while (cameFrom.containsKey(current)) {
current = cameFrom.get(current);
path.add(current);
}
Collections.reverse(path);
return path;
}

public static List<Point> astar(int[][] grid, Point start, Point goal) {
if (grid[start.row()][start.col()] != 0 || grid[goal.row()][goal.col()] != 0) {
return null;
}

Comparator<Node> comparator = Comparator.comparingInt(node -> node.fScore);
PriorityQueue<Node> open = new PriorityQueue<>(comparator);
Map<Point, Point> cameFrom = new HashMap<>();
Map<Point, Integer> gScore = new HashMap<>();

gScore.put(start, 0);
open.add(new Node(start, manhattan(start, goal), 0));

while (!open.isEmpty()) {
Node current = open.poll();
if (current.point.equals(goal)) {
return reconstruct(cameFrom, current.point);
}

for (Point next : neighbors(current.point, grid)) {
int tentative = current.gScore + 1;
int best = gScore.getOrDefault(next, Integer.MAX_VALUE);
if (tentative < best) {
cameFrom.put(next, current.point);
gScore.put(next, tentative);
int fScore = tentative + manhattan(next, goal);
open.add(new Node(next, fScore, tentative));
}
}
}

return null;
}

private record Node(Point point, int fScore, int gScore) {}

public static void main(String[] args) {
int[][] grid = new int[][]{
{0, 0, 0, 0, 1, 0},
{1, 1, 0, 0, 1, 0},
{0, 0, 0, 0, 0, 0},
{0, 1, 1, 1, 1, 0},
{0, 0, 0, 0, 0, 0},
};
Point start = new Point(0, 0);
Point goal = new Point(4, 5);
List<Point> path = astar(grid, start, goal);
System.out.println("Grid path: " + path);
}
}
98 changes: 98 additions & 0 deletions javascript/astar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// A* search for 4-directional grid maps.

function manhattan(a, b) {
return Math.abs(a[0] - b[0]) + Math.abs(a[1] - b[1]);
}

function neighbors([row, col], grid) {
const results = [];
const rows = grid.length;
const cols = grid[0].length;
const deltas = [
[1, 0],
[-1, 0],
[0, 1],
[0, -1],
];

for (const [dr, dc] of deltas) {
const nr = row + dr;
const nc = col + dc;
if (nr >= 0 && nr < rows && nc >= 0 && nc < cols && grid[nr][nc] === 0) {
results.push([nr, nc]);
}
}

return results;
}

function reconstructPath(cameFrom, current) {
const path = [current];
while (cameFrom.has(key(current))) {
current = cameFrom.get(key(current));
path.push(current);
}
return path.reverse();
}

function key([r, c]) {
return `${r},${c}`;
}

function astar(grid, start, goal) {
if (grid[start[0]][start[1]] !== 0 || grid[goal[0]][goal[1]] !== 0) {
return null;
}

const open = [];
const cameFrom = new Map();
const gScore = new Map([[key(start), 0]]);

const push = (node, g) => {
const f = g + manhattan(node, goal);
open.push({ node, g, f });
open.sort((a, b) => a.f - b.f);
};

push(start, 0);

while (open.length > 0) {
const { node: current, g: currentG } = open.shift();
if (current[0] === goal[0] && current[1] === goal[1]) {
return reconstructPath(cameFrom, current);
}

for (const next of neighbors(current, grid)) {
const tentativeG = currentG + 1;
const nextKey = key(next);
const best = gScore.get(nextKey) ?? Number.POSITIVE_INFINITY;
if (tentativeG < best) {
cameFrom.set(nextKey, current);
gScore.set(nextKey, tentativeG);
push(next, tentativeG);
}
}
}

return null;
}

function demo() {
const grid = [
[0, 0, 0, 0, 1, 0],
[1, 1, 0, 0, 1, 0],
[0, 0, 0, 0, 0, 0],
[0, 1, 1, 1, 1, 0],
[0, 0, 0, 0, 0, 0],
];
const start = [0, 0];
const goal = [4, 5];
const path = astar(grid, start, goal);
console.log("Grid path:", path);
}

if (require.main === module) {
demo();
}

module.exports = { astar, manhattan };
83 changes: 83 additions & 0 deletions python/astar.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
"""A simple A* search implementation for 4-directional grid maps."""

from __future__ import annotations

import heapq
from typing import Dict, Iterable, List, Optional, Tuple

Grid = List[List[int]]
Point = Tuple[int, int]


def manhattan(a: Point, b: Point) -> int:
"""Return Manhattan distance between two points."""
return abs(a[0] - b[0]) + abs(a[1] - b[1])


def neighbors(point: Point, grid: Grid) -> Iterable[Point]:
"""Yield walkable neighbors of ``point`` on the grid."""
rows, cols = len(grid), len(grid[0])
row, col = point
for dr, dc in ((1, 0), (-1, 0), (0, 1), (0, -1)):
nr, nc = row + dr, col + dc
if 0 <= nr < rows and 0 <= nc < cols and grid[nr][nc] == 0:
yield (nr, nc)


def reconstruct_path(came_from: Dict[Point, Point], current: Point) -> List[Point]:
"""Reconstruct the path from the goal back to the start."""
path: List[Point] = [current]
while current in came_from:
current = came_from[current]
path.append(current)
path.reverse()
return path


def astar(grid: Grid, start: Point, goal: Point) -> Optional[List[Point]]:
"""Find the lowest-cost path between ``start`` and ``goal``.

The grid must be a 2D list where ``0`` marks walkable tiles and any other
value is treated as an obstacle. Returns ``None`` when no path exists.
"""

if grid[start[0]][start[1]] != 0 or grid[goal[0]][goal[1]] != 0:
return None

open_heap: List[Tuple[int, int, Point]] = []
heapq.heappush(open_heap, (manhattan(start, goal), 0, start))

came_from: Dict[Point, Point] = {}
g_score: Dict[Point, int] = {start: 0}

while open_heap:
_, cost, current = heapq.heappop(open_heap)
if current == goal:
return reconstruct_path(came_from, current)

for neighbor in neighbors(current, grid):
tentative_cost = cost + 1
if tentative_cost < g_score.get(neighbor, float("inf")):
came_from[neighbor] = current
g_score[neighbor] = tentative_cost
f_score = tentative_cost + manhattan(neighbor, goal)
heapq.heappush(open_heap, (f_score, tentative_cost, neighbor))

return None


def demo() -> None:
grid = [
[0, 0, 0, 0, 1, 0],
[1, 1, 0, 0, 1, 0],
[0, 0, 0, 0, 0, 0],
[0, 1, 1, 1, 1, 0],
[0, 0, 0, 0, 0, 0],
]
start, goal = (0, 0), (4, 5)
path = astar(grid, start, goal)
print("Grid path:", path)


if __name__ == "__main__":
demo()