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:
- Are there any potential issues or inefficiencies in the code that I should be aware of?
- Are there any best practices I should follow or refactorings I should consider?
- Is my code readable and well-documented? Are there places where I can improve comments or method names for clarity?
- Have I correctly handled user input and error scenarios?
- Any suggestions for improving the overall structure and organization of the code?
- Do you notice any possible improvements in terms of code maintainability and extensibility?