3
\$\begingroup\$

Just finished my first java class that taught me all the way up to oop and abstract classes. Decided to start some projects over the summer. Started with tic tac toe since it sounded fairly easy to do. Let me know if there are things that I need to improve or just general good advice so I don't make bad habits when coding. most of the code i thought of without help except when checking win conditions or if the board was full i got help from online so a lot of what i did seems kind of sloppy.

import java.util.Scanner;
import java.util.Random;
public class TicTacToe
{
Scanner scanner = new Scanner(System.in);
private String[][] gameBoard = {
    {" ", "|", " ", "|", " "},
    {"-", "+", "-", "+", "-"},
    {" ", "|", " ", "|", " "},
    {"-", "+", "-", "+", "-"},
    {" ", "|", " ", "|", " "}
};
private boolean[] space = new boolean[9];
Random rand = new Random();
private String name = "default player";
private static final String CPU_NAME = "cpu";
private String playerSymbol = "";
private String cpuSymbol = "";
private static String turnName = "";

public TicTacToe (String name)
{
    this.name = name;
}
public void printGameBoard ()
{
    for (int i = 0; i < gameBoard.length; i++)
    {
        for (int j = 0; j < gameBoard[i].length; j++)
        {
            System.out.print(gameBoard[i][j]);
        }
        System.out.println();
    }
}

public void whosTurn(String turnName)
{
    if (turnName.equals(name))
    {
        turnName = name;
    }
    if (turnName.equals(CPU_NAME))
    {
        turnName = CPU_NAME;
    }
}

public void whatSymbol()
{
    System.out.println("What Symbol would you want? O or X?");
    String choice = scanner.nextLine();
    if(choice.toLowerCase().equals("o"))
    {
        playerSymbol = "O";
        cpuSymbol = "X";
        game();
    }
    else if (choice.toLowerCase().equals("x"))
    {
        playerSymbol = "X";
        cpuSymbol = "O";
        game();
    }
    else
    {
        System.out.println("Invalid input. try again");
        whatSymbol();
    }
}

public void playerTurn(int choice)
{
    switch (choice)
    {
        case 1:
            if(turnName.equals(name))
            {
                gameBoard[0][0] = playerSymbol;
                break;
            }
            if(turnName.equals(CPU_NAME))
            {
                gameBoard[0][0] = cpuSymbol;
                break;
            }
        case 2:
            if(turnName.equals(name))
            {
                gameBoard[0][2] = playerSymbol;
                break;
            }
            if(turnName.equals(CPU_NAME))
            {
                gameBoard[0][2] = cpuSymbol;
                break;
            }
        case 3:
            if(turnName.equals(name))
            {
                gameBoard[0][4] = playerSymbol;
                break;
            }
            if(turnName.equals(CPU_NAME))
            {
                gameBoard[0][4] = cpuSymbol;
                break;
            }
        case 4:
            if(turnName.equals(name))
            {
                gameBoard[2][0] = playerSymbol;
                break;
            }
            if(turnName.equals(CPU_NAME))
            {
                gameBoard[2][0] = cpuSymbol;
                break;
            }
        case 5:
            if(turnName.equals(name))
            {
                gameBoard[2][2] = playerSymbol;
                break;
            }
            if(turnName.equals(CPU_NAME))
            {
                gameBoard[2][2] = cpuSymbol;
                break;
            }
        case 6:
            if(turnName.equals(name))
            {
                gameBoard[2][4] = playerSymbol;
                break;
            }
            if(turnName.equals(CPU_NAME))
            {
                gameBoard[2][4] = cpuSymbol;
                break;
            }
            
        case 7:
            if(turnName.equals(name))
            {
                gameBoard[4][0] = playerSymbol;
                break;
            }
            if(turnName.equals(CPU_NAME))
            {
                gameBoard[4][0] = cpuSymbol;
                break;
            }
        case 8:
            if(turnName.equals(name))
            {
                gameBoard[4][2] = playerSymbol;
                break;
            }
            if(turnName.equals(CPU_NAME))
            {
                gameBoard[4][2] = cpuSymbol;
                break;
            }
        case 9:
            if(turnName.equals(name))
            {
                gameBoard[4][4] = playerSymbol;
                break;
            }
            if(turnName.equals(CPU_NAME))
            {
                gameBoard[4][4] = cpuSymbol;
                break;
            }
        default:
            System.out.println("Invalid input. try again");
            game();
    }
}

public void cpuMove()
{
    boolean run = true;
    while(run)
    {
        int choice = rand.nextInt(9) + 1;
        if (!space[choice - 1])
        {
            playerTurn(choice);
            space[choice - 1] = true;
            turnName = name;
            System.out.println("---------------------------------------------------");
            System.out.println("Cpu chose: " + choice);
            run = false;
        }
    }
}


public void whoStarts ()
{
    System.out.println("Who starts first? " + name + " or " + CPU_NAME);
    String starter = scanner.nextLine();
    if (starter.toLowerCase().equals(name.toLowerCase()))
    {
        turnName = name;
        whatSymbol();
    }
    else if (starter.toLowerCase().equals(CPU_NAME.toLowerCase()))
    {
        turnName = CPU_NAME;
        whatSymbol();
    }
    else
    {
        System.out.println("Invalid input. try again");
        System.out.println("---------------------------------------------------");
        whoStarts();
    }
}

public void startGame()
{
    System.out.println("Do you want to play the game?");
    String answer = scanner.nextLine();
    switch(answer.toLowerCase())
    {
        case "yes":
            whoStarts();
            break;
        case "no":
            System.out.println("thats too bad. See you next time !!");
            break;
        default:
            System.out.println("Invalid input. try again");
            System.out.println("---------------------------------------------------");
            startGame();
    }

}
public void game()
{
    boolean gameWon = false;
    while(!gameWon)
    {
        int choice = 0;
        System.out.println("---------------------------------------------------");
        printGameBoard();
        System.out.println("Choices : 1 to 9");
        System.out.println(name + "(" + playerSymbol + "): ");
        choice = scanner.nextInt();

        if(turnName.equals(name))
        {
            if(!space[choice - 1])
            {
                playerTurn(choice);
                space[choice - 1] = true;
                turnName = CPU_NAME;
                gameWon = checkGameStatus();
            }
            else
            {
                System.out.println("---------------------------------------------------");
                System.out.println("Occupied Space. Try again.");
            }
            
        }
        if(turnName.equals(CPU_NAME))
        {
            cpuMove();
            gameWon = checkGameStatus();
        }
    }
    
}

public boolean checkGameStatus ()
{
    if(checkWin(playerSymbol))
    {
        System.out.println("---------------------------------------------------");
        System.out.println("Player " + name + " has won!!");
        System.out.println("---------------------------------------------------");
        printGameBoard();
        return true;
    }
    else if (checkWin(cpuSymbol))
    {
        System.out.println("---------------------------------------------------");
        System.out.println("The cpu has won!! you lost");
        System.out.println("---------------------------------------------------");
        printGameBoard();
        return true;
    }
    else if(checkFull())
    {
        System.out.println("---------------------------------------------------");
        System.out.println("The game is tie");
        System.out.println("---------------------------------------------------");
        printGameBoard();
        return true;
    }
    return false;
}

public boolean checkWin (String symbol)
{
    for (int i = 0; i < 6; i += 2)
    {
        if(gameBoard[i][0].equals(symbol) && gameBoard[i][2].equals(symbol)
            && gameBoard[i][4].equals(symbol))
        {
            return true;
        }

        if(gameBoard[0][i].equals(symbol) && gameBoard[2][i].equals(symbol)
            && gameBoard[4][i].equals(symbol))
        {
            return true;
        }
    }

    if(gameBoard[0][0].equals(symbol) && gameBoard[2][2].equals(symbol)
        && gameBoard[4][4].equals(symbol))
    {
        return true;
    }
    if(gameBoard[0][4].equals(symbol) && gameBoard[2][2].equals(symbol) 
        && gameBoard[4][0].equals(symbol))
    {
        return true;
    }
    return false;
}

public boolean checkFull ()
{
    for (boolean spaceFilled : space) {
        if (!spaceFilled) {
            return false; 
        }
    }
    return true;
}
}


public class Main {
public static void main(String[] args)
{

    TicTacToe johnDoe = new TicTacToe("John Doe");
    johnDoe.startGame();
}
}
\$\endgroup\$
1
  • \$\begingroup\$ There's a LOT of repetition in this code. Find ways to reduce that. Eg: Imagine playerTurn() if the game were 8x8 instead of being 3x3... The giant switch() merely translates 1-9 into row and col... Don't Repeat Yourself = DRY... AND, (my favourite), the code shows the presence AND absence of else following an if() block of statements that end with return... Don't use else where it is not needed... That should get your started... Cheers! \$\endgroup\$
    – Fe2O3
    Commented Jun 21 at 1:01

2 Answers 2

4
\$\begingroup\$

I have a few suggestions, but I'll just mention two.

I would recommend defining the game board as just a 3 by 3 array containing only X, O, or blank. The way you've coded it here makes easy to print the game board, but annoying to do anything else.

For example, by redefining the game board as suggested, the playerTurn function can be simplified to:

public void playerTurn(int choice)
{
    String symbol;
    if (turnName.equals(name)) {
        symbol = playerSymbol;
    }
    else {
        symbol = cpuSymbol;
    }
    
    //note that forward slash means 'integer division', which rounds down.
    //The percent sign means modulo, or the remainder after doing a division.
    gameBoard[(choice-1)/3][(choice-1)%3] = symbol;    

}

And I imagine you'd likely be able to make significant simplifications elsewhere too.

And the second suggestion is to just add comments to your code. You've picked good function and variable names, which go a long way to self-document, but you should also get into the habit of explaining what your code does so that when you're managing more difficult projects, you don't get lost.

\$\endgroup\$
5
  • \$\begingroup\$ String symbol = cpuSymbol; // assumed would eliminate the need for else... Just assign one value, and change it (if( ) ...) when that value is incorrect for this instance... \$\endgroup\$
    – Fe2O3
    Commented Jun 21 at 7:46
  • \$\begingroup\$ @Fe2O3 Sure, but small steps. I considered writing it as a ternary expression but that isn't exactly beginner friendly \$\endgroup\$ Commented Jun 23 at 23:48
  • \$\begingroup\$ There's nothing wrong with your proposed code. Yes, a ternary might be peeking into the next chapters of the book. You mentioned, "make significant simplifications", so I thought I'd add my comment. All good... Cheers! :-) (Declaring a variable without assigning it a value immediately can be risky business... Trivial in this case, but worth noting for the beginner... Cheers! ) \$\endgroup\$
    – Fe2O3
    Commented Jun 24 at 0:09
  • \$\begingroup\$ "I have a few suggestions, but I'll just mention two." Why? \$\endgroup\$
    – Bobby
    Commented Jul 1 at 21:16
  • \$\begingroup\$ @Bobby Laziness lmao. No but actually I'm not particularly good at Java/object oriented stuff. I'll probably make a fool of myself if I try to comment on anything much further than basic best practices. \$\endgroup\$ Commented Jul 2 at 5:53
2
\$\begingroup\$

Start Over

Start from scratch. You need to build a sense of the big picture of coding Tic-Tac-Toe. That perspective serves for any coding project.

The before and after will give you insight that drilling into the collective tsunami of detailed review suggestions does not.

Think First, Code Later

Do not skip this essential first step no matter how trivial the project may be. Think about the parts and how they interact. Think about how the game starts, proceeds, ends. Think about elements you want to add.

Think about general program flow. Identify and name the parts and steps. These are method calls effectively. Seeing the "god's eye view" will yield insight to details you may not have thought about yet. As you sketch out code you'll think of other details.

Screen display is always separate from the game code itself. Separate methods will format names, board, etc. for desired display.

Where something seems to be a separate thing with different yet similar code, assume they are the same thing. You will be right virtually always.

Design program for ease of coding. We do not care how hard it might be to display. Program elements shall never be directly modified for the sake of display.

Player

  • computer is also just a player.
  • Write code for a player. "Computer" is just an interesting edge case.
  • I want formal names. That means an object to keep name & symbol together.
  • Display on screen is a separate issue.
    • override ToString !!

User Prompting

  • Loop, until they get it right - a general guideline
  • Prompt for X player, then Y.
  • X always plays first.
  • Don't let user choose anything, except their names. Giving users maximal flexibility induces TOO MUCH CODE DOING NOTHING and should be very low priority!.

Board

  • A 3 x 3 square
  • Make a data structure. Linear array? 2D array? other?
  • Convenient referencing by row, column, diagonal. Coding this is tedious but less error prone overall.
  • Display on screen is a separate issue. - Hey! separate row/col/diag references will help here I bet!
    • override ToString !!

Game Play

  • turn based games are fundamentally a continuous loop terminated by one or more clearly defined end states. In "T3" it's all XorY in a row or column or diagonal.
  • edge case - full board with no winner is an ending state.
  • both X & Y must take a turn before testing for a winner
  • Limit player choice to name. Default everything else.
  • X always goes first.



WARNING! Not compilable Java.

Game Sketch

The "comments" are thoughts I thought of as I wrote this sketch

Initialize();   // instantiate board, init variables
bool playAgain = true;

do {
  PromptUsers(); 

  Player winner = null;  // was bool until I sorted out the Board.
  bool noWinner = true; // so need a new game loop control

  while ( noWinner ) {
    DisplayBoard();  // duh. took only 20 minutes to realize this!
    xMove = promptX();  // pass "X" / "Y" then it's 1 method.

    DisplayBoard();   
    yMove = promptY();  // Player.currentMove property will be good.

    winner = HasWinner();
    noWinner = winner == null;
  } // while

  DisplayBoard();  // before, during, or after "DisplayWinner()"?
                // a separate method makes experiment easy !!!!
  DisplayWinner(winner);    
                // oh, tie game. That's a game loop ending state
                // need to capture X or Y & Name or "tie" - a "CAT" game.
} while (PlayAgain())  // be nice to users, easier than restarting program
              // A low priority nice-to-have thing


Player Object

The sketch above convinced me I need an object

public Player {
  public Player(string Name, string, Symbol) {
    this.Name = Name;
    this.Symbol = Symbol;
    Won = false;
    CurrentMove = "";
  }

  string Symbol; // maybe just a Char type
  string Name;
  bool   Won;   // don't need "outside" variables if we do this
  string CurrentMove;  // ditto

// always override ToString for easy "data dump" during development
// as needed for program display requirements
  override public string ToString() {
    return 'this.Name + " : " + this.Symbol + " :  Won? " + Won';
  }
} 

Board Object

OO programming manages complexity.

The basic structure - the 1D array - is broken down into its referenced as natural sub-parts. Testing for a winner will be simple all the way through the call chain right to the basic board structure.

NEVER say "that's too much code!" It was simple to write ( 75% copy-paste job!). Easy to test. Easier to use. Reading it is clear and understandable.

public Board {
  protected String[] Board = ["", "", "", 
                              "", "", "",
                              "", "", ""];
  public String[] Row1 = {Board[0],Board[1],Board[2]};
    // ...
  public String[] Column1 = {Board[0],Board[3],Board[6]};
    // ...
  public String[] Diagonal1 = {Board[0],Board[4],Board[8]};
  public String[] Diagonal2 = {Board[2],Board[4],Board[6]};

  public string ToString() {
     return "Board layout to be determined";
  }
} //Board

Testing for Winner

// returns null if game not over
// returns a CatPlayer object if it's a draw
// Otherwise returns winning player
public Player HasWinner(player1, player2) {
  Player winner = null;

  if (WinRow(Player1)    ||
      WinColumn(Player1) ||
      WinDiagonal(Player1) 
    { Player1.Won = true;
      return Player1; 
    }

   if (WinRow(Player2)    ||
       WinColumn(Player2) ||
       WinDiagonal(Player2) 
    { Player2.Won = true;
      return Player2; 
    }
    
    // no winner. Cat Game.
    if (T3_Board.isFull()) {  // not written yet
      CatPlayer.Won = true;
      return CatPlayer;
    }

    // game must not be over yet, board is not full
    return null;
}

public bool WinRow(Player Joe) {
  winner = true;

  for(string xy : T3_Board.Row1)
    winner = Joe.Symbol == xy ? winner : false;

  for(string xy : T3_Board.Row2)
    winner = Joe.Symbol == xy ? winner : false;

  for(string xy : T3_Board.Row3)
    winner = Joe.Symbol == xy ? winner : false;

  return winner;
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.