Blueprint for Building a Single-Player TicTacToe Game
Tags: systemdesign, architecture, hld, beginners
Published: 2024-02-26 02:07:20

For a standalone TicTacToe game, where no backend and two-player functionality is involved and the game runs locally on a single device, the system design can be simplified.
In this article, we are not discussing UI/UX for the game. Instead, we are focusing on the core components of the game, which can be played/tested from the command line.

# Flow:

The game starts with an empty board and prompts Player 1 to make the first move.

Players take turns making moves by selecting an empty space on the board.

After each move, the game logic checks for win conditions (three symbols in a row, column, or diagonal) or a draw (no empty spaces left).

If a win or draw condition is met, the game ends and the result is displayed.

If neither condition is met, the game continues until a win or draw occurs.

# Functional Requirements

**Game Setup** 
- The game should provide options for setting up the players with name

**Gameplay **

- Players take turns to place their marks (X or O) on an empty cell of the grid.
The game should enforce alternating turns between players

- Each player should be able to click or select the cell they want to place their mark in.
Winning Conditions

- The game should detect when a player has won by placing three of their marks in a row, column, or diagonal.

- If a player achieves a winning condition, the game should display a message indicating the winner

- The game should prevent further moves once a player has won.
	
**Draw Condition:**

If all cells are filled and no player has won, the game should recognise a draw or tie condition and display a message indicating the draw.

**Restart/Reset:** 

- Players should have the option to restart the game after a win, draw, or at any point during the game.

- The game board should be cleared, and the players should have the option to start a new game.

**Scorekeeping:**

- The game may keep track of the number of wins for each player across multiple rounds.

# Non-Functional Requirements

**Performance:**

- The game should respond to user actions promptly, with minimal delay in rendering the game board and processing moves.

- The application should consume a reasonable amount of system resources, such as CPU and memory, to ensure smooth gameplay even on low-end devices.

**Reliability:**

- The game should be stable and robust, with minimal crashes or unexpected terminations during gameplay.

- It should handle errors gracefully, providing informative error messages to users in case of unexpected events.

# Architecture 

### HLD

### Overview:

- The Tic Tac Toe game is a two-player game played on a 3x3 grid.

- Players take turns marking spaces with their respective symbols, typically 'X' and 'O', until one player wins or the game ends in a draw.

### Components:

**Game(Engine- TicTacToe):** The key component responsible for coordinating all the other components.

**Game Board:** Represented as a 3x3 grid where players place their symbols.

**Players:** Two players, typically labeled Player 1 and Player 2, each assigned a unique symbol ('X' or 'O').

**Validator:** Controls the flow of the game, validates moves, checks for a win or draw condition, and determines the winner.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e0ghkcqb55hqktnpv1jn.png)

## LLD

Here's a simplified Low-Level Design (LLD) for a standalone TicTacToe game


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sjewjyvc8yovge4y4wxs.png)


**Game(Engine-TicTacToe)** - The key component responsible for coordinating all the other components. Controls the game flow, 
including managing player turns, handling user input, updating the game board, and determining the game outcome.

_Implementation in Swift_


```
class Game {
    
    var player1: Player!
    var player2: Player!
    var currentPlayer: Player? = nil
    private var board: Board!
    private var validator: Validator!
    private var playedCount = 0
    
    init(player1: Player, player2: Player) {
        self.player1 = player1
        self.player2 = player2
        self.board = Board()
        self.validator = Validator()
    }
    
    func startGame() {
        
        let toss  = Int.random(in: 1...2)
        
        if toss == 1 {
            player1.mark = .O
            currentPlayer = player1
            player2.mark = .X
        } else {
            player2.mark = .O
            currentPlayer = player2
            player1.mark = .X
        }
        
        print("\(currentPlayer?.name ?? ""), with current score : \(currentPlayer?.score ?? 0), should play now")
        playedCount = 0
    }


    func playAgain() {
        
        currentPlayer = nil
        board.refresh()
        playedCount = 0
        
        print("\(player1.name) with current points \(player1.score) and \(player2.name) with soore \(player2.score) have decided to playe again\n\n")
        self.startGame()
        
    }


    
    func play(loc: (Int, Int)) {
        guard let plyr = currentPlayer else {
            return
        }
        
        if validator.isCellEmpty(loc, cells: board.cells) {
            board.place(loc: loc, player: plyr)
            playedCount += 1
            print("\(plyr.name) susccessfully marked \(plyr.mark.string) at \(loc)")
            if validator.isGameOver(plyr, cells: board.cells) {
                plyr.score += 1
                print("\(plyr.name) won the game, his/her current score is \(plyr.score)")
                currentPlayer = nil
            } else if playedCount == board.cells.count * board.cells[0].count {
                print("Gove draw, both the players dont get any point.")
                currentPlayer = nil
            } else {
                switchPlayer()
            }
        }
    }
    
    func switchPlayer() {
        
        if currentPlayer == nil {
            return
        }
        if currentPlayer == player1 {
            currentPlayer = player2
        } else {
            currentPlayer = player1
        }
        
        print("Player switched")
    }
}
```

**GameBoard:** Responsible for managing 3 X 3 Grid.

```
class Board {
    
    var cells =  Array(repeating: Array(repeating: Cell(0,0), count: 3), count: 3)
    
    init() {
        for row in 0...2 {
            for col in 0...2 {
                cells[row][col].update(location: (row, col))
            }
        }
    }
    
    func place(loc: (Int, Int), player: Player) {
        cells[loc.0][loc.1].update(mark: player.mark)
    }
    
    func refresh() {
        cells =  Array(repeating: Array(repeating: Cell(0,0), count: 3), count: 3)
    }
}
```

**Player:** Represents a player in the game, with attributes such as name, symbol (X or O) and score.

```
class Player: Equatable {
    
    let name: String
    var mark: Mark = .None
    var score: Int = 0
    
    init(name: String, mark: Mark = .None) {
        self.name = name
        self.mark = mark
    }
    
    static func ==(lhs: Player, rhs: Player) -> Bool {
        
        return lhs.name == rhs.name
    }
    
}
```

**Validator:** Validate user movements, such as checking if a cell is empty when the user tries to mark it, and determine whether the game has ended in a draw or if there is a winner

```
class Validator {
    
    func isCellEmpty(_ loc: (Int, Int), cells: [[Cell]]) -> Bool {
        return cells[loc.0][loc.1].mark == .None
    }
    
    func isGameOver(_ player: Player, cells: [[Cell]] ) -> Bool {
        
        if cells[0][0].mark == player.mark && cells[1][0].mark == player.mark && cells[2][0].mark == player.mark {
            return true
        }
        
        if cells[0][1].mark == player.mark && cells[1][1].mark == player.mark && cells[2][1].mark == player.mark {
            return true
        }
        
        
        if cells[0][2].mark == player.mark && cells[1][2].mark == player.mark && cells[2][2].mark == player.mark {
            return true
        }
        
        if cells[0][0].mark == player.mark && cells[0][1].mark == player.mark && cells[0][2].mark == player.mark {
            return true
        }
        
        if cells[1][0].mark == player.mark && cells[1][1].mark == player.mark && cells[1][2].mark == player.mark {
            return true
        }
        
        if cells[2][0].mark == player.mark && cells[2][1].mark == player.mark && cells[2][2].mark == player.mark {
            return true
        }
        
        if cells[0][0].mark == player.mark && cells[1][1].mark == player.mark && cells[2][2].mark == player.mark {
            return true
        }
        
        if cells[0][2].mark == player.mark && cells[1][1].mark == player.mark && cells[2][0].mark == player.mark {
            return true
        }
        
        return false
    }
}

```

## Usage

```
import Foundation

let player1 = Player(name: "A")
let player2 = Player(name: "B")

let game = Game(player1: player1, player2: player2)

game.startGame()

game.play(loc: (0,0))
game.play(loc: (2,1))
game.play(loc: (0,1))
game.play(loc: (2,2))
game.play(loc: (0,2))


game.playAgain()

game.play(loc: (0,0))
game.play(loc: (2,1))
game.play(loc: (0,1))
game.play(loc: (2,2))
game.play(loc: (0,2))

game.playAgain()

game.play(loc: (0,0))
game.play(loc: (0,1))
game.play(loc: (0,2))
game.play(loc: (1,0))
game.play(loc: (1,1))
game.play(loc: (1,2))
game.play(loc: (2,0))
game.play(loc: (2,1))
game.play(loc: (2,2))
```

## Conclusion

In conclusion, the system design for the Tic Tac Toe game encompasses a well-structured architecture comprising components such as the game board, players, game logic, user interface, data structures, flow control, error handling, testing, extensions, and documentation. By utilising a 2D array for the game board, a Player class for player information, and functions to manage game logic, the design ensures an efficient and scalable implementation.