Design Tic-Tac-Toe

Updated on 22 May, 2025
Design Tic-Tac-Toe header image

Problem Statement

In this challenge, the task is to simulate a tic-tac-toe game on an n x n board designed for two players, where n can range between 2 and 100. Players are labeled as player 1 and player 2, corresponding to two different symbols (such as X and O). The game functions based on simple yet traditional tic-tac-toe rules where the players take turns placing their symbols on an unoccupied space on the board. The winner is the first player to align their symbol across n consecutive spaces horizontally, vertically, or diagonally.

This game logic is encapsulated within a class named TicTacToe, which has two main components:

  • The constructor TicTacToe(int n) initializes the board with a given size of n.
  • The method int move(int row, int col, int player) processes each player's move on the board at the specified row and column. A move is automatically valid (i.e., targets an empty cell), and the method returns:
    • 0 if the move did not conclude the game;
    • 1 if player 1 wins due to this move; or
    • 2 if player 2 wins due to this move.

The board starts empty and is dynamically filled as the game progresses until a winner is declared or the board is full, anticipating no further moves once a winning condition is met.

Examples

Example 1

Input:

["TicTacToe", "move", "move", "move", "move", "move", "move", "move"]
[[3], [0, 0, 1], [0, 2, 2], [2, 2, 1], [1, 1, 2], [2, 0, 1], [1, 0, 2], [2, 1, 1]]

Output:

[null, 0, 0, 0, 0, 0, 0, 1]

Explanation:

TicTacToe ticTacToe = new TicTacToe(3);
Assume that player 1 is "X" and player 2 is "O" in the board.
ticTacToe.move(0, 0, 1); // return 0 (no one wins)
|X| | |
| | | | // Player 1 makes a move at (0, 0).
| | | |
ticTacToe.move(0, 2, 2); // return 0 (no one wins)
|X| |O|
| | | | // Player 2 makes a move at (0, 2).
| | | |
ticTacToe.move(2, 2, 1); // return 0 (no one wins)
|X| |O|
| | | | // Player 1 makes a move at (2, 2).
| | |X|
ticTacToe.move(1, 1, 2); // return 0 (no one wins)
|X| |O|
| |O| | // Player 2 makes a move at (1, 1).
| | |X|
ticTacToe.move(2, 0, 1); // return 0 (no one wins)
|X| |O|
| |O| | // Player 1 makes a move at (2, 0).
|X| |X|
ticTacToe.move(1, 0, 2); // return 0 (no one wins)
|X| |O|
|O|O| | // Player 2 makes a move at (1, 0).
|X| |X|
ticTacToe.move(2, 1, 1); // return 1 (player 1 wins)
|X| |O|
|O|O| | // Player 1 makes a move at (2, 1).
|X|X|X|

Constraints

  • 2 <= n <= 100
  • player is 1 or 2.
  • 0 <= row, col < n
  • (row, col) are unique for each different call to move.
  • At most n2 calls will be made to move.

Approach and Intuition

The mechanics of the TicTacToe game are built to manage efficiently the state of the board and quickly determine a winner after each move. The given examples and constraints suggest a strategic approach centered on the following observations:

  1. Board Initialization: The TicTacToe(int n) constructor simply sets up an n x n game board. Although not explicit in the constraints, typically this involves creating an array (or a similar data structure) initialized to zero (representing empty cells).

  2. Move Placement and Win Checking:

  3. When a player makes a move by calling the move(int row, int col, int player) method, the game first logs the move on the board.

  4. Immediately following the move, the game checks if this new move caused the player to win. This involves checking all possible win scenarios related to the position of the latest move:

    • Horizontal (the entire row where the move was made),
    • Vertical (the entire column where the move was made),
    • Diagonal (if the move is on a diagonal of the matrix, includes both the primary diagonal from top-left to bottom-right and the secondary diagonal from top-right to bottom-left).
  5. The checking process should be efficient, focusing only on relevant parts of the board affected by the latest move.

  6. Efficiency Considerations: Given constraints like 2 <= n <= 100, and up to n^2 moves, direct checks for win conditions should be optimized to avert computationally expensive operations each time. Strategies might include maintaining count arrays for rows, columns, and diagonals to avoid re-computing entire lines with each move.

Through a meticulous update to its state and conditions after every move, the game not only verifies the state but also ensures compliance with tic-tac-toe's typical flow, preserving the integrity and rules of this timeless game.

Solutions

  • C++
  • Java
cpp
class TicTacToe {
public:
    vector<int> rowSums;
    vector<int> colSums;
    int mainDiagonal;
    int secondaryDiagonal;

    TicTacToe(int size) {
        rowSums.assign(size, 0);
        colSums.assign(size, 0);
        mainDiagonal = 0;
        secondaryDiagonal = 0;
    }

    int makeMove(int row, int col, int player) {
        int value = (player == 1) ? 1 : -1;
        // update player score tracking
        rowSums[row] += value;
        colSums[col] += value;
        // diagonal adjustments
        if (row == col) {
            mainDiagonal += value;
        }
        if (col == (colSums.size() - row - 1)) {
            secondaryDiagonal += value;
        }
        int dimension = rowSums.size();
        // determines if player wins
        if (abs(rowSums[row]) == dimension ||
            abs(colSums[col]) == dimension ||
            abs(mainDiagonal) == dimension ||
            abs(secondaryDiagonal) == dimension) {
            return player;
        }
        return 0; // continue game
    }
};

Implement a Tic-Tac-Toe game using C++ where the functionality is encapsulated within a class named TicTacToe. The core elements of the class include:

  • Member Variables:

    • rowSums: Tracks sum of values in each row. Winning condition checks involve examining the absolute values of these sums.
    • colSums: Similar to rowSums, but for columns.
    • mainDiagonal: Sum of the main diagonal entries.
    • secondaryDiagonal: Sum of the secondary diagonal entries.
  • Constructor - TicTacToe(int size):

    • Initializes vectors rowSums and colSums with zeros based on the size of the Tic-Tac-Toe board provided.
    • Sets mainDiagonal and secondaryDiagonal to 0.
  • Method - int makeMove(int row, int col, int player):

    • Accepts the position (row, col) for the move and the player making the move (1 or 2).
    • Converts player parameter to 1 for Player 1 and -1 for Player 2 - assists in simplifying win condition tracking by summing these integers.
    • Updates the respective row and column sums along with the diagonals, if applicable (checks if move is on either diagonal).
    • After updating, checks each, i.e., row, column, and diagonals, to determine if the move made resulted in a win condition, i.e., the absolute value of the sum equals the size of the board (indicating all cells in that line are occupied by the same player).
    • Returns the player number if there's a winning sequence or 0 if the game is to continue.

Developing the game using this approach minimizes the time complexity of determining wins to constant time, making this implementation efficient, especially for a larger board size. Each makeMove operation resolves in constant time, enhancing the performance compared to checking the entire board linearly after every move.

java
public class Game {
    int[] rowTrack;
    int[] colTrack;
    int diagonalSum;
    int antiDiagonalSum;

    public Game(int size) {
        rowTrack = new int[size];
        colTrack = new int[size];
    }

    public int makeMove(int row, int col, int player) {
        int mark = (player == 1) ? 1 : -1;
        rowTrack[row] += mark;
        colTrack[col] += mark;

        if (row == col) {
            diagonalSum += mark;
        }
        if (col == (colTrack.length - row - 1)) {
            antiDiagonalSum += mark;
        }
        
        int n = rowTrack.length;
        if (Math.abs(rowTrack[row]) == n ||
            Math.abs(colTrack[col]) == n ||
            Math.abs(diagonalSum) == n ||
            Math.abs(antiDiagonalSum) == n) {
            return player;
        }
        return 0;
    }
}

The provided Java code implements a basic version of Tic-Tac-Toe game logic for an n x n board. Here's a succinct summary of how it operates:

  • Initialization:

    • The Game class is initialized with two arrays, rowTrack and colTrack, to keep track of the sum of marks along each row and column, respectively.
    • Two additional integer variables, diagonalSum and antiDiagonalSum, are used to track the sum of the main diagonal and the anti-diagonal.
  • Making a Move:

    • The makeMove method takes three parameters: row, col, and player. The player parameter identifies which player is making the move, using 1 for player 1 and -1 for player 2.
    • When a move is made at a certain cell, the corresponding row and column trackers are updated by the player's mark value (either 1 or -1).
    • Checks are made to see if placing a mark updates the diagonals—if the current move impacts either diagonal, the respective sum is updated accordingly.
  • Determining a Winner:

    • After every move, the method checks if the absolute value of the sums in rowTrack for the current row, colTrack for the current column, diagonalSum, or antiDiagonalSum equals the board size n.
    • If any of these conditions hold, the current player is declared the winner as this means a whole row, column, or diagonal has been filled with their marks.
  • Return Value:

    • The makeMove method returns the player number if there is a winner in the current move, otherwise, it returns 0, indicating that the game continues.

This implementation effectively handles each move's impacts on the game state, providing a straightforward method to not only track plays but also quickly determine the win status without needing to scan the entire board after each move. The utilization of sums to represent rows, columns, and diagonals simplifies the verification process for a winning condition, allowing for an efficient gameplay experience.

Comments

No comments yet.