MinMax AI for Checkers - java

I have created simple Checkers game in Java with minmax AI, but I doesn't work. I use recursive version of minmax, but there must be something wrong with it, because it returns moves, that aren't best, but maybe first generated.
public int minmax(int [][] board, int depth, int curPlayer){
ArrayList<Move> moves = findMoves(curPlayer, board);
if (depth == 0 || moves.size() == 0){
return heurValue(curPlayer, board);
}
int bestVal = 0;
if (curPlayer == GameCore.BLACK){
bestVal = Integer.MIN_VALUE;
curPlayer = GameCore.RED;
}else{
bestVal = Integer.MAX_VALUE;
curPlayer = GameCore.BLACK;
}
for(int i = 0; i<moves.size(); i++){
Move m = moves.get(i);
int [][] boardNew = makeMove(m, board);
int value = minmax(boardNew, depth-1, curPlayer);
board = undoMove(m, boardNew);
// computer plays as black
if (curPlayer == GameCore.BLACK){
if (value < bestVal){
bestMove = m;
bestVal = value;
}
}else{
if (value >= bestVal){
bestMove = m;
bestVal = value;
}
}
}
return bestVal;
}
If I call minmax with depth = 1 it should "return 7 values (there are 7 possible moves), but it returns only 1 if I move from 2,4 to 3,3... but when I tried to debug it, ArrayList moves has correct size. So I don't know what is wrong with it. :(
EDIT:
By "return" I mistakenly mean that first condition (when depth is 0 or moves are empty) happens only once, but if it was correct it should happen 7 times. Sorry for my bad english.
Do you know some site, where is correct recursive pseudocode for minmax (better with alpha/beta, because I will need to expand it) or could you help me to fix this? It must be only trifle.
Thank you!

You've written this to only return the best value, i.e. return bestVal; If you want it to return them all, store them in a List of some sort and change the method signature accordingly.

EDIT: So it is not a signature problem as I first thought.
I've done a quick search about the MinMax algorythm and this is what i found an article
Quickly what I think is important from this page is :
The values here represents how good a move is. So the MAX player will try to select the move with highest value in the end.
So if I'm rigth, MinMax will only return one move, the one with the bestValue.

Related

Minimax Connect 4 AI trouble

I am making connect 4 AI, except the game continues until all 42 spaces are filled.
Score is kept by every 4 in a row gets 1 point.
public int[] Max_Value(GameBoard playBoard, int depth){
GameBoard temp = new GameBoard(playBoard.playBoard);
int h = 0, tempH = 999, tempCol=0;
int myDepth = depth - 1;
int[] tempH2 = new int[2];
boolean noChildren = true;
if(myDepth != -1){
for(int i = 0; i < 7; i++){
if(temp.isValidPlay(i)){
count++;
temp.playPiece(i);
noChildren = false;
tempH2 = Min_Value(temp, myDepth);
if(tempH2[1] < tempH){
tempH=tempH2[1];
tempCol = i;
}
temp.removePiece(i);
}
}
}
int[] x = new int[2];
if(noChildren){
h = temp.getHeuristic();
}
else{
h = tempH;
x[0]=tempCol;
}
x[1]=h;
return x;
}
public int[] Min_Value(GameBoard playBoard, int depth){
GameBoard temp = new GameBoard(playBoard.playBoard);
int h = 0, tempH = -999, tempCol=0;
int myDepth = depth - 1;
int[] tempH2 = new int[2];
boolean noChildren = true;
if(myDepth != -1){
for(int i = 0; i < 7; i++){
if(temp.isValidPlay(i)){
count++;
temp.playPiece(i);
noChildren = false;
tempH2 = Max_Value(temp, myDepth);
if(tempH2[1] > tempH){
tempH=tempH2[1];
tempCol = i;
}
temp.removePiece(i);
}
}
}
int[] x = new int[2];
if(noChildren){
h = temp.getHeuristic();
}
else{
h = tempH;
x[0]=tempCol;
}
x[1]=h;
return x;
}
I feel like I just stumbled through everything, and it feels like terrible code. However, I have never attempted anything like this before, and would appreciate any input. I can't tell where I am going wrong. My evaluation function just gives 1 point for each 4 in a row it can find for any given state. The main function calls the Min_Value function to start things off with a depth of 10.
I am attempting to return the column as well as the value of the heuristic. I hope I have provided enough information. Thanks for any insight.
Allright, after implementing the methods not shown (like evaluation, playmove, remove etc.) I was able to debug this. Assuming that these functions are implemented in some correct way in your version, the mistake is that you never actually call the evaluation function if depth is -1:
You have this:
[...]if(myDepth != -1)
{/*restofthecode*/}[...]
But what you need is something like this:
[...]if(myDepth == -1)
{
return temp.getHeuristic();
}
/*restofthecode*/
[...]
That way, whenever you reach depth -1 (a leaf in your minimax tree), the board will be evaluated and the value returned (which is excactly what you need in minimax).
Do this modification in both parts (min and max) and everything shuold be allright. If there are other problems, feel free to ask.
Even though it isn't stated in the question, I think you are not getting good moves from your search, right?
Without looking through your while code, I can already say that your program will only work during the last 10 moves of the game (last 10 empty fields or forced win in 10). Otherwise, your program will return either the last or the first move it evaluated. That is because of your evaluation function, where you only handle a win (respectively 4 in a row), but not 2 in a row, traps, 3 in a row, etc.). It will think of all moves as equal if it can't force a win.
This is a problem, because starting with an empty field, a win can only be forced by the starting player, and just with the 2nd last piece to be placed on the board. (In your version 4 in a row forced).
And since your searchdepth (10) is smaller than the maximum game moves (42), your program will always play its first move.
If the rest of your algorithm is correctly implemented, you can fix this by simply improve your evaluation function, so that it can differ between "good" and "bad" game positions.

Find smallest int (path size) in recursive method

I'm trying to find the smallest path size in a maze that uses recursion.
To do this, the maze has to go through all the possible paths and then keep updating the "shortest length".
I was able to have it go through all the possible lists and print these coordinates and the path size, but I'm unable to find the minimum of values, as the path last found is always updated to be the "shortest length".
So, I was thinking of adding all of the solution path lengths to an ArrayList<Integer> list, and then create a separate static class outside of the recursion solve method, where I find the minimum and return it's value to the solve() method and proceed from there. Is this the most optimum way to do this? Or can I find the shortest length and corresponding coordinates in the solve() method?
This is what code for the recursive case:
else {
for(int i = 0; i < directions.length; i++)
{
Coord nextSpot = currentSpot.addTo(directions[i]);
if(nextSpot.validSpot(maze))
if(!newPath.contains(nextSpot)){
ArrayList<Coord> solution = //The recursive call
solve(newPath,nextSpot,goal,maze);
if(solution != null){
int shortestLength = 100; //arbitrary large length
// lengths.add(lengthSolution); ?? Possible alternative?
System.out.println(lengthSolution);
System.out.println(solution);
if( solution.size() < shortestLength ){
shortestLength = solution.size();
System.out.println(shortestLength);
}
}
}//ifs
}//for
return null;
}//else (recursive case)
int globalsteps =0;
int maze(x,y,steps) {
if(icangoup){ maze(x+1,y,steps +1);}
if(icangodown){ maze(x-1,y,steps +1);}
if(icangoleft){ maze(x,y+1,steps +1);}
if(icangoright){ maze(x,y-1,steps +1);}
if(ifoundtheexit!!!!) {
if(globalsteps == 0 || steps < globalsteps)
globalsteps = steps;
}
}
}

When navigating a 2D array, check neighboring elements for valid path relative to point of entry?

The nature of this problem has changed since submission, but the question isn't fit for deletion. I've answered the problem below and marked it as a community post.
I'm writing a recursive path-navigating function and the final piece I need involves knowing which cell you came from, and determining where to go next.
The Stage
You are given a 2d array where 0's denote an invalid path and 1's denote a valid path. As far as I know, you are allowed to manipulate the data of the array you're navigating, so I mark a traveled path with 2's.
The Goal
You need to recursively find and print all paths from origin to exit. There are four mazes, some with multiple paths, dead ends, or loops.
I've written code that can correctly handle all three cases, except the method for finding the next path is flawed in that it starts at a fixed location relative to your current index, and checks for a travelled path; If you encounter it, it's supposed to retreat.
While this works in most cases, it fails in a case when the first place it checks happens to be the place you came from. At this point, it returns out and ends prematurely.
Because of this, I need to find a way to intelligently start scanning (clockwise or anti-clockwise) based on where you came from, so that that place is always the last place checked.
Here is some code describing the process (note: edge cases are handled prior to this, so we don't need to worry about that):
private static void main()
{
int StartX = ;//Any arbitrary X
int StartY = ;//Any arbitrary Y
String Path = ""; //Recursive calls will tack on their location to this and print only when an exit path is found.
int[][] myArray = ;//We are given this array, I just edit it as I go
Navigator(StartX, StartY, Path, myArray);
}
private static void Navigator(int locX, int locY, String Path, int[][] myArray)
{
int newX = 0; int newY = 0;
Path = Path.concat("["+locX+","+locY+"]");
//Case 1: You're on the edge of the maze
boolean bIsOnEdge = (locX == 0 || locX == myArray.length-1 || locY == 0 || locY == myArray[0].length-1);
if (bIsOnEdge)
{
System.out.println(Path);
return;
}
int[][] Surroundings = surroundingsFinder(locX, locY, myArray);
for (int i = 0; i <= 7; i++)
{
//Case 2: Path encountered
if (Surroundings[0][i] == 1)
{
myArray[locX][locY] = 2;
newX = Surroundings[1][i];
newY = Surroundings[2][i];
Navigator(newX, newY, myArray, Path);
}
//Case 3: Breadcrumb encountered
if (Surroundings[0][i] == 2)
{
myArray[locX][locY] = 1;
return;
}
}
}
//generates 2D array of your surroundings clockwise from N to NW
//THIS IS THE PART THAT NEEDS TO BE IMPROVED, It always starts at A.
//
// H A B
// G - C
// F E D
//
static int[][] surroundingsFinder(int locX, int locY, int[][] myArray)
{
int[][] Surroundings = new int[3][8];
for (int i = -1; i <= 1; i++)
{
for (int j = -1; j <= 1; j++)
{
}
}
//Can be done simpler, is done this way for clarity
int xA = locX-1; int yA = locY; int valA = myArray[xA][yA];
int xB = locX-1; int yB = locY+1; int valB = myArray[xB][yB];
int xC = locX; int yC = locY+1; int valC = myArray[xC][yC];
int xD = locX+1; int yD = locY+1; int valD = myArray[xD][yD];
int xE = locX+1; int yE = locY; int valE = myArray[xE][yE];
int xF = locX+1; int yF = locY-1; int valF = myArray[xF][yF];
int xG = locX; int yG = locY-1; int valG = myArray[xG][yG];
int xH = locX-1; int yH = locY-1; int valH = myArray[xH][yH];
int[][] Surroundings = new int[3][8];
Surroundings[0][0] = valA; Surroundings[1][0] = xA; Surroundings[2][0] = yA;
Surroundings[0][1] = valB; Surroundings[1][1] = xB; Surroundings[2][1] = yB;
Surroundings[0][2] = valC; Surroundings[1][2] = xC; Surroundings[2][2] = yC;
Surroundings[0][3] = valD; Surroundings[1][3] = xD; Surroundings[2][3] = yD;
Surroundings[0][4] = valE; Surroundings[1][4] = xE; Surroundings[2][4] = yE;
Surroundings[0][5] = valF; Surroundings[1][5] = xF; Surroundings[2][5] = yF;
Surroundings[0][6] = valG; Surroundings[1][6] = xG; Surroundings[2][6] = yG;
Surroundings[0][7] = valH; Surroundings[1][7] = xH; Surroundings[2][7] = yH;
return Surroundings;
}
Can anyone help me with this? As you can see, surroundingsFinder always finds A first, then B all the way to H. This is fine if and only if you entered from H. But if fails on cases where you entered from A, so I need to make a way to intelligently determine where to start finding. Once I know this, I can probably adapt the logic so I no longer use a 2D array of values, as well. But so far I can't come up with the logic for the smart searcher!
NOTE: I am aware that Java does not optimize middle-recursion. It seems impossible to get tail recursion working for a problem like this.
The Solution
The initial goal was to print, from start to end, all of the paths that exit the array.
An earlier rendition of the script wrote "0" on treaded locations rather than "2", but for some reason I imagined that I needed the "2" and I needed to differentiate between "treaded path" and "invalid path".
In fact, due to the recursive nature of the problem, I discovered that you can in fact solve the problem writing only 0's as you go. Also, I no longer needed to keep track of where I came from and instead of checking clockwise over a matrix, I was iterating from left to right down the 3x3 matrix surrounding me, skipping my own cell.
Here is the completed code for such a solution. It prints to console upon finding an exit (edge) and otherwise traces itself around the maze, complete with recursion. To start the function, you are given a square 2D array of 0's and 1's where 1 is a valid path and 0 is invalid. You are also given a set of coordinates where you are "dropped in" (locX, locY) and an empty string that accumulates coordinates, forming a path that is later printed out (String Path = "")
Here is the code:
static void Navigator(int locX, int locY, int[][] myArray, String Path)
{
int newX = 0;
int newY = 0;
Path = Path.concat("["+locX+","+locY+"]");
if ((locX == 0 || locX == myArray.length-1 || locY == 0 || locY == myArray[0].length-1))
{//Edge Found
System.out.println(Path);
pathCnt++;
myArray[locX][locY] = 1;
return;
}
for (int row = -1; row <= 1; row++)
{
for (int col = -1; col <= 1; col++)
{
if (!(col == 0 && row == 0) && (myArray[locX+row][locY+col] == 1))
{ //Valid Path Found
myArray[locX][locY] = 0;
Navigator(locX+row, locY+col, myArray, Path);
}
}
}
//Dead End Found
myArray[locX][locY] = 1;
return;
} System.out.println(Path);
pathCnt++;
swamp[locX][locY] = 1;
return;
}
for (int row = -1; row <= 1; row++)
{
for (int col = -1; col <= 1; col++)
{
if (!(col == 0 && row == 0) && (swamp[locX+row][locY+col] == 1))
{ //Valid Path Found
swamp[locX][locY] = 0;
Navigator(locX+row, locY+col, swamp, Path);
}
}
}
//Dead End Found
swamp[locX][locY] = 1;
return;
}
As you may determine yourself, every time we "enter" a cell, we have 8 neighbors to check for validity. First, to save on run time and to avoid going out of the array during our for loop (it can't find myArray[i][j] if i or j point it outside, and it will error out), we check for edges. Since we're given the area of our swamp we use a truth comparison statement that essentially says ("(am I on the top or left edge?) or (am I on the bottom or right edge?)"). If we ARE on an edge, we print out the Path we're holding (thanks to deep copy, we have a unique copy of the original Path that only prints if we're on an edge, and includes our full set of coordinates).
If we aren't on an edge, then we start looking around us. We start at top left and move horizontally to bottom right, with a special check to make sure we're not checking where we're standing.:
A B C
D . E
F G H
This loop checks only for 1's and only calls the function up again should that happen. Why? Because it is the second-to-last case. There is only one extra situation that will occur, and if we reach the end of the function it means we hit that case. Why write extra code (checking for 0's to specifically recognize it?
So, as I just mentioned, if we exit the for loop, it means we didn't encounter any 1's at all. It means we're surrounded by zeros! It means we've hit a dead end, and that means that all we have to do is error our away out of that instance of the function, ergo the final return;.
All in all, the final function is simple. But coming from no background and having to realize the patterns and meanings of these cases, and after several failed attempts at this, it can take quite a bit of work. I was several days at work on perfecting this.
Happy coding, Everyone!
Your issue seems to be with:
if (Surroundings[0][i] == 2)
{
myArray[locX][locY] = 1;
return;
}
Perhaps this should be changed to:
if (Surroundings[0][i] == 2)
{
// not sure why you need this if it's already 1
myArray[locX][locY] = 1;
// go to next iteration of the "i" loop
// and keep looking for next available path
continue;
}
Your recursive method will automatically return when none of the surrounding cells satisfy the condition if (Surroundings[0][i] == 1).
PS: It's conventional to name your variables using small letter as the first character. For example: surroundings, path, startX or myVar

Java TicTacToe MiniMax Recursively AI

I'm currently working on an Android App containing TicTacToe with an AI opponent.
I've come real far, but for some reason not all moves the opponent calculates are leading him to victory or a tie.
I've made a recursive MiniMax algorithm (no pruning yet), and I hope somebody is able to see what's wrong here, since I really couldn't find it. I've drawn many trees so far and
I've done a lot of debugging in Eclipse ADE and every step seems to be right, but in the end, it's obviously not.
So here's my function:
public int[] MiniMax( int[][] gameboard, boolean isCPUTurn, int[] move, int depth ){
if (gameboard[1][1]==EMPTY) { // Always take middle if possible
int[] best = {2,1,1};
return best;
}
// Recursion base-case
int winner = checkWinner();
if ( winner == 1 || winner == 2 || winner == 3 ){ // Game is over
int[] returnSet = {-1,-1,-1}; // leeg
// adjust scores for Minimax Player<----Tie---->CPU
// 0 1 2
switch (winner){
case 1:
returnSet[0] = 0 ; // Player wins
break;
case 2:
returnSet[0] = 2; // CPU wins
break;
case 3:
returnSet[0] = 1; // TIE
break;
default: // impossible..
returnSet[0] = 1;
}
// The last move led to the end of the game, return this outcome and move
returnSet[1] = move[0];
returnSet[2] = move[1];
return returnSet;
}
// A move is still possible, the game's not over yet
else{
int[] bestMove = null; // contains the best move found for this turn
for (int i=0; i<3; i++){ // check all child nodes/boards
for (int j=0; j<3; j++){
if( gameboard[i][j] == EMPTY ) // if the spot's not yet filled
{
if (isCPUTurn) gameboard[i][j] = TWO; // this move is CPU's
else gameboard[i][j] = ONE; // this move is Player's
int[] thismove = {i,j}; // {move Row, move Column}
// recursion
int[] result = MiniMax( gameboard, !isCPUTurn, thismove, depth+1);
// result = { winner result, move Row, move Column}
gameboard[i][j] = EMPTY; // delete last move after recursion
// check if child is best option
if (bestMove == null) // first child is always the best initially
bestMove = result;
else {
if (!isCPUTurn) // for CPU, higher is better
{ if (result[0]>bestMove[0])
bestMove = result;
//if (bestMove[0]==2) return bestMove; Pruning thingy
}
else if (isCPUTurn) // for Player, lower is better
{ if (result[0]<bestMove[0])
bestMove = result;
//if (bestMove[0]==0) return bestMove; Pruning thingy
}
}
}
}
}
return bestMove; // returns the best move found after checking all possible childs
}
}
As you can see, this function calls itself directly, using middle recursion.
It also calls the checkWinner function, which has 4 possible outcomes: 0 if nobody has won yet and the board is not filled, 1 if player 1 (you) has won, 2 if player 2 (AI) has won and 3 if it's a tie (and thus the board is filled).
So yea, I hope somebody finds the solution :D
Thanks in advance!
The problem is that you are always returning the move from the deepest level. In the lines where you check if you have improved
, you should set bestMoveto contain the coordinates iand j, not the coordinates from the move one level deeper, namely result.

Java - Maximum sum in path through a 2D array

Basically I have a problem that goes something similar to this:
There is a garden of strawberry plants represented by a 2D, square array. Each plant(each element) has a number of strawberries. You start at the top left corner of the array, and you can only move to the right or down. I need to design a recursive method to calculate the paths through the garden and then output which one yields the most strawberries.
I think I have an understanding of really really simple recursion problems, but this problem has gone way over my head. I'm not really sure where to start or where to go as far as creating a recursive method.
Any help related to the code or helping me understand the concept behind this problem is greatly appreciated. Thanks.
Like dasblinkenlight said, the most efficient way to do this is using a memoization or dynamic programming technique. I tend to prefer dynamic programming, but I'll use pure recursion here.
The answer centers around the answer to one fundamental question: "If I'm in the square in row r and column c on my field, how can I evaluate the path from the top left to here such that the number of strawberries is maximized?"
The key to realize is that there's only two ways to get in the plot in row r and column c: either I can get there from above, using the plot in row r-1 and column c, or I can get there from the side, using the plot in row r and column c-1. After that, you just need to make sure you know your base cases...which means, fundamentally, my purely recursive version would be something like:
int[][] field;
int max(int r, int c) {
//Base case
if (r == 0 && c == 0) {
return field[r][c];
}
//Assuming a positive number of strawberries in each plot, otherwise this needs
//to be negative infinity
int maxTop = -1, maxLeft = -1;
//We can't come from the top if we're in the top row
if (r != 0) {
maxTop = field[r-1][c];
}
//Similarly, we can't come from the left if we're in the left column
if (c != 0) {
maxLeft = field[r][c-1];
}
//Take whichever gives you more and return..
return Math.max(maxTop, maxLeft) + field[r][c];
}
Call max(r-1, c-1) to get your answer. Notice there's a lot of inefficiency here; you'll do much better by using dynamic programming (which I'll provide below) or memoization (which has already been defined). The thing to remember, though, is that both the DP and memoization techniques are simply more efficient ways that come from the recursive principles used here.
DP:
int maxValue(int[][] field) {
int r = field.length;
int c = field[0].length;
int[][] maxValues = new int[r][c];
for (int i = 0; i < r; i++) {
for (int j = 0; j < c; j++) {
if (i == 0 && j == 0) {
maxValues[i][j] = field[i][j];
} else if (i == 0) {
maxValues[i][j] = maxValues[i][j-1] + field[i][j];
} else if (j == 0) {
maxValues[i][j] = maxValues[i-1][j] + field[i][j];
} else {
maxValues[i][j] = Math.max(maxValues[i][j-1], maxValues[i-1][j]) + field[i][j];
}
}
}
return maxValues[r-1][c-1];
}
In both cases, if you want to recreate the actual path, just keep a 2D table of booleans that corresponds with "Did I come from above or to the left"? If the most strawberry path comes from above, put true, otherwise put false. That can allow you to retrace the patch after the calculation.
Notice that this is still recursive in principal: at each step, we're looking back at our previous results. We just happen to be caching our previous results so we don't waste a bunch of work, and we're attacking the subproblems in an intelligent order so that we can always solve them. For more on dynamic programming, see Wikipedia.
You can do it using memoization. Here is Java-like pseudodoce (memo, R, and C are assumed to be instance variables available to the max method).
int R = 10, C = 20;
int memo[][] = new int[R][C];
for (int r=0 ; r != R ; r++)
for (int c = 0 ; c != C ; c++)
memo[r][c] = -1;
int res = max(0, 0, field);
int max(int r, int c, int[][] field) {
if (memo[r][c] != -1) return memo[r][c];
int down = 0; right = 0;
if (r != R) down = max(r+1, c, field);
if (c != C) right = max(r, c+1, field);
return memo[r][c] = (field[r][c] + Math.max(down, right));
}
You can solve this with DP tabulation method, with which you can save space from O(m*n) to just O(n). With DP Memorization, you need m*n matrix to store intermediate values. Following is my Python code. Hope it can help.
def max_path(field):
dp = [sum(field[0][:i]) for i in range(1, len(field[0]) + 1)]
for i in range(1, len(field)):
for j in range(len(dp)):
dp[j] = min(dp[j], dp[j - 1] if j > 0 else float('inf')) + field[i][j]
return dp[-1]

Categories