I'm developing a minimax alogorithm for use in a modified checkers game. In my evaluation function every score is multiplied by 10 and then a random number between 1 and 10 is added/subtracted from it (max or min node depending). However, when running the program it always executes the same sequence of moves. I've checked the evaluation function and it definitely returns randomised values for nodes that would be of equal value so I can only assume the problem lies in the minimax function itself, any ideas? The other functions, generateMoves and simulateMove also work correctly.
private int minimax(State state, int depth, int min, int max) {
ArrayList<Move> moves = generateMoves(state.board, state.colour);
char opponent = (state.colour == DraughtBoard.WHITE) ? DraughtBoard.BLACK : DraughtBoard.WHITE;
if (moves.size() == 1)
nextMove = moves.get(0);
int bestScore;
Move bestMove = new Move();
int score = 0;
if (depth == 0 || moves.size() == 0) {
return evaluateBoard(state);
}
if (colour == DraughtBoard.WHITE) {
bestScore = min;
for (Move move : moves) {
char[][] temp = state.board.clone();
boolean scored = simulateMove(move, temp);
State nextState = new State(temp, opponent, state.whiteScore, state.blackScore);
if (scored) state.whiteScore++;
score = minimax(state, depth-1, bestScore, max);
if (score > bestScore) {
bestScore = score;
bestMove = move;
}
if (bestScore > max) return max;
}
nextMove = bestMove;
return bestScore;
} else {
bestScore = max;
for (Move move : moves) {
char[][] temp = state.board.clone();
boolean scored = simulateMove(move, temp);
State nextState = new State(temp, opponent, state.whiteScore, state.blackScore);
if (scored) state.blackScore++;
score = minimax(state, depth-1, min, bestScore);
if (score < bestScore) {
bestScore = score;
bestMove = move;
}
if (bestScore < min) return min;
}
nextMove = bestMove;
return bestScore;
}
}
char[][] temp = state.board.clone(); will only do a swallow copy (except you wrote your own clone() method)
Which means temp has the same references as board therefor you will change board while calling siumlateMove.
This may causes your problem.
deep copy of a 2d array
Related
I read a tutorial about minimax, and tried to make a tac tac toe AI.
But the code doesn't work optimally for some reason, which I cannot find. The ai can place pieces, but it's not a smart ai. I expected it to be unbeatable. The higher the depth is, the dumber the ai becomes.
The 'game' is my an other class, where the actual game is.
private Game game;
private Piece[][] board;
private Piece ai = Piece.CIRCLE;
private Piece player = Piece.CROSS;
public AI(Game game) {
this.game = game;
this.board = game.getBoard();
}
public int[] move() {
int[] result = minimax(1, ai);
return new int[] {result[1], result[2]};
}
private int[] minimax(int depth, Piece piece) {
List<int[]> possibleMoves = generateMoves();
int bestScore = (piece == ai) ? Integer.MIN_VALUE : Integer.MAX_VALUE;
int currentScore;
int bestRow = -1;
int bestCol = -1;
if (possibleMoves.isEmpty() || depth == 0) {
// Game over or depth reached
bestScore = evaluate();
}
else {
for (int[] move : possibleMoves) {
// Try this move for the player
board[move[0]][move[1]] = player;
if (piece == ai) { // ai is maximizing player
currentScore = minimax(depth - 1, player)[0];
if (currentScore > bestScore) {
bestScore = currentScore;
bestRow = move[0];
bestCol = move[1];
}
}
else { // player is minimizing player
currentScore = minimax(depth - 1, ai)[0];
if (currentScore < bestScore) {
bestScore = currentScore;
bestRow = move[0];
bestCol = move[1];
}
}
// Undo move
board[move[0]][move[1]] = null;
}
}
return new int[] {bestScore, bestRow, bestCol};
}
private List<int[]> generateMoves() {
List<int[]> possibleMoves = new ArrayList<int[]>();
// If game over
if (game.getWinner() != null) {
return possibleMoves; // return empty list
}
// Add possible moves to list
for (int x = 0; x < 3; x++) {
for (int y = 0; y < 3; y++) {
if (game.getBoard()[x][y] == null) {
possibleMoves.add(new int[] {x, y});
}
}
}
return possibleMoves;
}
private int evaluate() {
int score = 0;
// Evaluate
score += evaluateLine(0, 0, 0, 1, 0, 2); // row 0
score += evaluateLine(1, 0, 1, 1, 1, 2); // row 1
score += evaluateLine(2, 0, 2, 1, 2, 2); // row 2
score += evaluateLine(0, 0, 1, 0, 2, 0); // col 0
score += evaluateLine(0, 1, 1, 1, 2, 1); // col 0
score += evaluateLine(0, 2, 1, 2, 2, 2); // col 0
score += evaluateLine(0, 0, 1, 1, 2, 2); // diag 1
score += evaluateLine(0, 2, 1, 1, 2, 0); // diag 2
return score;
}
// Return +100, +10, +1 for 3-, 2-, 1-in-a-line for ai
// Return -100, -10, -1 for 3-, 2-, 1-in a line for player
// Else return 0
private int evaluateLine(int row1, int col1, int row2, int col2, int row3, int col3) {
int score = 0;
// First cell
if (board[row1][col1] == ai) {
score = 1;
}
else if (board[row1][col1] == player) {
score = -1;
}
// Second cell
if (board[row2][col2] == ai) {
if (score == 1) { // board1 is ai
score = 10;
}
else if (score == -1) { // board1 is player
return 0;
}
else { // board1 is empty
score = 1;
}
}
else if (board[row2][col2] == player) {
if (score == -1) { // board1 is player
score = -10;
}
else if (score == 1) { // board1 is ai
return 0;
}
else { // board1 is empty
score = -1;
}
}
// Third cell
if (board[row3][col3] == ai) {
if (score > 0) { // board1 and/or board2 is ai
score *= 10;
}
else if (score < 0) { // board1 and/or board2 is player
return 0;
}
else { // board1 and/or board2 is empty
score = 1;
}
}
else if (board[row3][col3] == player) {
if (score < 0) { // board1 and/or board2 is player
score *= 10;
}
else if (score > 1) { // board1 and/or board2 is ai
return 0;
}
else { // board1 and/or board2 is empty
score = -1;
}
}
return score;
}
A couple of things I noticed:
The first line in the loop going through possible moves says board[move[0]][move[1]] = player;. That should be piece instead of player, now your AI thinks that only pieces of the human player ever end up on the board.
Minimax should be very easily capable of searching the complete game tree in less than a second. Therefore, I'd recommend allowing to to search as deep as it likes, instead of limiting to a search depth of 1. This would also eliminate the need for creating that heuristic evaluation function; you'd only give a large score for winning, 0 for tie, and a very negative score for losing. The main reason I'm recommending this is that I suspect there may be something wrong with the evaluation function too, though I'm not sure since I did not check it in detail. If you really do insist on terminating the search early and using a heuristic evaluation function, you need to make sure that the function is ''symmetrical''. With that, I mean that evaluating the board from the perspective of one player should always result in exactly -1 times the score of the same board were evaluated from the perspective of the opponent.
minimax is returning a move in terms of a row/column pair, not a score. So
currentScore = minimax(depth - 1, player)[0];
makes no sense. It probably causes any move to row 3 to look better than any move to row 1 or row 2.
minmax needs to hand
back a score in addition to the best move.
I am a beginner and I am trying to develop Connect4 game by applying minimax algorithm, I am stuck at the condition that determines whether it's min player turn or max player turn. I've got the feeling it's something reduculs but I've been thinking for two days trying to figure it out.
Any help?
private int evaluatePlayerMove(int depth, int maxDepth, int col, int alpha, int beta) {
boardsAnalyzed++;
int evaluatedMove=0; // For evaluating min player move or max player move
int min = Integer.MAX_VALUE, minScore = 0; // For min player
int max = Integer.MIN_VALUE, maxScore = 0; // For max player
if (col != -1) {
// Check whether it's min player turn or max player turn
// If it's min player turn then evaluate min move:
if(//it's min player turn){
minScore = board.getHeuristicScore(Board.MARK_BLACK, col, depth, maxDepth);
if(board.blackWinFound()) {
blackWinFound = true;
return minScore;
}
if (depth == maxDepth) {
return minScore;
}
for (int c = 0; c < Board.COLUMNS; c++) {
if (board.isColumnAvailable(c)) {
board.mark(c, Board.MARK_RED);
int value = evaluatePlayerMove(depth + 1, maxDepth, c, alpha, beta);
board.unset(c);
if (value < min) {
min = value;
if (depth == 0) {
column = c;
}
}
if (value < beta) {
beta = value;
}
if (alpha >= beta) {
return beta;
}
}
}
if (min == Integer.MAX_VALUE) {
return 0;
}
evaluatedMove = min;
}
// If it's max player turn then evaluate max move:
if(//it's max player turn) {
maxScore = board.getHeuristicScore(Board.MARK_RED, col, depth, maxDepth);
if (board.redWinFound()) {
redWinFound = true;
return maxScore;
}
if (depth == maxDepth) {
return maxScore;
}
for (int c = 0; c < Board.COLUMNS; c++) {
if (board.isColumnAvailable(c)) {
board.mark(c, Board.MARK_BLACK);
int value = evaluatePlayerMove(depth + 1, maxDepth, c, alpha, beta);
board.unset(c);
if (value > max) {
max = value;
if (depth == 0) {
column = c;
}
}
if (value > alpha) {
alpha = value;
}
if (alpha >= beta) {
return alpha;
}
}
}
if (max == Integer.MIN_VALUE) {
return 0;
}
evaluatedMove= max;
}
}
return evaluatedMove;
}
In most real-time AI situations, it is your AI program versus a human player. So, usually, if you are building a min-max tree, the AI program will only be either min or max, and the same will be the root of the tree. For e.g. if you try at the AI program as max, the root of the tree will always remain max and you need only compute moves for max (min moves will be user inputs). For such situation, i would recommend using the depth of the tree as your checking condition.
if(root == max){
for any node n:
if(n.depth%2 == 0){
n is max
}
else{
n is min
}
}
Because depth is usually used in almost all problems, it would be an efficient way.
However, if it is an homework problem and you indeed need to compute moves for both min and max, I would recommend using a instance static boolean variable isMax which should be flipped after every move.
public int minimax(Board b) { //player1 is AI
//depth+=10;
System.out.println("Current board is " );
System.out.println("To play now on this board is " + b.whoseTurn.getId() );
b.display();
if( (GameController.hasWon(b.whoseTurn,b))
|| (GameController.hasWon(b.whoseNotInTurn, b))
|| (GameController.isDraw(b.whoseTurn,b,b.whoseNotInTurn))) {
return evaluate(b);
}
ArrayList<Integer> possibleMoves = generateMoves(b);
if(b.whoseTurn.getId() == "AI") {
bestScore = -1000000;
for(int i = 0; i < possibleMoves.size(); i++) {
int move = possibleMoves.get(i);
b.myBoard.get(move).setSymbol(b.whoseTurn.getSymbol());
swapPlayers(b);
score = minimax (b);
if(score > bestScore) {
bestScore = score;
bestMove = move;
System.out.println("In maximum Move is " + move + "BESTScore is" + score);
}
unmakeMove(b.myBoard.get(move));
}
return bestMove;
}
else {
bestScore = 1000000;
for(int i = 0; i < possibleMoves.size(); i++) {
int move = possibleMoves.get(i);
b.myBoard.get(move).setSymbol(b.whoseTurn.getSymbol());
swapPlayers(b);
score = minimax(b);
if(score < bestScore) {
myMoves.add(move);
bestScore = score;
bestMove = move;
System.out.println("In minimum Move is " + move + "Score is" + score);
}
unmakeMove(b.myBoard.get(move));
}
return bestMove;
}
Where am I making a mistake here? Here is a link to the entire code.
Let's try this:
You return evaluate(b) at the end of the recursion to get the score, that's good. But all the levels of recursion above are going to return bestMove aren't they? So you lose your bestScore in the recursion because you don't propagate it properly to the top.
Going through some past Java exercises so I can improve my programming in general and I've been stuck on this for quite a while. I'm going to post all of the source that's required, since this project is quite large and there's a lot of interfaces and subclasses that just offer to confuse.
public class Board {
public final static char NOUGHT = 'O';
public final static char CROSS = 'X';
public final static char EMPTY = ' ';
// Each cell is indexed as follows:
// 1 2 3
// 4 5 6
// 7 8 9
private char[][] grid; // a matrix to store the positions of the board
private int numOfMarks; // number of moves made on the board
private int lastMarkPosition; //position of last move maode in the board
public Board() {
grid = new char[3][3];
for (int row = 0; row < 3; row++) {
for (int col = 0; col < 3; col++) {
grid[row][col] = EMPTY;
}
}
numOfMarks = 0;
lastMarkPosition = 0;
}
//post: Returns true if the board is finished.
// A board is finished because either there is a winner or the board is full.
public boolean isFinished() {
return numOfMarks == 9 || getWinnerMark() != EMPTY;
}
//post: Records the position of the last mark made on the board.
public void setLastMarkPosition(int lastPosition){
lastMarkPosition = lastPosition;
}
//post: Returns the position of the last mark
public int getLastMarkPosition(){
return lastMarkPosition;
}
//post: Returns the mark ('X' or 'O') of the winner player if a winning condition exists in the board,
// returns EMPTY otherwise.
public char getWinnerMark() {
for (int i = 0; i < 3; i++) {
// check if there are three in a horizontal row
if (grid[i][0] != EMPTY && grid[i][0] == grid[i][1]
&& grid[i][1] == grid[i][2]) {
return grid[i][0];
}
// check if there are three in a vertical row
if (grid[0][i] != EMPTY && grid[0][i] == grid[1][i]
&& grid[1][i] == grid[2][i]) {
return grid[0][i];
}
}
// check if there are three in a diagonal row
if (grid[1][1] != EMPTY
&& (grid[1][1] == grid[0][0] && grid[1][1] == grid[2][2] || grid[1][1] == grid[0][2]
&& grid[1][1] == grid[2][0])) {
return grid[1][1];
}
// otherwise, return EMPTY as there is no winner
return EMPTY;
}
//post: Sets the given mark at the given position in the board
public void setMark(int pos, char mark) throws GameException {
if (numOfMarks == 9) {
throw new GameException("attempted to set mark on a full board.");
}
if (pos < 1 || pos > 9) {
throw new GameException(
"attempted to set mark in a wrong position: " + pos);
}
if (mark != NOUGHT && mark != CROSS) {
throw new GameException("attempted to set an invalid mark: "
+ String.valueOf(mark));
}
// perform board update
int row = (pos - 1) / 3;
int col = (pos - 1) % 3;
if (grid[row][col] != EMPTY) {
throw new GameException(
"attempted to set mark on a non-empty position: "
+ pos);
} else {
grid[row][col] = mark;
numOfMarks++;
}
}
//post: Returns the mark that is at a given position in the board
public char getMark(int pos) {
return grid[(pos-1)/3][(pos-1)%3];
}
//post: If the grid is not full, calculates whose turn is, based on the marks in the grid.
// Returns EMPTY if the board is already full.
public char getTurn() {
if (numOfMarks == 9) {
return EMPTY;
} else if (numOfMarks % 2 == 0) {
// by default, CROSS should go first
return CROSS;
} else {
return NOUGHT;
}
}
//post: Copy the board and returns it
public Board makeCopy() {
Board copy = new Board();
for (int row = 0; row < 3; row++) {
for (int col = 0; col < 3; col++) {
copy.grid[row][col] = this.grid[row][col];
}
}
copy.numOfMarks = this.numOfMarks;
return copy;
}
//post: Prints the given board
public static void display(Board board) {
for (int row = 0; row < 3; row++) {
for (int col = 0; col < 3; col++) {
System.out.print(" ");
char mark = board.grid[row][col];
if (mark != EMPTY) {
System.out.print(mark);
} else {
System.out.print((row)*3+(col+1));
}
System.out.print(" ");
if (col < 2) {
System.out.print("|");
}
}
System.out.println();
if (row < 2) {
System.out.println("-----------");
}
}
}
GameTreeInterface
public interface GameTreeInterface {
//post: Returns the board at the root of the game tree.
public Board getRootItem();
//post: Expands the game tree fully by adding all possible boards in the game.
// It uses a recursive auxiliary method.
public void expand();
//pre: The game tree is fully expanded.
//post: Assigns a score to each board in the game tree.
// It uses a recursive auxiliary method.
public void assignScores();
//pre: Each board in the game tree has a score.
//post: Computes an array of positions (1..9) optimal available moves.
// These are the last mark positions in the children boards that have the highest score.
public int[] BestMoves();
//post: Returns the number of boards stored in a game tree.
// It uses a recursive auxiliary method.
public int size();
}
GameTree
public class GameTree implements GameTreeInterface {
private GameTreeNode root; // reference to the root board of a game tree
public GameTree(Board board) {
this.root = new GameTreeNode(board);
}
// post: Returns the board at the root of a game tree.
public Board getRootItem() {
return root.getBoard();
}
// post: Returns the number of boards stored in a game tree.
// It uses a recursive auxiliary method.
public int size() {
return sizeTree(root) + 1;
}
// post: Returns the number of boards stored in a game tree, excluded
// the root.
private int sizeTree(GameTreeNode node) {
int total = 0;
for (int i = 1; i < node.numberOfChildren(); i++) {
if (node.getChild(i).getBoard() != null)
total++;
}
return total;
}
// post: Expands the game tree fully by adding all possible boards in
// the game.
// It uses a recursive auxiliary method.
public void expand() {
expandTree(root);
}
// post: Expands the game tree from the given node by adding
// all the possible moves that the computer and the user player
// can make, until the game is finished, from the given node onwards.
private void expandTree(GameTreeNode node) {
if (!node.getBoard().isFinished()) {
char c = node.getBoard().getTurn();
for (int i = 1; i < 9; i++) {
if (node.getBoard().getMark(i) == Board.EMPTY) {
GameTreeNode n = new GameTreeNode(node.getBoard());
n.getBoard().setMark(i, c);
n.getBoard().setLastMarkPosition(i);
node.getChildren().add(2, n);
expandTree(n);
}
}
}
}
// pre: The game tree is fully expanded.
// post: Assigns a score to each board in the game tree.
// It uses a recursive auxiliary method.
public void assignScores() {
char player = (root.getBoard()).getTurn();
assignScoresTree(root, player);
}
// post: Assigns scores to each board in a game tree for the computer
// player.
private void assignScoresTree(GameTreeNode node, char player) {
Board board = node.getBoard();
if (board.isFinished()) {
// base case of recursion
// score 3 for a winning board for the given player,
// score 2 for a draw baord,
// score 1 for a losing board for the given player
char winner = board.getWinnerMark();
if (winner == Board.EMPTY) {
// this is a draw!
node.setScore(2);
} else {
node.setScore(winner == player ? 3 : 1);
}
}
else {
// tries to assign the scores to all the children boards
// first, recursively
int minScore = Integer.MAX_VALUE;
int maxScore = Integer.MIN_VALUE;
GenericList<GameTreeNode> children = node.getChildren();
for (Iterator<GameTreeNode> it = children.iterator(); it
.hasNext();) {
GameTreeNode child = it.next();
assignScoresTree(child, player);
// keeps track of the maximum and minimum scores
// of the children boards so far
int childScore = child.getScore();
if (childScore > maxScore)
maxScore = childScore;
if (childScore < minScore)
minScore = childScore;
}
// Assigns score to the current board in the recursion
// according to the player's turn
if (board.getTurn() == player) {
// get the maximum score as the player wants to
// win
node.setScore(maxScore);
} else {
// get the minimum score (as the player wants
// the opponent to lose;)
node.setScore(minScore);
}
}
}
// pre: Each board in the game tree has a score.
// post: Computes an array of positions (1..9) optimal available moves.
// These are the last mark positions in the children boards that have
// the highest score.
public int[] BestMoves() {
int maxScore = Integer.MIN_VALUE;
GenericList<GameTreeNode> highestScoreBoards = new GenericList<GameTreeNode>();
GenericList<GameTreeNode> children = root.getChildren();
for (Iterator<GameTreeNode> it = children.iterator(); it
.hasNext();) {
GameTreeNode nextBoard = it.next();
int curScore = nextBoard.getScore();
if (maxScore < curScore) {
maxScore = curScore;
highestScoreBoards.clear();
highestScoreBoards.add(1, nextBoard);
} else if (maxScore == curScore) {
highestScoreBoards.add(1, nextBoard);
}
}
int[] moves = new int[highestScoreBoards.size()];
for (int i = 0; i < moves.length; i++) {
Board board = (highestScoreBoards.get(i + 1))
.getBoard();
moves[i] = board.getLastMarkPosition();
}
return moves;
}
}
GameTreeNode
public class GameTreeNode{
private GameTreeItem item; // includes the board object and the score
private GenericList<GameTreeNode> children; //list of gameTreeNodes of possible next moves.
public GameTreeNode(Board newBoard) {
this.item = new GameTreeItem(newBoard);
this.children = new GenericList<GameTreeNode>();
}
//post: Returns the board stored in a GameTreeNode
public Board getBoard() {
return item.board;
}
//post: Returns the children stored in a GameTreeNode, as a list of GameTreeNodes
public GenericList<GameTreeNode> getChildren(){
return children;
}
//post: Returns the score of the board
public int getScore() {
return item.score;
}
//post: Sets the score of the board to be equal to the given score
public void setScore(int score) {
item.score = score;
}
//post: Removes all the children
public void removeChildren(){
children = null;
}
//post: Returns the number of children
public int numberOfChildren(){
return children.size();
}
//post: Returns the child at a given position in the list of children, as a
GameTreeNode
public GameTreeNode getChild(int i){
return children.get(i);
}
//Inner class for storing a board and the score
private class GameTreeItem{
private Board board;
private int score;
public GameTreeItem(Board newBoard) {
this.board = newBoard;
score = 0;
}
}
Apologies for the rather large amount of code but I don't think I could explain the problem without having everything here. There's still a lot of extra code for the GenericList but it's a pretty straightforward implementation for a generic linked list.
If anyone does go through this code, my problem is in the GameTree class with the sizeTree() method. I offered a solution but I think it's far too simple to be correct. As I understand it, a GameTreeNode includes a GameTreeItem containing the current state of play and a reference to a linked list of GameTreeNode. However, my specification says that I have to use recursion to implement this method but I'm not sure how. Would my method work since it goes through every single child of the root and checks for the boards?
I know it's a long shot but if anyone can offer any help I'd really appreciate it!
I'm creating a simple engine that plays Othello, using minimax with alpha beta cuts.
It's playing well, but sometimes i get a weird index out of bounds exception (near the
endgame, always).
Here' my algorithm
private float minimax(OthelloBoard board, OthelloMove best, float alpha, float beta, int depth)
{
calls++;
float bestResult = -Float.MAX_VALUE;
OthelloMove garbage = new OthelloMove();
int state = board.getState();
int currentPlayer = board.getCurrentPlayer();
if (state == OthelloBoard.STATE_DRAW)
return 0.0f;
if ((state == OthelloBoard.STATE_BLACK_WINS) && (currentPlayer == OthelloBoard.BLACK))
return Float.MAX_VALUE;
if ((state == OthelloBoard.STATE_WHITE_WINS) && (currentPlayer == OthelloBoard.WHITE))
return Float.MAX_VALUE;
if ((state == OthelloBoard.STATE_BLACK_WINS) && (currentPlayer == OthelloBoard.WHITE))
return -Float.MAX_VALUE;
if ((state == OthelloBoard.STATE_WHITE_WINS) && (currentPlayer == OthelloBoard.BLACK))
return -Float.MAX_VALUE;
if (depth == maxDepth)
return OthelloHeuristics.eval(currentPlayer, board);
ArrayList<OthelloMove> moves = board.getAllMoves(currentPlayer);
for (OthelloMove mv : moves)
{
board.makeMove(mv);
alpha = - minimax(board, garbage, -beta, -alpha, depth + 1);
board.undoMove(mv);
if (beta <= alpha)
return alpha;
if (alpha > bestResult)
{
best.setFlipSquares(mv.getFlipSquares());
best.setIdx(mv.getIdx());
best.setPlayer(mv.getPlayer());
bestResult = alpha;
}
}
return bestResult;
}
Inside makeMove and undoMove i update the game state(black wins, white wins, draw).
I also toggle the players inside these methods. When a player has no moves i make a dummy
move without changing the board, and toggle the players.
There's a lot more of code, but i think the problem happens when the algorithm hits the
game over position. This problem doesn't happen when i set the engine to play random moves, so the problem should be the alpha beta algorithm.
Here is getAllMoves, this call getFlips:
public ArrayList<OthelloMove> getAllMoves(int player)
{
ArrayList<OthelloMove> moves = new ArrayList<OthelloMove>();
for (int i = 10; i < 90; i++)
{
int col = i % 10;
if (col != 0 && col != 9)
{
if (cells[i] == EMPTY)
{
ArrayList<Integer> flips = getFlips(i, player);
if (flips.size() > 0)
{
OthelloMove mv = new OthelloMove();
mv.setFlipSquares(flips);
mv.setIdx(i);
mv.setPlayer(player);
moves.add(mv);
}
}
}
}
return moves;
}
Here is getFlips.
public ArrayList<Integer> getFlips(int idx, int player)
{
int opponent = getOpponent(player);
ArrayList<Integer> flips = new ArrayList<Integer>();
if (cells[idx] != EMPTY)
return flips;
for (Integer dir : DIRECTIONS)
{
int distance = 1;
int tempIdx = idx;
while (cells[tempIdx += dir] == opponent)
distance++;
if ((cells[tempIdx] == player) && (distance > 1))
{
while (distance-- > 1)
{
tempIdx -= dir;
flips.add(tempIdx);
}
}
}
return flips;
}
Here is updateState:
public void updateState()
{
int opponent = getOpponent(currentPlayer);
int playerMoves = getAllMoves(currentPlayer).size();
int opponentMoves = getAllMoves(opponent).size();
if ( ((playerMoves == 0) && (opponentMoves == 0)) || (emptyCells == 0))
{
int blackDiscs = countDiscs(BLACK);
int whiteDiscs = countDiscs(WHITE);
if (blackDiscs > whiteDiscs)
state = STATE_BLACK_WINS;
else if (blackDiscs < whiteDiscs)
state = STATE_WHITE_WINS;
else
state = STATE_DRAW;
}
}
Thanks!
I am not familiar with the game specifically, but I believe it has something to do with the fact hat in the line:
while (cells[tempIdx += dir] == opponent)
You should also check you are not out of bound, otherwise - if there is still an opponent on the end of the board, you will keep increasing dir
Try changing this line to:
while (tempIdx + dir >= 0 && tempIdx + dir < cells.length && cells[tempIdx += dir] == opponent)
As a rule of thumb, usually it is a good practice in array accesses, especially in loops, to guard against going out of bound by checking the length explicitly.
Found the problem, thanks anyway.
The bug was a situation where a player can't move, and must pass the turn.
The tricky is to play a 'ghost move' (i.e. a move that doesn't change the board), and
toggle the players turn, so that the Minimax doesn't even notice this situation.
I was doing this, but in the wrong place! The code is like:
public void makeMove (OthelloMove move)
{
int player = move.getPlayer();
ArrayList<Integer> flips = move.getFlipSquares();
if (flips != null)
{
int idx = move.getIdx();
cells[idx] = player;
for (Integer flip : flips)
cells[flip] = player;
emptyCells--;
this.updatePhase();
}
this.toogleCurrentPlayer();
}
public void undoMove (OthelloMove move)
{
int player = move.getPlayer();
ArrayList<Integer> flips = move.getFlipSquares();
int opponent = getOpponent(player);
if (flips != null)
{
int idx = move.getIdx();
cells[idx] = EMPTY;
for (Integer flip : flips)
cells[flip] = opponent;
emptyCells++;
this.updatePhase();
}
this.toogleCurrentPlayer();
}