Related
I have the below code for a A* pathfinder, however it is taking upwards of 10 minutes to find a solution using a simple 1024 x 1024 array.
I had to comment out //Collections.sort(this.openList); as it was throwing a comparison method violates its general contract! error when running.
Is the algorithm correct and any idea why the bottleneck? Some people using C++ are getting a response time of 40ms, not 10+ mins!
When using the maze array it does it in the blink of an eye, but thats using something like a 14x10 array, rather than 1024 from the collisionMap.
Is the method flawed in some way? Or using the wrong data structures?
import java.util.List;
import javax.imageio.ImageIO;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.GraphicsConfiguration;
import java.awt.Paint;
import java.awt.image.BufferedImage;
import java.io.Console;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
class AStarTwo {
// Closed list, open list and calculatedPath lists
private final List<Node> openList;
private final List<Node> closedList;
private final List<Node> calcPath;
// Collision Map to store tha map in
private final int[][] collisionMap;
// Current node the program is executing
private Node currentNode;
// Define the start and end coords
private final int xstart;
private final int ystart;
private int xEnd, yEnd;
// Node class
static class Node implements Comparable {
public Node parent;
public int x, y;
public double g;
public double h;
Node(Node parent, int xpos, int ypos, double g, double h) {
this.parent = parent;
this.x = xpos;
this.y = ypos;
this.g = g;
this.h = h;
}
// Compare f value (g + h)
#Override
public int compareTo(Object o) {
Node that = (Node) o;
return (int)((this.g + this.h) - (that.g + that.h));
}
}
// construct and initialise
public AStarTwo(int[][] collisionMap, int xstart, int ystart) {
this.openList = new ArrayList<>();
this.closedList = new ArrayList<>();
this.calcPath = new ArrayList<>();
this.collisionMap = collisionMap;
this.currentNode = new Node(null, xstart, ystart, 0, 0);
this.xstart = xstart;
this.ystart = ystart;
}
// returns a List<> of nodes to target
public List<Node> findPathTo(int xTo, int yTo) {
this.xEnd = xTo;
this.yEnd = yTo;
// Add this to the closed list
this.closedList.add(this.currentNode);
// Add neighbours to openList for iteration
addNeigborsToOpenList();
// Whilst not at our target
while (this.currentNode.x != this.xEnd || this.currentNode.y != this.yEnd) {
// If nothing in the open list then return with null - handled in error message in main calling func
if (this.openList.isEmpty()) {
return null;
}
// get the lowest f value and add it to the closed list, f calculated when neighbours are sorted
this.currentNode = this.openList.get(0);
this.openList.remove(0);
this.closedList.add(this.currentNode);
addNeigborsToOpenList();
}
// add this node to the calculated path
this.calcPath.add(0, this.currentNode);
while (this.currentNode.x != this.xstart || this.currentNode.y != this.ystart) {
this.currentNode = this.currentNode.parent;
this.calcPath.add(0, this.currentNode);
}
return this.calcPath;
}
// Searches the current list for neighbouring nodes returns bool
private static boolean checkNeighbourHasBeenSearched(List<Node> array, Node node) {
return array.stream().anyMatch((n) -> (n.x == node.x && n.y == node.y));
}
// Calculate distance from current node to the target
private double distance(int dx, int dy) {
return Math.hypot(this.currentNode.x + dx - this.xEnd, this.currentNode.y + dy - this.yEnd); // return hypothenuse
}
// Add neighbouring nodes to the open list to iterate through next
#SuppressWarnings("unchecked")
private void addNeigborsToOpenList() {
Node node;
for (int x = -1; x <= 1; x++) {
for (int y = -1; y <= 1; y++) {
node = new Node(this.currentNode, this.currentNode.x + x, this.currentNode.y + y, this.currentNode.g, this.distance(x, y));
// if we are not on the current node
if ((x != 0 || y != 0)
&& this.currentNode.x + x >= 0 && this.currentNode.x + x < this.collisionMap[0].length // check collision map boundaries
&& this.currentNode.y + y >= 0 && this.currentNode.y + y < this.collisionMap.length
&& this.collisionMap[this.currentNode.y + y][this.currentNode.x + x] != -1) { // check if tile is walkable (-1)
// and finally check we haven't already searched the nodes
if(!checkNeighbourHasBeenSearched(this.openList, node) && !checkNeighbourHasBeenSearched(this.closedList, node)){
node.g = node.parent.g + 1.; // Horizontal/vertical cost = 1.0
node.g += collisionMap[this.currentNode.y + y][this.currentNode.x + x]; // add movement cost for this square
// Add diagonal movement cost sqrt(hor_cost² + vert_cost²) + 0.4
if (x != 0 && y != 0) {
node.g += .4;
}
// Add the node to the List<>
this.openList.add(node);
}
}
}
}
// sort in ascending order
//Collections.sort(this.openList);
}
public static void main(String[] args) {
int [][] maze =
{ {1,1,1,1,1,1,1,1,1,1,1,1,1},
{1,0,-1,0,-1,0,1,0,0,0,0,0,1},
{1,0,-1,0,0,0,1,0,1,1,1,0,1},
{1,0,0,0,-1,-1,-1,0,0,0,0,0,1},
{1,0,1,0,0,0,0,0,1,1,1,0,1},
{1,0,1,0,-1,-1,-1,0,1,0,0,0,-1},
{1,0,-1,0,-1,0,0,0,-1,-1,-1,0,-1},
{1,0,1,0,-1,-1,-1,0,1,0,-1,0,-1},
{1,0,0,0,0,0,0,0,0,0,1,0,1},
{1,1,1,1,1,1,1,1,1,1,1,1,1}
};
// Define the size of the grid
final int sizeOf = 20;
int[][] collisionMap = new int[sizeOf][];
for(int i=0;i < sizeOf; i++) {
// -1 = blocked
// 0+ = cost
collisionMap[i] = new int[sizeOf];
}
// set the value of the nodes
for (int k = 0; k < sizeOf; k++) {
for (int j = 0; j < sizeOf; j++) {
if(j == 30 && k < 100) {
collisionMap[k][j] = -1;
} else if (j == 50 && k > 230) {
collisionMap[k][j] = -1;
}else {
collisionMap[k][j] = 0;
}
}
}
AStarTwo as = new AStarTwo(maze, 9, 9);
List<Node> path = as.findPathTo(0,0);
if(path == null) {
System.out.println("Unable to reach target");
}
// create image buffer to write output to
BufferedImage img = new BufferedImage(sizeOf, sizeOf, BufferedImage.TYPE_INT_RGB);
// Set colours
int r = 255;
int g = 0;
int b = 0;
int colRed = (r << 16) | (g << 8) | b;
r = 0;
g = 255;
b = 0;
int colGreen = (r << 16) | (g << 8) | b;
r = 0;
g = 0;
b = 255;
int colBlue = (r << 16) | (g << 8) | b;
r = 255;
g = 255;
b = 255;
int colWhite = (r << 16) | (g << 8) | b;
int i = 0;
int j = 0;
if (path != null) {
path.forEach((n) -> {
System.out.print("[" + n.x + ", " + n.y + "] ");
maze[n.y][n.x] = 2;
});
for (int[] maze_row : maze) {
for (int maze_entry : maze_row) {
switch (maze_entry) {
// normal tile
case 0:
img.setRGB(j, i, colWhite);
break;
// final path
case 2:
img.setRGB(j, i, colBlue);
break;
// Object to avoid
case -1:
img.setRGB(j, i, colRed);
break;
// Any other value
default:
img.setRGB(j, i, colWhite);
}
j++;
}
// count j - reset as if it were a for loop
if(i != 12) {
j=0;
}
i++;
System.out.println();
}
}
// output file
File f = new File("aStarPath.png");
try {
ImageIO.write(img, "PNG", f);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("i: " + i + ", j: " + j);
}
}
I suspect your problem is this line:
return array.stream().anyMatch((n) -> (n.x == node.x && n.y == node.y));
which is called around O(n^2) times and will take time proportional to the size of the array (which will also be O(n^2) in the worst case for a n by n maze).
You want a faster method of performing this test.
For example:
Use a set to hold the open and closed lists instead of list
Or use an extra field in the node structure to indicate if it is in the open or closed list
Background: I am making a 2D moba game and i need pathfind for all the monsters in the game. I want to give a startPos and a endPos and have the monster travel there avoiding objects.
Question:
I have been trying to implement path finding into my game for awhile now and i just cant see to get it working. All i want is some method/class where i can give it a 2d array of values (ie. true = occupied & false = free), startPos, endPos and it gives me a list of moves to get to the end. All my implementations have failed thus far. Can anyone help by giving me code that is easy to implement?
Note:
So far i have tried implementing A and it either ignored walls or sent the character into a completely random direction.
*I did get it working but in a ugly and wrong way. I had the charater move forward until it hit and wall. Then it turned right and kept moving until it could turn left and continue towards the destination. This works but i dont think people want their teams monsters running around on walls
Edit:
Code below is now working! I found that for some reason the points were backwards so i had to invert the Point list. No all i need to do is interpolate between points to give smooth movement. However, I do ask is there any way i cant add more bias towards walls. For example making it so the point never goes within 1 unit of a wall?
package NavMesh;
import java.awt.Point;
import java.util.ArrayList;
import java.util.List;
import toolbox.Maths;
public class MovementPath {
private Node[][] mapOriginal;
private Node[][] mapPath;
public boolean solving = true;
public int startX, startY, finishX, finishY, cells;
private int checks = 0;
private int length = 0;
int realStartX, realStartY, realFinishX, realFinishY;
NavMesh mesh;
private Algorithm alg;
List<Point> path = new ArrayList<Point>();
public MovementPath(NavMesh mesh,int startX, int startY, int finishX, int finishY) {
this.mapOriginal = mesh.getMapCopy();
this.mesh = mesh;
this.startX = startX;
this.startY = startY;
this.finishX = finishX;
this.finishY = finishY;
this.cells = mapOriginal.length;
realStartX = startX;
realStartY = startY;
realFinishX = finishX;
realFinishY = finishY;
this.startX = (int) (Math.floor((float) startX / (float) mesh.cellWidth));
this.startY = (int) (Math.floor((float) startY / (float) mesh.cellHeight));
this.finishX = (int) (Math.floor((float) finishX / (float) mesh.cellWidth));
this.finishY = (int) (Math.floor((float) finishY / (float) mesh.cellHeight));
mapPath = new Node[mapOriginal.length][mapOriginal.length];
System.arraycopy(mapOriginal, 0, mapPath, 0, mapOriginal.length);
mapPath[this.startX][this.startY] = new Node(0,this.startX,this.startY);;
mapPath[this.finishX][this.finishY] = new Node(1,this.finishX,this.finishY);
addPointCentered(realFinishX,realFinishY);
alg = new Algorithm();
//alg.AStar();
alg.Dijkstra();
addPointCentered(realStartX,realStartY);
mesh.drawMap(Integer.toString(Maths.randomRange(0, 1000)), mapPath);
}
public Path getPath(){
//System.out.println("Returning path with " + getPathPoints().size() + " points");
return new Path(getPathPoints());
}
private void addPointCentered(int x, int y) {
path.add(new Point(x+(mesh.cellWidth/2),y+(mesh.cellHeight/2)));
}
public List<Point> getPathPoints(){
List<Point> rPath = new ArrayList<Point>();
for(int i = path.size()-1; i >= 0; i--) {
rPath.add(path.get(i));
}
return rPath;
}
class Algorithm { //ALGORITHM CLASS
//A STAR WORKS ESSENTIALLY THE SAME AS DIJKSTRA CREATING A PRIORITY QUE AND PROPAGATING OUTWARDS UNTIL IT FINDS THE END
//HOWEVER ASTAR BUILDS IN A HEURISTIC OF DISTANCE FROM ANY NODE TO THE FINISH
//THIS MEANS THAT NODES THAT ARE CLOSER TO THE FINISH WILL BE EXPLORED FIRST
//THIS HEURISTIC IS BUILT IN BY SORTING THE QUE ACCORDING TO HOPS PLUS DISTANCE UNTIL THE FINISH
public void AStar() {
ArrayList<Node> priority = new ArrayList<Node>();
priority.add(mapPath[startX][startY]);
while(solving) {
if(priority.size() <= 0) {
solving = false;
break;
}
int hops = priority.get(0).getHops()+1;
ArrayList<Node> explored = exploreNeighbors(priority.get(0),hops);
if(explored.size() > 0) {
priority.remove(0);
priority.addAll(explored);
} else {
priority.remove(0);
}
sortQue(priority); //SORT THE PRIORITY QUE
}
}
public void Dijkstra() {
ArrayList<Node> priority = new ArrayList<Node>(); //CREATE A PRIORITY QUE
priority.add(mapPath[startX][startY]); //ADD THE START TO THE QUE
while(solving) {
if(priority.size() <= 0) { //IF THE QUE IS 0 THEN NO PATH CAN BE FOUND
solving = false;
break;
}
int hops = priority.get(0).getHops()+1; //INCREMENT THE HOPS VARIABLE
ArrayList<Node> explored = exploreNeighbors(priority.get(0), hops); //CREATE AN ARRAYLIST OF NODES THAT WERE EXPLORED
if(explored.size() > 0) {
priority.remove(0); //REMOVE THE NODE FROM THE QUE
priority.addAll(explored); //ADD ALL THE NEW NODES TO THE QUE
} else { //IF NO NODES WERE EXPLORED THEN JUST REMOVE THE NODE FROM THE QUE
priority.remove(0);
}
}
}
public ArrayList<Node> sortQue(ArrayList<Node> sort) { //SORT PRIORITY QUE
int c = 0;
while(c < sort.size()) {
int sm = c;
for(int i = c+1; i < sort.size(); i++) {
if(sort.get(i).getEuclidDist(finishX,finishY)+sort.get(i).getHops() < sort.get(sm).getEuclidDist(finishX,finishY)+sort.get(sm).getHops())
sm = i;
}
if(c != sm) {
Node temp = sort.get(c);
sort.set(c, sort.get(sm));
sort.set(sm, temp);
}
c++;
}
return sort;
}
/*
public ArrayList<Node> exploreNeighbors(Node current, int hops) { //EXPLORE NEIGHBORS
ArrayList<Node> explored = new ArrayList<Node>(); //LIST OF NODES THAT HAVE BEEN EXPLORED
for(int a = -1; a <= 1; a++) {
for(int b = -1; b <= 1; b++) {
int xbound = current.getX()+a;
int ybound = current.getY()+b;
if((xbound > -1 && xbound < cells) && (ybound > -1 && ybound < cells)) { //MAKES SURE THE NODE IS NOT OUTSIDE THE GRID
Node neighbor = mapPath[xbound][ybound];
if((neighbor.getHops()==-1 || neighbor.getHops() > hops) && neighbor.getType()!=2) { //CHECKS IF THE NODE IS NOT A WALL AND THAT IT HAS NOT BEEN EXPLORED
explore(neighbor, current.getX(), current.getY(), hops); //EXPLORE THE NODE
explored.add(neighbor); //ADD THE NODE TO THE LIST
}
}
}
}
return explored;
}
*/
public ArrayList<Node> exploreNeighbors(Node current, int hops) { //EXPLORE NEIGHBORS
ArrayList<Node> explored = new ArrayList<Node>(); //LIST OF NODES THAT HAVE BEEN EXPLORED
//test(hops, current, explored,current.getX(),current.getY());
//test(hops, current, explored,current.getX()+1,current.getY());
//test(hops, current, explored,current.getX()-1,current.getY());
//test(hops, current, explored,current.getX(),current.getY()+1);
//test(hops, current, explored,current.getX(),current.getY()-1);
for(int a = -1; a <= 1; a++) {
for(int b = -1; b <= 1; b++) {
test(hops, current, explored,current.getX()+a,current.getY()+b);
}
}
return explored;
}
private void test(int hops, Node current, ArrayList<Node> explored, int xbound, int ybound) {
if((xbound > -1 && xbound < cells) && (ybound > -1 && ybound < cells)) { //MAKES SURE THE NODE IS NOT OUTSIDE THE GRID
Node neighbor = mapPath[xbound][ybound];
if((neighbor.getHops()==-1 || neighbor.getHops() > hops) && neighbor.getType()!=2) { //CHECKS IF THE NODE IS NOT A WALL AND THAT IT HAS NOT BEEN EXPLORED
explore(neighbor, current.getX(), current.getY(), hops); //EXPLORE THE NODE
explored.add(neighbor); //ADD THE NODE TO THE LIST
}
}
}
public void explore(Node current, int lastx, int lasty, int hops) { //EXPLORE A NODE
if(current.getType()!=0 && current.getType() != 1) //CHECK THAT THE NODE IS NOT THE START OR FINISH
current.setType(4); //SET IT TO EXPLORED
current.setLastNode(lastx, lasty); //KEEP TRACK OF THE NODE THAT THIS NODE IS EXPLORED FROM
current.setHops(hops); //SET THE HOPS FROM THE START
checks++;
if(current.getType() == 1) { //IF THE NODE IS THE FINISH THEN BACKTRACK TO GET THE PATH
backtrack(current.getLastX(), current.getLastY(),hops);
}
}
public void backtrack(int lx, int ly, int hops) { //BACKTRACK
length = hops;
while(hops > 1) { //BACKTRACK FROM THE END OF THE PATH TO THE START
Node current = mapPath[lx][ly];
current.setType(5);
addPointCentered(lx*mesh.cellWidth,ly*mesh.cellHeight);
//System.out.println("New Point: " + path.get(path.size()-1).toString());
lx = current.getLastX();
ly = current.getLastY();
hops--;
}
solving = false;
}
}
}
Try A*, I used that for a path finding problem. It is easy to implement for grid based movement and very fast. I implemented it using the pseudocode on the wikipedia page.
trying to create a Conways Game of life, but apparently the shapes are not like they have to be. Perhaps someone can help me find the issue.
For example the glider :
- X - - - -
- - X X - -
- X X - - -
- - - - - -
becomes this
- - X X - -
- X - - - -
X X X - - -
- X X X - -
but should be like this :
- - X - - -
- - - X - -
- X X X - -
- - - - - -
And my code looks like this
public Frame(int x, int y) {
setWidth(x);
setHeight(y);
if (x<1)
frame = null;
else if (y<1)
frame = null;
else {
frame = new String [x][y];
for (int i=0; i<frame.length; i++) {
for (int j=0; j<frame[i].length; j++) {
frame [i][j] = DEAD;
}
}
} // else
} // construktor
public Integer getNeighbourCount(int x, int y) {
Frame cell = new Frame(getHeight(), getWidth());
int counter = 0;
if(frame[x][y].equals(ALIVE))
{
counter = counter - 1;
}
for(int i=x-1; i<=x+1;i++){
if(i<frame.length && i>0){
for(int j=y-1; j<=y+1;j++){
if(j<frame[i].length && j>0){
if (frame[i][j]==ALIVE) {
counter++;
}
}
}
}
}
return counter;
}
public Frame nextFrame() {
// Returns next frame
Frame cell = new Frame(getWidth(), getHeight());
//cell.frame = new String[getWidth()][getHeight()];
for(int i = 0; i < frame.length; i++){
for(int j =0; j <frame[i].length;j++){
int n = getNeighbourCount(i,j);
if(cell.frame[i][j]==null) {
cell.frame[i][j] = DEAD;
}
if (isAlive(i, j) && n < 2 || n > 3) {
cell.frame[i][j] = DEAD;
}
if (isAlive(i, j) && n == 3 || n == 2){
cell.frame[i][j] = ALIVE;
}
if(!isAlive(i, j) && n == 3) {
cell.frame[i][j] = ALIVE;
}
if(isAlive(i, j) && n > 3){
cell.frame[i][j] = DEAD;
}
frame[i][j] = cell.frame[i][j];
}
}
cell.toString();
return cell;
}
`
Full code http://pastebin.com/LMwz724H
Here's a solution that works - using an enum for each cell and getting the i/j and x/y stuff right (I think). It certainly generates the correct first iteration:
static class GameOfLife {
final int w;
final int h;
State[][] frame;
enum State {
Dead, Alive;
}
public GameOfLife(int w, int h) {
this.w = w;
this.h = h;
frame = new State[h][w];
}
public void alive(int x, int y) {
frame[y][x] = State.Alive;
}
public void tick() {
frame = nextGeneration();
}
private int surroundingPopulation(int x, int y) {
int pop = 0;
for (int i = y - 1; i <= y + 1; i++) {
for (int j = x - 1; j <= x + 1; j++) {
// On frame - vertically.
if ((i >= 0 && i < h)
// On frame horizontally.
&& (j >= 0 && j < w)
// Alive
&& (frame[i][j] == State.Alive)
// Not the center.
&& (i != y || j != x)) {
pop += 1;
}
}
}
return pop;
}
private State[][] nextGeneration() {
State[][] next = new State[h][w];
for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++) {
int pop = surroundingPopulation(x, y);
// Any live cell
if (frame[y][x] == State.Alive) {
if (pop < 2) {
// ... with fewer than two live neighbours dies, as if caused by under-population.
next[y][x] = State.Dead;
} else if (pop > 3) {
// ... with more than three live neighbours dies, as if by overcrowding.
next[y][x] = State.Dead;
} else {
// ... with two or three live neighbours lives on to the next generation.
next[y][x] = State.Alive;
}
} else {
// Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction.
if (pop == 3) {
next[y][x] = State.Alive;
}
}
}
}
return next;
}
#Override
public String toString() {
StringBuilder s = new StringBuilder();
for (State[] row : frame) {
for (State c : row) {
s.append(c == State.Alive ? "X" : " ");
}
s.append("\r\n");
}
return s.toString();
}
}
public void test() {
GameOfLife g = new GameOfLife(6, 6);
g.alive(1, 0);
g.alive(2, 1);
g.alive(3, 1);
g.alive(1, 2);
g.alive(2, 2);
System.out.println("Before:\r\n" + g);
g.tick();
System.out.println("After:\r\n" + g);
}
I believe the problem is that you are copying the new value as you iterate through the loop. This means neighbours are using the value from the next tick rather than the current one.
You can fix this by waiting until you calculated all new values in your new frame: cell.frame and then iterate through the frame again and copy from cell.frame to frame.
An alternative (better in my view) is to have away of cloning a frame during construction. Then you could change your nextFrame method to create a clone of frame and use the clone to set the new values in frame.
You are changing the DEAD and ALIVE frames while you iterate through the grid. You need to store the coordinates which should die or become alive and perform that afterwards.
Store the coordinates in two ArrayLists (dead, alive). The first and second position is the x and y axis, and change those coordinates according to whether they should become alive or not.
Here's a snippet from a simple test I wrote a while back. As others have mentioned, don't change values on an active board while still reading them. Instead, clone the board and make changes to the copy while reading the current board.
Another problem I bumped into a few times was iterating over y, then x for each y, but referring to x,y when accessing a point. It feels back to front :)
// Rules:
// 1) Any live cell with fewer than two live neighbours dies, as if caused by under-population.
// 2) Any live cell with two or three live neighbours lives on to the next generation.
// 3) Any live cell with more than three live neighbours dies, as if by overcrowding.
// 4) Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction.
void mutateGrid() {
// Copy existing grid into the next generation's grid
boolean[][] mutatedGrid = new boolean[gridXWidth][gridYHeight];
for (int i = 0; i < gridXWidth; i++) {
System.arraycopy(grid[i], 0, mutatedGrid[i], 0, gridYHeight);
}
// Start mutation rules
for (int y = 0; y < gridYHeight; y++) {
for (int x = 0; x < gridXWidth; x++) {
int liveNeighbours = countLiveNeighbours(x,y);
if (liveNeighbours < 2 || liveNeighbours > 3) {
mutatedGrid[x][y] = false;
}
else if (liveNeighbours == 3) {
mutatedGrid[x][y] = true;
}
}
}
grid = mutatedGrid;
}
int countLiveNeighbours(int x, int y) {
int count = 0;
for (int j = y-1; j <= y+1; j++) {
for (int i = x-1; i <= x+1; i++) {
if (i < 0 || j < 0 || i >= gridXWidth || j >= gridYHeight){
continue;
}
if (grid[i][j]) {
count++;
}
}
}
count -= grid[x][y]?1:0; // remove self from count
return count;
}
This question already has answers here:
How do I do a deep copy of a 2d array in Java?
(7 answers)
Closed 8 years ago.
So I am trying to make an algorithm for finding full paths in NxN grid. For example in 1x1 grid there is 1 possible path, in 2x2 grid there is 1, in 3x3 there is 2 and in 4x4 there is 8. The idea is to find scenarios where we can fill every spot of the grid by moving.
I have made a recursive function for the job and here is the code:
public static int getRoutesHelp(int[][] table, int x, int y)
{
if(x > table.length-1 || x < 0 || y < 0 || y > table.length-1)
return 0;
if(table[x][y] == 1)
return 0;
table[x][y] = 1;
if(isDeadEnd(table, x, y))
{
if(isTableFull(table))
return 1;
}
else
{
int a = getRoutesHelp(table, x-1, y);
int d = getRoutesHelp(table, x, y+1);
int b = getRoutesHelp(table, x+1, y);
int c = getRoutesHelp(table, x, y-1);
return a+b+c+d;
}
return 0;
}
public static int getRoutes(int size)
{
int[][] table = new int[size][size];
// init table
for(int i = 0; i < size; i++)
{
for(int a = 0; a < size; a++)
{
table[i][a] = 0;
}
}
return getRoutesHelp(table, 0 ,0);
}
So basically I start from 0.0 and start moving to all possible directions and by repeating this I get the amount of successful routes. The problem is that after the assignment of int d the original table is somehow filled with 1 but it should be empty as far as I understand because java passes a copy of the table right? I've been fighting with this for like 4 hours and can't really find the problem so any help is appreciated. Empty slots in table are marked with 0 and filled slots with 1.
EDIT: I managed to fix the issue I had with the copying and now my other problem is that with 5x5 grid my algorithm returns 52 routes and it should be 86. So it works with 4x4 grid okay, but once I move further it breaks.
Added the isDeadEnd function here
public static boolean isDeadEnd(int[][] table, int x, int y)
{
int toCheck[] = new int[4];
toCheck[0] = x-1; // left
toCheck[1] = y-1; // top
toCheck[2] = x+1; // right
toCheck[3] = y+1; // bottom
int valuesOfDirections[] = new int[4]; // left, top, right, bottom
for(int i = 0; i < 4; i++)
{
int tarkastettava = toCheck[i];
if(tarkastettava > table.length-1 || tarkastettava < 0)
{
valuesOfDirections[i] = 1;
}
else
{
if(i == 0 || i == 2)
{
valuesOfDirections[i] = table[tarkastettava][y];
}
else
{
valuesOfDirections[i] = table[x][tarkastettava];
}
}
}
for(int i = 0; i < 4; i++)
{
if(valuesOfDirections[i] == 0)
{
return false;
}
}
return true;
}
Come to think of it, you probably can do a simple backtrack here:
table[x][y] = 1;
if(isDeadEnd(table, x, y)) {
if(isTableFull(table))
return 1;
}
table[x][y] = 0;
}
And later:
int res = a + b + c + d;
if (res == 0) {
// backtrack here too
table[x][y] = 0;
}
return res;
I'm working on a tictactoe board for practice making classes and i have ran into a problem with my algorithm. it seems to be returning the best move offensive, but it doesn't play defense. i dont know where i have messed up and cant seem to find it. i have looked over a lot of things on here about it and ive compared it to simular projects, but still can't seem to get it. here is my code.
package TicTacToe;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Scanner;
public class Solution {
private static GameBoard currentBoard;
private static Player botPlayer;
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
String player;
System.out.println("ENTER bot: ");
player = in.next();
if(player.equalsIgnoreCase("X")) {
botPlayer = Player.X;}
else {botPlayer = Player.O;}
String board[] = new String[3];
for(int i = 0; i < 3; i++) {
System.out.println("ENTER board: ");
board[i] = in.next();
}
currentBoard = new GameBoard(3,3, board);
List<Space> OpenSpaces = getOpenSquares(currentBoard);
MakeMove(OpenSpaces);
System.exit(-1);
}
public static List<Space> getOpenSquares(GameBoard GB) {
List<Space> OpenSpaces = new ArrayList<Space>();
for(int r = 0; r < 3; r++) {
for(int c = 0; c < 3; c++) {
if(GB.squares[r][c] == Player.Open) {
OpenSpaces.add(new Space(r,c));
}
}
}
return OpenSpaces;
}
private static Space bestMove;
private static Space currentMove;
private static Space previousMove;
private static void MakeMove(List<Space> OpenSpaces) {
if(OpenSpaces.size() == currentBoard.Size) {
Random random = new Random();
bestMove = new Space(random.nextInt(2),2);
} else {
for(Space child: OpenSpaces) {
currentMove = GetBestMove(currentBoard,botPlayer);
if (currentMove != null){
}else{
continue;}
if(previousMove != null && previousMove.Rank < currentMove.Rank ||
previousMove == null && currentMove != null) {
bestMove = currentMove;
}
previousMove = currentMove;
}
}
if (bestMove != null) {
currentBoard.squares[bestMove.X][bestMove.Y] = botPlayer;
System.out.println("the best move is: " + currentMove.X + " " + currentMove.Y);
}
}
private static Space GetBestMove(GameBoard gb, Player p) {
Space bestSpace = null;
List<Space> cloneOpenSpaces = getOpenSquares(gb);
GameBoard cloneBoard = null;
cloneBoard = gb.Clone();
for(Space Open: cloneOpenSpaces) {
cloneBoard = gb.Clone();
Space newSpace = Open;
cloneBoard.squares[newSpace.X][newSpace.Y] = p;
if(cloneBoard.Winner == Player.Open && cloneOpenSpaces.size() > 0) {
Player InP;
if(p == Player.X) {
InP = Player.O;
}else {
InP = Player.X;
}
Space tempMove = GetBestMove(cloneBoard, InP);
if(tempMove != null){
newSpace.Rank = tempMove.Rank;
}
} else {
if(cloneBoard.Winner == Player.Open) {
newSpace.Rank = 0;
}else if(cloneBoard.Winner == Player.O) {
newSpace.Rank = -1;
}else if(cloneBoard.Winner == Player.X) {
newSpace.Rank = 1;
}
}
System.out.println(newSpace.Rank);
if(bestSpace == null ||
(p == Player.X && newSpace.Rank < ((Space)bestSpace).Rank)||
(p == Player.O && newSpace.Rank > ((Space)bestSpace).Rank)) {
bestSpace = newSpace;
}
}
return (Space)bestSpace;
}
public static enum Player {
X (1),
O (-1),
Open (0);
private final double value;
Player(double value){
this.value = value;
}
}
public static class Space {
public int X;
public int Y;
public double Rank;
public Space(int x, int y) {
this.X = x;
this.Y = y;
Rank = 0;
}
public Space() {
}
}
public static class GameBoard {
public int Rows;
public int getRows() {
return this.Rows;
}
public void setRows(int rows) {
Rows = rows;
}
public int Columns;
public int getColumns() {
return this.Columns;
}
public void setColumns(int columns) {
Columns = columns;
}
public Player[][] squares;
//public Player[x][y]
public Player getPlayer(int x, int y) {
return this.squares[x][y];
}
public void setPlayer(int x, int y, Player player) {
squares[x][y] = player;
}
public boolean Full;
public boolean isFull() {
for(int r = 0; r < 2; r++) {
for(int c = 0; c < 2; c++) {
if (squares[r][c] != Player.Open) {return false;}
}
}
return true;
}
public int Size;
public int getSize() {
return this.Size;
}
public void setSize(int size) {
Size = size;
}
public List<Space> OpenSquares;
public List<Space> getOpenSquares() {
List<Space> OpenSquares = new ArrayList<Space>();
for(int r = 0; r < Rows; r++) {
for(int c = 0; c < Columns; c++) {
if(squares[r][c] == Player.Open) {
OpenSquares.add(new Space(r,c));
}
}
}
return this.OpenSquares;
}
public Player Winner;
public Player getWinner() {
int count = 0;
//columns
for (int x = 0; x < Rows; x++)
{
count = 0;
for (int y = 0; y < Columns; y++) {
count += squares[x][y].value;
}
if (count == 3) {
return Player.X;
}else if (count == -3) {
return Player.O;
}
}
//rows
for (int x = 0; x < Rows; x++) {
count = 0;
for (int y = 0; y < Columns; y++) {
count += squares[y][x].value;
}
if (count == 3) {
return Player.X;
}else if (count == -3) {
return Player.O;
}
}
// Diagonals right to left
count = 0;
count += squares[0][0].value;
count += squares[1][1].value;
count += squares[2][2].value;
if (count == 3) {
return Player.X;
}else if (count == -3) {
return Player.O;
}
// Diagonals left to right
count = 0;
count += squares[0][2].value;
count += squares[1][1].value;
count += squares[2][0].value;
if (count == 3) {
return Player.X;
}else if (count == -3) {
return Player.O;
}
return Player.Open;
}
public GameBoard Clone() {
GameBoard b = new GameBoard(Rows,Columns);
b.squares = (Player[][])this.squares.clone();
b.Winner = getWinner();
return b;
}
// Class initializer
public GameBoard(int boardRows, int boardColumns, String[] board) {
// Set the dimensions
Rows = boardRows;
Columns = boardColumns;
// Create game spaces
squares = new Player[Rows][Columns];
for(int r = 0; r < Rows; r++) {
for(int c = 0; c < Columns; c++) {
//squares[i][n] = Player.Open;
if(board[r].charAt(c) == 'X') {
squares[r][c] = Player.X;
}
if(board[r].charAt(c) == 'O') {
squares[r][c] = Player.O;
}
if(board[r].charAt(c) == '_') {
squares[r][c] = Player.Open;
}
}
}
this.Winner = getWinner();
this.OpenSquares = getOpenSquares();
//Size of the board
this.Size = Rows * Columns;
}
// clone Class initializer
public GameBoard(int boardRows, int boardColumns) {
// Set the dimensions
Rows = boardRows;
Columns = boardColumns;
// Create game spaces
squares = new Player[Rows][Columns];
for(int r = 0; r < Rows; r++) {
for(int c = 0; c < Columns; c++) {
squares[r][c] = Player.Open;
}
}
this.Winner = getWinner();
this.OpenSquares = getOpenSquares();
//Size of the board
Size = Rows * Columns;
}
}
}
all of the classes are at the bottom. Thanks in advance for any help and corrections. :)
i made it recursive in the following code, although i still cant figure out the scoring.. if the value is either 1, 0, or -1 then if there are multipule moves with the same value it will just take the 1st one which may not be the best move "blocking.
private static Space GetBestMove(GameBoard gb, Player p) {
Space bestSpace = null;
List<Space> cloneOpenSpaces = getOpenSquares(gb);
GameBoard cloneBoard = null;
cloneBoard = gb.Clone();
for(Space Open: cloneOpenSpaces) {
cloneBoard = gb.Clone();
Space newSpace = Open;
cloneBoard.squares[newSpace.X][newSpace.Y] = p;
if(cloneBoard.Winner == Player.Open && cloneOpenSpaces.size() > 0) {
Player InP;
if(p == Player.X) {
InP = Player.O;
}else {
InP = Player.X;
}
***Space tempMove = GetBestMove(cloneBoard, InP);***
if(tempMove != null){
newSpace.Rank = tempMove.Rank;
}
the results of the test are as follows
test 1
ENTER bot:
O
ENTER board:
[ ][O][ ]
ENTER board:
[ ][ ][ ]
ENTER board:
[ ][X][X]
-1.0
-1.0
-1.0
-1.0
-1.0
-1.0
-1.0
-1.0
-1.0
the best move is: 0 2
test 2
ENTER bot:
O
ENTER board:
[ ][X][X]
ENTER board:
[ ][ ][ ]
ENTER board:
[ ][O][ ]
1.0
1.0
1.0
1.0
1.0
-1.0
1.0
-1.0
-1.0
1.0
-1.0
1.0
1.0
-1.0
-1.0
the best move is: 1 1
I haven't ran your code, but I think I may know why you are having issues. The minimax algorithm is recursive in nature. You look at each open space, and determine some sort of score for each one. I see this in your code. However, what I don't see is the recursion that equates to the logic "if I move here, then what options will my opponent have during his next turn". Notice that you can keep calling the same scoring function, but scoring both players' options. This is where the computation can get intensive, and where stuff like pruning comes into play. Say I want to look 3 moves ahead. Say there are initially 5 open spaces. For each of the 5 open spaces, I examine my options and give a score to each one. Then I pretend to move there, and send the new board through the scoring function, and assume my opponent will take the highest scoring move of the remaining 4 possible moves. Then I pretend he moves there, and I again run the board through the scoring function, now with 2 hypothetical moves on it. You continue this for a set "depth", or number of potential moves, and pick the move that results in the highest value, assuming the opponent will do what you calculated they would.
I realize this was long-winded, but I hope there was a little bit of value buried in there somewhere. Take a look at your code, figure out where you are scoring moves (if you see a win, take it; if you can block a win, take it; etc.). Then continue calling this function where you keep adding fake/potential moves (those with the highest value from your scoring function), and once you reach the depth, you can simply pick the move that is likely to give you the most valuable outcome.
Basically, in your code, you should call GetBestMove(...) once from MakeMove(...). However, GetBestMove(...) should repeatedly call itself, with a modified board each time; and each time, it will return the best move given a hypothetical (or real) board. What I don't see in your code is that recursive call to GetBestMove(...), and the necessary upkeep that goes along with it. This explains why you only get aggressive behavior; it only looks to see what the best immediate move is, without any regard to what your opponent might be able to do if you make that move!
If my assumptions are wrong, provide a test case where you expect some behavior, but are getting something different.