Machine Coding Problem

Tic-Tac-Toe

maco30maco60macoAllgamesscalable-nxn-board-&-win-strategies
Commonly Asked By:GoogleMicrosoftAmazonUber

Requirements & System Scope

Functional Scope (In-Scope)

  • Scalable NxN Grid Board: Support flexible board dimensions beyond standard 3x3 setups.
  • Multi-Player Integration: Handle gameplay turn management for two or more players dynamically.
  • Pluggable Win Strategies: Decouple win detection patterns using a clean strategy system.
  • O(1) Win Checks: Detect horizontal, vertical, and diagonal win configurations instantly in constant time.
  • Draw Detection: Track moves counts to declare complete board draw games correctly.

Explicit Boundaries (Out-of-Scope)

  • No Intelligent AI Bots: Out-of-scope to write Minimax or Alpha-Beta pruning move predictors.
  • No Play History Replays: Focuses on active gameplay state structures rather than serializing records.

Class Diagram & Entity Relationships

Structural layout showing clean separation of active game orchestrators, stateful boards, and win check strategies:

Loading...
  • Stateful Board: Board houses the atomic move placements, coordinates grid queries, and encapsulates moves count.
  • Decoupled Win strategies: WinStrategy abstracts different winning configurations, separating state tracking from grid representation.
  • Game Orchestrator: TicTacToeGame manages thread-safe turn synchronization, transitions, and play sequences.

Design Patterns & SOLID Principles

  • Strategy Pattern (Win Strategies): Decouples winning algorithms (standard O(1) line verification vs. positional corner-check configurations) from the main game loop, making it fully Open-Closed compliant.
  • Single Responsibility Principle (SRP):Board is only responsible for placing symbols on the grid, WinStrategy is solely responsible for verifying victory conditions, and TicTacToeGame handles round-robin turns and concurrency.
  • O(1) Vector Tracking Pattern: By maintaining player-specific accumulator vectors for rows, columns, and diagonals, StandardWinStrategy computes victory instantly on-the-fly in constant time rather than performing $O(N^2)$ board sweeps on every turn.

Core Execution Workflows

Turn Traversal Loop

  1. Game fetches the current active player symbol.
  2. User invokes move coordinates: makeMove(r, c).
  3. Board places the symbol:
    1. If target cell is occupied or boundaries are exceeded, abort and return false.
    2. Otherwise, fill grid space and update metrics.
  4. Game notifies strategy: winStrategy.onMove(r, c, symbol):
    1. For standard strategy, increment corresponding row, column, diagonal, and anti-diagonal arrays per player.
  5. Game evaluates victory condition: winStrategy.checkWin(board, r, c, symbol):
    1. If standard victory check is triggered, evaluate if modified accumulator arrays equal the board size.
    2. If corners strategy is loaded, verify if all four extreme grids match the symbol.
  6. If grid is completely filled without a win, declare a draw.
  7. Rotate player index to next seat.

Concurrency & Thread Safety Strategy

In networked multiplayer matches, concurrent input events can happen if two players make moves simultaneously:

  • Synchronized Move Execution: Wrap the main game loop inside standard ReentrantLock execution blocks to prevent overlapping moves from modifying cell arrays simultaneously.
  • Atomicity Preservation: Ensures validation, move placement, and strategy execution happen in a single synchronized block, avoiding double-bookings on high-concurrency connections.

Complete Clean Code Blueprint

Practical, high-performance reference implementations in Java and Python:

// โ”€โ”€โ”€ JAVA BLUEPRINT โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
import java.util.*;
import java.util.concurrent.locks.ReentrantLock;

class Player {
    private final String name;
    private final char symbol;

    public Player(String name, char symbol) {
        this.name = name;
        this.symbol = symbol;
    }
    public String getName() { return name; }
    public char getSymbol() { return symbol; }
}

class Board {
    private final int size;
    private final char[][] grid;
    private int movesCount = 0;

    public Board(int size) {
        this.size = size;
        this.grid = new char[size][size];
        for (int i = 0; i < size; i++) {
            Arrays.fill(grid[i], ' ');
        }
    }

    public boolean placeMove(int r, int c, char symbol) {
        if (r < 0 || r >= size || c < 0 || c >= size || grid[r][c] != ' ') {
            return false;
        }
        grid[r][c] = symbol;
        movesCount++;
        return true;
    }

    public boolean isFull() {
        return movesCount == size * size;
    }

    public char getSymbolAt(int r, int c) {
        return grid[r][c];
    }

    public int getSize() {
        return size;
    }

    public void printBoard() {
        for (int i = 0; i < size; i++) {
            for (int j = 0; j < size; j++) {
                System.out.print(" " + grid[i][j] + " ");
                if (j < size - 1) System.out.print("|");
            }
            System.out.println();
            if (i < size - 1) {
                System.out.println("-".repeat(size * 4 - 1));
            }
        }
        System.out.println();
    }
}

interface WinStrategy {
    void init(int size);
    void onMove(int r, int c, char symbol);
    boolean checkWin(Board board, int r, int c, char symbol);
}

class StandardWinStrategy implements WinStrategy {
    private int size;
    private final Map<Character, int[]> rowCounts = new HashMap<>();
    private final Map<Character, int[]> colCounts = new HashMap<>();
    private final Map<Character, Integer> diagCounts = new HashMap<>();
    private final Map<Character, Integer> antiDiagCounts = new HashMap<>();

    @Override
    public void init(int size) {
        this.size = size;
        rowCounts.clear();
        colCounts.clear();
        diagCounts.clear();
        antiDiagCounts.clear();
    }

    @Override
    public void onMove(int r, int c, char symbol) {
        rowCounts.putIfAbsent(symbol, new int[size]);
        colCounts.putIfAbsent(symbol, new int[size]);
        diagCounts.putIfAbsent(symbol, 0);
        antiDiagCounts.putIfAbsent(symbol, 0);

        rowCounts.get(symbol)[r]++;
        colCounts.get(symbol)[c]++;

        if (r == c) {
            diagCounts.put(symbol, diagCounts.get(symbol) + 1);
        }
        if (r + c == size - 1) {
            antiDiagCounts.put(symbol, antiDiagCounts.get(symbol) + 1);
        }
    }

    @Override
    public boolean checkWin(Board board, int r, int c, char symbol) {
        int[] rows = rowCounts.get(symbol);
        int[] cols = colCounts.get(symbol);
        if (rows == null || cols == null) return false;

        int diag = diagCounts.getOrDefault(symbol, 0);
        int antiDiag = antiDiagCounts.getOrDefault(symbol, 0);

        return rows[r] == size || cols[c] == size || diag == size || antiDiag == size;
    }
}

class FourCornersWinStrategy implements WinStrategy {
    private int size;

    @Override
    public void init(int size) {
        this.size = size;
    }

    @Override
    public void onMove(int r, int c, char symbol) {}

    @Override
    public boolean checkWin(Board board, int r, int c, char symbol) {
        if (size < 2) return false;
        return board.getSymbolAt(0, 0) == symbol &&
               board.getSymbolAt(0, size - 1) == symbol &&
               board.getSymbolAt(size - 1, 0) == symbol &&
               board.getSymbolAt(size - 1, size - 1) == symbol;
    }
}

class TicTacToeGame {
    private final Board board;
    private final List<Player> players;
    private final WinStrategy winStrategy;
    private final ReentrantLock gameLock = new ReentrantLock();
    private int currentPlayerIndex = 0;
    private boolean isGameOver = false;
    private Player winner = null;

    public TicTacToeGame(int size, List<Player> players, WinStrategy winStrategy) {
        this.board = new Board(size);
        this.players = players;
        this.winStrategy = winStrategy;
        this.winStrategy.init(size);
    }

    public boolean makeMove(int r, int c) {
        gameLock.lock();
        try {
            if (isGameOver) {
                System.out.println("Move failed: Game is already over.");
                return false;
            }

            Player player = players.get(currentPlayerIndex);
            System.out.println("Player " + player.getName() + " (" + player.getSymbol() + ") moves to (" + r + ", " + c + ")");

            if (!board.placeMove(r, c, player.getSymbol())) {
                System.out.println("Move failed: Cell (" + r + ", " + c + ") is invalid or occupied.");
                return false;
            }

            winStrategy.onMove(r, c, player.getSymbol());
            board.printBoard();

            if (winStrategy.checkWin(board, r, c, player.getSymbol())) {
                isGameOver = true;
                winner = player;
                System.out.println("GAME OVER! Winner: " + player.getName() + " (" + player.getSymbol() + ")");
                return true;
            }

            if (board.isFull()) {
                isGameOver = true;
                System.out.println("GAME OVER! It's a DRAW.");
                return true;
            }

            currentPlayerIndex = (currentPlayerIndex + 1) % players.size();
            return true;
        } finally {
            gameLock.unlock();
        }
    }

    public boolean isGameOver() { return isGameOver; }
    public Player getWinner() { return winner; }
}

public class TicTacToeDriver {
    public static void main(String[] args) {
        System.out.println("=== TIC-TAC-TOE GAME SIMULATION (STANDARD WIN) ===");
        List<Player> players2 = Arrays.asList(
            new Player("Alice", 'X'),
            new Player("Bob", 'O')
        );
        TicTacToeGame game1 = new TicTacToeGame(3, players2, new StandardWinStrategy());

        game1.makeMove(0, 0); // Alice
        game1.makeMove(1, 0); // Bob
        game1.makeMove(1, 1); // Alice
        game1.makeMove(2, 0); // Bob
        game1.makeMove(2, 2); // Alice wins (Diagonal)

        System.out.println("=== TIC-TAC-TOE 3-PLAYER GAME SIMULATION ===");
        List<Player> players3 = Arrays.asList(
            new Player("Alice", 'X'),
            new Player("Bob", 'O'),
            new Player("Charlie", 'A')
        );
        TicTacToeGame game2 = new TicTacToeGame(4, players3, new StandardWinStrategy());
        game2.makeMove(0, 0); // Alice
        game2.makeMove(0, 1); // Bob
        game2.makeMove(0, 2); // Charlie
        game2.makeMove(1, 1); // Alice
        game2.makeMove(1, 2); // Bob
        game2.makeMove(1, 3); // Charlie
        game2.makeMove(2, 2); // Alice
        game2.makeMove(2, 3); // Bob
        game2.makeMove(2, 0); // Charlie
        game2.makeMove(3, 3); // Alice wins (Diagonal)

        System.out.println("=== TIC-TAC-TOE FOUR CORNERS WIN SIMULATION ===");
        TicTacToeGame game3 = new TicTacToeGame(3, players2, new FourCornersWinStrategy());
        game3.makeMove(0, 0); // Alice
        game3.makeMove(1, 1); // Bob
        game3.makeMove(0, 2); // Alice
        game3.makeMove(1, 0); // Bob
        game3.makeMove(2, 0); // Alice
        game3.makeMove(2, 1); // Bob
        game3.makeMove(2, 2); // Alice wins (Four Corners!)
    }
}

๐Ÿ’ฌReview

Help Us Improve

How helpful was this walkthrough?

Click a star to rate. We actively use this feedback to refine and update our system design content.

Placeholder
Optional but highly appreciated!

Discussion

Share your thoughts, ask questions, or help others.

Loading comments...