đź“…Week-7 (Day-3) - Create a Tic Tac Toe Game: Complete Guide with UML and Code Example

Payal KumariPayal Kumari
8 min read

NOTE: - I started my 8-week system design journey with Coder Army. I will be journaling every day, recording what I learn, reflecting on it, and sharing it with my network to help newcomers to system design.

Namaste developers! đź‘‹
Welcome to another exciting day of the #8WeeksLLDChallenge — and we’re diving into a classic game we all know and love — Tic Tac Toe! 🎮

From childhood fun to coding interviews, Tic Tac Toe is simple to play but powerful when used to understand System Design and Object-Oriented Programming.

But have you ever thought about how to design it properly — using classes, objects, responsibilities, and clean architecture?

Today, we’ll build a Tic Tac Toe system using Java and UML, applying solid design principles like:

  • Players & Game Board

  • Game State Management

  • Winner Checking Logic

  • Designing with OOP (SOLID) principles

  • Using UML to visualize our structure

(Hindi: Bachpan mein toh sabne tic tac toe khela hai, lekin kabhi socha hai is game ko system ke form mein kaise banayein — jahan har player, move aur result smartly manage ho?)

Aaj hum seekhenge kaise ek modular, scalable Tic Tac Toe system banaya jata hai — ekdum real-world ke tarah, with proper classes and interactions.

We’ll explore 3 powerful design patterns:

  • Strategy Pattern – For flexible game rules

  • Factory Pattern – To create game objects easily

  • Observer Pattern – For real-time game notifications

(Hindi: Aaj hum Tic Tac Toe banayenge lekin ek system designer ki tarah! Isme hum 3 design patterns ka use karenge jo aapke coding ko architect-level bana denge!)

đź’  Why System Design for Games?

System design makes your code scalable, testable and future-proof.

(Hindi: System Design se aapka code scalable, testable aur future-proof ban jata hai. Ek choti si game se hum bohot kuch seekh sakte hain:

  • âś… Code Reusability

  • âś… Clean Separation of Logic

  • âś… Extensibility (easily add new rules later)

Agar kal aapko 4x4 ya 5x5 wala Tic Tac Toe banana ho, toh aapko pura code nahi likhna padega — bus naye rules define karo aur ho gaya!)

đź’ Let's Break Down The Components

1. Board Class

  • Initializes the grid

  • Checks for valid moves

  • Displays the current state of the board

📍 Real-Life Analogy:
(Hindi: Socho ek chess board ki tarah — woh khud game nahi khelta, lekin har piece ki position ko manage karta hai.
Tic Tac Toe ka board bhi wahi karta hai — game control nahi karta, bas track rakhta hai ki kya ho raha hai! )

2. Player Class

  • Stores each player's name, symbol (X or O), and score

  • Helps in deciding whose turn is next

📍 Real-Life Analogy:
(Hindi: Bilkul real players ki tarah — har kisi ka apna symbol hota hai, aur turns ek queue mein chalte hain.
Game fair chalana ho toh players ka proper tracking hona zaroori hai! )

3. Strategy Pattern - Game Rules

Interface: TicTacToeRules

Implementation: StandardTicTacToeRules

Use: Define whether a move is valid, check if any player has won, and determine if the game has ended in a draw.

(Hindi: Define karo kya ek move valid hai, koi player jeeta ya nahi, aur game draw hua ya nahi.

📍Real-Life Example: Standard rules vs custom rules — jaise kabhi kabhi hum 4x4 Tic Tac Toe khelte hain apne doston ke sath. Bas rules change hue, logic same!

Strategy Pattern aapko multiple game logic implement karne ki flexibility deta hai!)

4. Factory Pattern - Game Creation

We use TicTacToeGameFactory to create different types of games. Currently only STANDARD, but you can add more like TIMED or CHALLENGE!

📍Real-Life Analogy: (Hindi: Pizza bana rahe ho aur factory se alag-alag type ka base milta hai (thin crust, cheese burst). Base change, baaki same! 🍕)

5. Observer Pattern - Notification System

We use ConsoleNotifier which implements IObserver. It notifies when:

  • Game starts

  • Player makes move

  • Someone wins or it’s a draw

📍Real-Life Analogy: (Hindi: Like cricket match me commentator announce karta hai jab run ya wicket hota hai — wahi kaam yaha ConsoleNotifier karta hai! )

đź’  UML Diagram Overview (Visual Architecture)

đź’ Code

import java.util.*;

// Observer Pattern - for future notification service
interface IObserver {
    void update(String msg);
}

// Sample observer implementation
class ConsoleNotifier implements IObserver {
    public void update(String msg) {
        System.out.println("[Notification] " + msg);
    }
}

// Symbol/Mark class
class Symbol {
    private char mark;

    public Symbol(char m) {
        mark = m;
    }

    public char getMark() {
        return mark;
    }
}

// Board class - Dumb object that only manages the grid
class Board {
    private Symbol[][] grid;
    private int size;
    private Symbol emptyCell;

    public Board(int s) {
        size = s;
        emptyCell = new Symbol('-');
        grid = new Symbol[size][size];
        for(int i = 0; i < size; i++) {
            for(int j = 0; j < size; j++) {
                grid[i][j] = emptyCell;
            }
        }
    }

    public boolean isCellEmpty(int row, int col) {
        if(row < 0 || row >= size || col < 0 || col >= size) {
            return false;
        }
        return grid[row][col] == emptyCell;
    }

    public boolean placeMark(int row, int col, Symbol mark) {
        if(row < 0 || row >= size || col < 0 || col >= size) {
            return false;
        }
        if(!isCellEmpty(row, col)) {
            return false;
        }
        grid[row][col] = mark;
        return true;
    }

    public Symbol getCell(int row, int col) {
        if(row < 0 || row >= size || col < 0 || col >= size) {
            return emptyCell;
        }
        return grid[row][col];
    }

    public int getSize() {
        return size;
    }

    public Symbol getEmptyCell() {
        return emptyCell;
    }

    public void display() {
        System.out.print("\n  ");
        for(int i = 0; i < size; i++) {
            System.out.print(i + " ");
        }
        System.out.println();

        for(int i = 0; i < size; i++) {
            System.out.print(i + " ");
            for(int j = 0; j < size; j++) {
                System.out.print(grid[i][j].getMark() + " ");
            }
            System.out.println();
        }
        System.out.println();
    }
}

// Player class --> 
class TicTacToePlayer {
    private int playerId;
    private String name;
    private Symbol symbol;
    private int score;

    public TicTacToePlayer(int playerId, String n, Symbol s) {
        this.playerId = playerId;
        name = n;
        symbol = s;
        score = 0;
    }

    // Getters and setters
    public String getName() { 
        return name; 
    }

    public Symbol getSymbol() { 
        return symbol; 
    }

    public int getScore() { 
        return score; 
    }

    public void incrementScore() { 
        score++;
    }
}

// Strategy Pattern for game rules
interface TicTacToeRules {
    boolean isValidMove(Board board, int row, int col);
    boolean checkWinCondition(Board board, Symbol symbol);
    boolean checkDrawCondition(Board board);
}

// Standard Tic Tac Toe rules
class StandardTicTacToeRules implements TicTacToeRules {
    public boolean isValidMove(Board board, int row, int col) {
        return board.isCellEmpty(row, col);
    }

    public boolean checkWinCondition(Board board, Symbol symbol) {
        int size = board.getSize();

        // Check rows
        for(int i = 0; i < size; i++) {
            boolean win = true;
            for(int j = 0; j < size; j++) {
                if(board.getCell(i, j) != symbol) {
                    win = false;
                    break;
                }
            }
            if(win) return true;
        }

        // Check columns
        for(int j = 0; j < size; j++) {
            boolean win = true;
            for(int i = 0; i < size; i++) {
                if(board.getCell(i, j) != symbol) {
                    win = false;
                    break;
                }
            }
            if(win) return true;
        }

        // Check main diagonal
        boolean win = true;
        for(int i = 0; i < size; i++) {
            if(board.getCell(i, i) != symbol) {
                win = false;
                break;
            }
        }
        if(win) return true;

        // Check anti-diagonal
        win = true;
        for(int i = 0; i < size; i++) {
            if(board.getCell(i, size-1-i) != symbol) {
                win = false;
                break;
            }
        }
        return win;
    }

    // If all cells are filled and no winner
    public boolean checkDrawCondition(Board board) {
        int size = board.getSize();
        for(int i = 0; i < size; i++) {
            for(int j = 0; j < size; j++) {
                if(board.getCell(i, j) == board.getEmptyCell()) {
                    return false;
                }
            }
        }
        return true;
    }
}

// Game class --> Observable
class TicTacToeGame {
    private Board board;
    private Deque<TicTacToePlayer> players;
    private TicTacToeRules rules;
    private List<IObserver> observers;
    private boolean gameOver;

    public TicTacToeGame(int boardSize) {
        board = new Board(boardSize);
        players = new ArrayDeque<>();
        rules = new StandardTicTacToeRules();
        observers = new ArrayList<>();
        gameOver = false;
    }

    public void addPlayer(TicTacToePlayer player) {
        players.addLast(player);
    }

    public void addObserver(IObserver observer) {
        observers.add(observer);
    }

    public void notify(String msg) {
        for(IObserver observer : observers) {
            observer.update(msg);
        }
    }

    public void play() {
        if(players.size() < 2) {
            System.out.println("Need at least 2 players!");
            return;
        }

        notify("Tic Tac Toe Game Started!");

        Scanner scanner = new Scanner(System.in);

        while(!gameOver) {
            board.display();

            // Take out the current player from dequeue
            TicTacToePlayer currentPlayer = players.peekFirst();
            System.out.print(currentPlayer.getName() + " (" + currentPlayer.getSymbol().getMark() + ") - Enter row and column: ");

            int row = scanner.nextInt();
            int col = scanner.nextInt();

            // check if move is valid
            if(rules.isValidMove(board, row, col)) {
                board.placeMark(row, col, currentPlayer.getSymbol());
                notify(currentPlayer.getName() + " played (" + row + "," + col + ")");

                if(rules.checkWinCondition(board, currentPlayer.getSymbol())) {
                    board.display();
                    System.out.println(currentPlayer.getName() + " wins!");
                    currentPlayer.incrementScore();

                    notify(currentPlayer.getName() + " wins!");

                    gameOver = true;
                }
                else if(rules.checkDrawCondition(board)) {
                    board.display();

                    System.out.println("It's a draw!");
                    notify("Game is Draw!");

                    gameOver = true;
                }
                else {
                    // Move player to back of queue
                    players.removeFirst();
                    players.addLast(currentPlayer);
                }
            }
            else {
                System.out.println("Invalid move! Try again.");
            }
        }
    }
}

// Enum & Factory Pattern for game creation
enum GameType {
    STANDARD
}

class TicTacToeGameFactory {
    public static TicTacToeGame createGame(GameType gt, int boardSize) {
        if(GameType.STANDARD == gt) {
            return new TicTacToeGame(boardSize);
        }
        return null;
    }
}

// Main class for Tic Tac Toe
public class TicTacToeMain {
    public static void main(String[] args) {
        System.out.println("=== TIC TAC TOE GAME ===");

        // Create game with custom board size
        Scanner scanner = new Scanner(System.in);
        System.out.print("Enter board size (e.g., 3 for 3x3): ");
        int boardSize = scanner.nextInt();

        TicTacToeGame game = TicTacToeGameFactory.createGame(GameType.STANDARD, boardSize);

        // Add observer
        IObserver notifier = new ConsoleNotifier();
        game.addObserver(notifier);

        // Create players with custom symbols
        TicTacToePlayer player1 = new TicTacToePlayer(1, "Aditya", new Symbol('X'));
        TicTacToePlayer player2 = new TicTacToePlayer(2, "Harshita", new Symbol('O'));

        game.addPlayer(player1);
        game.addPlayer(player2);

        // Play the game
        game.play();

        scanner.close();
    }
}

đź’ Benefits of This Design

  • Modular Design – Easy to extend

  • Testable – Each class is testable individually

  • Maintainable – Less bugs in future

  • Design Patterns used in real-world applications

đź’ Final Thoughts

System Design is not just for interviews – it’s a real skill for clean code and better architecture.

Week - 7 (Day-3) Completed âś… System Design

NOTE : - A big thanks to my mentors Rohit Negi Sir and Aditya Sir for launching this amazing 8-week course absolutely free on YouTube via CoderArmy9 :- youtube.com/@CoderArmy9 . 🙌

👉 Share this blog with your connections! Let’s keep learning, growing, and supporting one another on this journey. 🚀

✍️ Payal Kumari 👩‍💻

Jai Hind 🇮🇳 | #CoderArmy #LearningInPublic #SystemDesign #TechForAll #MentorshipMatters #8weeksLLdChallenge #LowLevelDesign #LLD 👩‍💻

1
Subscribe to my newsletter

Read articles from Payal Kumari directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Payal Kumari
Payal Kumari

I'm a passionate full-stack developer with a strong foundation in the MERN stack—building and maintaining scalable web applications using React.js, Node.js, and Next.js. My journey in open source began with Hacktoberfest 2023, where I made four impactful pull requests that sparked a love for collaborative coding, global learning, and open knowledge sharing. Since then, I’ve contributed to and mentored projects in top open source programs like GSSoC’24, SSOC’24, and C4GT’24. As a Google Gen AI Exchange Hackathon ’24 Finalist and Google’s Women Techmakers (WTM) Ambassador, I’ve been privileged to support diverse communities in building meaningful tech solutions. My work as a Top 50 Mentor for GSSoC ’24 reflects my commitment to nurturing new talent in tech. Beyond development, I serve as a Student Career Guide, Profile Building Expert & Evangelist at Topmate.io, where I conduct workshops, guide students through resume building and career strategy, and help mentees navigate open source and tech careers. Recognized among the Top 5% of mentors and featured on “Topmate Discover,” I take pride in making mentorship accessible and impactful. My technical voice has also been acknowledged by LinkedIn, where I’ve earned the Top Voice badge seven times in domains like web development, programming, and software engineering. In addition, I hold LinkedIn Golden Badges for Research Skills, Interpersonal Skills, Critical Thinking, and Teamwork—signaling a well-rounded approach to both individual contribution and team collaboration. Graduating with an MCA from Chandigarh University in 2023, I’ve continued to fuel my curiosity by writing technical articles and sharing practical MERN stack insights across platforms. Whether it’s building polished UIs, optimizing backend performance, or guiding a mentee through their first pull request, I’m driven by the power of community and continuous learning. Let’s connect! I'm open to collaborations, mentorship, or building something impactful together. Reach out to me at kumaripayal7488@gmail.com or visit my profile on Topmate.io.