Problems building A* algorithm - java

I am trying to build an app implementing A*, and I am having trouble working on the logic. The method here takes in 4 ints (startX/Y, goalX/Y) and then using the A* algorithm, it will build an ArrayList and return it, so the main method can take iterate through and display the path A* builds. But what I am getting is a jumpy path that eventually builds a very thick path to the goal node. Can anybody pinpoint where my mistake is.
Note: open and closed are priority queues and Tile implements comparable.
public ArrayList<Tile> findPath(int sX, int sY, int gX, int gY)
{
ArrayList<Tile> path = new ArrayList<Tile>();
open.offer(gameMap[sX][sY]);
Tile currentNode = gameMap[sX][sY];
Tile goalNode = gameMap[gX][gY];
int cX;
int cY;
while(open.size() > 0){
currentNode = open.poll();
closed.offer(currentNode);
path.add(currentNode);
cX = currentNode.getX();
cY = currentNode.getY();
if(currentNode == goalNode){
break;
}
if((cX > 0 && cX < gameMap.length - 1) && (cY > 0 && cY < gameMap.length -1)){
for(int i = -1; i < 2; i++){
for(int j = 1; j > -2; j--){
if(i == 0 && j == 0){}
else{
if((gameMap[cX + i][cX + j].type != 1) && !closed.contains(gameMap[cX + i][cX + j])){
if(!open.contains(gameMap[cX + i][cX + j])){
open.offer(gameMap[cX + i][cX + j]);
gameMap[cX + i][cX + j].parent = currentNode;
}
}
}
}
}
}
}
// while(currentNode != gameMap[sX][sY]){
// path.push(currentNode);
// currentNode = currentNode.parent;
// }
return path;
}

First off, I don't think your closed set needs to be a priority queue. It's just a set of nodes that have been looked at.
You seem to be missing the core part of how A* works, which is why I think this path finder is not working to well for you.
Here's the main idea:
Have a heuristic function that guesses how far away the destination is. Ideally, that function will be admissible, meaning that it will never overestimate the distance.
For tile grids, this can be done using manhattan distance (x difference + y difference) since that is the minimum distance, so it will always be admissible.
Whenever you take a tile out of your open list and add it to the closed set, you need to update the known value of how far away the neighboring tiles are (keeping the lowest known value). Since you have the known value for the tile you are putting in the closed set, you just add 1 to all the neighbors' known values.
By updating these values, the open list may shift order (which is why a priority queue is a good choice here). The heuristic value will probably remain the same, but the known value will get more refined.
Once you reach the destination, you will have a set of closed nodes that all have a known distance. You then backtrack from the destination, looking at each neighbor that is also in the closed set and choosing the one with the lowest known distance.
In terms of how to implement this, you may want to consider having your Tile class be wrapped in another class called SearchTile or something like that. It could look like this:
//You may not want to use public variables, depending on your needs
public class SearchTile implements Comparable<SearchTile> {
public final Tile tile;
//These need to be updated
public int knownDistance = 0;
public int heuristicDistance = 0;
public SearchTile(final Tile tile) {
this.tile = tile;
}
#Override
public int compareTo(final SearchTile other) {
if (knownDistance + heuristicDistance > other.knownDistance + other.heuristicDistance) {
return 1;
} else if (knownDistance + heuristicDistance < other.knownDistance + other.heuristicDistance) {
return -1;
} else {
return 0;
}
}
}
The cool thing about A* is that in the ideal case, it should go straight to the destination. In cases with walls, it will take the best guess and as long as the heuristic is admissible, it will come up with the optimal solution.

I've not completely entered in the details of your implementation, but it comes to my mind that the way in which you are inserting the nodes in OPEN might be a cause of trouble:
if(!open.contains(gameMap[cX + i][cX + j])){
open.offer(gameMap[cX + i][cX + j]);
gameMap[cX + i][cX + j].parent = currentNode;
}
Your goal here is to manage avoiding repeated elementes in your OPEN list, but it might happen that sometimes you have to replace the element because you have encountered a way in which you reach it with a better cost. In this case you need to remove the node already inserted in OPEN and reintroduce it with a lower cost (and thus with highest priority). If you do not allow this, you might be generating suboptimal paths as it seems to be your case.
Additionaly, some logic of the algorithm is missing. You should store the accumulated cost from the start, G, and the estimated cost to goal, H, for each node you create. The OPEN list is ordered according to the value of G+H, which I didn't notice in your code to be done this way. Anyway, I recommend you to take a look of some existing implementation of A* like one of the Hipster4j library to have more details on how this works.
Hope my answer helped!

Related

Discovering a region with no mines on minesweeper

I am new in this page, it hope get to some help, basically I am doing a minesweeper game on Java but it have a problem with a function: discover the region with no mines and no numbers like in the game on windows, when you click in one place and all withe cells appear. I tried make recursive but I can't, some help?
Sorry for the code, the original is in spanish but i tried make a pseudocode:
Matriz = multidimensional Array (the minesweeper)
min and max returns the index min and max to iterate (8 sorroud cells)
private void discoverWitheCell(int x, int y) {
if(matriz[x][y].getdiscovered() == false){
matriz[x][y].setDiscovered(true);
}
else{
if(matriz[x][y].getNumberOfMinesArround() == 0){
for(int i=min(x);i<max(x);i++)
for(int j=min(y);j<max(y);j++)
discoverWhiteCell(i,j);
}
}
}
There's not a lot of code here but I feel like you're coming at it backwards.
Sorry, I'm not a Java speaker so I'm guessing at some of the syntax. Note that this can go out of bounds--personally, I would add a layer of empty cells around my map so I never need to concern myself with bounds checking.
private void ClickSquare(int x, int y)
{
// Did the user click an already exposed square? If so, ignore
if (matriz[x][y].getDiscovered()) return;
matriz[x][y].SetDiscovered(true);
if (matriz[x][y].getNumberOfMinesAround != 0) return;
// If empty, click all the neighbors
for (int xloop = x - 1; xloop <= x + 1; xloop++)
for (int yloop = y - 1; yloop <= y + 1; yloop++)
ClickSquare(xloop, yloop);
}
I believe you have the discovered test messed up and your version appears to be able to go into infinite (until the stack overflows) recursion as if the neighbor is also zero it will come back to the original cell. My version stops this recursion by only processing a cell if it hasn't already been processed.

Optimal render draw-order function with specified z-index values

I found recently the default renderable sort function in LibGDX wasn't quite up to my needs. (see; Draw order changes strangely as camera moves? )
Essentially a few objects rendered in front when they should render behind.
Fortunately, the renderables in question always have a guarantied relationship. The objects are attached to eachother so when one moves the other moves. One object can be seen as being literally "pinned" to the other, so always in front.
This gave me the idea that if I specified a "z-index" (int) and "groupname" (String) for each object, I could manually take over the draw order, and for things with the same groupname, ensure they are positioned next to eachother in the list, in the order specified by the z-index. (low to high)
//For example an array of renderables like
0."testgroup2",11
1."testgroup",20
2."testgroup2",10
3.(no zindex attribute)
4."testgroup",50
//Should sort to become
0."testgroup",20
1."testgroup",50
2.(no zindex attribute)
3."testgroup2",10
4."testgroup2",11
// assuming the object2 in testgroup2 are closer to the camera, the one without a index second closest, and the rest furthest<br>
//(It is assumed that things within the same group wont be drastically different distances)
I implemented a sort system in libgdx to do this as followed;
/**
* The goal of this sorter is to sort the renderables the same way LibGDX would do normally (in DefaultRenderableSorter)<br>
* except if they have a ZIndex Attribute.<br>
* A Zindex attribute provides a groupname string and a number.<br>
* Renderables with the attribute are placed next to others of the same group, with the order within the group determined by the number<br>
*
* For example an array of renderables like;<br><br>
* 0."testgroup",20<br>
* 1."testgroup2",10<br>
* 2.(no zindex attribute)<br>
* 3."testgroup",50<br>
* <br>Should become;<br><br>
* 0."testgroup",20<br>
* 1."testgroup",50<br>
* 2.(no zindex attribute)<br>
* 3."testgroup2",10<br>
* <br>
* assuming the object in testgroup2 is closer to the camera, the one without a index second closest, and the rest furthest<br>
* (It is assumed that things within the same group wont be drastically different distances)<br>
*
* #param camera - the camera in use to determine normal sort order when we cant place in a existing group
* #param resultList - an array of renderables to change the order of
*/
private void customSorter(Camera camera, Array<Renderable> resultList) {
//make a copy of the list to sort. (This is probably a bad start)
Array <Renderable> renderables = new Array <Renderable> (resultList);
//we work by clearing and rebuilding the Renderables array (probably not a good method)
resultList.clear();
//loop over the copy we made
for (Renderable o1 : renderables) {
//depending of if the Renderable as a ZIndexAttribute or not, we sort it differently
//if it has one we do the following....
if (o1.material.has(ZIndexAttribute.ID)){
//get the index and index group name of it.
int o1Index = ((ZIndexAttribute)o1.material.get(ZIndexAttribute.ID)).zIndex;
String o1GroupName = ((ZIndexAttribute)o1.material.get(ZIndexAttribute.ID)).group;
//setup some variables
boolean placementFound = false; //Determines if a placement was found for this renderable (this happens if it comes across another with the same groupname)
int defaultPosition = -1; //if it doesn't find another renderable with the same groupname, this will be its position in the list. Consider this the "natural" position based on distance from camera
//start looping over all objects so far in the results (urg, told you this was probably not a good method)
for (int i = 0; i < resultList.size; i++) {
//first get the renderable and its ZIndexAttribute (null if none found)
Renderable o2 = resultList.get(i);
ZIndexAttribute o2szindex = ((ZIndexAttribute)o2.material.get(ZIndexAttribute.ID));
if (o2szindex!=null){
//if the renderable we are comparing too has a zindex, then we get its information
int o2index = o2szindex.zIndex;
String o2groupname = o2szindex.group;
//if its in the same group as o1, then we start the processing of placing them nexto eachother
if (o2groupname.equals(o1GroupName)){
//we either place it in front or behind based on zindex
if (o1Index<o2index){
//if lower z-index then behind it
resultList.insert(i, o1);
placementFound = true;
break;
}
if (o1Index>o2index){
//if higher z-index then it should go in front UNLESS there is another of this group already there too
//in which case we just continue (which will cause this to fire again on the next renderable in the inner loop)
if (resultList.size>(i+1)){
Renderable o3 = resultList.get(i+1);
ZIndexAttribute o3szindex = ((ZIndexAttribute)o3.material.get(ZIndexAttribute.ID));
if (o3szindex!=null){
String o3groupname = o3szindex.group;
if (o3groupname!=null && o3groupname.equals(o1GroupName)){
//the next element is also a renderable with the same groupname, so we loop and test that one instead
continue;
}
}
}
// Gdx.app.log("zindex", "__..placeing at:"+(i+1));
//else we place after the current one
resultList.insert(i+1, o1);
placementFound = true;
break;
}
}
}
//if no matching groupname found we need to work out a default placement.
int placement = normalcompare(o1, o2); //normal compare is the compare function in DefaultRenderableSorter.
if (placement>0){
//after then we skip
//(we are waiting till we are either under something or at the end
} else {
//if placement is before, then we remember this position as the default (but keep looking as there still might be matching groupname, which should take priority)
defaultPosition = i;
//break; //break out the loop
}
}
//if we have checked all the renderables positioned in the results list, and none were found with matching groupname
//then we use the defaultposition to insert it
if (!placementFound){
//Gdx.app.log("zindex", "__no placement found using default which is:"+defaultPosition);
if (defaultPosition>-1){
resultList.insert(defaultPosition, o1);
} else {
resultList.add(o1);
}
}
continue;
}
//...(breath out)...
//ok NOW we do placement for things that have no got a ZIndexSpecified
boolean placementFound = false;
//again, loop over all the elements in results
for (int i = 0; i < resultList.size; i++) {
Renderable o2 = resultList.get(i);
//if not we compare by default to place before/after
int placement = normalcompare(o1, o2);
if (placement>0){
//after then we skip
//(we are waiting till we are either under something or at the end)
continue;
} else {
//before
resultList.insert(i, o1);
placementFound = true;
break; //break out the loop
}
}
//if no placement found we go at the end by default
if (!placementFound){
resultList.add(o1);
};
} //go back to check the next element in the incomeing list of renderables (that is, the copy we made at the start)
//done
}
//Copy of the default sorters compare function
//;
private Camera camera;
private final Vector3 tmpV1 = new Vector3();
private final Vector3 tmpV2 = new Vector3();
public int normalcompare (final Renderable o1, final Renderable o2) {
final boolean b1 = o1.material.has(BlendingAttribute.Type) && ((BlendingAttribute)o1.material.get(BlendingAttribute.Type)).blended;
final boolean b2 = o2.material.has(BlendingAttribute.Type) && ((BlendingAttribute)o2.material.get(BlendingAttribute.Type)).blended;
if (b1 != b2) return b1 ? 1 : -1;
// FIXME implement better sorting algorithm
// final boolean same = o1.shader == o2.shader && o1.mesh == o2.mesh && (o1.lights == null) == (o2.lights == null) &&
// o1.material.equals(o2.material);
o1.worldTransform.getTranslation(tmpV1);
o2.worldTransform.getTranslation(tmpV2);
final float dst = (int)(1000f * camera.position.dst2(tmpV1)) - (int)(1000f * camera.position.dst2(tmpV2));
final int result = dst < 0 ? -1 : (dst > 0 ? 1 : 0);
return b1 ? -result : result;
}
As far as I can tell my customSorter function produces the order I want - the renderables now look like they are drawn in the right order.
However, this also seems like a hackjob, and I am sure my sorting algorithm is horrendously inefficient.
I would like advice on how to either;
a) Improve my own algorithm, especially in regards to any quirks to bare in mind when doing cross-platform LibGDX development (ie, array types, memory management in regards to android/web etc)
b) Alternative more efficient solutions having a similar "z index override" of the normal draw-order sorting.
Notes;
. The grouping is necessary. This is because while things are firmly stuck relatively to eachother within a group, groups themselves can also move about in front/behind eachother. (but not between). This makes it tricky to do a "global" override of the draw order, rather then a local one per group.
. If it helps, I can add/change the zindexattribute object in any way.
. I am thinking somehow "pre-storeing" each group of objects in a array could help things, but not 100% sure how.
First of all do never copy a list if not needed. The list with renderables could be really huge since it also could contain resources. Copying will be very very slow. If you need something local and you need performance try to make it final since it can improve the performance.
So a simple approach would be the default sorting of Java. You need to implement a Comperator for your class for example the Class with z index could look like this:
public class MyRenderable {
private float z_index;
public MyRenderable(float i)
{
z_index = i;
}
public float getZ_index() {
return z_index;
}
public void setZ_index(float z_index) {
this.z_index = z_index;
}
}
If you want a faster sort since your list wont change that much on runtime you could implement a insertion sort since it does a faster job if the list is kind of presorted. If it is not pre sorted it does take longer but in general it should only be the first sort call where it is alot disordered in your case.
private void sortList(ArrayList<MyRenderable> array) {
// double starttime = System.nanoTime();
for (int i = 1; i < array.size(); i++) {
final MyRenderable temp = array.get(i);
int j = i - 1;
while (j >= 0 && array.get(j).getZ_index() < temp.getZ_index()) {
array.set(j + 1, array.get(j));
j--;
}
array.set(j + 1, temp);
}
// System.out.println("Time taken: " + (System.nanoTime() - starttime));
}
To use this method you simply call it with your Array
sortList(renderbales);
In your case you need to take care of the ones that do not have a Z index. Maybe you could give them a 0 since they'll get sorted at the right position(i guess). Else you can use the given methods in z case and the regular in no z case as you do already.
After the conversation in the comments. I dont think it is a good idea to push everything into one list. It's hard to sort and would be very slow. A better approach would be a list of groups. Since you want to have groups, programm a group. Do not use String names, use IDs or types (way more easy to sort and it doesn't really matter). So a simple group would be this:
public class Group{
//think about privates and getters or methods to add things which also checks some conditions and so on
public int groupType;
public ArrayList<MyRenderable> renderables;
}
And now all your groups into a list. (this contains all your renderbales then)
ArrayList<Group> allRenderables = new ArrayList<>();
Last but not least sort the groups and sort the renderables. Since i dont think that your group ids/names will change on runtime, sort them once or even use a SortedSet instead of a ArrayList. But basically the whole sorting looks like this:
for(Group g: allRenderables)
sortRenderables(g.renderables); //now every group is sorted
//now sort by group names
sortGroup(allRenderables);
With the following insertionsorts as shown above
public static void sortRenderables(ArrayList<MyRenderable> array) {
for (int i = 1; i < array.size(); i++) {
final MyRenderable temp = array.get(i);
int j = i - 1;
while (j >= 0 && array.get(j).getZ_index() < temp.getZ_index()) {
array.set(j + 1, array.get(j));
j--;
}
array.set(j + 1, temp);
}
}
public static void sortGroup(ArrayList<Group> array) {
for (int i = 1; i < array.size(); i++) {
final Group temp = array.get(i);
int j = i - 1;
while (j >= 0 && array.get(j).groupType < temp.groupType) {
array.set(j + 1, array.get(j));
j--;
}
array.set(j + 1, temp);
}
}

Chess alpha beta returning wrong move for the board

I am trying to implement a chess game with alpha beta pruning. The following is almost working, but it returns wrong moves.
For example, the following can occur.
White (user) to move, white king position - a1 / Black (computer), black king position - h1
White moves its king from a1 - a2, then black return the move g2 - g1???
It appears that the computer returns a move for the wrong node (board representation), as if the best evaluation of a given board position is not being propagated all the way back up the tree. So in one of the simulated positions explored, the computer "imagines" its king moving to g2 and then returns the move to be made from this position, not realising that this position is a simulated position and not the representation of the actual board (the root node?).
How can I correct the code to make the computer return a move for the actual board representation and not one of the simulations by mistake?
Thank you.
Initial call alphaBeta(3, ChessEngine.invertBoard(ChessEngine.board), -10000, 10000, true);
private static int alphaBetaEvaluate = 0;
private static int alphaBetaSelectedSquare = 0;
private static int alphaBetaMoveToSquare = 0;
public static int alphaBeta(int depth, char[] board, int alpha, int beta, boolean maxPlayer) {
//create a copy of the board
char[] boardCopy = board.clone();
//if terminal state has not been met, keep searching
if (maxPlayer == true && depth > 0) {
//for all of the moves that max can make
for (int i = 0; i < board.length; i++) {
for (int move : ChessEngine.getValidMoves(i, boardCopy)) {
//make the move
boardCopy[move] = boardCopy[i];
boardCopy[i] = '.';
alphaBetaEvaluate = rating(board, boardCopy, i, move);
//store the best move to make
int temp = alphaBeta(--depth, ChessEngine.invertBoard(boardCopy), -10000, 10000, false);
if (temp > alpha) {
alphaBetaSelectedSquare = i;
alphaBetaMoveToSquare = move;
alpha = temp;
}
//reset the board for the next simulated move
boardCopy = board.clone();
if (beta <= alpha) {
break;
}
}
}
return alpha;
} else if (maxPlayer == false && depth > 0) {
//for all of the moves that min can make
for (int i = 0; i < board.length; i++) {
for (int move : ChessEngine.getValidMoves(i, boardCopy)) {
//make the move
boardCopy[move] = boardCopy[i];
boardCopy[i] = '.';
beta = Math.min(beta, alphaBeta(--depth, ChessEngine.invertBoard(boardCopy), -10000, 10000, true));
//reset the board for the next simulated move
boardCopy = board.clone();
if (beta <= alpha) {
break;
}
}
}
return beta;
}
return alphaBetaEvaluate;
}
I dont get your implementation after all. First of all what you want to do is create a tree. A decision tree and propagates the decision up. You want to maximize your evaluation and also expect that the enemy will select the move that minimizes your evaluation in return.
So inverting the board does not sound so reasonable for me unless you know that the evaluation you do uppon the situation is correctly adjusting.
Another serious problem for me is that you always call the min/max for the next move with -10k and 10k as the bounderies for alpha and beta. This way your algorithm does not 'learn' from previous moves.
What you need is to check the algorithm again (wikipedia for instance, which I used) and see that they use alpha and beta being modified by former evaluation. This way the calculation in higher depth can firstly stop and secondly evaluate the best move better.
I am no expert in this. its decades ago when I wrote my implementation and I used something different.
Another idea is not to use min and max within the same method but use the min and max methods instead. It makes it more likely you spot other defects.
Also do not use two kings for evaluation. There is no goal in that. Two kings are random, cant win. One thing might be two knights or four queens and alike. It is not so random and you can see the queens dancing around without being able to catch each other. Or use three knights versus a single queen.
And try to create yourself some unit tests around your other parts. Just to insure that the parts are working correctly independently. And why are you using characters? Why not using enums or objects. You can reuse the objets for each field (its more like kinds of figures).
But anyhow this is style and not algorithm correctness.

Negamax chess algorithm: How to use final return?

I've made a negamax algorithm for a chess-like game and I want to know how to use the final board value result. I understand the final return of the negamax algorithm represents what the board value will be after the player takes his best possible move, but that isn't exactly useful information. I need to know what that move is, not what it's worth.
Here's the code:
public int negamax(Match match, int depth, int alpha, int beta, int color) {
if(depth == 0) {
return color*stateScore(match);
}
ArrayList<Match> matches = getChildren(match, color);
if(matches.size() == 0) {
return color*stateScore(match);
}
int bestValue = Integer.MIN_VALUE;
for(int i = 0; i != matches.size(); i++) {
int value = -negamax(matches.get(i), depth-1, -beta, -alpha, -color);
if(value > bestValue) {
bestValue = value;
}
if(value > alpha) {
alpha = value;
}
if(alpha >= beta) {
break;
}
}
return bestValue;
}
public void getBestMove(Match match, int color) {
int bestValue = negamax(match, 4, Integer.MIN_VALUE, Integer.MAX_VALUE, color);
// What to do with bestValue???
}
I thought of re-evaluating the children of the current match state after bestValue is determined. Then I iterate through them and find which of those children has a stateScore equal to bestValue. But that wouldn't work because a lot of them will have the same stateScore anyway, it's what they can lead to which counts...
I can see you're doing a qsearch and alpha-beta. Your algorithm is well-known but you're missing a key part.
Let me sketch out the basic algorithm for chess search, it applies even to Stockfish (the strongest engine in the world).
search(Position p) {
if (leaf node)
qsearch(p)
if (need to do move reduction)
do_move_reduction_and_cut_off(p)
moves = generate_moves(p)
for_each(move in moves) {
p.move(move)
v = -search(p, -beta, -alpha)
p.undo(move)
store the score and move into a hash table
if (v > beta)
cutoff break;
}
This is just a very brief sketch, but all chess algorithms follow it. Compare your version with it, do you notice that you haven't done p.move(move) and p.undo(move)?
Basically, the traditional approach generates a list of moves for a given position. Loop through the moves, play it and undo it and search it. If you do it, you know exactly which move produces which score.
Also notice the line for storing the move and score into a hash table. If you do this, you can easily reconstruct the entire principal variation from a root node.
I don't know what exactly is inside your Java class Match, but in any case your attempt was close but no exactly the classical way to do a search. Remember you'll need to give a position object in a search algorithm but instead you gave it a Match object, which is wrong.

Alpha-beta move ordering

I have a basic implementation of alpha-beta pruning but I have no idea how to improve the move ordering. I have read that it can be done with a shallow search, iterative deepening or storing the bestMoves to transition table.
Any suggestions how to implement one of these improvements in this algorithm?
public double alphaBetaPruning(Board board, int depth, double alpha, double beta, int player) {
if (depth == 0) {
return board.evaluateBoard();
}
Collection<Move> children = board.generatePossibleMoves(player);
if (player == 0) {
for (Move move : children) {
Board tempBoard = new Board(board);
tempBoard.makeMove(move);
int nextPlayer = next(player);
double result = alphaBetaPruning(tempBoard, depth - 1, alpha,beta,nextPlayer);
if ((result > alpha)) {
alpha = result;
if (depth == this.origDepth) {
this.bestMove = move;
}
}
if (alpha >= beta) {
break;
}
}
return alpha;
} else {
for (Move move : children) {
Board tempBoard = new Board(board);
tempBoard.makeMove(move);
int nextPlayer = next(player);
double result = alphaBetaPruning(tempBoard, depth - 1, alpha,beta,nextPlayer);
if ((result < beta)) {
beta = result;
if (depth == this.origDepth) {
this.bestMove = move;
}
}
if (beta <= alpha) {
break;
}
}
return beta;
}
}
public int next(int player) {
if (player == 0) {
return 4;
} else {
return 0;
}
}
Node reordering with shallow search is trivial: calculate the
heuristic value for each child of the state before recursively
checking them. Then, sort the values of these states [descending
for max vertex, and ascending for min vertex], and recursively invoke
the algorithm on the sorted list. The idea is - if a state is good at
shallow depth, it is more likely to be good at deep state as well,
and if it is true - you will get more prunnings.
The sorting should be done before this [in both if and else clauses]
for (Move move : children) {
storing moves is also trivial - many states are calculated twice,
when you finish calculating any state, store it [with the depth of
the calculation! it is improtant!] in a HashMap. First thing you do
when you start calculation on a vertex - is check if it is already
calculated - and if it is, returned the cached value. The idea behind
it is that many states are reachable from different paths, so this
way - you can eliminate redundant calculations.
The changes should be done both in the first line of the method [something like if (cache.contains((new State(board,depth,player)) return cache.get(new State(board,depth,player))] [excuse me for lack of elegance and efficiency - just explaining an idea here].
You should also add cache.put(...) before each return statement.
First of all one has to understand the reasoning behind the move ordering in an alpha-beta pruning algorithm. Alpha-beta produces the same result as a minimax but in a lot of cases can do it faster because it does not search through the irrelevant branches.
It is not always faster, because it does not guarantee to prune, if fact in the worse case it will not prune at all and search absolutely the same tree as minimax and will be slower because of a/b values book-keeping. In the best case (maximum pruning) it allows to search a tree 2 times deep at the same time. For a random tree it can search 4/3 times deeper for the same time.
Move ordering can be implemented in a couple of ways:
you have a domain expert who gives you suggestion of what moves are better. For example in chess promotion of a pawn, capturing high value pieces with lower value piece are on average good moves. In checkers it is better to kill more checkers in a move then less checker and it is better to create a queen. So your move generation function return better moves before
you get the heuristic of how good is the move from evaluating the position at the 1 level of depth smaller (your shallow search / iterative deepening). You calculated the evaluation at the depth n-1, sorted the moves and then evaluate at the depth n.
The second approach you mentioned has nothing to do with a move ordering. It has to do with a fact that evaluation function can be expensive and many positions are evaluated many time. To bypass this you can store the values of the position in hash once you calculated it and reuse it later.

Categories