0

I've written this code for tic-tac-toe AI implementing the MiniMax algorithm. The AI wins every time (full depth search). But there might be something wrong. Here's the output that makes me suspect the implementations correctness:

0 0 0

0 0 0

0 0 0

Who's gonna move first? (1) You : (2) Me?

1

Your move: 

2 2

0 0 0 

0 0 0 

0 0 2 

Score: -1 Point: 0 0

Score: -1 Point: 0 1

Score: -1 Point: 0 2

Score: -1 Point: 1 0

Score: 0 Point: 1 1

Score: 0 Point: 1 2

Score: 0 Point: 2 0

Score: 0 Point: 2 1

0 0 0 

0 1 0 

0 0 2 

Your move: 

0 0

2 0 0 

0 1 0 

0 0 2 

Score: 0 Point: 0 1

Score: 0 Point: 0 2

Score: 0 Point: 1 0

Score: 0 Point: 1 2

Score: 0 Point: 2 0

Score: 0 Point: 2 1

2 1 0 

0 1 0 

0 0 2 

Now all 0s at these points mean that the AI can draw the match in the worst case if it takes turn and mark these points as 1. (AI is 1, Player is 2). That is definitely wrong. There are two points where defeat is for sure (Point 0 2 and Point 2 0) so there must have been -1 for the score at these points. But the AI takes the first move and wins (why?). I've not been able to defeat the AI, even with thousands of random tests.

Here is the full code. It'd be very helpful if you could tell me the things that are going wrong.

import java.util.*;

class Point {

int x, y;

public Point(int x, int y) {
    this.x = x;
    this.y = y;
}
}

public class Board {

//Copy constructor
public Board(Board b) {
    for (int i = 0; i < 3; ++i) {
        for (int j = 0; j < 3; ++j) {
            this.board[i][j] = b.board[i][j];
        }
    }
}

public Board() {
}

int[][] board = new int[3][3];

List<Point> availablePoints;

//Get the playable locations available on the board
public List<Point> getAvailableStates() {
    availablePoints = new ArrayList<>();
    for (int i = 0; i < 3; ++i) {
        for (int j = 0; j < 3; ++j) {
            if (board[i][j] == 0) {
                availablePoints.add(new Point(i, j));
            }
        }
    }
    return availablePoints;
}

public boolean isGameOver() {
    //Game is over is someone has won, or board is full (draw)
    return (hasXWon() || hasOWon() || getAvailableStates().isEmpty());
}

public boolean hasXWon() {
    if ((board[0][0] == board[1][1] && board[0][0] == board[2][2] && board[0][0] == 1)
            || (board[0][2] == board[1][1] && board[0][2] == board[2][0] && board[0][2] == 1)) {
        //System.out.println("X Diagonal Win");
        return true;
    }
    for (int i = 0; i < 3; ++i) {
        if (((board[i][0] == board[i][1] && board[i][0] == board[i][2] && board[i][0] == 1)
                || (board[0][i] == board[1][i] && board[0][i] == board[2][i] && board[0][i] == 1))) {
            //System.out.println("X Row or Column win");
            return true;
        }
    }
    return false;
}

public boolean hasOWon() {
    if ((board[0][0] == board[1][1] && board[0][0] == board[2][2] && board[0][0] == 2)
            || (board[0][2] == board[1][1] && board[0][2] == board[2][0] && board[0][2] == 2)) {
        //System.out.println("O Diagonal Win");
        return true;
    }
    for (int i = 0; i < 3; ++i) {
        if ((board[i][0] == board[i][1] && board[i][0] == board[i][2] && board[i][0] == 2)
                || (board[0][i] == board[1][i] && board[0][i] == board[2][i] && board[0][i] == 2)) {
            //System.out.println("O Row or Column win");
            return true;
        }
    }
    return false;
}

public void placeAMove(Point point, int player) {
    board[point.x][point.y] = player;   //player = 1 for X, 2 for O
}

class PointsAndScores {

    int score;
    Point point;

    PointsAndScores(int score, Point point) {
        this.score = score;
        this.point = point;
    }
}

List<PointsAndScores> pointsAndScores = new ArrayList<>();

//The actual MinMax Logic is in this function
public int evaluateBoardPositions(int RecursionTurn) {

    List<Point> availablePoints = getAvailableStates();

    if (hasXWon()) {
        return +1;
    } else if (hasOWon()) {
        return -1;
    } else if (availablePoints.isEmpty()) {
        return 0;
    } else {
        int minOrMax = -999;

        List<Integer> scores = new ArrayList<>();

        for (Point point : availablePoints) {

            Board newBoard = new Board(this);
            if (RecursionTurn == 1) {
                newBoard.placeAMove(point, 1);
                scores.add(newBoard.evaluateBoardPositions(2));
                Collections.sort(scores, Collections.reverseOrder());
                minOrMax = scores.get(0);
            }
            if (RecursionTurn == 2) {
                newBoard.placeAMove(point, 2);
                scores.add(newBoard.evaluateBoardPositions(1));
                Collections.sort(scores);
                minOrMax = scores.get(0);
            }
            pointsAndScores.add(new PointsAndScores(minOrMax, point));
        }
        return minOrMax;
    }
}

//Return the move with the highest score i.e. +1 for our Bot.
public Point returnBestMove() {
    int MAX = -100000;
    int best = -1;

    for (int i = 0; i < pointsAndScores.size(); ++i) {
        if (MAX < pointsAndScores.get(i).score) {
            MAX = pointsAndScores.get(i).score;
            best = i;
        }
    }

    return pointsAndScores.get(best).point;
}
Scanner scan = new Scanner(System.in);

void takeHumanInput() {
    System.out.println("Your move: ");
    int x = scan.nextInt();
    int y = scan.nextInt();
    Point point = new Point(x, y);
    placeAMove(point, 2);
}

public void displayBoard() {
    System.out.println();

    for (int i = 0; i < 3; ++i) {
        for (int j = 0; j < 3; ++j) {
            System.out.print(board[i][j] + " ");
        }
        System.out.println();

    }
}

void placeFirstMove() {
    Random rand = new Random();
    Point p = new Point(rand.nextInt(3), rand.nextInt(3));
    placeAMove(p, 1);
}

public static void main(String[] args) {

    Board b = new Board();
    b.displayBoard();

    System.out.println("Who's gonna move first? (1) You : (2) Me?");
    Scanner scan = new Scanner(System.in);
    int choice = scan.nextInt();

    if (choice == 2) {
        b.placeFirstMove();
        b.displayBoard();
    }
    while (!b.isGameOver()) {
        b.takeHumanInput();
        if (b.isGameOver()) {
            break;
        }
        b.displayBoard();

        b.pointsAndScores.clear();
        b.evaluateBoardPositions(1);
        Point p = b.returnBestMove();

        for (PointsAndScores pas : b.pointsAndScores) {
            System.out.println("Score: " + pas.score + " Point: " + pas.point.x + " " + pas.point.y);
        }
        b.placeAMove(p, 1);
        b.displayBoard();
    }
    if (b.hasXWon()) {
        System.out.println("Unfortunately, you lost!");
    } else if (b.hasOWon()) {
        System.out.println("Never gets displayed. Computer Always wins or draws");
    } else {
        System.out.println("It's a draw!");
    }
}
}
llanato
  • 2,508
  • 6
  • 37
  • 59
CodeWriter
  • 77
  • 1
  • 10
  • I've already done that: else if (hasOWon()) { return -1; } – CodeWriter Aug 27 '14 at 13:47
  • Sorry, you're right. It's not very easy to debug tangled code >.< `pointsAndScores.add(new PointsAndScores(minOrMax, point));` Are you sure you should be adding the min/max of moves available so far here, instead of the score for the particular move? – Ordous Aug 27 '14 at 14:04
  • I use this list only for the final move that MAX has to take. This list adds all the points and their scores calculated. Other recursion lists (this list rebuilt during recursion) and their elements are just ignored (I know this is a really bad design). That's why I need to clear it before the computer takes the next move every time. – CodeWriter Aug 27 '14 at 14:16
  • Yes, that should not influence the *decision* the program makes, but it does make it's *debug output* incorrect. Thus making debugging more difficult. – Ordous Aug 27 '14 at 14:18

1 Answers1

0
public int evaluateBoardPositions(int RecursionTurn, int bestPoint) {

    List<Point> availablePoints = getAvailableStates();

    int minOrMax = -999;
    if (hasXWon()) {
        return +1;
    } else if (hasOWon()) {
        return -1;
    } else if (availablePoints.isEmpty()) {
        return 0;
    }
    else {
        for (Point point : availablePoints) {

            Board newBoard = new Board(this);
            if (RecursionTurn == 1) {
                newBoard.placeAMove(point, 1);
                bestPoint = newBoard.evaluateBoardPositions(2,bestPoint);
                pointsAndScores.add(new PointsAndScores(bestPoint, point));
                Collections.sort(pointsAndScores);
                minOrMax = pointsAndScores.get(pointsAndScores.size()-1).score;
            }
            else {
                newBoard.placeAMove(point, 2);
                bestPoint = newBoard.evaluateBoardPositions(1,bestPoint);
                pointsAndScores.add(new PointsAndScores(bestPoint, point));
                Collections.sort(pointsAndScores);
                minOrMax = pointsAndScores.get(0).score;
            }
        }
        return minOrMax;
    }
}
Paul Roub
  • 36,322
  • 27
  • 84
  • 93
CTLaur
  • 1
  • 1