Question

I'm working on a tictactoe board for practice making classes and i have ran into a problem with my algorithm. it seems to be returning the best move offensive, but it doesn't play defense. i dont know where i have messed up and cant seem to find it. i have looked over a lot of things on here about it and ive compared it to simular projects, but still can't seem to get it. here is my code.

package TicTacToe;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Scanner;


public class Solution {

private static GameBoard currentBoard; 
private static Player botPlayer; 

public static void main(String[] args) {

    Scanner in = new Scanner(System.in);
    String player;
    System.out.println("ENTER bot: ");
    player = in.next();

    if(player.equalsIgnoreCase("X")) {
        botPlayer = Player.X;}
    else {botPlayer = Player.O;}

    String board[] = new String[3];
    for(int i = 0; i < 3; i++) {
        System.out.println("ENTER board: ");
        board[i] = in.next();
    }

        currentBoard = new GameBoard(3,3, board);
        List<Space> OpenSpaces = getOpenSquares(currentBoard);
        MakeMove(OpenSpaces);

    System.exit(-1);
}

public static List<Space> getOpenSquares(GameBoard GB) {
    List<Space> OpenSpaces = new ArrayList<Space>();
    for(int r = 0; r < 3; r++) {
        for(int c = 0; c < 3; c++) {
            if(GB.squares[r][c] == Player.Open) {
                OpenSpaces.add(new Space(r,c));
            }
        }
    }
    return OpenSpaces;
}

private static Space bestMove;
private static Space currentMove;
private static Space previousMove;


private static void MakeMove(List<Space> OpenSpaces) {
    if(OpenSpaces.size() == currentBoard.Size) {
        Random random = new Random();
        bestMove = new Space(random.nextInt(2),2);
    } else {
        for(Space child: OpenSpaces) {
            currentMove = GetBestMove(currentBoard,botPlayer);
            if (currentMove != null){

            }else{
                continue;}
            if(previousMove != null && previousMove.Rank < currentMove.Rank ||
                    previousMove == null && currentMove != null) {
                bestMove = currentMove;
            }
            previousMove = currentMove;
        }   
    }
    if (bestMove != null) {
        currentBoard.squares[bestMove.X][bestMove.Y] = botPlayer;
        System.out.println("the best move is: " + currentMove.X + " " + currentMove.Y);
    } 
}

private static Space GetBestMove(GameBoard gb, Player p) {
    Space bestSpace = null;
    List<Space> cloneOpenSpaces = getOpenSquares(gb);
    GameBoard cloneBoard = null;
    cloneBoard = gb.Clone();
        for(Space Open: cloneOpenSpaces) {
            cloneBoard = gb.Clone();
            Space newSpace = Open;
            cloneBoard.squares[newSpace.X][newSpace.Y] = p;
            if(cloneBoard.Winner == Player.Open && cloneOpenSpaces.size() > 0) {
                Player InP;
                if(p == Player.X) {
                    InP = Player.O;
                }else {
                    InP = Player.X;
                    }

                Space tempMove = GetBestMove(cloneBoard, InP);
                if(tempMove != null){
                    newSpace.Rank = tempMove.Rank;
                }


            } else {
                if(cloneBoard.Winner == Player.Open) {
                    newSpace.Rank = 0;
                }else if(cloneBoard.Winner == Player.O) {
                    newSpace.Rank = -1;
                }else if(cloneBoard.Winner == Player.X) {
                    newSpace.Rank = 1;
                }

            }
            System.out.println(newSpace.Rank);
            if(bestSpace == null || 
                    (p == Player.X && newSpace.Rank < ((Space)bestSpace).Rank)||
                    (p == Player.O && newSpace.Rank > ((Space)bestSpace).Rank)) {
                bestSpace = newSpace;
            }               
        }
    return (Space)bestSpace;
}

public static enum Player {
    X (1),
    O (-1),
    Open (0);

    private final double value;
    Player(double value){
        this.value = value;
    }
}

public static class Space {
    public int X;
    public int Y;
    public double Rank;

    public Space(int x, int y) {
        this.X = x;
        this.Y = y;
        Rank = 0;
    }


    public Space() {
    }
}

public static class GameBoard {

    public int Rows;
    public int getRows() {
        return this.Rows;
    }
    public void setRows(int rows) {
        Rows = rows;
    }

    public int Columns;
    public int getColumns() {
        return this.Columns;
    }
    public void setColumns(int columns) {
        Columns = columns;
    }

    public Player[][] squares;

    //public Player[x][y] 
    public Player getPlayer(int x, int y) {
        return this.squares[x][y];
    }

    public void setPlayer(int x, int y, Player player) {
        squares[x][y] = player;
    }

    public boolean Full;

    public boolean isFull() {
        for(int r = 0; r < 2; r++) {
            for(int c = 0; c < 2; c++) {
                if (squares[r][c] != Player.Open) {return false;}
            }
        }
        return true;
    }

    public int Size;
    public int getSize() {
        return this.Size;
    }
    public void setSize(int size) {
        Size = size;
    }

    public List<Space> OpenSquares;
    public List<Space> getOpenSquares() {
        List<Space> OpenSquares = new ArrayList<Space>();
        for(int r = 0; r < Rows; r++) {
            for(int c = 0; c < Columns; c++) {
                if(squares[r][c] == Player.Open) {
                    OpenSquares.add(new Space(r,c));
                }
            }
        }
        return this.OpenSquares;
    }

    public Player Winner;
    public Player getWinner() {
        int count = 0; 

        //columns 
        for (int x = 0; x < Rows; x++) 
        { 
            count = 0;
            for (int y = 0; y < Columns; y++) {
                count += squares[x][y].value;
            }
            if (count == 3) {
                return Player.X; 
            }else if (count == -3) {
                return Player.O; 
            }
        } 

        //rows 
        for (int x = 0; x < Rows; x++) { 
            count = 0; 
            for (int y = 0; y < Columns; y++) {
                count += squares[y][x].value; 
            }
            if (count == 3) {
                return Player.X; 
            }else if (count == -3) {
                return Player.O; 
            }
        } 

        // Diagonals right to left 
        count = 0; 
        count += squares[0][0].value; 
        count += squares[1][1].value; 
        count += squares[2][2].value; 
        if (count == 3) {
            return Player.X; 
        }else if (count == -3) {
            return Player.O; 
        } 


        // Diagonals left to right 
        count = 0; 
        count += squares[0][2].value; 
        count += squares[1][1].value; 
        count += squares[2][0].value; 
        if (count == 3) {
            return Player.X; 
        }else if (count == -3) {
            return Player.O; 
        }
        return Player.Open; 
    }

    public GameBoard Clone() {
        GameBoard b = new GameBoard(Rows,Columns);
        b.squares = (Player[][])this.squares.clone();
        b.Winner = getWinner();
        return b;
    }

    // Class initializer 
    public GameBoard(int boardRows, int boardColumns, String[] board) {

        // Set the dimensions 
        Rows = boardRows;
        Columns = boardColumns;

        // Create game spaces 
        squares = new Player[Rows][Columns];
        for(int r = 0; r < Rows; r++) {
            for(int c = 0; c < Columns; c++) {
                //squares[i][n] = Player.Open;
                if(board[r].charAt(c) == 'X') {
                    squares[r][c] = Player.X;
                }
                if(board[r].charAt(c) == 'O') {
                    squares[r][c] = Player.O;
                }
                if(board[r].charAt(c) == '_') {
                    squares[r][c] = Player.Open;
                }
            }
        }
        this.Winner = getWinner();
        this.OpenSquares = getOpenSquares();
        //Size of the board
        this.Size = Rows * Columns;
    }

    // clone Class initializer
    public GameBoard(int boardRows, int boardColumns) {
        // Set the dimensions 
        Rows = boardRows;
        Columns = boardColumns;

        // Create game spaces 
        squares = new Player[Rows][Columns];
        for(int r = 0; r < Rows; r++) {
            for(int c = 0; c < Columns; c++) {
                squares[r][c] = Player.Open;
            }   
        }
        this.Winner = getWinner();
        this.OpenSquares = getOpenSquares();
        //Size of the board
        Size = Rows * Columns;
    }
}
}

all of the classes are at the bottom. Thanks in advance for any help and corrections. :)

i made it recursive in the following code, although i still cant figure out the scoring.. if the value is either 1, 0, or -1 then if there are multipule moves with the same value it will just take the 1st one which may not be the best move "blocking.

private static Space GetBestMove(GameBoard gb, Player p) {
Space bestSpace = null;
List<Space> cloneOpenSpaces = getOpenSquares(gb);
GameBoard cloneBoard = null;
cloneBoard = gb.Clone();
    for(Space Open: cloneOpenSpaces) {
        cloneBoard = gb.Clone();
        Space newSpace = Open;
        cloneBoard.squares[newSpace.X][newSpace.Y] = p;
        if(cloneBoard.Winner == Player.Open && cloneOpenSpaces.size() > 0) {
            Player InP;
            if(p == Player.X) {
                InP = Player.O;
            }else {
                InP = Player.X;
                }

            ***Space tempMove = GetBestMove(cloneBoard, InP);***
            if(tempMove != null){
                newSpace.Rank = tempMove.Rank;
            }

the results of the test are as follows

test 1

ENTER bot: 
O

ENTER board: 
[ ][O][ ]

ENTER board: 
[ ][ ][ ]

ENTER board: 
[ ][X][X]

-1.0
-1.0
-1.0
-1.0
-1.0
-1.0
-1.0
-1.0
-1.0

the best move is: 0 2

test 2

ENTER bot: 
O

ENTER board: 
[ ][X][X]

ENTER board: 
[ ][ ][ ]

ENTER board: 
[ ][O][ ]


1.0
1.0
1.0
1.0
1.0
-1.0
1.0
-1.0
-1.0
1.0
-1.0
1.0
1.0
-1.0
-1.0
the best move is: 1 1
Was it helpful?

Solution

I haven't ran your code, but I think I may know why you are having issues. The minimax algorithm is recursive in nature. You look at each open space, and determine some sort of score for each one. I see this in your code. However, what I don't see is the recursion that equates to the logic "if I move here, then what options will my opponent have during his next turn". Notice that you can keep calling the same scoring function, but scoring both players' options. This is where the computation can get intensive, and where stuff like pruning comes into play. Say I want to look 3 moves ahead. Say there are initially 5 open spaces. For each of the 5 open spaces, I examine my options and give a score to each one. Then I pretend to move there, and send the new board through the scoring function, and assume my opponent will take the highest scoring move of the remaining 4 possible moves. Then I pretend he moves there, and I again run the board through the scoring function, now with 2 hypothetical moves on it. You continue this for a set "depth", or number of potential moves, and pick the move that results in the highest value, assuming the opponent will do what you calculated they would.

I realize this was long-winded, but I hope there was a little bit of value buried in there somewhere. Take a look at your code, figure out where you are scoring moves (if you see a win, take it; if you can block a win, take it; etc.). Then continue calling this function where you keep adding fake/potential moves (those with the highest value from your scoring function), and once you reach the depth, you can simply pick the move that is likely to give you the most valuable outcome.

Basically, in your code, you should call GetBestMove(...) once from MakeMove(...). However, GetBestMove(...) should repeatedly call itself, with a modified board each time; and each time, it will return the best move given a hypothetical (or real) board. What I don't see in your code is that recursive call to GetBestMove(...), and the necessary upkeep that goes along with it. This explains why you only get aggressive behavior; it only looks to see what the best immediate move is, without any regard to what your opponent might be able to do if you make that move!

If my assumptions are wrong, provide a test case where you expect some behavior, but are getting something different.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top