I want to implement Minimax in my Abalone game but I don't know how to do it.
To be exact I don't know when the algo need to max or min the player.
If I have understand the logic, I need to min the player and max the AI ?
This is the wikipedia pseudo code
function minimax(node, depth, maximizingPlayer)
if depth = 0 or node is a terminal node
return the heuristic value of node
if maximizingPlayer
bestValue := -∞
for each child of node
val := minimax(child, depth - 1, FALSE))
bestValue := max(bestValue, val);
return bestValue
else
bestValue := +∞
for each child of node
val := minimax(child, depth - 1, TRUE))
bestValue := min(bestValue, val);
return bestValue
(* Initial call for maximizing player *)
minimax(origin, depth, TRUE)
And my implementation
private Integer minimax(Board board, Integer depth, Color current, Boolean maximizingPlayer) {
Integer bestValue;
if (0 == depth)
return ((current == selfColor) ? 1 : -1) * this.evaluateBoard(board, current);
Integer val;
if (maximizingPlayer) {
bestValue = -INF;
for (Move m : board.getPossibleMoves(current)) {
board.apply(m);
val = minimax(board, depth - 1, current, Boolean.FALSE);
bestValue = Math.max(bestValue, val);
board.revert(m);
}
return bestValue;
} else {
bestValue = INF;
for (Move m : board.getPossibleMoves(current)) {
board.apply(m);
val = minimax(board, depth - 1, current, Boolean.TRUE);
bestValue = Math.min(bestValue, val);
board.revert(m);
}
return bestValue;
}
}
And my evaluate function
private Integer evaluateBoard(Board board, Color player) {
return board.ballsCount(player) - board.ballsCount(player.other());
}
It depends on your evaluation function; in your case, assuming the goal is to have more balls on the board than your opponent, the Player would be maximizing & the AI would be minimizing.
The usual method in 2 player games is to always maximize, but negate the value
as it is passed up to the parent.
Your evaluation function is not very usefull for minimax search as it would be constant for most moves of the game. The moves in Abalone are a lot less dramatic than Chess. Try using the sum of distances between all the player's marbles. This function gives minimax something to work with.
You also need to ensure that selfColor is the colour of the player to move when you make the initial call to minimax.
The recursion end could also be written
if (0 == depth)
return this.evaluateBoard(board, selfColor);
Out of scope of the question, but could be relevant to you: I find negamax easier to work with.
Related
I want to implement KD tree in java for a data structure project but I have problem with special method that this project wants. In below you can see the format of method that I want.
float[][] findMNearest(float[] point, int m) {}
I implement find nearest neighbor method but for m nearest neighbor I have problem and I can't understand algorithm for solution.
In this picture you can see my implementation for nearest neighbor.
java
private void nearest(KDNode root, KDNode target, int index) {
if (root == null)
return;
visited++;
float d = root.distance(target);
if (best == null || d < bestDistance) {
bestDistance = d;
best = root;
}
if (bestDistance == 0)
return;
float dx = root.getCoordinates()[index] - target.getCoordinates()[index];
index = (index + 1) % k;
nearest(dx > 0 ? root.getLeft() : root.getRight(), target, index);
if (dx * dx >= bestDistance)
return;
nearest(dx > 0 ? root.getRight() : root.getLeft(), target, index);
}
I don't want to use ready library, too.
At the end my friend help me!
Full source of project for answer this question and other questions about kdtree and its methods at https://github.com/Iman9mo/KDTree
I'm trying to find the min for a binary tree, NOT binary search tree, but can't get the answer, code is below. Please tell me what is wrong.
Edit: So with the help of a poster, I was able to get the code working. I made code 1 to work, but I don' understand why in code 2, we need to check for Math.min again when in code 1 we didn't have to do that.
Code 1:
static int min = 0;
static public int findMinimumValue(Node root) {
min = root.data;
findMinimumValue(root, root.data);
return min;
}
static public int findMinimumValue(Node root, int x) {
if(root == null) {
return min;
} else {
if(root.data < min) {
min = root.data;
}
int left = findMinimumValue(root.left, x);
int right = findMinimumValue(root.right, x);
return min;
}
}
Code 2:
static public int findSecondMinimumValue(Node root) {
// min = root.data;
return findSecondMinimumValue(root, root.data);
}
static public int findSecondMinimumValue(Node root, int min) {
if(root == null) {
return min;
} else {
if(root.data < min) {
min = root.data;
}
int left = findSecondMinimumValue(root.left, min);
int right = findSecondMinimumValue(root.right, min);
return Math.min(left, right);
}
}
Step 1: Identify the problem
Follow through what all you have done in the code base. Let's make a binary tree:
5
/ \
2 1
Clearly the minimum is 1, right? So, follow through your code with this example.
public int findMinimumValue(TreeNode root) {
return findMinimumValue(root, root.val);
}
The root will be the starting point.
int left = findMinimumValue(root.left, min);
int right = findMinimumValue(root.right, min);
This is what each and every node will see (if it is not null). It's a recursive call to the left, then to the right after it has gone as left as it can.
finalMinimumValue(TreeNode(5), 5)
Calls the following:
int left = finalMinimumValue(TreeNode(2), 5);
int right = finalMinimumValue(TreeNode(1), 5)
finalMinimumValue(TreeNode(2), 5)
Calls the following:
int left = finalMinimumValue(null, 5);
int right = finalMinimumValue(null, 5)
finalMinimumValue(null, 5)
Does the following code:
return min;
What is min? min is, well, 5.
Does that make sense? We traversed over 2, yet still kept the min as 5.
Step 2: Fix the problem
We concluded in step 1 that it makes no sense for us to not update min if we are currently at a node that is not the minimum. So, let's update it before we recursively go down also.
public int findMinimumValue(TreeNode root) {
return findMinimumValue(root, root.val);
}
public int findMinimumValue(TreeNode root, int min) {
if (root == null) {
return min;
} else {
// update your min variable here by comparing it with the node you currently are at!
int left = findMinimumValue(root.left, min);
int right = findMinimumValue(root.right, min);
if (left < min) {
min = left;
}
if (right < min) {
min = right;
}
return min;
}
}
Step 3: Test the solution
Let's follow through with the same example. We are expecting it to say it is 1.
5
/ \
2 1
1: Compute left for the root node
Our Node's value is 5. Is 5 less than our Node's value (5)? No. So, don't update the min.
Next, call the left child, Node(2)
2: Compute the min to return for Node 2
Our Node's value is 2. Is 5 less than our Node's value (2)? Yes! So, update our min value.
Now min is 2.
Next, call the left child, null. Since its left child is null, we return min, which is 2.
Now, call the right child, null. Since its right child is null, we return min, which is 2.
Well, left equals 2, right equals 2. So, return 2!
3: Compute the min to return for Node 1
Our Node's value is 1. Is 5 less than our Node's value (1)? Yes! So update the min.
Now min is 1.
Next, call the left child, null. Since its left child is null, we return min, which is 1.
Now, call the right child, null. Since its right child is null, we return min, which is 1.
Well, left equals 1, right equals 1. So, return 1!
4: Compute the min to return for the root node.
The left returned us 2.
The right returned us 1.
Since 1 is less than 2, 1 is the answer.
It works!
I'm trying to build a chess AI. My negamax function with alpha-beta pruning (ABP) runs much slower (about 8 times) than separate min and max functions also with ABP, though the moves returned are equal.
My board evaluation function always returns a value with respect to the red player, i.e. the higher the better for red. For Negamax only, this value is multiplied by -1 for the black player when returning at depth 0.
My Negamax function:
int alphaBeta(Board board, int depth, int alpha, int beta) {
if (depth <= 0 || board.isGameOver()) { // game over == checkmate/stalemate
int color = board.getCurrPlayer().getAlliance().isRed() ? 1 : -1;
return BoardEvaluator.evaluate(board, depth) * color;
}
int bestVal = Integer.MIN_VALUE + 1;
for (Move move : MoveSorter.simpleSort(board.getCurrPlayer().getLegalMoves())) {
MoveTransition transition = board.getCurrPlayer().makeMove(move);
if (transition.getMoveStatus().isAllowed()) { // allowed == legal && non-suicidal
int val = -alphaBeta(transition.getNextBoard(), depth - 1, -beta, -alpha);
if (val >= beta) {
return val; // fail-soft
}
if (val > bestVal) {
bestVal = val;
alpha = Math.max(alpha, val);
}
}
}
return bestVal;
}
The root call:
-alphaBeta(transition.getNextBoard(), searchDepth - 1,
Integer.MIN_VALUE + 1, Integer.MAX_VALUE); // +1 to avoid overflow when negating
My min and max functions:
int min(Board board, int depth, int alpha, int beta) {
if (depth <= 0 || board.isGameOver()) {
return BoardEvaluator.evaluate(board, depth);
}
int minValue = Integer.MAX_VALUE;
for (Move move : MoveSorter.simpleSort(board.getCurrPlayer().getLegalMoves())) {
MoveTransition transition = board.getCurrPlayer().makeMove(move);
if (transition.getMoveStatus().isAllowed()) {
minValue = Math.min(minValue, max(transition.getNextBoard(), depth - 1, alpha, beta));
beta = Math.min(beta, minValue);
if (alpha >= beta) break; // cutoff
}
}
return minValue;
}
int max(Board board, int depth, int alpha, int beta) {
if (depth <= 0 || board.isGameOver()) {
return BoardEvaluator.evaluate(board, depth);
}
int maxValue = Integer.MIN_VALUE;
for (Move move : MoveSorter.simpleSort(board.getCurrPlayer().getLegalMoves())) {
MoveTransition transition = board.getCurrPlayer().makeMove(move);
if (transition.getMoveStatus().isAllowed()) {
maxValue = Math.max(maxValue, min(transition.getNextBoard(), depth - 1, alpha, beta));
alpha = Math.max(alpha, maxValue);
if (alpha >= beta) break; // cutoff
}
}
return maxValue;
}
The root calls for red and black players respectively:
min(transition.getNextBoard(), searchDepth - 1, Integer.MIN_VALUE, Integer.MAX_VALUE);
max(transition.getNextBoard(), searchDepth - 1, Integer.MIN_VALUE, Integer.MAX_VALUE);
I'm guessing there's a bug with the cutoff in the Negamax function although I followed the pseudocode from here. Any help is appreciated, thanks!
EDIT: alphaBeta() is called about 6 times more than min() and max() combined, while the number of beta cutoffs is only about 2 times more.
Solved. I should have posted my full code for the root calls as well -- didn't realise I wasn't passing in the new value for beta. Alpha/beta was actually being updated in the root method for separate min-max.
Updated root method for Negamax:
Move bestMove = null;
int bestVal = Integer.MIN_VALUE + 1;
for (Move move : MoveSorter.simpleSort(currBoard.getCurrPlayer().getLegalMoves())) {
MoveTransition transition = currBoard.getCurrPlayer().makeMove(move);
if (transition.getMoveStatus().isAllowed()) {
int val = -alphaBeta(transition.getNextBoard(), searchDepth - 1, Integer.MIN_VALUE + 1, -bestVal);
if (val > bestVal) {
bestVal = val;
bestMove = move;
}
}
}
return bestMove;
Apologies for the lack of information provided in my question -- I didn't expect the bug to be there.
I am working through the Minimax algorithm with Alpha-Beta Pruning example found here. In the example, they use an array to implement the search tree. I followed the example, but also tried implementing it with a binary search tree as well. Here are the values I'm using in the tree: 3, 5, 6, 9, 1, 2, 0, -1.
The optimal value at the end should be 5. With the BST implementation, I keep getting 2.
I think this is the problem, but I don't know how to get around it:
I wrote the code to return out of recursion if it sees a leaf node to stop from getting null pointer exceptions when trying to check the next value. But instead, I think it's stopping the search too early (based off of what I see when stepping through the code with the debugger). If I remove the check though, the code fails on a null pointer.
Can someone point me in the right direction? What am I doing wrong?
Here's the code:
public class AlphaBetaMiniMax {
private static BinarySearchTree myTree = new BinarySearchTree();
static int MAX = 1000;
static int MIN = -1000;
static int opt;
public static void main(String[] args) {
//Start constructing the game
AlphaBetaMiniMax demo = new AlphaBetaMiniMax();
//3, 5, 6, 9, 1, 2, 0, -1
demo.myTree.insert(3);
demo.myTree.insert(5);
demo.myTree.insert(6);
demo.myTree.insert(9);
demo.myTree.insert(1);
demo.myTree.insert(2);
demo.myTree.insert(0);
demo.myTree.insert(-1);
//print the tree
System.out.println("Game Tree: ");
demo.myTree.printTree(demo.myTree.root);
//Print the results of the game
System.out.println("\nGame Results:");
//run the minimax algorithm with the following inputs
int optimalVal = demo.minimax(0, myTree.root, true, MAX, MIN);
System.out.println("Optimal Value: " + optimalVal);
}
/**
* #param alpha = 1000
* #param beta = -1000
* #param nodeIndex - the current node
* #param depth - the depth to search
* #param maximizingPlayer - the current player making a move
* #return - the best move for the current player
*/
public int minimax(int depth, MiniMaxNode nodeIndex, boolean maximizingPlayer, double alpha, double beta) {
//Base Case #1: Reached the bottom of the tree
if (depth == 2) {
return nodeIndex.getValue();
}
//Base Case #2: if reached a leaf node, return the value of the current node
if (nodeIndex.getLeft() == null && maximizingPlayer == false) {
return nodeIndex.getValue();
} else if (nodeIndex.getRight() == null && maximizingPlayer == true) {
return nodeIndex.getValue();
}
//Mini-Max Algorithm
if (maximizingPlayer) {
int best = MIN;
//Recur for left and right children
for (int i = 0; i < 2; i++) {
int val = minimax(depth + 1, nodeIndex.getLeft(), false, alpha, beta);
best = Math.max(best, val);
alpha = Math.max(alpha, best);
//Alpha Beta Pruning
if (beta <= alpha) {
break;
}
}
return best;
} else {
int best = MAX;
//Recur for left and right children
for (int i = 0; i < 2; i++) {
int val = minimax(depth + 1, nodeIndex.getRight(), true, alpha, beta);
best = Math.min(best, val);
beta = Math.min(beta, best);
//Alpha Beta Pruning
if (beta <= alpha) {
break;
}
}
return best;
}
}
}
Output:
Game Tree:
-1 ~ 0 ~ 1 ~ 2 ~ 3 ~ 5 ~ 6 ~ 9 ~
Game Results:
Optimal Value: 2
Your problem is your iterations are depending on a loop control of 2, and not a node == null finding for nodeIndex.getRight()(for max) getLeft(for min.)
Remember a tree has
1 head(first level)
2nd level = 2
3rd level = 4
4th 8
and so on. So your algorithm for looping will not even go down 3 levels.
for (int i = 0; i < 2; i++) {
int val = minimax(depth + 1, nodeIndex.getLeft(), false, alpha, beta);
best = Math.max(best, val);
alpha = Math.max(alpha, best);
//Alpha Beta Pruning
if (beta <= alpha) {
break;
}
Change your loops to control iteration correctly and you should find the highest value easily.
Im trying to build a game tree to my game in order to find my next move.
At first, Im building the tree using a recursive algorithm, and then, to find the best move Im using the alpha - beta pruning algorithm.
I want to build the game tree using the alpha - beta pruning in order to minimize the size of the game tree, but Im having problem writing the algorithm.
Could you help me add the alpha - beta pruning to the expand algorithm?
Here is the expand algorithm:
public void expand(int depth)
{
expand++;
if(depth > 0)
{
this.children = new ArrayList<GameTreeNode>();
List<Move> possibleMoves = this.b.possibleMoves(this.b.turn);
ReversiBoard tmp = null;
for(Move m : possibleMoves)
{
TurnState nextState = (this.state == TurnState.PLUS ? TurnState.MINUS : TurnState.PLUS);
tmp = new ReversiBoard(this.b);
tmp.makeMove(m);
int nextTurn = (turn == PLAYER1 ? PLAYER2 : PLAYER1);
if(tmp.possibleMoves(nextTurn).isEmpty())
nextTurn = turn;
this.children.add(new GameTreeNode(tmp, nextState, m, nextTurn));
for(GameTreeNode child : children)
child.expand(depth - 1);
}
}
}
Here is the alpha - beta pruning code:
int alphaBetaMax( int alpha, int beta, int depthleft ) {
alphaBetaNum++;
if ( depthleft == 0 ) return this.b.evaluate();
for (GameTreeNode tree : this.children) {
bestValue = alphaBetaMin( alpha, beta, depthleft - 1 );
if( bestValue >= beta )
{
bestMove = tree.move;
return beta; // fail hard beta-cutoff
}
if( bestValue > alpha )
alpha = bestValue; // alpha acts like max in MiniMax
}
return alpha;
}
int alphaBetaMin( int alpha, int beta, int depthleft ) {
alphaBetaNum++;
if ( depthleft == 0 ) return -this.b.evaluate();
for ( GameTreeNode tree : this.children) {
bestValue = alphaBetaMax( alpha, beta, depthleft - 1 );
if( bestValue <= alpha )
{
bestMove = tree.move;
return alpha; // fail hard alpha-cutoff
}
if( bestValue < beta )
beta = bestValue; // beta acts like min in MiniMax
}
return beta;
}
public void summonAlphaBeta(int depth)
{
this.bestValue = alphaBetaMax(Integer.MIN_VALUE, Integer.MAX_VALUE, depth);
}
Thank You!
You have two options.
You could just combine the two algorithms by converting your expand method into expandAndReturnMin and expandAndReturnMax methods which each take the alpha and beta values as arguments. Ideally any shared code would be put into a third method to keep your code clean.
Here is some example code for you to consider. In this example I've assumed a static member is storing the best move.
public int bestValue(Board board, int depth, int alpha, int beta, boolean aiPlayer) {
if (depth >= MAX_DEPTH || board.possibleMoves(aiPlayer).isEmpty()) {
return board.getValue();
} else {
for (Move move: board.possibleMoves(aiPlayer) {
int value = bestValue(board.makeMove(move), depth + 1, alpha, beta, !aiPlayer);
if (aiPlayer && value > alpha) {
alpha = value;
bestMove = move;
if (alpha >= beta)
break;
} else if (!aiPlayer && value < beta) {
beta = value;
bestMove = move;
if (beta >= alpha)
break;
}
}
return aiPlayer ? alpha : beta;
}
}
The best initial move is determined by:
board.bestValue(board, 0, Integer.MIN_VALUE, Integer.MAX_VALUE, true);
and then using board.getBestMove().
A more elegant solution would be to store the alpha and beta values in the tree itself. That is very simple: after generating each child node you update the values in the current node. Then if they fall outside the allowed range you can stop generating child nodes. This is the more standard approach and is computationally cheap but makes the nodes use more memory.