5
\$\begingroup\$

Right now I'm studying Java, and as a part of understanding arrays, I've implemented a Tic-Tac-Toe game based on two-dimensional array.

Any suggestions/comments are much appreciated!

Source code will be below, also available in GitHub repo.

Main.java

package TicTacToe;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));

        int x = -1;
        int y = -1;

        System.out.print("Enter name for player №" + Player.playersCount + ": ");
        Player player1 = new Player(reader.readLine(), "X");
        System.out.print("Enter name for player №" + Player.playersCount + ": ");
        Player player2 = new Player(reader.readLine(), "O");

        TicTacToe game1 = new TicTacToe(3);
        game1.initializeGame(player1, player2);

        while (true) {
            try {
                if (x < 0) {
                    System.out.print("Enter x: ");
                    x = Integer.parseInt(reader.readLine());
                }
                if (y < 0) {
                    System.out.print("Enter y: ");
                    y = Integer.parseInt(reader.readLine());
                }
            } catch (NumberFormatException e) {
                System.out.println("Your input is incorrect, value must Integer!");
                continue;
            }

            if (!game1.checkInput(x, y)) continue;
            if (!game1.turnAllowed(x, y))  { x = -1; y = -1; continue; }

            TicTacToe.turnNumber++;
            if (TicTacToe.turnNumber % 2 != 0) {
                if (game1.makeATurn(x, y, player1)) {
                    System.out.println(player1.name + " wins!");
                    break;
                }
            } else {
                if (game1.makeATurn(x, y, player2)) {
                    System.out.println(player2.name + " wins!");
                    break;
                }
            }
            x = -1;
            y = -1;
        }
    }

}

TicTacToe.java

package TicTacToe;
import org.jetbrains.annotations.NotNull;

public class TicTacToe {
    static int turnNumber = 0;
    String[][] gamingBoard;

    public TicTacToe(int n) {
        gamingBoard = new String[n][n];
    }

    void initializeGame(Player player1, Player player2) {
        for (int i = 0; i < gamingBoard.length; i++) {
            for (int j = 0; j < gamingBoard.length; j++) {
                gamingBoard[i][j] = "-";
            }
        }
        printGameBoard();
    }

    void printGameBoard() {
        for (int i = 0; i < gamingBoard.length; i++) {
            for (int j = 0; j < gamingBoard.length; j++) {
                System.out.print(gamingBoard[i][j]);
            }
            System.out.println();
        }
    }

    boolean turnAllowed(int x, int y) {
        if (gamingBoard[x][y].equals("-")) {
            return true;
        } else {
            System.out.println("Choose another field!");
            return false;
        }
    }

    boolean checkInput(int x, int y) {
        try {
            if (x <= gamingBoard.length - 1 && y <= gamingBoard.length - 1) {
                return true;
            }
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("Error: " + e);

        }
        System.out.println("Your input is incorrect, values must be between 0 and 2");
        return false;
    }

    boolean makeATurn(int x, int y, @NotNull Player player) {
        gamingBoard[x][y] = player.playingMark;
        printGameBoard();
        boolean winner = false;

        for (int i = 0; i < gamingBoard[i].length; i++) {
            if (!gamingBoard[x][i].equals(player.playingMark))
                break;
            if (i == gamingBoard.length - 1) {
                winner = true;
            }
        }

        for (int i = 0; i < gamingBoard.length; i++) {
            if (!gamingBoard[i][y].equals(player.playingMark))
                break;
            if (i == gamingBoard.length - 1) {
                winner = true;
            }
        }

        if (x == y) {
            for (int i = 0; i < gamingBoard.length; i++) {
                if (!gamingBoard[i][i].equals(player.playingMark))
                    break;
                if (i == gamingBoard.length - 1) {
                    winner = true;
                }
            }
        }

        if (x + y == gamingBoard.length - 1) {
            for (int i = 0; i < gamingBoard.length; i++) {
                if (!gamingBoard[i][(gamingBoard.length - 1) - i].equals(player.playingMark))
                    break;
                if (i == gamingBoard.length - 1) {
                    winner = true;
                }
            }
        }
        return winner;
    }
}

Player.java

package TicTacToe;

public class Player {
    String name;
    String playingMark;
    static int playersCount = 0;

    public Player(String name, String playingMark) {
        playersCount++;
        this.name = name;
        this.playingMark = playingMark;
    }
}

Thank you!

\$\endgroup\$

1 Answer 1

4
\$\begingroup\$

Naming & Readability

Choosen naming mostly conveys purpose (expressive) and follows conventions (camelCase).

Design & Structure

Very good design for a learner:

  • TicTacToe represents a game instance currently playing
    • having a board (composition) recording current board state
    • initializes game (registering 2 players, resetting board)
    • checks for allowed moves (target field on board and not occupied)
    • executes a player's turn and evaluates if turning player is a winner (horizontal, vertical, if indicated both diagonals in a row)
  • Player represents one player (of minimum required 2), including his mark

Improvements:

  • apply information hiding: public getters and setters allow access and modification of private member variables (take advantage of Lombok)
  • UI invades gaming-logic! What about a separate TicTacToeUI class that is responsible for input (player name, moves) and output (render board, show winner), probably validation (check input). This concept is called Separation of Concerns (similarly also applied as MVC). Beneficial for a later extension, e.g from text-based input (TUI) towards graphical (GUI) or web-based using a REST interface.
  • Encapsulate round-based iteration inside the game class. So main is just responsible for initiating and starting the game (additionally stopping, if an exit-handler may be implemented allowing a long game to be aborted). That is what the Single Responsibility Principal (SRP) suggests.
  • split long methods like makeATurn: one moveTo(player, x,y) another boolean hasWon(player, x, y) which calls evaluateHorizontal, etc. (SRP) in order to check the rows separately.

Stability & Technical Improvement

  • ArrayIndexOutOfBoundsException is not thrown in checkInput. Although it does not check for negative index values (which have a special meaning for the game-loop!)
  • magic numbers like -1 should be defined as constant to express purpose in their name
  • although a class-variable like static int playerCount is possible (Beware: counts players of all game instances!), what's the purpose?
  • turnNumber should not be static, but initialized within constructor
\$\endgroup\$
2
  • \$\begingroup\$ Thank you so much for detailed answer! I'll continue to improve my code following your recommendations. \$\endgroup\$ Commented Apr 14, 2020 at 16:14
  • 1
    \$\begingroup\$ Continue marks a favourable attitude! I will also improve my answer, if I have refinements to add. \$\endgroup\$
    – hc_dev
    Commented Apr 14, 2020 at 16:18

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.