Skip to content

Cleaned code a bit #4

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
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
9 changes: 9 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#
# https://help.github.com/articles/dealing-with-line-endings/
#
# Linux start script should use lf
/gradlew text eol=lf

# These are Windows script files and should use crlf
*.bat text eol=crlf

7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
*.idea
out/**
*.iml
*.iml
# Ignore Gradle project-specific cache directory
.gradle

# Ignore Gradle build output directory
build
53 changes: 47 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,50 @@
# A-Star-Java-Implementation
A* also called A Star, algorithm java implementation

This is a java implementation of the A Star algorithm. I couldn't find any good java implementations of this famous AI algorithm on the web so I decided to make my own.
A*, also called A Star, is a graph traversal and path search algorithm, which is used in many fields of computer science due to its completeness, optimality, and optimal efficiency.

The implementation includes 3 files:
- AStar.java : Main algorithm class.
- Node.java : Class for the nodes used by the algorithm.
- AStarTest.java : Class with a main method and a simple test for the algorithm implementation
The implementation includes several files:
- AStar.java: Main algorithm class.
- Node.java: Class for the nodes used by the algorithm.
- searchstrategy package: Strategy pattern with several map search algorithms:
1. Diagonals - when enabled, also considers diagonal moves
2. Manhattan distance - default mode, only considers vertical and horizontal moves


## A* specifications

- For node n, gScore[n] is the cost of the cheapest path currently known from start to n.
- For node n, fScore[n] = gScore[n] + heuristic(n), where gScore(n) is the distance from the starting node to n
and h(n) is a heuristic value of estimation distance from node n to finish node.
- h(n) is a heuristic value of estimation distance from node n to finish node

## Example provided

### Search Area

0 1 2 3 4 5 6
0 - - - - - - -
1 - - - B - - -
2 - I - B - F -
3 - - - B - - -
4 - - - - - - -
5 - - - - - - -

### Search Path with diagonals

0 1 2 3 4 5 6
0 - - - * - - -
1 - - * B * - -
2 - I* - B - *F -
3 - - - B - - -
4 - - - - - - -
5 - - - - - - -

### Search Path without diagonals

0 1 2 3 4 5 6
0 - - * * * - -
1 - - * B * - -
2 - I* * B * *F -
3 - - - B - - -
4 - - - - - - -
5 - - - - - - -
37 changes: 37 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* This file was generated by the Gradle 'init' task.
*
* This generated file contains a sample Java application project to get you started.
* For more details take a look at the 'Building Java & JVM projects' chapter in the Gradle
* User Manual available at https://docs.gradle.org/7.6/userguide/building_java_projects.html
*/

plugins {
// Apply the application plugin to add support for building a CLI application in Java.
application
}

repositories {
// Use Maven Central for resolving dependencies.
mavenCentral()
}

dependencies {
// Use JUnit Jupiter for testing.
testImplementation("org.junit.jupiter:junit-jupiter:5.9.1")

// This dependency is used by the application.
implementation("com.google.guava:guava:31.1-jre")

testImplementation("org.hamcrest:hamcrest:2.2")
}

application {
// Define the main class for the application.
mainClass.set("com.ai.astar.App")
}

tasks.named<Test>("test") {
// Use JUnit Platform for unit tests.
useJUnitPlatform()
}
126 changes: 126 additions & 0 deletions app/src/main/java/com/ai/astar/domain/AStar.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package com.ai.astar.domain;

import com.ai.astar.domain.searchstrategy.DiagonalMapChecker;
import com.ai.astar.domain.searchstrategy.ManhattanMapChecker;
import com.ai.astar.domain.searchstrategy.MapChecker;
import com.ai.astar.domain.searchstrategy.NoOpChecker;

import java.util.*;

public class AStar {
private static final int DEFAULT_MANHATTAN_COST = 10;
private static final int DEFAULT_DIAGONAL_COST = 14;
private final Node[][] searchArea;
private final PriorityQueue<Node> openList;
private final Set<Node> closedSet;
private final Node initialNode;
private final Node finalNode;
private final MapChecker diagonalsChecker;
private final MapChecker manhattanChecker;

public AStar(
int rows,
int cols,
Node initialNode,
Node finalNode,
int[][] blocksArray,
boolean searchDiagonals,
int diagonalCost,
int hvCost) {
this.initialNode = initialNode;
this.finalNode = finalNode;
this.searchArea = new Node[rows][cols];
this.openList = new PriorityQueue<>(Comparator.comparingInt(Node::fScore));
initNodes();
initBlocks(blocksArray);
this.closedSet = new HashSet<>();
if (searchDiagonals) {
this.diagonalsChecker = new DiagonalMapChecker(searchArea, openList, closedSet, diagonalCost);
} else {
this.diagonalsChecker = new NoOpChecker(null, null, null);
}
this.manhattanChecker = new ManhattanMapChecker(searchArea, openList, closedSet, hvCost);
}

public AStar(int rows, int cols, Node initialNode, Node finalNode, int[][] blocksArray, boolean searchDiagonals) {
this(rows, cols, initialNode, finalNode, blocksArray, searchDiagonals, DEFAULT_DIAGONAL_COST, DEFAULT_MANHATTAN_COST);
}

private void initNodes() {
for (int i = 0; i < searchArea.length; i++) {
for (int j = 0; j < searchArea[0].length; j++) {
Node node = Node.of(i, j);
node.calculateHeuristic(finalNode);
this.searchArea[i][j] = node;
}
}
}

private void initBlocks(int[][] blocksArray) {
for (int[] block : blocksArray) {
int row = block[0];
int col = block[1];
if (row < 0 || row >= searchArea.length) {
continue;
}
if (col < 0 || col >= searchArea[0].length) {
continue;
}
this.searchArea[row][col].setAsBlocked();
}
}

public List<Node> findPath() {
openList.add(initialNode);
while (!openList.isEmpty()) {
Node currentNode = openList.poll();
closedSet.add(currentNode);
if (isFinalNode(currentNode)) {
return bestPath(currentNode);
} else {
addAdjacentNodes(currentNode);
}
}
return new ArrayList<>();
}

private List<Node> bestPath(Node currentNode) {
List<Node> path = new ArrayList<>();
path.add(currentNode);
Node parent;
while ((parent = currentNode.parent()) != null) {
path.add(0, parent);
currentNode = parent;
}
return path;
}

private void addAdjacentNodes(Node currentNode) {
int row = currentNode.row();
int col = currentNode.col();
addAdjacentUpperRow(currentNode, row, col);
addAdjacentMiddleRow(currentNode, row, col);
addAdjacentLowerRow(currentNode, row, col);
}

private void addAdjacentLowerRow(Node currentNode, int row, int col) {
diagonalsChecker.checkNode(currentNode, col, row + 1);
manhattanChecker.checkNode(currentNode, col, row + 1);
}

private void addAdjacentMiddleRow(Node currentNode, int row, int col) {
manhattanChecker.checkNode(currentNode, col - 1, row);
manhattanChecker.checkNode(currentNode, col + 1, row);
}

private void addAdjacentUpperRow(Node currentNode, int row, int col) {
diagonalsChecker.checkNode(currentNode, col, row - 1);
manhattanChecker.checkNode(currentNode, col, row - 1);
}

private boolean isFinalNode(Node currentNode) {
return currentNode.equals(finalNode);
}

}

99 changes: 99 additions & 0 deletions app/src/main/java/com/ai/astar/domain/Node.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package com.ai.astar.domain;

public class Node {

private int gScore;
private int fScore;
private int h;
private final int row;
private final int col;
private boolean isBlocked;
private Node parent;

private Node(int row, int col) {
super();
this.row = row;
this.col = col;
}

public static Node of(int row, int col) {
return new Node(row, col);
}

public void calculateHeuristic(Node finalNode) {
this.h = Math.abs(finalNode.row() - row) + Math.abs(finalNode.col() - col);
}

public void updateNode(Node currentNode, int cost) {
this.parent = currentNode;
updateGScore(currentNode.gScore(), cost);
updateFScore();
}

public boolean checkBetterPath(Node currentNode, int cost) {
int updatedScore = currentNode.gScore() + cost;
if (updatedScore < gScore()) {
updateNode(currentNode, cost);
return true;
}
return false;
}

public int gScore() {
return gScore;
}

private void updateGScore(int currentGScore, int cost) {
this.gScore = currentGScore + cost;
}

public int fScore() {
return fScore;
}

private void updateFScore() {
this.fScore = gScore + h;
}

public Node parent() {
return parent;
}

public boolean isBlocked() {
return isBlocked;
}

public void setAsBlocked() {
this.isBlocked = true;
}

public int row() {
return row;
}

public int col() {
return col;
}

@Override
public int hashCode() {
int result = row;
result = 31 * result + col;
return result;
}

@Override
public boolean equals(Object arg0) {
if (arg0 == null) {
return false;
}
Node other = (Node) arg0;
return this.row() == other.row() && this.col() == other.col();
}

@Override
public String toString() {
return "Node [row=" + row + ", col=" + col + "]";
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.ai.astar.domain.searchstrategy;

import com.ai.astar.domain.Node;

import java.util.PriorityQueue;
import java.util.Set;

public class DiagonalMapChecker extends MapChecker {

private final int diagonalCost;

public DiagonalMapChecker(Node[][] searchArea, PriorityQueue<Node> openList, Set<Node> closedSet, int diagonalCost) {
super(searchArea, openList, closedSet);
this.diagonalCost = diagonalCost;
}

@Override
public void checkNode(Node currentNode, int col, int row) {
check(currentNode, col - 1, row, diagonalCost);
check(currentNode, col + 1, row, diagonalCost);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.ai.astar.domain.searchstrategy;

import com.ai.astar.domain.Node;

import java.util.PriorityQueue;
import java.util.Set;

public class ManhattanMapChecker extends MapChecker {

private final int manhattanCost;

public ManhattanMapChecker(Node[][] searchArea, PriorityQueue<Node> openList, Set<Node> closedSet, int manhattanCost) {
super(searchArea, openList, closedSet);
this.manhattanCost = manhattanCost;
}

@Override
public void checkNode(Node currentNode, int col, int row) {
check(currentNode, col, row, manhattanCost);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.ai.astar.domain.searchstrategy;

import com.ai.astar.domain.Node;

public interface MapCheck {

void checkNode(Node currentNode, int col, int row);

}
Loading