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 playersEach player should be able to click or select the cell they want to place their mark in.
Winning ConditionsThe 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.
LLD
Here's a simplified Low-Level Design (LLD) for a standalone TicTacToe game
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.
Top comments (0)