I'm creating a Java program to play the Connect Four game using the Minimax and Alpha-Beta Pruning algorithms. I'm adapting the following pseudocode for the Minimax algortihm:
Minimax pseudocode
The thing is, at the end of the game, one of the values I have to print is the number of nodes expanded during the execution of the algorithm, to give and idea of the memory usage and to compare it with the number of nodes expanded by Alpha-Beta Pruning. However, I don't really know what is the best way of keeping track of this value. A colleague of mine kept it as a field in the class that represented the grid. His solution involves keeping track of a certain maxDepth variable declared inside the Minimax class:
static Values decision(Tabuleiro tab,int depth){
Instant start = Instant.now();
int maxDepth = 1,children = 1;
Values v = new Values(-1*infinity,1);
for(Tabuleiro a: LibTabuleiro.makeDescendants(tab, 2)){
Values tmp = min(a,depth-1); maxDepth = Math.max(maxDepth,tmp.getY());
v = Values.max(v, new Values(tmp.getX(),a.getLast())); children++;
}
tab.setSize(maxDepth+children); /*setSize sets the field in the grid class
which contains the maximum number of nodes stored in memory simultaneously*/
Instant end = Instant.now();
return new Values((int)Duration.between(start,end).toMillis(),v.getY());
}
which in turn involves the use of this Values class:
public class Values {
int value;
int depth;
Values(int v,int d){
value = v;
depth = d;
}
int getX(){return value;}
int getY(){return depth;}
boolean lowerThan(Values other){return this.value<=other.value;}
boolean greaterThan(Values other){return this.value>=other.value;}
static Values max(Values a,Values b){
if(a.value >= b.value) return a;
else return b;
}
static Values min(Values a,Values b){
if(a.value <= b.value) return a;
else return b;
}
}
but I was wondering if there is a more straightforward way of doing this as, to be perfectly honest, I don't understand everything his code does and I shouldn't copy it either.
Thanks in advance,
Related
I try to write a MinMax program in Java for connect-four game, but this program should also be applicable to other games. But, I encountered a problem, which I cannot pass for few days. The values for nodes are not set properly. I am sharing my piece of code which is responsible for generating a tree.
Maybe you will notice where I made a mistake.
If anyone could help me with this, I will be very happy.
public Node generateTree(Board board, int depth) {
Node rootNode = new Node(board);
generateSubtree(rootNode, depth);
minMax(rootNode, depth);
return rootNode;
}
private void generateSubtree(Node subRootNode, int depth) {
Board board = subRootNode.getBoard();
if (depth == 0) {
subRootNode.setValue(board.evaluateBoard());
return;
}
for (Move move : board.generateMoves()) {
Board tempBoard = board.makeMove(move);
Node tempNode = new Node(tempBoard);
subRootNode.addChild(tempNode);
generateSubtree(tempNode, depth - 1);
}
}
public void minMax(Node rootNode, int depth) {
maxMove(rootNode, depth);
}
public int maxMove(Node node, int depth) {
if (depth == 0) {
return node.getValue();
}
int bestValue = Integer.MIN_VALUE;
for (Node childNode : node.getChildren()) {
int tempValue = minMove(childNode, depth - 1);
childNode.setValue(tempValue);
if (tempValue > bestValue) {
bestValue = tempValue;
}
}
return bestValue;
}
public int minMove(Node node, int depth) {
if (depth == 0) {
return node.getValue();
}
int bestValue = Integer.MAX_VALUE;
for (Node childNode : node.getChildren()) {
int tempValue = maxMove(childNode, depth - 1);
childNode.setValue(tempValue);
if (tempValue < bestValue) {
bestValue = tempValue;
}
}
return bestValue;
}
Board class is the representation of the board state.
Move class hold the move to perform (integer [0-8] for tic-tac-toe, [0-6] for Connect Four).
Node class holds the Move and value how good given move is. Also, holds all its children.
In the code I use this method like this:
Node newNode = minmax.generateTree(board, depth, board.getPlayer());
Move newMove = new TicTacToeMove(board.getPlayer(), newNode.getBestMove().getMove(), depth);
board = board.makeMove(newMove);
And when it's obvious that given move is a losing move (or winning), I do not receive this move.
Alright, you did make a couple of mistakes. About 3-4, depending on how you count ;) Took me a bit of debugging to figure it all out, but I finally got an answer for you :D
Mistake #1: All your parents always get twins (that poor mother)
This is only the case with the code you uploaded, not the code in your question, so maybe we count it as half a mistake?
Since your trees aren't that big yet and it won't destroy your algorithm, this was the least important one anyway. Still, it's something to watch out for.
In your uploaded code, you do this in your generateSubtree method:
Node tempNode = new Node(tempBoard, move, subRootNode);
subRootNode.addChild(tempNode);
As that constructor already adds the child to the subRootNode, the second line always adds it a second time.
Mistake #2: That darn depth
If you haven't reached your desired depth yet, but the game is already decided, you completely ignore that. So in your provided example that won't work, if - for example - you look at making move 7 instead of 3 (which would be the 'right' move) and then the opponent does move 3, you don't count it as -10 points because you haven't reached your depth yet. It still won't get any children, so even in your minmax, it will never realize it's a screwed up way to go.
Which is why every move is 'possible' in this scenario and you just get the first one returned.
In the previous moves, there was luckily always a way to reach a losing move with your opponents third move (aka move #5), which is why those were called correctly.
Alright, so how do we fix it?
private void generateSubtree(Node subRootNode, int depth, int player) {
Board board = subRootNode.getBoard();
List<Move> moveList = board.generateMoves();
if (depth == 0 || moveList.isEmpty()) {
subRootNode.setValue(board.evaluateBoard(player));
return;
}
for (Move move : moveList) {
Board tempBoard = board.makeMove(move);
Node tempNode = new Node(tempBoard, move, subRootNode);
generateSubtree(tempNode, depth - 1, player);
}
}
Just get the move list beforehand and then look if it's empty (your generateMoves() method of the Board class (thank god you provided that by the way ;)) already checks if the game is over, so if it is, there won't be any moves generated. Perfect time to check the score).
Mistake #3: That darn depth again
Didn't we just go over this?
Sadly, your Min Max algorithm itself has the same problem. It will only even look at your values if you have reached the desired depth. You need to change that.
However, this is a bit more complicated, since you don't have a nice little method that already checks if the game is finished for you.
You could check to see if your value was set, but here's the problem: It might be set to 0 and you need to take that into account as well (so you can't just do if (node.getValue() != 0)).
I just set the initial value of each node to -1 instead and did a check against -1. It's not... you know... pretty. But it works.
public class Node {
private Board board;
private Move move;
private Node parent;
private List<Node> children = new ArrayList<Node>();;
private boolean isRootNode = false;
private int value = -1;
...
And this in the maxMove:
public int maxMove(Node node, int depth) {
if (depth == 0 || node.getValue() != -1) {
return node.getValue();
}
int bestValue = Integer.MIN_VALUE;
for (Node childNode : node.getChildren()) {
int tempValue = minMove(childNode, depth - 1);
childNode.setValue(tempValue);
if (tempValue > bestValue) {
bestValue = tempValue;
}
}
return bestValue;
}
It works the same for minMove of course.
Mistake #4: The player is screwing with you
Once I changed all that, it took me a moment with the debugger to realize why it still wouldn't work.
This last mistake was not in the code you provided in the question btw. Shame on you! ;)
Turns out it was this wonderful piece of code in your TicTacToeBoard class:
#Override
public int getPlayer() {
// TODO Auto-generated method stub
return 0;
}
And since you called
MinMax minmax = new MinMax();
Node newNode = minmax.generateTree(board, (Integer) spinner.getValue(), board.getPlayer());
in your makeMove method of TicTacToeMainWindow, you would always start out with the wrong player.
As you can probably guess yourself, you just need to change it to:
public int getPlayer() {
return this.player;
}
And it should do the trick.
Also:
Just a couple of things I'd like to remark at this point:
Clean up your imports! Your TicTacToe actually still imports your ConnectFour classes! And for no reason.
Your board is rotated and mirrored in your board array. WHY? You know how annoying that is to debug? I mean, I guess you probably do :D Also, if you're having problems with your code and you need to debug it's extremely helpful to overwrite your boards toString() method, because that will give you a very nice and easy way to look at your board in the debugger. You can even use it to rotate it again, so you see don't have to look at it lying on the side ;)
While we're at the subject of the board... this is just me but... I always tried clicking on the painted surface first and then had to remember: Oh yeah, there were buttons :D I mean... why not just put the images on the buttons or implement a MouseListener so you can actually just click on the painted surface?
When providing code and/or example images, please take out your test outputs. I'm talking about the Player 1 won!s of course ;)
Please learn what a complete, verifiable and minimal example is for the next time you ask a question on StackOverflow. The one in your question wasn't complete or verifiable and the one you provided on github was... well... not complete (the images were missing), but complete enough. It was also verifiable, but it was NOT minimal. You will get answers a LOT sooner if you follow the guidelines.
i want to add to my code an exception in the bottom part
if(legalNeighbor(map,i1,j1,i1,j2)==false&&i1!=i2&&j1!=j2){
return 0;
what i want to do is: if the "if" above is true, instead of doing "return 0" i will throw an exception that will check the code from this point
if (locationNeighbor(map,i1+1,j1,i1,j1)==2&&map2[i1+1][j1]==-1){ //down
map2[i1+1][j1]=counter;
return distance(map,i1+1,j1,i2,j2,map2,counter+1);
}
if (locationNeighbor(map,i1,j1+1,i1,j1)==3&&map2[i1][j1+1]==-1){
and then again if the "if" happens i will check the code from this area
if (locationNeighbor(map,i1,j1+1,i1,j1)==3&&map2[i1][j1+1]==-1){ //right
map2[i1][j1+1]=counter;
return distance(map,i1,j1+1,i2,j2,map2,counter+1);
}
and then last time i check
if (locationNeighbor(map,i1,j1-1,i1,j1)==4&&map2[i1][j1-1]==-1){ //left
map2[i1][j1-1]=counter;
return distance(map,i1,j1-1,i2,j2,map2,counter+1);
}
this is my code
public static int distance(int[][] map, int i1, int j1, int i2, int j2, int[][]map2, int counter) {
if(legalNeighbor(map,i1,j1,i2,j2)==true){ // if its 1 step before the end
map2[i2][j2]=counter; // put the last number
}
if (locationNeighbor(map,i1-1,j1,i1,j1)==1&&map2[i1-1][j1]==-1){ //up
map2[i1-1][j1]=counter;
return distance(map,i1-1,j1,i2,j2,map2,counter+1);
}
if (locationNeighbor(map,i1+1,j1,i1,j1)==2&&map2[i1+1][j1]==-1){ //down
map2[i1+1][j1]=counter;
return distance(map,i1+1,j1,i2,j2,map2,counter+1);
}
if (locationNeighbor(map,i1,j1+1,i1,j1)==3&&map2[i1][j1+1]==-1){ //right
map2[i1][j1+1]=counter;
return distance(map,i1,j1+1,i2,j2,map2,counter+1);
}
if (locationNeighbor(map,i1,j1-1,i1,j1)==4&&map2[i1][j1-1]==-1){ //left
map2[i1][j1-1]=counter;
return distance(map,i1,j1-1,i2,j2,map2,counter+1);
}
print(map2);
if(legalNeighbor(map,i1,j1,i1,j2)==false&&i1!=i2&&j1!=j2){
return 0;
}
else{
int x=map2[i2][j2];
return x;
}
}
is this even possible with exception?
Anything is possible, but it's probably not a good idea. Exceptions should be truly exceptional, not something that is likely to happen.
Not only that, but they are expensive in terms of performance. You should not be using exceptions to control flow logic.
Why isn't a true/false return from a method named isLegalNeighbor not sufficient?
You really have two methods here: One to calculate distance and another to determine a legal neighbor. Split them up. A method should do one thing well, and its name should make clear what that is.
One more bit: I find your code very hard to read. I would start thinking more about style and how to write more readable code if I were you.
Forget about the exception, it's probably not what you want here. First explain what your are trying to achieve. This is completely unobvious from your code.
It's strange that the first if-statement sets a value in map2 but does not return. This means the value might be overwritten by the code below. Is that intended?
I also think there's an error in the line:
if(legalNeighbor(map,i1,j1,i1,j2)==false&&i1!=i2&&j1!=j2){
The 4th argument i1 should be i2, right?
To make your code more readable you should introduce instance fields and methods with meaningful names. If your tutor is forcing you to use static methods and meaningless names like i1 etc you should fire him/her. :-D
Here's an idea of what I mean:
// I chose weird names because I really don't know what's in these arrays
// You should make it clear from the name what it represents
private int[][] fooBar;
private int[][] bazBop;
public int distance(int fromX, int fromY, int toX, int toY, int counter) {
if (legalNeighbor(fromX, fromY, toX, toY) == true) { // if its 1 step before the end
bazBop[toX][toY] = counter; // put the last number
}
int newX = fromX - 1;
int newY = fromY;
if (locationNeighbor(newX, newY, fromX, fromY) == 1 && bazBop[newX][newY] == -1) { //up
bazBop[newX][newY] = counter;
return distance(newX, newY, toX, toY, counter + 1);
}
// ...
}
I'm pretty new to the grid world, I need guidance on how to approach an algorithm using GridGain, the algorithm is a recursive TravellingSalesmanProblem.
TSP looks like this:
public int tsp(int hops, byte[] path, int length, int minimum,
DistanceTable distance) {
int city, dist, me;
int NTowns = distance.dist.length;
// stop searching, this path is too long...
if (length + distance.lowerBound[NTowns - hops] >= minimum) {
return minimum;
}
if (hops == NTowns) {
/* Found a full route better than current best route,
* update minimum. */
return length;
}
/* "path" really is a partial route.
* Call tsp recursively for each subtree. */
me = path[hops - 1]; /* Last city of path */
/* Try all cities that are not on the initial path,
* in "nearest-city-first" order. */
for (int i = 0; i < NTowns; i++) {
city = distance.toCity[me][i];
if (city != me && !present(city, hops, path)) {
dist = distance.dist[me][i];
int min;
path[hops] = (byte) city;
min = tsp(hops + 1, path, length + dist, minimum, distance);
minimum = minimum < min ? minimum : min;
}
}
return minimum;
}
I believe I need to do an aggregation, like GG's Fibonacci example, the problem is I don't know what to set to the GridFuture, since I have a recursive call within a loop (I believe I can't create as many futures as recursive calls I have, doesn't make sense). I've search for more examples but I couldn't map any to my algorithm.
Basically I need to translate that into GridGain... any suggestion will be greatly appreciated.
There is no problem in creating futures to launch recursive computations. I think you should create as man futures as you have invocations and you can do that in the loop. Have you given that a try?
GridGain Fibonacci example is exactly the right approach here.
So I'm trying to teach myself how to implement a binary search in Java, as the topic might have given away, but am having some trouble.
See, I tend to be a little stubborn, and I'd rather not just copy some implementation off the internet.
In order to teach myself this, I created a very (VERY) rough little class which looks as follows:
public class bSearch{
/**
* #param args
*/
public static void main(String[] args) {
int one = 1;
int two = 2;
int three = 3;
int four = 4;
int five = 5;
int six = 6;
ArrayList tab = new ArrayList();
tab.add(one);
tab.add(two);
tab.add(three);
tab.add(four);
tab.add(five);
tab.add(six);
System.out.println(bSearch(tab, 53));
}
#SuppressWarnings({ "rawtypes", "unchecked" })
public static int bSearch(ArrayList tab, int key) {
if (tab.size() == 0)
return 0;
if ((int) tab.get(tab.size() / 2) == key)
return key;
ArrayList smallerThanKey = new ArrayList();
ArrayList largerThanKey = new ArrayList();
for (int i = 0; i < (tab.size() + 1) / 2; i++) {
smallerThanKey.add(tab.get(i));
}
System.out.println("Smaller array = " + smallerThanKey);
for (int i = (tab.size() + 1) / 2; i < tab.size(); i++) {
largerThanKey.add(tab.get(i));
}
System.out.println("Larger array = " + largerThanKey);
if (key < (int) tab.get(tab.size() / 2)) {
bSearch(smallerThanKey, key);
} else {
bSearch(largerThanKey, key);
}
return key;
}
}
As you can see, it's pretty far from beautiful, but it's clear enough for a noobie like myself to understand, anyway.
Now, here's the problem; when I feed it a number that is in the ArrayList, it feeds the number back to me (hurray!), but when I feed it a number that's not in the ArrayList, it still feeds me my number back to me (boo!).
I have a feeling my error is very minor, but I just can't see it.
Or am I all wrong, and there is some larger fundamental error?
Your help is deeply appreciated!
UPDATE
Thanks for all the constructive comments and answers! Many helpful pointer in the right direction by several of you. +1 for everyone who bumped me along the right path.
By following the advice you gave, mostly relating to my recursions not ending properly, I added a few return statements, as follows;
if (key < (int) tab.get(tab.size() / 2)) {
return bSearch(smallerThanKey, key);
} else {
return bSearch(largerThanKey, key);
}
Now, what this does is one step closer to what I want to achieve.
I now get 0 if the number is nowhere to be found, and the number itself if it is to be found. Thus progress is being made!
However, it does not work if I have it search for a negative number or zero (not that I know why I should, but just throwing that out there).
Is there a fix for this, or am I barking up the wrong tree in questioning?
EDIT
Just as a quick solution to the exact question you're asking: you need to change the last few lines to the following
if (key < (int) tab.get(tab.size() / 2)) {
return bSearch(smallerThanKey, key);
} else {
return bSearch(largerThanKey, key);
}
}
Having said that, let me point out a few more issues that I see here:
(a) you can use generics. That is use ArrayList<Integer> rather than just ArrayList this will save you from all those casts.
(b) Instead of returning the value that you found you'd be better off returning the index in the ArrayList where the value is located, or -1 if it was not found. Here's why: returning the key provides the caller with very little new information. I mean - the caller already known what key is. If you return the index to the key you let the caller know if the key was found or not, and if it was found where in the list it resides.
(c) You essentially copying the entire list each time you go into bSearch(): you copy roughly half of the list into smallerThanKey and (roughly) half into greaterThanKey. This means that the complexity of this implementation is not O(log n) but instead O(n).
(EDIT #2)
Summarizing points (a), (b), (c) here's how one could write that method:
public static int bSearch(ArrayList<Integer> tab, int key) {
return bSearch(tab, 0, tab.size(), key);
}
public static int bSearch(ArrayList<Integer> tab, int begin, int end, int key) {
int size = end - begin;
if (size <= 0)
return -1;
int midPoint = (begin + end) / 2;
int midValue = tab.get(midPoint);
if (midValue == key)
return midPoint;
if (key < midValue) {
return bSearch(tab, begin, midPoint, key);
} else {
return bSearch(tab, midPoint + 1, end, key);
}
}
As you can see, I added a second method that takes a begin, end parameters. These parameters let the method which part of the list it should look at. This is much cheaper than creating a new list and copying elements to it. Instead, the recursive function just uses the list object and simply calls itself with new begin, end values.
The return value is now the index of the key inside the list (or -1 if not found).
Your recursion is not properly ended. At the end of the method you recursively call the bSearchmethod for the left or right part of the array. At that point you need to return the search result of the recursive calls.
The idea of the binary search is: If your current node is not the key, look at the left if the value of the current node is bigger than the key or look at the right if it is smaller. So after looking there you need to return the search result from there.
if (key < (int) tab.get(tab.size() / 2)) {
return bSearch(smallerThanKey, key);
} else {
return bSearch(largerThanKey, key);
}
As a side remark, have a look at System.arraycopy and it is always a good idea to not suppress warnings.
I think the issue is here:
if (key < (int) tab.get(tab.size() / 2)) {
bSearch(smallerThanKey, key);
} else {
bSearch(largerThanKey, key);
}
return key;
You're just throwing away the result of your recursive call to bSearch and returning key. So it isn't really much of a surprise you get back whatever number you feed into the method.
Remember how binary search is supposed to work -- if the value isn't in the middle, return the result of searching in the left/right half of the array. So you need to do something with those recursive calls....
And with binary search, you really should be more concerned about finding the location of whatever you're looking for, not its value -- you know that already! So what you think was the binary search working right was a bit mistaken -- searching for 1 should have returned 0 -- the index/location of 1.
Also, you shouldn't need to deal with copying arrays and such -- that's an operation that is unnecessary for searches. Just use parameters to indicate where to begin/end searching.
I've built a model of the solar system in Java. In order to determine the position of a planet it does do a whole lot of computations which give a very exact value. However I am often satisfied with the approximate position, if that could make it go faster. Because I'm using it in a simulation speed is important, as the position of the planet will be requested millions of times.
Currently I try to cache the position of a planet throughout its orbit and then use those coordinates over and over. If a position in between two values is requested I perform a linear interpolation. This is how I store values:
for(int t=0; t<tp; t++) {
listCoordinates[t]=super.coordinates(ti+t);
}
interpolator = new PlanetOrbit(listCoordinates,tp);
PlanetOrbit has the interpolation code:
package cometsim;
import org.apache.commons.math3.util.FastMath;
public class PlanetOrbit {
final double[][] coordinates;
double tp;
public PlanetOrbit(double[][] coordinates, double tp) {
this.coordinates = coordinates;
this.tp = tp;
}
public double[] coordinates(double julian) {
double T = julian % FastMath.floor(tp);
if(coordinates.length == 1 || coordinates.length == 0) return coordinates[0];
if(FastMath.round(T) == T) return coordinates[(int) T];
int floor = (int) FastMath.floor(T);
if(floor>=coordinates.length) floor=coordinates.length-5;
double[] f = coordinates[floor];
double[] c = coordinates[floor+1];
double[] retval = f;
retval[0] += (T-FastMath.floor(T))*(c[0]-f[0]);
retval[1] += (T-FastMath.floor(T))*(c[1]-f[1]);
retval[2] += (T-FastMath.floor(T))*(c[2]-f[2]);
return retval;
}
}
You can think of FastMath as Math but faster. However, this code is not much of a speed improvement over calculating the exact value every time. Do you have any ideas for how to make it faster?
There are a few issues I can see, the main ones I can see are as follows
PlanetOrbit#coordinates seems to actually change the values in the variable coordinates. As this method is supposed to only interpolate I expect that your orbit will actually corrupt slightly everytime you run though it (because it is a linear interpolation the orbit will actually degrade towards its centre).
You do the same thing several times, most clearly T-FastMath.floor(T) occures 3 seperate times in the code.
Not a question of efficiency or accuracy but the variable and method names are very opaque, use real words for variable names.
My proposed method would be as follows
public double[] getInterpolatedCoordinates(double julian){ //julian calendar? This variable name needs to be something else, like day, or time, or whatever it actually means
int startIndex=(int)julian;
int endIndex=(startIndex+1>=coordinates.length?1:startIndex+1); //wrap around
double nonIntegerPortion=julian-startIndex;
double[] start = coordinates[startIndex];
double[] end = coordinates[endIndex];
double[] returnPosition= new double[3];
for(int i=0;i< start.length;i++){
returnPosition[i]=start[i]*(1-nonIntegerPortion)+end[i]*nonIntegerPortion;
}
return returnPosition;
}
This avoids corrupting the coordinates array and avoids repeating the same floor several times (1-nonIntegerPortion is still done several times and could be removed if needs be but I expect profiling will show it isn't significant). However, it does create a new double[] each time which may be inefficient if you only need the array temporarily. This can be corrected using a store object (an object you used previously but no longer need, usually from the previous loop)
public double[] getInterpolatedCoordinates(double julian, double[] store){
int startIndex=(int)julian;
int endIndex=(startIndex+1>=coordinates.length?1:startIndex+1); //wrap around
double nonIntegerPortion=julian-startIndex;
double[] start = coordinates[startIndex];
double[] end = coordinates[endIndex];
double[] returnPosition= store;
for(int i=0;i< start.length;i++){
returnPosition[i]=start[i]*(1-nonIntegerPortion)+end[i]*nonIntegerPortion;
}
return returnPosition; //store is returned
}