2
\$\begingroup\$

I have recently developed a Java program for a simple Tic-Tac-Toe game and would like to request a code review to improve its quality and learn from experienced developers. Below is the code for my game:

Board.java

import java.util.Scanner;

public class Board {
    private char [] [] board;
    private final int SIZE;

    private char currentPlayer;

    private int validMoves;

    private static final char PLAYER_X = 'X';
    private static final char PLAYER_O = 'O';
    private static final char EMPTY_CELL = '.';

    private static final int DEFAULT_SIZE = 3;



    public Board() {
        this(DEFAULT_SIZE);
    }
    public Board(int size) {
        this.SIZE = size;
        reset();
    }


    public void reset() {
        board = new char [SIZE][SIZE];
        for (int i = 0; i < SIZE; i++) {
            for (int j = 0; j < SIZE; j++) {
                board[i][j] = EMPTY_CELL;
            }
        }
        currentPlayer = PLAYER_X;
        validMoves = 0;
    }
    void togglePlayer() {
        currentPlayer = (currentPlayer == PLAYER_X ? PLAYER_O : PLAYER_X);
    }



    public char getCurrentPlayer() {
        return currentPlayer;
    }


    boolean isBoardFull() {
        return validMoves == SIZE * SIZE;
    }
    public void print() {
        for (int i = 0; i < SIZE; i++) {
            for (int j = 0; j < SIZE; j++) {
                System.out.print(board[i][j] + " ");
            }
            System.out.println();
        }
    }




    int [] getValidCell() {
        int row, col;
        boolean boardPrinted = true;
        do {
            // print the board when user makes  an invalid move
            if (!boardPrinted)
                print();
            System.out.println("It's " + currentPlayer + " turn");
            System.out.println("Enter row and column number you want to play: ");
            System.out.println("Please choose an empty cell from 1 to " + SIZE + " ");
            Scanner sc = new Scanner(System.in);
            row = sc.nextInt() - 1;
            col = sc.nextInt() - 1;

            boardPrinted = false;
        } while(isValidCell(row, col));



        return new int [] {row, col};


    }

    boolean isValidCell(int row, int col) {
        return row >= 0 && row < SIZE && col >= 0 && col < SIZE && board[row][col] != EMPTY_CELL;
    }
    public boolean checkWin() {
        // Check rows, columns, and diagonals for a win

        for (int i = 0; i < SIZE; i++) {
            // Check row
            if (board[i][0] != EMPTY_CELL && board[i][0] == board[i][1] && board[i][1] == board[i][2]) {
                return true;
            }
            // Check column
            if (board[0][i] != EMPTY_CELL && board[0][i] == board[1][i] && board[1][i] == board[2][i]) {
                return true;
            }
        }

        // Check diagonals
        if (board[0][0] != EMPTY_CELL && board[0][0] == board[1][1] && board[1][1] == board[2][2]) {
            return true;
        }
        if (board[0][2] != EMPTY_CELL && board[0][2] == board[1][1] && board[1][1] == board[2][0]) {
            return true;
        }

        return false;
    }


    public boolean isGameOver() {

        return checkWin() || isBoardFull();
    }

    public void makeMove() {
        int [] cell = getValidCell();
        int row = cell[0];
        int col = cell[1];
        validMoves++;
        board[row][col] = currentPlayer;

        if (!isGameOver()) {
            togglePlayer();
        }
    }




}

TicTacToe.java

public class TicTacToe {

    Board board;

    public TicTacToe(int size) {
        board = new Board(size);

    }

    public void play() {
        while (!board.isGameOver()) {
            board.print();
            board.makeMove();
        }
        board.print();

        System.out.println("Game is over");
        if (!board.isBoardFull()) {
            System.out.print("Winner is " + board.getCurrentPlayer());
        } else {
            System.out.println("Draw");
        }
    }
}

Main.java

public class Main {
    public static void main(String[] args) {
        TicTacToe tic = new TicTacToe(3);
        tic.play();
    }
}

Questions:

  1. Are there any potential issues or inefficiencies in the code that I should be aware of?
  2. Are there any best practices I should follow or refactorings I should consider?
  3. Is my code readable and well-documented? Are there places where I can improve comments or method names for clarity?
  4. Have I correctly handled user input and error scenarios?
  5. Any suggestions for improving the overall structure and organization of the code?
  6. Do you notice any possible improvements in terms of code maintainability and extensibility?
\$\endgroup\$
1
  • \$\begingroup\$ Please do not edit the question, especially the code, after an answer has been posted. Changing the question may cause answer invalidation. Everyone needs to be able to see what the reviewer was referring to. What to do after the question has been answered. What you can do in this case if you are interested in additional reviews of the code is to ask a follow up question with a link back to this question. \$\endgroup\$
    – pacmaninbw
    Commented Sep 22, 2023 at 16:07

2 Answers 2

3
\$\begingroup\$

First of all, look up SOLID pronciples and study them. Especially the "S" which stands for "single responsibility principle": each component should be responsible for one thing only. These are the most important things I have learned as a programmer and I wish I had learned them 10 years sooner.

Board

By the name I would assume Board class contains the state of the game: the size of the board and the locations of played moves. Nothing else. I might think that it could even be a generic board class that could be used for chess, checkers, etc. The class however is responsible for managing user input, player turn, etc. There's output written in the middle of the game logic controlled by boolean flags. A lot of responsibilities violating the "S" in SOLID. It's a bit of spaghetti code.

TicTacToe

The TicTacToe class has very little to do with "Tic Tac Toe" the game. It is just a generic "main loop" for a turn based game. The fundamentals of "Tic Tac Toe" are actually written into the Board class. Whatever is in the TicTacToe could as well be put into the Main class. The naming is thus a bit misleading.

I would refactor the classes so that Board only contains the game state without other logic. It would provide a method for checking what a board location contains (X, O or empty) and a method for placing a marker in a location. TicTacToe then provides a metod for making a move while enforcing legality and information of whose turn it is and whether the game has ended or not. It would thus contain the rules to the Tic Tac Toe game.

Then I would write a new class that would only handle reading user input and printing the game state to console. Maybe it could be called "TicTacToeCLI" (for command line interface). And the main class would set these three classes up and run the main loop.

\$\endgroup\$
4
\$\begingroup\$

Your code overall is fairly readable as the naming of variables and methods is good.

Here are some thoughts on behavior:

  1. It’s not always the case that winning means the board isn’t empty. You can have a situation arise where the last available move makes the last player win. I’d recommend indicating the final state of win or draw, and who the winner is more clearly than just by currentPlayer and isBoardFull. Consider returning more than a Boolean for the board state.
  2. Clarity could be improved if you renamed validMoves to be movesMade as currently it could be misread to mean moves remaining.
  3. The usage of the word valid in the method for getValidCell is confusing as it could easily be interpreted as a cell for a valid next move, which would mean you want it to be empty, which is the opposite of what you are checking.
  4. Your bounds checking and logic for the win condition are incorrect. You need to handle cases in larger boards where the three-in-a-row condition are further away from the starting edges.
\$\endgroup\$
1
  • \$\begingroup\$ Nice catch, I fixed it. \$\endgroup\$
    – drunk mado
    Commented Sep 22, 2023 at 15:44

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