0

First of all, I hope that I'm allowed to ask for some debugging help here. With that said, I've created this simple little tic tac toe program, and it basically is done, but this semantic error has been killing me.

Obviously, I've spent some time trying to figure out the problem on my own, but you could say that I've given up on that so here I am now :)

For some brief overview, the board is represented by an array of type char ttt[3][3]. Players are also variables of type char, so they are either 'X' or 'O' and the coordinates of the board are entered as letters:

A sample run will look like this:

********************************
  ---a------b------c---
  |      |      |     |
  ---d------e------f---
  |      |      |     |
  ---g------h------i---
  |      |      |     |
  ---------------------
player1: O, it is your turn.
Select a cell [a, b, c, ... i]
a
********************************
  ---a------b------c---
  |  O   |      |     |
  ---d------e------f---
  |      |      |     |
  ---g------h------i---
  |      |      |     |
  ---------------------
player2: X, it is your turn.
Select a cell [a, b, c, ... i]

The array ttt[3][3] is initialized such that every element is just ' '.

For the most part, the program runs fine. To try to save some time for you guys, I'm confident that the following methods are running perfectly

  • boolean winner(char player)
  • boolean gameIsDraw()
  • void displayBoard()
  • String playerID(char player)
  • and the main method

Where I do see a problem is most likely contained within my getPlayerInput(char player) method:

void getPlayerInput(char player)
{       
    int row = 0;
    int col = 0;

    System.out.println(playerID(player) + ", it is your turn.");
    System.out.println("Select a cell [a, b, c, ... i]");

    char answer;
    answer = scan.next().charAt(0);

    switch(answer)
    {
    case 'a':
        row = 0;
        col = 0;
        break;          
    case 'b':
        row = 0;
        col = 1;
        break;          
    case 'c':
        row = 0;
        col = 2;
        break;          
    case 'd':
        row = 1;
        col = 0;
        break;          
    case 'e':
        row = 1;
        col = 1;
        break;          
    case 'f':
        row = 1;
        col = 2;
        break;
    case 'g':
        row = 2;
        col = 0;
        break;
    case 'h':
        row = 2;
        col = 1;
        break;
    case 'i':
        row = 2;
        col = 2;
        break;

    default:
        System.out.println("Invalid location, try again.");
        getPlayerInput(player);             
    }

    if(ttt[row][col] != ' ')
    {
        System.out.println("This square is taken. Try again.");
        getPlayerInput(player);
    }
    else
    {
        ttt[row][col] = player;
    }            
}

To me, it looks fine, but my output indicates otherwise. The method includes two failsafes,

  1. if the user inputs something outside of the board range (chars outside of 'a' through 'i'),

  2. or if the user selects a letter/location on the board that is already taken up by another 'X' or 'O'.

In both cases, the method prints out that some faulty input was entered and then calls getPlayerInput() again.

What I have come to notice via debugging is that if only valid inputs are entered in, the program seems to run fine. However, if a faulty input is entered (either type) and then a valid input is entered, sometimes the method will print that a faulty input was still entered.

For example,

********************************
  ---a------b------c---
  |      |      |     |
  ---d------e------f---
  |      |      |     |
  ---g------h------i---
  |      |      |     |
  ---------------------
player1: O, it is your turn.
Select a cell [a, b, c, ... i]
a
********************************
  ---a------b------c---
  |  O   |      |     |
  ---d------e------f---
  |      |      |     |
  ---g------h------i---
  |      |      |     |
  ---------------------
player2: X, it is your turn.
Select a cell [a, b, c, ... i]
z
Invalid location, try again.
player2: X, it is your turn.
Select a cell [a, b, c, ... i]
e
This square is taken. Try again.
player2: X, it is your turn.
Select a cell [a, b, c, ... i]
f
********************************
  ---a------b------c---
  |  O   |      |     |
  ---d------e------f---
  |      |  X   |  X  |
  ---g------h------i---
  |      |      |     |
  ---------------------
player1: O, it is your turn.
Select a cell [a, b, c, ... i]

Notice that I entered the char's a-z-e-f. 'z' obviously is an invalid character, so the method worked as intended (so far), printed that it was invalid input, then the method ran again, asking for input. 'e' was then entered, obviously a valid location, but the method printed that the "square was already taken" when it obviously wasn't. But, entering in a different char 'f' allowed to me exit it.

The end result is that player 'X' got two turns and filled both squares 'e' and 'f'.

It should be noted that if a user continually enters in bad input, that he should be stuck in that method until a valid input is entered, but this is clearly a case where good input is somehow misinterpreted as bad input, and the loop can't be exited unless a different instance of good input is entered in.

So, with all of that said, help meee?? Regardless, I highly appreciate anyone who's had the patience to read this far...

Should you like to run the code yourself, here is the source:

import java.util.*;

class TicTacToe
{
    char ttt[][] = new char[3][3];
    static final char player1 = 'O';
    static final char player2 = 'X';
    Scanner scan  =new Scanner(System.in);


    String playerID(char player)
    {    
        if (player == player1)
            return "player1: "+player;
        else
            return "player2: "+ player;
    }

    void getPlayerInput(char player)
    {       
        int row = 0;
        int col = 0;

        System.out.println(playerID(player) + ", it is your turn.");
        System.out.println("Select a cell [a, b, c, ... i]");

        char answer;
        answer = scan.next().charAt(0);

        switch(answer)
        {
        case 'a':
            row = 0;
            col = 0;
            break;          
        case 'b':
            row = 0;
            col = 1;
            break;          
        case 'c':
            row = 0;
            col = 2;
            break;          
        case 'd':
            row = 1;
            col = 0;
            break;          
        case 'e':
            row = 1;
            col = 1;
            break;          
        case 'f':
            row = 1;
            col = 2;
            break;
        case 'g':
            row = 2;
            col = 0;
            break;
        case 'h':
            row = 2;
            col = 1;
            break;
        case 'i':
            row = 2;
            col = 2;
            break;

        default:
            System.out.println("Invalid location, try again.");
            getPlayerInput(player);             
        }

        if(ttt[row][col] != ' ')
        {
            System.out.println("This square is taken. Try again.");
            getPlayerInput(player);
        }
        else
        {
            ttt[row][col] = player;
        }            
    }

    boolean gameIsDraw()
    {       
        boolean isDraw = true;
        for(int i = 0; i < 3; i++)
        {
            for(int j = 0; j < 3; j++)
            {
                if(ttt[i][j] == ' ')
                {
                    isDraw = false;
                }
            }
        }

        return isDraw;
    }

    boolean winner(char player)
    {
        boolean hasWon = false;

        // possible horizontal wins
        for(int i = 0; i < 3; i++)
        {
            if(ttt[i][0] == player && ttt[i][1] == player && ttt[i][2] == player)
            {
                hasWon = true;
            }
        }

        // possible vertical wins
        for(int i = 0; i < 3; i++)
        {
            if(ttt[0][i] == player && ttt[1][i] == player && ttt[2][i] == player)
            {
                hasWon = true;
            }
        }

        // one diagonal win    
        if(ttt[0][0] == player && ttt[1][1] == player && ttt[2][2] == player)
        {
            hasWon = true;
        }

        // other diagonal win
        if(ttt[0][2] == player && ttt[1][1] == player && ttt[2][0] == player)
        {
            hasWon = true;
        }

        return hasWon;
    }


void displayBoard()
    {
        System.out.println("********************************");        
        System.out.println("      ---a------b------c---");

        for (int i=0; i<3; i++)
        {
            for (int j=0; j< 3; j++)
            {
              if (j == 0) System.out.print("      |  "); 
              System.out.print(ttt[i][j]);
              if (j < 2) System.out.print( "   |  ");
              if (j==2)  System.out.print("  |");
            }
            System.out.println();
            switch (i)
            {
            case 0:
                System.out.println("      ---d------e------f---");
                break;
            case 1:
                System.out.println("      ---g------h------i---");
                break;
            case 2:
                System.out.println("      ---------------------");
                break;
            }
        }
    }


void newgame()
{
    char currPlayer = player1;
    for(int i=0; i<3; i++)
        for(int j=0; j<3; j++)
            ttt[i][j] =' ';

    boolean continueFlag = true;        
    while (continueFlag)
    {
        displayBoard();
        if (gameIsDraw())
        {
            System.out.println("Game Ends in Draw");
            continueFlag = false;
        }
        else
        {
            getPlayerInput(currPlayer);
            if (winner(currPlayer))
            {
                System.out.println("We have a winner: " + playerID(currPlayer));
                displayBoard();
                continueFlag = false;
            }
            else
            { 
                if (currPlayer == player1) currPlayer = player2;
                    else currPlayer = player1;
            }
         }
    }

}


public static void main(String[] args)
{
    TicTacToe game = new TicTacToe();
    String str;
    do
    {
        game.newgame();

        System.out.println("Do you want to play Tic-Tac-Toe (y/n)?");
        str= game.scan.next();
    } while ("y".equals(str));

    System.out.println("Bye");
}    
} 
HelloMyNameIsRay
  • 73
  • 2
  • 3
  • 8

1 Answers1

1

The real problem is that getPlayerInput is invoking itself recursively. This issue would have been more obvious if row and column were initialized to bad values instead of real values, e.g. int row = -1;. Recursion is generally well suited for large problems that can be more easily handled when split into smaller identical problems. In this case, the problem is simply getting a single valid input, a task which can not be split up into simpler tasks.

Instead of recursion, the method should be using iteration for input validation. For example:

void getPlayerInput(char player) {       
    int row = -1;
    int col = -1;

    System.out.println(playerID(player) + ", it is your turn.");

    while(row==-1) {
        System.out.println("Select a cell [a, b, c, ... i]");

        char answer;
        answer = scan.next().charAt(0);

        switch(answer) {
        case 'a':
            row = 0;
            col = 0;
            break;          
        // <snip>
        default:
            System.out.println("Invalid location, try again.");
        }
        if(row !- -1 && ttt[row][col] != ' ') {
            System.out.println("This square is taken. Try again.");
            row = -1;
        }
    }
    ttt[row][col] = player;
}
Tim Bender
  • 19,152
  • 2
  • 44
  • 56
  • Thanks man! In addition to your help, I slept on it and figured it out. I will mention that your suggestion unfortunately results in a run-time-error because the if-statement checks for an out-of-bounds location if bad input was entered in, but that was easily remedied with another while loop. The key idea of using iteration instead of recursion I'm sure was the main point. Still, is it possible if you could offer an explanation as to why recursion is not a valid strategy here? – HelloMyNameIsRay Aug 04 '13 at 17:25
  • I tried adding some more explanation and fixed the code. In any case that RuntimeException is precisely what you want! Something was wrong and it failed hard. In the original code, since row,column as set to 0,0 initially, bad input would have always lead to an attempt to play in the 0,0 square. If that square was taken, a recursive call would give the user yet another chance with a second recursive call (remember the first happens in the default block of the switch), basically doubling the input attempts the user gets. – Tim Bender Aug 04 '13 at 20:07