Minimax algorithm bug - java

I've been trying to learn the minimax algorithm and I've stumbled upon a bug which I cannot figure out how to solve.
Code:
private List<Integer> generatemoves(int[] evalFields) {
List<Integer> nextMoves = new ArrayList<Integer>();
for (int i = 0; i < evalFields.length; i++) {
if (evalFields[i] == 0) {
nextMoves.add(i);
}
}
return nextMoves;
}
private int evaluateLine(int p1, int p2, int p3, int[] evalFields) {
int score = 0;
if (evalFields[p1] == 1) {
score = 1;
} else if (evalFields[p1] == 10) {
score = -1;
}
if (evalFields[p2] == 1) {
if (score == 1) {
score = 10;
} else if (score == -1) {
return 0;
} else {
score = 1;
}
} else if (evalFields[p2] == 10) {
if (score == -1) {
score = -10;
} else if (score == 1) {
return 0;
} else {
score = -1;
}
}
if (evalFields[p3] == 1) {
if (score > 0) {
score *= 10;
} else if (score < 0) {
return 0;
} else {
score = 1;
}
} else if (evalFields[p3] == 10) {
if (score < 0) {
score *= 10;
} else if (score > 1) {
return 0;
} else {
score = -1;
}
}
return score;
}
private int evaluateBoard(int [] evalFields) {
int score = 0;
score += evaluateLine(0, 1, 2, evalFields);
score += evaluateLine(3, 4, 5, evalFields);
score += evaluateLine(6, 7, 8, evalFields);
score += evaluateLine(0, 3, 6, evalFields);
score += evaluateLine(1, 4, 7, evalFields);
score += evaluateLine(2, 5, 8, evalFields);
score += evaluateLine(0, 4, 8, evalFields);
score += evaluateLine(2, 4, 6, evalFields);
return score;
}
private int bestMove(int currentTurn, int[] board) {
int move;
int bestScore;
if (currentTurn == 1) {
bestScore = Integer.MIN_VALUE;
} else {
bestScore = Integer.MAX_VALUE;
}
List<Integer> nextMoves = generatemoves(board);
List<Integer> bestScores = new ArrayList<Integer>();
for (int i = 0; i < nextMoves.size(); i++) {
int[] newBoards = new int[9];
for (int j = 0; j < board.length; j++) {
newBoards[j] = board[j];
}
newBoards[nextMoves.get(i)] = turn;
bestScores.add(evaluateBoard(newBoards));
}
for (int scores : bestScores) {
if (currentTurn == 1) {
if (scores > bestScore) bestScore = scores;
} else {
if (scores < bestScore) bestScore = scores;
}
}
move = nextMoves.get(bestScores.indexOf(bestScore));
return move;
}
This is the most relevant part of the code. What it does or what I think it does is that it generates every possible move from the board which is called fields. Then it calculates a score for each move. It then proceeds to make the move which results in the highest or lowest score, x(1) is trying to get the highest and O(10) the lowest. The bug that occurs is that when the player starts and takes the field in the middle, then the ai acts normally but after the players second turn the ai starts to act strange:
[ ][ ][ ] [O][ ][ ] [O][ ][O]
[ ][x][ ] => [ ][x][ ] => [x][x][ ]
[ ][ ][ ] [ ][ ][ ] [ ][ ][ ]
If the player chooses this:
[O][ ][ ] [O][ ][ ]
[ ][x][x] => [O][x][x]
[ ][ ][ ] [ ][ ][ ]
Then the ai acts nomally.
I don't know what is wrong or even if I've understood the minimax algorithm correctly.
****edit****
Added this code still have the same problem
private int[] evaluateMove(int [] board, int currentTurn) {
int bestScore;
int currentScore;
int bestMove = -1;
if (currentTurn == 1) {
bestScore = Integer.MIN_VALUE;
} else {
bestScore = Integer.MAX_VALUE;
}
List<Integer> nextMoves = generatemoves(board);
if (nextMoves.isEmpty()) {
bestScore = evaluateTheBoard(board);
} else {
for (int move : nextMoves) {
int[] nextBoard = new int[9];
for (int i = 0; i < nextBoard.length; i ++) {
nextBoard[i] = board[i];
}
nextBoard[move] = currentTurn;
currentScore = evaluateMove(nextBoard, nextTurn())[0];
if (currentTurn == 1) {
if (currentScore > bestScore) {
bestScore = currentScore;
bestMove = move;
}
} else {
if (currentScore < bestScore) {
bestScore = currentScore;
bestMove = move;
}
}
}
}
return new int[] {bestScore, bestMove};
}

I think you are misunderstanding how to look ahead in a game like this. Do not 'total' the values returned by evaluateLine.
Here is pseudocode for the minimax score of a tic-tac-toe board (what evaluateBoard should return). Note that evaluateBoard will need to have a notion of currentTurn.
function evaluateBoard(board, currentTurn)
// check if the game has already ended:
if WhiteHasWon then return -10
if BlackHasWon then return +10
// WhiteHasWon returns true if there exists one or more winning 3-in-a-row line for white.
// (You will have to scan for all 8 possible 3-in-a-row lines of white pieces)
// BlackHasWon returns true if there exists one or more winning 3-in-a-row line for black
if no legal moves, return 0 // draw
// The game isn't over yet, so look ahead:
bestMove = notset
resultScore = notset
for each legal move i for currentTurn,
nextBoard = board
Apply move i to nextBoard
score = evaluateBoard(nextBoard, NOT currentTurn).score
if score is <better for currentTurn> than resultScore, then
resultScore = score
bestMove = move i
return (resultScore, bestMove)
One very key difference between this and your version and my version is that my version is recursive. Yours only goes one level deep. Mine calls evaluateBoard from inside evaluateBoard, which would be an infinite loop if we aren't careful (once the board fills up, it can't go any deeper, so it's not actually infinite)
Another difference is that yours totals stuff when it shouldn't. The resulting score from tic-tac-toe is -10,0, or 10 only once you've looked to the end of the game. You should be picking the best possible move available to that player at that time, and ignoring all other possibilities completely because you only care about the "best" line of play. The game score is equal to the result of optimal play.
Expanding <better for currentTurn> is messy in minimax, which is why negamax is cleaner. White prefers low scores and black prefers high scores, so you need some if statements to make it choose the appropriate preferred score. You have this part already (at the end of your best move code), but it needs to be evaluated inside the recursion instead of just at the end.

Related

Finite Coins - using recursion

Given 1 coin from multiple denominations (E.G. a 1 coin, 5 coin, 16 coin), and a sum, return true or false determining if the sum can be made.
boolean go(int[] coins, int goal)
{
//Will set each time, but shouldn't matter as toggle is at bottom
boolean ans = false;
//loop running in recursion till founds ans
//Really bad time complexity lol
for (int i = 0; i < coins.length && (!ans); i++) {
if ((goal - coins[i] == 0) || goal == 0) {
return true;
}
if (goal > coins[i]) {
int[] red = new int[coins.length - 1];
//it necessary because making list with one less
int it = 0;
//Setting new list to avoid runtime
for(int x = 0; x < coins.length; x++){
if(!(i == x)){
red[it] = coins[i];
it += 1;
}
}
//Run with new list
ans = go(red, goal - coins[i]);
}
}
return ans;
}
This is my code so far. I have made it recursive, yet one of the test cases returns true when it should not. The test case in particular is [111, 1, 2, 3, 9, 11, 20, 30], with the goal doing 8; This should return false (as it cannot add up to 8), but in this case, returns true.
Other test cases work fine, so I believe my code has some sort of an exception.
I have tried to move the base case upward, and make a reverse variation...
boolean go(int[] coins, int goal)
{
boolean ans = false;
if(goal == 0){
return true;
}else if(goal < 0){
return false;
}
for (int i = 0; i < coins.length && (!ans); i++) {
if (goal >= coins[i]) {
int[] red = new int[coins.length - 1];
int it = 0;
for(int x = 0; x < coins.length; x++){
if(!(i == x)){
red[it] = coins[i];
it += 1;
}
}
ans = go(red, goal - coins[i]);
}
}
return ans;
}
is what I've tried, but the base case doesn't seem to affect anything
The bug is in your copying of the coins array: red[it] = coins[i] should really be red[it] = coins[x]...
For time complexity, you don't really have to do a loop inside the method. Each denomination is either part of the sum or not, so you can always remove the first denomination and test with and without it:
boolean go(int[] coins, int goal) {
if(goal == 0)
return true;
else if(coins.length == 0 || goal < 0)
return false;
else {
int[] tailOfCoins = Arrays.copyOfRange(coins, 1, coins.length);
return go(tailOfCoins, goal) || go(tailOfCoins, goal - coins[0]);
}
}

How to change value of 2D bool array?

I have this assignment for school:
Imagine a chess board and an ant. The ant is randomly put on the
board and after that it can walk up, down, left and right (not diagonally). The ant cannot walk over the edge of the chess board
(if it tries, it is not counted as a movement). The task is to create
a program called Ants.java that simulates the walking over the
chess board of an ant. To walk to another square than the one
the ant is currently on is called a “step” (even though it will take
the ant several steps to move…). Each simulation should calculate the number of “steps” the ant takes to visit all squares on
the chess board. The simulations must be done ten times and
an average should be calculated at the end of the simulation.
An example run of the simulation is shown below:
Ants
Number of steps in simulation 1: 708
Number of steps in simulation 2: 818
Number of steps in simulation 3: 953
Number of steps in simulation 4: 523
Number of steps in simulation 5: 671
Number of steps in simulation 6: 338
Number of steps in simulation 7: 535
Number of steps in simulation 8: 702
I am quite sure that I'm about 95% done. However, I need an array to store bool values, to see if the ant has visited the square on the board or not. I can't really figure out how to do it. Ignore the "isVisited", it was my first idea.
This is my code right now:
public static void main(String[] args) {
// Sums every step in the 10 iterations
double totalNumber = 0;
boolean[][] grid = new boolean[8][8];
for (int r = 0; r< grid.length; r++)
{
for (int c = 0 ; c<grid[0].length; c++)
{
grid[r][c] = false;
}
}
// Just a loop to make the ant walk 10 times
for (int k = 1; k <= 10; k++) {
// Setting board to false, not visited
boolean isVisited = false;
// Creating spawn points
int x = (int) (Math.random() * (8 + 1)) + 1;
int y = (int) (Math.random() * (8 + 1)) + 1;
// Setting spawn point to
isVisited = true;
// Variables, steps, min coord and max coords
int count = 0;
int minY = 1;
int maxY = 8;
int minX = 1;
int maxX = 8;
// All the unchecked places
int unchecked = 64;
// Places where the ant has been
int alreadyChecked = 0;
// While there's more than 0 unchecked places, random 1 - 4
while (unchecked > 0) {
int random = (int) (Math.random() * 4 + 1);
// West
if (random == 1) {
// Move to the left
x--;
// If the ant falls off
if (x < minX) {
// Bump it back
x++;
}
// If the place is visited
if (isVisited) {
// Already checked
alreadyChecked++;
// Count step anyway
count++;
}
// If it's not
if(!isVisited) {
// Set to visited
isVisited = true;
// Remove 1 from the unchecked
unchecked--;
// And take a step
count++;
}
}
// East
if (random == 2) {
x++;
if (x > maxX) {
x--;
}
if (isVisited) {
alreadyChecked++;
count++;
}
if(!isVisited) {
isVisited = true;
unchecked--;
count++;
}
}
// North
if (random == 3) {
y++;
if (y > maxY) {
y--;
}
if (isVisited) {
alreadyChecked++;
count++;
}
if(!isVisited) {
isVisited = true;
unchecked--;
count++;
}
}
// South
if (random == 4) {
y--;
if (y < minY) {
y++;
}
if (isVisited) {
alreadyChecked++;
count++;
}
isVisited = true;
unchecked--;
count++;
}
}
/**
* This simulation assumes Ant movement is discrete relative to grid cells
* i.e. its either in one of these cells at a time, overlapping two cells in not allowed!!
* **/
public class AntMovementSimulation
{
int onBoard[][] = null;
int antPosX = 0;
int antPosY = 0;
int antPrevPosX = 0;
int antPrevPosY = 0;
int directionOfMovement = 0;
int stepsCount = 0;
AntMovementSimulation()
{
onBoard = new int[8][8];
//initialize each position in onBoard to -1 ,implying Ant has not been placed yet, not even once!!
for( int i = 0 ; i < 8 ; i++ )
{
for( int j = 0 ; j < 8 ; j++ )
{
onBoard[i][j] = -1;//implying Ant has not been placed yet, not even once!!
}
}
//place Ant in random cell
antPosX = (int)Math.round(Math.random()*7);//generating random number between 0 and 7, since index is from 0 to 7 as there are 8 cell!!
antPosY = (int)Math.round(Math.random()*7);
//assigning 1 to onBoard at index antPosX,antPosY to indicate Ant has been placed there
onBoard[antPosX][antPosY] = 1;
}
/*this function returns false if any cell has -1,else true
* cause when all cells have been traversed , each cell have non negative value,either 0 or 1
* */
public boolean areAllCellsTraversed()
{
boolean result = true;
for( int i = 0 ; i < 8 ; i++ )
{
for( int j = 0 ; j < 8 ; j++ )
{
if( onBoard[i][j] == -1 )//implying this cell not traversed yet,i.e Ant not placed in this cell yet!!
{
result = false;
}
}
}
return result;
}
public void simulate()
{
//loop while all cells have been not traversed
while( !areAllCellsTraversed() )
{
directionOfMovement = (int)Math.round(Math.random()*3);//generating random number between 0 and 3
switch( directionOfMovement )
{
case 0://move left-to-right
antPosX += 1;
if( antPosX >= 7 ) antPosX = 0; //since largest array index is 1 less than its size, we compare with 7 instead of 8
break;
case 1://move right-to-left
antPosX -= 1;
if( antPosX <= 0 ) antPosX = 7;
break;
case 2://move top-to-bottom
antPosY += 1;
if( antPosY >= 7 ) antPosY = 0;
break;
case 3://move bottom-to-top
antPosY -= 1;
if( antPosY <= 0 ) antPosY = 7;
break;
}
//assign 0 to previous position, meaning Ant is no longer there
onBoard[antPrevPosX][antPrevPosY] = 0;
//assign 1 to new position , meaning Ant is here
onBoard[antPosX][antPosY] = 1;
stepsCount++;
antPrevPosX = antPosX;
antPrevPosY = antPosY;
}
//once all cells have been traversed , print result!!
printSteps();
}
/*prints the total number of step taken to traverse all cells*/
public void printSteps()
{
System.out.println("Total steps taken by Ant to traverse all cells = "+stepsCount);
}
public static void main(String[] args)
{
int sumOfTotalNumOfSteps = 0;
AntMovementSimulation[] amsArray = new AntMovementSimulation[10];
for( AntMovementSimulation ams: amsArray )
{
ams = new AntMovementSimulation();
ams.simulate();
sumOfTotalNumOfSteps += ams.stepsCount;
}
System.out.println("Average num of steps taken by Ant to traverse all cells = "+ sumOfTotalNumOfSteps/10);
}
}

What's wrong with my Tic Tac Toe AI?

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.

Finding 5 values in a row diagonally in Java

I have this 10x10 array:
private static int[][] intersections = new int[10][10];
I'm using this code to find if there are 5 values in a row horizontally:
public static int horizontalCheck() {
String horizontal = "";
for (int i = 0; i < intersections.length; i++) {
for (int j = 0; j < intersections[i].length; j++) {
horizontal += Integer.toString(intersections[i][j]);
}
if (horizontal.indexOf("11111") != -1) {
// White wins.
return 1;
} else if (horizontal.indexOf("22222") != -1) {
// Black wins.
return 2;
}
horizontal = "";
}
return 0;
}
And a similar code to do it vertically. But my question is, how could I find if there are 5 values in a row diagonally? The board is sized 10x10 and the diagonals can be both ways anywhere on the board. If you have any questions or need some more information on the code, make sure to ask.
I suggest you write a helper function for this. The function should take these parameters:
r0 - The starting row from which the check needs to start
c0 - The starting column from which the check needs to start
dr - The vertical step from {-1, 0, 1}
dc - The horizontal step from {-1, 0, 1}
len - The number of items to be found
num - The number to find.
Here is how this function may look:
private static boolean checkRow(int r0, int c0, int dr, int dc, int len, int num) {
for (int k = 0 ; k != len ; k++) {
int r = r0 + k*dr;
int c = c0 + k*dc;
if (r < 0 || c < 0 || r >= intersections.length || c > intersections[r].length || intersections[r][c] != num) {
return false;
}
}
return true;
}
With this function in hand, you can check for len items in a row in any direction that you wish:
// See if we've got five eights in any direction:
for (int r = 0 ; r != intersections.length ; r++) {
for (int c = 0 ; c != intersections[r].length ; c++) {
if (checkRow(r, c, 0, 1, 5, 8)) {
System.out.println("Horizontal, starting at "+r+" " +c);
}
if (checkRow(r, c, 1, 0, 5, 8)) {
System.out.println("Vertical, starting at "+r+" " +c);
}
if (checkRow(r, c, 1, 1, 5, 8)) {
System.out.println("Diagonal descending right, starting at "+r+" " +c);
}
if (checkRow(r, c, 1, -1, 5, 8)) {
System.out.println("Diagonal descending left, starting at "+r+" " +c);
}
}
}

Bug in Minimax Algorithm for Tic Tac Toe

I am currently trying to teach myself the Minimax algorithm and I have tried to implement it in java in tic tac toe. There is a bug in my algorithm however and I can't figure out what's causing it.
Below is the complete source code (Sorry for wall of text!):
public class TicTacToe {
private static boolean gameEnded = false;
private static boolean player = true;
private static Scanner in = new Scanner(System.in);
private static Board board = new Board();
public static void main(String[] args){
System.out.println(board);
while(!gameEnded){
Position position = null;
if(player){
position = makeMove();
board = new Board(board, position, PlayerSign.Cross);
}else{
board = findBestMove(board);
}
player = !player;
System.out.println(board);
evaluateGame();
}
}
private static Board findBestMove(Board board) {
ArrayList<Position> positions = board.getFreePositions();
Board bestChild = null;
int previous = Integer.MIN_VALUE;
for(Position p : positions){
Board child = new Board(board, p, PlayerSign.Circle);
int current = max(child);
System.out.println("Child Score: " + current);
if(current > previous){
bestChild = child;
previous = current;
}
}
return bestChild;
}
public static int max(Board board){
GameState gameState = board.getGameState();
if(gameState == GameState.CircleWin)
return 1;
else if(gameState == GameState.CrossWin)
return -1;
else if(gameState == GameState.Draw)
return 0;
ArrayList<Position> positions = board.getFreePositions();
int best = Integer.MIN_VALUE;
for(Position p : positions){
Board b = new Board(board, p, PlayerSign.Cross);
int move = min(b);
if(move > best)
best = move;
}
return best;
}
public static int min(Board board){
GameState gameState = board.getGameState();
if(gameState == GameState.CircleWin)
return 1;
else if(gameState == GameState.CrossWin)
return -1;
else if(gameState == GameState.Draw)
return 0;
ArrayList<Position> positions = board.getFreePositions();
int best = Integer.MAX_VALUE;
for(Position p : positions){
Board b = new Board(board, p, PlayerSign.Circle);
int move = max(b);
if(move < best)
best = move;
}
return best;
}
public static void evaluateGame(){
GameState gameState = board.getGameState();
gameEnded = true;
switch(gameState){
case CrossWin :
System.out.println("Game Over! Cross Won!");
break;
case CircleWin :
System.out.println("Game Over! Circle Won!");
break;
case Draw :
System.out.println("Game Over! Game Drawn!");
break;
default : gameEnded = false;
break;
}
}
public static Position makeMove(){
Position position = null;
while(true){
System.out.print("Select column(x-axis). 0, 1 or 2: ");
int column = getColOrRow();
System.out.print("Select row(y-axis). 0, 1 or 2: ");
int row = getColOrRow();
position = new Position(column, row);
if(board.isMarked(position))
System.out.println("Position already marked!");
else break;
}
return position;
}
private static int getColOrRow(){
int ret = -1;
while(true){
try{
ret = Integer.parseInt(in.nextLine());
} catch (NumberFormatException e){}
if(ret < 0 | ret > 2)
System.out.print("\nIllegal input... please re-enter: ");
else break;
}
return ret;
}
}
public enum PlayerSign{
Cross, Circle
}
public enum GameState {
Incomplete, CrossWin, CircleWin, Draw
}
public final class Position {
public final int column;
public final int row;
public Position(int column, int row){
this.column = column;
this.row = row;
}
}
public class Board {
private char[][] board; //e = empty, x = cross, o = circle.
public Board(){
board = new char[3][3];
for(int y = 0; y < 3; y++)
for(int x = 0; x < 3; x++)
board[x][y] = 'e'; //Board initially empty
}
public Board(Board from, Position position, PlayerSign sign){
board = new char[3][3];
for(int y = 0; y < 3; y++)
for(int x = 0; x < 3; x++)
board[x][y] = from.board[x][y];
board[position.column][position.row] = sign==PlayerSign.Cross ? 'x':'o';
}
public ArrayList<Position> getFreePositions(){
ArrayList<Position> retArr = new ArrayList<Position>();
for(int y = 0; y < 3; y++)
for(int x = 0; x < 3; x++)
if(board[x][y] == 'e')
retArr.add(new Position(x, y));
return retArr;
}
public GameState getGameState(){
if(hasWon('x'))
return GameState.CrossWin;
else if(hasWon('o'))
return GameState.CircleWin;
else if(getFreePositions().size() == 0)
return GameState.Draw;
else return GameState.Incomplete;
}
private boolean hasWon(char sign){ //8 ways to win.
if(board[1][1] == sign){
if(board[0][0] == sign && board[2][2] == sign)
return true;
if(board[0][2] == sign && board[2][0] == sign)
return true;
if(board[1][0] == sign && board[1][2] == sign)
return true;
if(board[0][1] == sign && board[2][1] == sign)
return true;
}
if(board[0][0] == sign){
if(board[0][1] == sign && board[0][2] == sign)
return true;
if(board[1][0] == sign && board[2][0] == sign)
return true;
}
if(board[2][2] == sign){
if(board[1][2] == sign && board[0][2] == sign)
return true;
if( board[2][1] == sign && board[2][0] == sign)
return true;
}
return false;
}
public boolean isMarked(Position position){
if(board[position.column][position.row] != 'e')
return true;
return false;
}
public String toString(){
String retString = "\n";
for(int y = 0; y < 3; y++){
for(int x = 0; x < 3; x++){
if(board[x][y] == 'x' || board[x][y] == 'o')
retString += "["+board[x][y]+"]";
else
retString += "[ ]";
}
retString += "\n";
}
return retString;
}
}
Here is the output when I run the program (Computer is circle):
[ ][ ][ ]
[ ][ ][ ]
[ ][ ][ ]
Select column(x-axis). 0, 1 or 2: 1
Select row(y-axis). 0, 1 or 2: 1
[ ][ ][ ]
[ ][x][ ]
[ ][ ][ ]
Child Score: 0
Child Score: 0
Child Score: 0
Child Score: 0
Child Score: 0
Child Score: 0
Child Score: 0
Child Score: 0
[o][ ][ ]
[ ][x][ ]
[ ][ ][ ]
Select column(x-axis). 0, 1 or 2: 0
Select row(y-axis). 0, 1 or 2: 1
[o][ ][ ]
[x][x][ ]
[ ][ ][ ]
Child Score: -1
Child Score: 0
Child Score: 0
Child Score: -1
Child Score: -1
Child Score: -1
[o][ ][o]
[x][x][ ]
[ ][ ][ ]
Select column(x-axis). 0, 1 or 2:
As you can see after the first move, the computer thinks that no matter what move it makes it can get a draw (Score = 0).
On the second move I put a cross on column 0, row 1. For some reason, the computer then thinks that there are two possible moves to reach a draw (Score = 0) and only four moves to lose (Score = -1). It then makes an incorrect move thinking it will get a draw.
I first thought that there was something wrong with the hasWon method, but I have tested all 8 ways of getting three in a row and they all return true.
I suspect that the problem exists somewhere in the findBestMove, max or min methods, but I haven't been able to figure out exactly what is causing it.
I would really appreciate it if someone could tell what is causing the bug or give any suggestions on how to better debug my recursive algorithm.
Looks to me like you mixed up parts of min and max. Currently, your max returns the value of the worst possible move (for him) the human could take, instead of the optimal move the computer could take. Likewise, min returns the value of the worst move the computer could take, instead of the optimal move for the opponent.
Fix this by switching the PlayerSigns in min and max, and findBestMove should call min, not max.

Categories