Search code examples
javauser-inputrepeattic-tac-toe

Java: Tic-Tac-Toe - How To AVOID Repeated Cell From Being Input By User?


How do I prevent the same tic tac toe coordinate from being inputted by the user?

The user input is taken at the main method in the Game Class.

The tic tac toe cells with [x, y] coordinates ranging from (0-2) can be either:

0(_), 1 (X) or 2 (O)

Grid Class with alpha beta search tree pruning algorithm

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

class Grid {

  List<Cell> availableCells;
  int[][] board = new int[3][3];
  Scanner scan = new Scanner(System.in);
  // Set limit to search tree depth
  int treeDepth = 9;

  List<CellsAndScores> rootsChildrenScore = new ArrayList<>();

  public int score() {
    int score = 0;

    // Check all columns
    for (int j = 0; j < 3; ++j) {
      int X = 0;
      int O = 0;
      for (int i = 0; i < 3; ++i) {
        if (board[i][j] == 0) {
        } else if (board[i][j] == 1) {
          X++;
        } else {
          O++;
        }
      }
      score += changeInScore(X, O);
    }

    // Check all rows
    for (int i = 0; i < 3; ++i) {
      int X = 0;
      int O = 0;
      for (int j = 0; j < 3; ++j) {
        if (board[i][j] == 0) {
        } else if (board[i][j] == 1) {
          X++;
        } else {
          O++;
        }
      }
      score += changeInScore(X, O);
    }

    int X = 0;
    int O = 0;

    // Check diagonal (first)
    for (int i = 0, j = 0; i < 3; ++i, ++j) {
      if (board[i][j] == 1) {
        X++;
      } else if (board[i][j] == 2) {
        O++;
      } else {
      }
    }

    score += changeInScore(X, O);

    X = 0;
    O = 0;

    // Check Diagonal (Second)
    for (int i = 2, j = 0; i > -1; --i, ++j) {
      if (board[i][j] == 1) {
        X++;
      } else if (board[i][j] == 2) {
        O++;
      } else {
      }
    }

    score += changeInScore(X, O);

    return score;
  }

  private int changeInScore(int X, int O) {
    int change;
    if (X == 3) {
      change = 100;
    } else if (X == 2 && O == 0) {
      change = 10;
    } else if (X == 1 && O == 0) {
      change = 1;
    } else if (O == 3) {
      change = -100;
    } else if (O == 2 && X == 0) {
      change = -10;
    } else if (O == 1 && X == 0) {
      change = -1;
    } else {
      change = 0;
    }
    return change;
  }

  public int alphaBetaMinimax(int alpha, int beta, int depth, int turn) {

    if (beta <= alpha) {
      System.out.println("Pruning at tree depth = " + depth + " alpha: " + alpha + " beta: " + beta);
      if (turn == 1)
        return Integer.MAX_VALUE;
      else
        return Integer.MIN_VALUE;
    }

    if (depth == treeDepth || gameOver()) {
      return score();
    }

    List<Cell> cellsAvailable = getAvailableStates();

    if (cellsAvailable.isEmpty()) {
      return 0;
    }

    if (depth == 0) {
      rootsChildrenScore.clear();
    }

    int maxValue = Integer.MIN_VALUE, minValue = Integer.MAX_VALUE;

    for (int i = 0; i < cellsAvailable.size(); ++i) {
      Cell cell = cellsAvailable.get(i);

      int currentScore = 0;

      if (turn == 1) {
        placeAMove(cell, 1);
        currentScore = alphaBetaMinimax(alpha, beta, depth + 1, 2);
        maxValue = Math.max(maxValue, currentScore);

        // Set alpha
        alpha = Math.max(currentScore, alpha);

        if (depth == 0) {
          rootsChildrenScore.add(new CellsAndScores(currentScore, cell));
        }
      } else if (turn == 2) {
        placeAMove(cell, 2);
        currentScore = alphaBetaMinimax(alpha, beta, depth + 1, 1);
        minValue = Math.min(minValue, currentScore);

        // Set beta
        beta = Math.min(currentScore, beta);
      }
      // reset board
      board[cell.x][cell.y] = 0;

      // Do not evaluate the rest of the branches after search tree is pruned
      if (currentScore == Integer.MAX_VALUE || currentScore == Integer.MIN_VALUE)
        break;
    }
    return turn == 1 ? maxValue : minValue;
  }

  public boolean gameOver() {
    // 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 List<Cell> getAvailableStates() {
    availableCells = new ArrayList<>();
    for (int i = 0; i < 3; ++i) {
      for (int j = 0; j < 3; ++j) {
        if (board[i][j] == 0) {
          availableCells.add(new Cell(i, j));
        }
      }
    }
    return availableCells;
  }

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

  public Cell returnBestMove() {
    int MAX = -100000;
    int best = -1;

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

    return rootsChildrenScore.get(best).cell;
  }

  public void displayBoard() {
    System.out.println();
    for (int i = 0; i < 3; ++i) {
      for (int j = 0; j < 3; ++j) {
        if (board[i][j] == 0)
          System.out.print("_" + " ");
        if (board[i][j] == 1)
          System.out.print("X" + " ");
        if (board[i][j] == 2)
          System.out.print("O" + " ");
      }
      System.out.println("");
    }
    System.out.println();
  }

  public void resetGrid() {
    for (int i = 0; i < 3; ++i) {
      for (int j = 0; j < 3; ++j) {
        board[i][j] = 0;
      }
    }
  }
}

Cell class

class Cell {

  int x, y;

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

  public String toString() {
    return "[" + x + ", " + y + "]";
  }
}

class CellsAndScores {

  int score;
  Cell cell;

  CellsAndScores(int score, Cell cell) {
    this.score = score;
    this.cell = cell;
  }
}

Game Class with main method - takes user input

import java.util.Random;

public class Game {

  public static void main(String[] args) {
    Grid grid = new Grid();
    Random random = new Random();

    grid.displayBoard();

    System.out.print("Who moves first? [1]Computer(X) [2]User(O): ");
    int turn = grid.scan.nextInt();
    if (turn == 1) {
      Cell p = new Cell(random.nextInt(3), random.nextInt(3));
      grid.placeAMove(p, 1);
      grid.displayBoard();
    }

    while (!grid.gameOver()) {
      int x = 0, y = 0;

      System.out.print("Please enter an x coordinate [0-2]: ");
      x = grid.scan.nextInt();
      System.out.print("Please enter an y coordinate [0-2]: ");
      y = grid.scan.nextInt();

      Cell userMove = new Cell(y, x);

      grid.placeAMove(userMove, 2); // 2 for O and O is the user
      grid.displayBoard();
      if (grid.gameOver())
        break;

      grid.alphaBetaMinimax(Integer.MIN_VALUE, Integer.MAX_VALUE, 0, 1);
      for (CellsAndScores pas : grid.rootsChildrenScore)
        System.out.println("Cell: " + pas.cell + " Score: " + pas.score);

      grid.placeAMove(grid.returnBestMove(), 1);
      grid.displayBoard();
    }
    if (grid.hasXWon()) {
      System.out.println("Unfortunately, you lost!");
      grid.resetGrid();
    } else if (grid.hasOWon()) {
      System.out.println("You win!");
      grid.resetGrid();
    } else {
      System.out.println("It's a draw!");
      grid.resetGrid();
    }
  }
}

Solution

  • My answer would be to add a boolean check method into your Grid.java class and then in your main method - call this boolean check method before the placeAMove() method.

    For example, in your Grid.java class, adding the following method:

    /*
     * Return true if space is ok to use.
     */
    public boolean isMoveOK(Cell cell) {
        return board[cell.x][cell.y] == 0;
    }
    

    This way, using your pre-existing 0/1/2 values that keep track of empty/X/O space values, you may provide a check to see if the space value is zero or not.

    This would be one way to use it in your main method, to answer your question of, 'How do I prevent the same tic tac toe coordinate from being inputted by the user?'

    Cell userMove = new Cell(y, x);
    
    if (grid.isMoveOK(userMove)) {
        grid.placeAMove(userMove, 2); // 2 for O and O is the user
    } else {
        System.out.println("Please try a different space/cell");
        continue;
    }
    
    grid.displayBoard();
    if (grid.gameOver())
        break;
    

    In this way, I am skipping the remaining loop code in your main method loop, until there's a valid open space. (When there is, then the program should proceed to check for winning values or whether to proceed playing)

    Hope this answers your question! :)

    Cheers