Home > Net >  TicTacToe.java issues with checkWinner method
TicTacToe.java issues with checkWinner method

Time:04-19

So I created a checkWinner method, using 'row' and 'col' private variables so I can locate the 'curPlayer' position in the 2D array.

import java.util.Scanner;

public class TicTacBoard
{
  private char[][] board;  // 2-D array of characters
  private char curPlayer; // the player whose turn it is (X or O)
  // added so I can locate the current player location in the board
  private int row;
  private int col;

  // Constructor: board will be size x size
  public TicTacBoard(int size)
  {
    board = new char[size][size];

    // initialize the board with all spaces:
    for(row=0; row < board.length; row  )
      for(col=0; col < board[row].length; col  )
        board[row][col] = ' ';

    curPlayer = 'X';  // X gets the first move
  }

  public void playGame()
  {
    display();
    do
    {
      takeTurn();
      display();
    }while(!checkWinner(row, col));
  }

  ///////  display  ////////
  // Display the current status of the board on the
  // screen, using hyphens (-) for horizontal lines
  // and pipes (|) for vertical lines.
  public void display()
  {
    System.out.println();
    dispRow(0);
    System.out.println("-----");
    dispRow(1);
    System.out.println("-----");
    dispRow(2);
    System.out.println();
  }

  // Display the current status of row r of the board
  // on the screen, using hyphens (-) for horizontal
  // lines and pipes (|) for vertical lines.
  private void dispRow(int r)
  {
    System.out.println(board[r][0]   "|"   board[r][1]
                         "|"   board[r][2]);
  }

  ///////  takeTurn  ////////
  // Allow the curPlayer to take a turn.
  // Send output to screen saying whose turn
  // it is and specifying the format for input.
  // Read user's input and verify that it is a
  // valid move.  If it's invalid, make them
  // re-enter it.  When a valid move is entered,
  // put it on the board.
  public void takeTurn()
  {
    Scanner scan = new Scanner(System.in);
    int row, col;
    boolean invalid;

    do{
      invalid = false; // assume correct entry
      System.out.println("It is now "   curPlayer   "'s turn.");
      System.out.println("Please enter your move in the form row column.");
      System.out.println("So 0 0 would be the top left, and 0 2 would be the top right.");
      row = scan.nextInt();
      col = scan.nextInt();

      if(row < 0 || col < 0 || row > 2 || col > 2)
      {
        System.out.println("Invalid entry: row and column must both be between 0 and 2 (inclusive).");
        System.out.println("Please try again.");
        invalid = true;
      }
      else if(board[row][col] != ' ')
      {
        System.out.println("Invalid entry: Row "   row   " at Column "   col
                             " already contains: "   board[row][col]);
        System.out.println("Please try again.");
        invalid = true;
      }
    }while(invalid);
    // Now that input validation loop is finished, put the move on the board:
    board[row][col] = curPlayer;

    // Switch to the other player (take turns):
    if(curPlayer == 'X')
      curPlayer = 'O';
    else
      curPlayer = 'X';
  }

  // If the game is over, print who won (if anyone),
  // and return true.  If the game is not over, return false.
  public boolean checkWinner(int row, int col)
  {
    // YOUR CODE GOES HERE
    int x = row;
    int y = col;
    // board length is always 3 here
    // check winner on column
    for (int i = 0; i < board.length; i  ) {
      if (board[x][i] != curPlayer)
        break;
      if (i == board.length - 1)
        System.out.println("Player "   curPlayer   " wins!");
        return true;
    }
    
    //check winner on row
    for (int i = 0; i < board.length; i  ) {
      if (board[i][y] != curPlayer)
        break;
      if (i == board.length - 1)
        System.out.println("Player "   curPlayer   " wins!");
        return true;
    }

    // checks winner on diagonal up 
    if (x == y) {
      for (int i = 0; i < board.length; i  ) {
        if (board[i][i] != curPlayer)
          break;
        if (i == board.length - 1)
          System.out.println("Player "   curPlayer   " wins!");
          return true; 
      }
    }

    // check winner on diagonal down
    if (x   y == board.length - 1){
      for (int i = 0; i < board.length; i  ) {
        if (board[i][(board.length-1)-i] != curPlayer)
          break;
        if (i == board.length - 1)
          System.out.println("Player "   curPlayer   " wins!");
        return true;
      }
    }

    // checks if board is full
    for (int i = 0; i < board.length; i  ) {
      for (int j = 0; j < board.length; j  ) {
        if (board[i][j] == '-')
          System.out.println("Nobody won, game ends in a draw!");
          return true;
      }
    }
    return false;
  }
}

The code works but I while I was checking I got this:

 | | 
-----
 | | 
-----
 | | 

It is now X's turn.
Please enter your move in the form row column.
So 0 0 would be the top left, and 0 2 would be the top right.
2 0

 | | 
-----
 | | 
-----
X| | 

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
    at TicTacBoard.checkWinner(TicTacBoard.java:126)
    at TicTacBoard.playGame(TicTacBoard.java:43)
    at Main.main(Main.java:14)

I thought the board length is always 3 with the location ranging from 0 to 3. Any solutions to this error? Any more efficient ways to do this? Please let me know!

CodePudding user response:

You have a "shadowing" problem - that is, you're shadowing the instance fields row and col with local variables in your takeTurn method.

In it's current state...

// Constructor: board will be size x size
public TicTacBoard(int size) {
    board = new char[size][size];

    // initialize the board with all spaces:
    for (row = 0; row < board.length; row  ) {
        for (col = 0; col < board[row].length; col  ) {
            board[row][col] = ' ';
        }
    }

    curPlayer = 'X';  // X gets the first move
}

after the constructor has run, row and col will be 3, but in takeTurn, you define row and col as local variables...

public void takeTurn() {
    Scanner scan = new Scanner(System.in);
    int row, col;
    boolean invalid;

This means, that when you call checkWinner in the playGame method...

public void playGame() {
    display();
    do {
        takeTurn();
        display();
    } while (!checkWinner(row, col));
}

You're passing the instance field values (of 3/3) and everything breaks.

So, the "quick" solution would be to remove the local declaration of row/col from takeTurn

public void takeTurn() {
    Scanner scan = new Scanner(System.in);
    //int row, col;
    boolean invalid;

You could also fix this in the constructor, but making row/col local variables

for (int row = 0; row < board.length; row  ) {
    for (int col = 0; col < board[row].length; col  ) {
        board[row][col] = ' ';
    }
}

but at some point, you need to update the row/col value for the player, but I might consider passing this information back from takeTurn rather than trying to use instance fields.

CodePudding user response:

You also have a subtle, but common bug in your if statements. Without brackets, { and }, only the line IMMEDIATELY following the if statement will be executed when the conditional statement above is true. Your INDENTATION, however, indicates that you expected a different behavior.

For instance, your very first for loop is:

for (int i = 0; i < board.length; i  ) {
  if (board[x][i] != curPlayer)
    break;
  if (i == board.length - 1)
    System.out.println("Player "   curPlayer   " wins!");
    return true;
}

Here, only the System.out.println() line is executed when the if statement is true. The indentation of the return true; statement indicates that you expect it to only run with the println(), only when the conditional is true.

The return true; line is NOT dependent upon the preceding if statement, though, because it is not within brackets and the if statement only runs the line immediately following it. This means that the for loop is only ever running ONE ITERATION because the return line is STAND-ALONE and executes every single time, regardless of how those if statements evaluate.

You should ALWAYS, ALWAYS, ALWAYS add brackets to your if statements, even if they are "one-liners". With that in mind, I'd expect it to look more like:

for (int i = 0; i < board.length; i  ) {
  if (board[x][i] != curPlayer) {
    break;
  }
  if (i == board.length - 1) {
    System.out.println("Player "   curPlayer   " wins!");
    return true;
  }
}

Now the return line is only executed when the preceding if statement is true.

  • Related