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.
Related
Trying to solve this problem with recursion and memoization but for input 7168 I'm getting wrong answer.
public int numSquares(int n) {
Map<Integer, Integer> memo = new HashMap();
List<Integer> list = fillSquares(n, memo);
if (list == null)
return 1;
return helper(list.size()-1, list, n, memo);
}
private int helper(int index, List<Integer> list, int left, Map<Integer, Integer> memo) {
if (left == 0)
return 0;
if (left < 0 || index < 0)
return Integer.MAX_VALUE-1;
if (memo.containsKey(left)) {
return memo.get(left);
}
int d1 = 1+helper(index, list, left-list.get(index), memo);
int d2 = 1+helper(index-1, list, left-list.get(index), memo);
int d3 = helper(index-1, list, left, memo);
int d = Math.min(Math.min(d1,d2), d3);
memo.put(left, d);
return d;
}
private List<Integer> fillSquares(int n, Map<Integer, Integer> memo) {
int curr = 1;
List<Integer> list = new ArrayList();
int d = (int)Math.pow(curr, 2);
while (d < n) {
list.add(d);
memo.put(d, 1);
curr++;
d = (int)Math.pow(curr, 2);
}
if (d == n)
return null;
return list;
}
I'm calling like this:
numSquares(7168)
All test cases pass (even complex cases), but this one fails. I suspect something is wrong with my memoization but cannot pinpoint what exactly. Any help will be appreciated.
You have the memoization keyed by the value to be attained, but this does not take into account the value of index, which actually puts restrictions on which powers you can use to attain that value. That means that if (in the extreme case) index is 0, you can only reduce what is left with one square (1²), which rarely is the optimal way to form that number. So in a first instance memo.set() will register a non-optimal number of squares, which later will get updated by other recursive calls which are pending in the recursion tree.
If you add some conditional debugging code, you'll see that map.set is called for the same value of left multiple times, and with differing values. This is not good, because that means the if (memo.has(left)) block will execute for cases where that value is not guaranteed to be optimal (yet).
You could solve this by incorporating the index in your memoization key. This increases the space used for memoization, but it will work. I assume you can work this out.
But according to Lagrange's four square theorem every natural number can be written as the sum of at most four squares, so the returned value should never be 5 or more. You can shortcut the recursion when you get passed that number of terms. This reduces the benefit of using memoization.
Finally, there is a mistake in fillSquares: it should add n itself also when it is a perfect square, otherwise you'll not find solutions that should return 1.
Not sure about your bug, here is a short dynamic programming Solution:
Java
public class Solution {
public static final int numSquares(
final int n
) {
int[] dp = new int[n + 1];
Arrays.fill(dp, Integer.MAX_VALUE);
dp[0] = 0;
for (int i = 1; i <= n; i++) {
int j = 1;
int min = Integer.MAX_VALUE;
while (i - j * j >= 0) {
min = Math.min(min, dp[i - j * j] + 1);
++j;
}
dp[i] = min;
}
return dp[n];
}
}
C++
// Most of headers are already included;
// Can be removed;
#include <iostream>
#include <cstdint>
#include <vector>
#include <algorithm>
// The following block might slightly improve the execution time;
// Can be removed;
static const auto __optimize__ = []() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
return 0;
}();
#define MAX INT_MAX
using ValueType = std::uint_fast32_t;
struct Solution {
static const int numSquares(
const int n
) {
if (n < 1) {
return 0;
}
static std::vector<ValueType> count_perfect_squares{0};
while (std::size(count_perfect_squares) <= n) {
const ValueType len = std::size(count_perfect_squares);
ValueType count_squares = MAX;
for (ValueType index = 1; index * index <= len; ++index) {
count_squares = std::min(count_squares, 1 + count_perfect_squares[len - index * index]);
}
count_perfect_squares.emplace_back(count_squares);
}
return count_perfect_squares[n];
}
};
int main() {
std::cout << std::to_string(Solution().numSquares(12) == 3) << "\n";
return 0;
}
Python
Here we can simply use lru_cache:
class Solution:
dp = [0]
#functools.lru_cache
def numSquares(self, n):
dp = self.dp
while len(dp) <= n:
dp += min(dp[-i * i] for i in range(1, int(len(dp) ** 0.5 + 1))) + 1,
return dp[n]
Here are LeetCode's official solutions with comments:
Java: DP
class Solution {
public int numSquares(int n) {
int dp[] = new int[n + 1];
Arrays.fill(dp, Integer.MAX_VALUE);
// bottom case
dp[0] = 0;
// pre-calculate the square numbers.
int max_square_index = (int) Math.sqrt(n) + 1;
int square_nums[] = new int[max_square_index];
for (int i = 1; i < max_square_index; ++i) {
square_nums[i] = i * i;
}
for (int i = 1; i <= n; ++i) {
for (int s = 1; s < max_square_index; ++s) {
if (i < square_nums[s])
break;
dp[i] = Math.min(dp[i], dp[i - square_nums[s]] + 1);
}
}
return dp[n];
}
}
Java: Greedy
class Solution {
Set<Integer> square_nums = new HashSet<Integer>();
protected boolean is_divided_by(int n, int count) {
if (count == 1) {
return square_nums.contains(n);
}
for (Integer square : square_nums) {
if (is_divided_by(n - square, count - 1)) {
return true;
}
}
return false;
}
public int numSquares(int n) {
this.square_nums.clear();
for (int i = 1; i * i <= n; ++i) {
this.square_nums.add(i * i);
}
int count = 1;
for (; count <= n; ++count) {
if (is_divided_by(n, count))
return count;
}
return count;
}
}
Java: Breadth First Search
class Solution {
public int numSquares(int n) {
ArrayList<Integer> square_nums = new ArrayList<Integer>();
for (int i = 1; i * i <= n; ++i) {
square_nums.add(i * i);
}
Set<Integer> queue = new HashSet<Integer>();
queue.add(n);
int level = 0;
while (queue.size() > 0) {
level += 1;
Set<Integer> next_queue = new HashSet<Integer>();
for (Integer remainder : queue) {
for (Integer square : square_nums) {
if (remainder.equals(square)) {
return level;
} else if (remainder < square) {
break;
} else {
next_queue.add(remainder - square);
}
}
}
queue = next_queue;
}
return level;
}
}
Java: Most efficient solution using math
Runtime: O(N ^ 0.5)
Memory: O(1)
class Solution {
protected boolean isSquare(int n) {
int sq = (int) Math.sqrt(n);
return n == sq * sq;
}
public int numSquares(int n) {
// four-square and three-square theorems.
while (n % 4 == 0)
n /= 4;
if (n % 8 == 7)
return 4;
if (this.isSquare(n))
return 1;
// enumeration to check if the number can be decomposed into sum of two squares.
for (int i = 1; i * i <= n; ++i) {
if (this.isSquare(n - i * i))
return 2;
}
// bottom case of three-square theorem.
return 3;
}
}
You have to use a for-each loop to check if the number you enter as a parameter is the median in an array you also enter as a parameter. I thought my logic was fine but it returns false for everything. Any guidance would be appreciated.
public static boolean isMedian(double[] arr, double m)
{
int countLow = 0;
int countHigh = 0;
int count = 0;
for(double e : arr)
if(arr[count] > m)
{
countHigh++;
count++;
}
else if(arr[count] < m)
{
countLow++;
count++;
}
if(countLow == countHigh)
return true;
else
return false;
}
public static void main(String[] args)
{
double[] array = {1.0, 2.0, 3.0, 4.0 , 5.0, 6.0, 7.0};
System.out.println(isMedian(array, 4.0));
}
You don’t change count when you’re at the median. This is why you should use e instead:
public static boolean isMedian(double[] arr, double m)
{
int countLow = 0;
int countHigh = 0;
for(double e : arr)
if(e > m)
{
countHigh++;
}
else if(e < m)
{
countLow++;
}
if(countLow == countHigh)
return true;
else
return false;
}
public static void main(String[] args)
{
double[] array = {1.0, 2.0, 3.0, 4.0 , 5.0, 6.0, 7.0};
System.out.println(isMedian(array, 4.0));
}
Here is a method that will accomplish the task for you:
public static boolean isMedian(double[] arr, double m){
double med;
ArrayList<Double> a = new ArrayList<Double>();
a.add(arr[0]);
a.add(Double.MAX_VALUE);
for(double 1: arr){
for(int j=0; j<a.size()&&j>=-10;j++){
if(i<a.get(j)){
a.add(j,i);
j=-200;
}
}
}
a.remove(a.size()-1);
if(arr.length%2==1){
med=a.get(arr.length/2);
} else{
med=(double)(a.get(arr.length/2)+a.get(arr.length/2-1))/2.0;
}
if (med==m)return true;
return false;
}
The basic answer to the question, which was stated by Anonymous, is that with the extended for loop, you don't want to reference the array by index. The assignment was to use an extended for loop, and I wanted to go with the (one-pass, no sorting, so O(n) unless I'm missing something) basic algorithm specified in the question. The idea that you're the median if the same number of numbers are above you as below you, can break down in a couple of ways.
1) As Andreas pointed out, if your number occurs more than once for example 1, 3, 3 and you ask if 3 is the median, it will say no, because the number of numbers below differs from the number of numbers above.
2) When there are an even number of numbers, even though you have the same number of numbers below and above, you might still not be smack in the middle. For 1 and 3, only 2 will do, not 2.5.
So I adapted the algorithm to handle all those special cases. That required tracking how many times the number itself occurred (or at least that was simplest, one could also calculate that by subtracting the sum of the other counts from the number of numbers) as well as the numbers immediately below and above in case we have to average them.
It's a bit spaghetti-like, and possibly would be better if there were separate methods for odd and even sizes, or if one thought really hard about unifying parts of various cases. But after testing all the cases mentioned, I think it works. In the comments I noted possible tweaks, such as watching out more carefully for floating point errors in calculation (one might also do so in comparisons).
public static boolean isMedian(double[] arr, double m) {
int countLow = 0;
int countHigh = 0;
int countM = 0; //track how many times the candidate number itself occurs
double supLow = 0.0; //maximum number below m, only meaningful if countLow is positive
double infHigh = 0.0; //minimum number above m, only meaningful if countHigh is positive
// int count = 0; as Anonymous said, not needed extended for loop handles looping over all e in arr
for (double e: arr)
if (e > m) {
if (countHigh == 0 || e < infHigh) {
infHigh = e;
}
countHigh++;
}
else if (e < m) {
if (countLow == 0 || e > supLow) {
supLow = e;
}
countLow++;
} else //e==m
{
countM++;
}
//System.out.println("countLow = "+countLow+" countHigh = "+ countHigh + " countM = " + countM);
if (arr.length % 2 == 1) { //odd sized array, easier case because no averaging needed
if (countM == 0) { //number does not occur at all, so certainly not in the middle
return false;
} else if (countM == 1) //number occurs once, is it in the middle?
{
return (countLow == countHigh);
} else { //number occurs more than once, is one of the occurrences in the middle?
int mStartIndex = countLow; //were the array to be sorted, the 0-based index of the first occurrence of m
int mEndIndex = mStartIndex + countM - 1; //were the array to be sorted, the 0-based index of the last occurrence of m
int middleIndex = arr.length / 2; // were the array to be sorted, 0-based index of the middle spot
return (middleIndex >= mStartIndex && middleIndex <= mEndIndex);
}
}
//still here, must be even size
//System.out.println("supLow = "+supLow+" infHigh = "+ infHigh);
if (countM == 0) {
if (countLow != countHigh) {
return false;
} else { //our number is between the two middle numbers, but is it the average?
return ((m + m) == (supLow + infHigh)); //using == with floating point addition, if that proves unreliable, do Math.abs(2*m-supLow-infHigh)<EPSILON
}
} else if (countM == 1) //number occurs once, which cannot be the median, if it is not in the 2 middle spots, it is lower or higher than both, even if it is, it cannot be the average of itself and a different number
{
return false;
} else { //number occurs more than once, does it occupy both middle spots?
int mStartIndex = countLow; //were the array to be sorted, the 0-based index of the first occurrence of m
int mEndIndex = mStartIndex + countM - 1; //were the array to be sorted, the 0-based index of the last occurrence of m
int firstMiddleIndex = arr.length / 2 - 1; // were the array to be sorted, 0-based index of the first of two middle spots
int secondMiddleIndex = firstMiddleIndex + 1;
return (firstMiddleIndex >= mStartIndex && secondMiddleIndex <= mEndIndex);
}
}
REVISION: broke up the code so no method is too long. Also bracketed for loop bodies.
private static boolean isMedianForOdd(double[] arr, double m) {
int countLow = 0;
int countHigh = 0;
for (double e: arr) {
if (e > m) {
countHigh++;
} else if (e < m) {
countLow++;
}
}
int countM = arr.length - countHigh - countLow; //how many times the candidate number itself occurs
if (countM == 0) { //number does not occur at all, so certainly not in the middle
return false;
} else if (countM == 1) //number occurs once, is it in the middle?
{
return (countLow == countHigh);
} else { //number occurs more than once, is one of the occurrences in the middle?
int mStartIndex = countLow; //were the array to be sorted, the 0-based index of the first occurrence of m
int mEndIndex = mStartIndex + countM - 1; //were the array to be sorted, the 0-based index of the last occurrence of m
int middleIndex = arr.length / 2; // were the array to be sorted, 0-based index of the middle spot
return (middleIndex >= mStartIndex && middleIndex <= mEndIndex);
}
}
private static boolean isMedianForEven(double[] arr, double m) {
int countLow = 0;
int countHigh = 0;
double supLow = 0.0; //maximum number below m, only meaningful if countLow is positive
double infHigh = 0.0; //minimum number above m, only meaningful if countHigh is positive
for (double e: arr) {
if (e > m) {
if (countHigh == 0 || e < infHigh) {
infHigh = e;
}
countHigh++;
} else if (e < m) {
if (countLow == 0 || e > supLow) {
supLow = e;
}
countLow++;
}
}
int countM = arr.length - countHigh - countLow; //how many times the candidate number itself occurs
if (countM == 0) {
if (countLow != countHigh) {
return false;
} else { //our number is between the two middle numbers, but is it the average?
return ((m + m) == (supLow + infHigh)); //using == with floating point addition, if that proves unreliable, do Math.abs(2*m-supLow-infHigh)<EPSILON
}
} else if (countM == 1) //number occurs once, which cannot be the median, if it is not in the 2 middle spots, it is lower or higher than both, even if it is, it cannot be the average of itself and a different number
{
return false;
} else { //number occurs more than once, does it occupy both middle spots?
int mStartIndex = countLow; //were the array to be sorted, the 0-based index of the first occurrence of m
int mEndIndex = mStartIndex + countM - 1; //were the array to be sorted, the 0-based index of the last occurrence of m
int firstMiddleIndex = arr.length / 2 - 1; // were the array to be sorted, 0-based index of the first of two middle spots
int secondMiddleIndex = firstMiddleIndex + 1;
return (firstMiddleIndex >= mStartIndex && secondMiddleIndex <= mEndIndex);
}
}
public static boolean isMedian(double[] arr, double m) {
if (arr.length % 2 == 1) {
return isMedianForOdd(arr, m);
} else {
return isMedianForEven(arr, m);
}
}
REVISION 2:
return(Math.abs(2*m-supLow-infHigh)< 0.000001);//avoid floating point issues, feel free to adjust the right hand side
should replace the line:
return ((m + m) == (supLow + infHigh));
or (MINOR REVISION 2'), with credit to Dawood says reinstate Monica for the idea, replace it with this set of lines if you do not wish to mess with specifying the precision.
BigDecimal bdM = BigDecimal.valueOf(m);
bdM = bdM.add(bdM).stripTrailingZeros();//2*m
BigDecimal bdInfSup = BigDecimal.valueOf(supLow);
bdInfSup = bdInfSup.add(BigDecimal.valueOf(infHigh)).stripTrailingZeros();
return(bdM.equals(bdInfSup));
public static int binarySearch(int[] numbers, int target) {
int min = 0;
int max = numbers.length – 1;
while (min <= max) {
int mid = (max + min) / 2;
if (numbers[mid] == target) {
return mid; // found it!
} else if (numbers[mid] < target) {
min = mid + 1; // too small
} else {
// numbers[mid] > target max = mid – 1; // too large
}
}
// not found
return –1;
}
Why reduce the array by 1?
I'm really not sure what you're referring to when you mean "a line," so I'll just explain every line.
public static int binarySearch(int[] numbers, int target) {
int min = 0; //sets the starting search point for the binary search because array slots
//start at 0.
int max = numbers.length – 1; //sets the maximum length for the binary search.
while (min <= max) { //ensures that the binary search completes itself.
int mid = (max + min) / 2; //every loop, binarySearch finds the exact middle
if (numbers[mid] == target) { //if the middle number of the array is the target number....
return mid; // found it! //returns the slot value where mid target was found
else if (numbers[mid] < target) { //if the middle number is less than
//the target number
min = mid + 1; // too small //value of the minimum is equal to the middle value
//plus one. This will essentially cut the array "in half," making the sorting process
//much quicker.
} else {
// numbers[mid] > target max = mid – 1; // too large //does the opposite of what
}
}
// not found
return –1; //no slot value found for the target number.
}
I hope this helped.
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
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();
}