Table of Contents
Overview
In this tutorial, we are going to design a chess game. Chess is a board game that is played between two players. More information about chess is available here
https://en.wikipedia.org/wiki/Chess
Before we start, please note that this is Low-Level Design Problem. We are going to design a chess game using Object Oriented Principles.
Designing a Chess Game is a very common system design interview question and as with any other system design interview this is what the interviewer is looking for
- Your knowledge of Object-Oriented Design
- How do you frame your design in terms of Design Patterns
Also, note that this question is not a distributed system design.
In this tutorial, we will discuss the low-level design of the Chess Game. Along with that, we will also see into the working code for the Chess Game. Here is the table of contents
- Requirements
- Actor in the design
- UML diagram
- The low-Level design represented in Go Programming Language
- Working Code
Requirements
The requirement of this tutorial is to design a chess game as per the rules of chess using Object Oriented Design Principles
This article can be referred to know more about the rules of chess
https://en.wikipedia.org/wiki/Rules_of_chess
The next step is to identify all actors in a game of Chess
All actors in a game of Chess
- Board
- Cell
- Player
- Human Player
- Computer Player
- Move
- Pieces
- King
- Queen
- Bishop
- Rook
- Knight
- Pawn
- Game
- Move
Let’s look at each of these actors
- Board – We have an 8*8 board in a game of chess. So total there are 64 cells
- Cell – A cell represents one of the cells of 8*8 = 64 cells
- Player – It represents one of the two players playing the game. A player can be a
- Human Player –
- Computer Player
- Move – It represents a move made by one of the players
- Pieces– There are different types of pieces in a chess game namely
- King
- Queen
- Bishop
- Rook
- Knight
- Pawn
- Game – The game class controls the entire flow of the chess game. It decides which player turn it is, the overall result of the game, etc.
- Move – It simply represents a move in the game.
Other than the above actors we also have GameStatus Enum that has the below types
- FirstPlayerWin
- SecondPlayerWin
- GameInProgress
- GameDraw
- Stalemate
UML Diagram
Below is the UML diagram for the Chess Game
Let’s understand this UML diagram by looking at different components and how each component integrate with other components
Piece
The simplest component in the above design is a Piece. As mentioned above a Piece is of 7 types namely king, queen, rook, bishop, knight, and pawn. The Piece class is an abstract class that has the below information
- white – it is set to true if the piece is white otherwise false
- killed – whether a piece is captured or not.
Other than that it also provides a function canMove() which returns true if a given piece can move from its current location on the board to a new location. This function has the below signature
- canMove(board, location, location)
Cell
The next simplest component is a cell. A cell represents a cell on the board. A cell will have the below components
- location – It represents the x and y coordinate of the cell on the board
- piece – The piece which is present on a given cell. It will be nil if the piece is not present at that cell location
Move
It represents a move in a chess game. There are three different moves that can happen in chess
- Resign
- Draw Offer
- Moving a piece from its current cell to a new cell
It has below components that represent either of the above three types of move.
- Current Location – The current location of the piece
- New Location – The new location to which a piece has moved to
- Piece – The piece which got moved
- Resign – The move is a resign or not
- Draw Offered – Weather a player offered a draw or not.
Player
There are two different types of player
- Human Player
- Computer Player
A player will have below fields or components
- isWhite – whether the player is playing as a white or black
- getNextMove – get the next move of the player
- agreeDraw – whether the player agreed to a draw offer
Board
Next is Board class. It represents the board of the game. It has below components
- Square – Two-dimensional array of Cell
- Dimension – The dimension of the board which will be always 8
Game Class
Next is the most important class in the system which is Game class. It has below components
- Board – It represents the board
- First Player – The first player
- Second Player – The second player
- First Player Turn – Will be true if the current turn is of the first player otherwise false
- Game Status – What is the current Game Status
- Moves – List of moves that have been made till now
Low–Level Diagram
Here is the low-level diagram in the Go Programming Language
Game
type game struct {
board *board
firstPlayer iPlayer
secondPlayer iPlayer
firstPlayerTurn bool
gameStatus GameStatus
moves []move
}
func (this *game) play() error {}
func (g *game) setGameStatus(win bool, whiteWon bool, stalemate bool) {}
func (g *game) printResult() {}
Game Status
type GameStatus uint8
const (
GameInProgress GameStatus = iota
GameDraw
Stalemate
FirstPlayerWin
SecondPlayerWin
)
Board
type board struct {
square [][]cell
dimension int
}
func (this *board) printBoard() {}
func (this *board) makeMove(move move) error {}
Cell
type cell struct {
location location
piece piece
}
Move
type move struct {
currLocation location
newLocation location
piece piece
resign bool
drawOffer bool
}
iPlayer Interface
type iPlayer interface {
isWhite() bool
getNextMove(board) move
agreeDraw(board) bool
getID() int
}
Human Player
type humanPlayer struct {
white bool
id int
}
func (this humanPlayer) getID() int {}
func (this humanPlayer) isWhite() bool {}
func (this humanPlayer) getNextMove(board board) move {}
func (this humanPlayer) agreeDraw(board board) bool {}
Computer Player
type computerPlayer struct {
white bool
id int
}
func (this computerPlayer) isWhite() bool {}
func (this computerPlayer) getID() int {}
func (this computerPlayer) getNextMove(board board) move {}
func (this computerPlayer) agreeDraw(board board) bool {}
Piece
type piece interface {
canMove(board, location, location) error
getPieceType() PieceType
isKilled() bool
isWhite() bool
}
Piece Type
type PieceType uint8
const (
King PieceType = iota
Queen
Rook
Bishop
Knight
Pawn
)
King Piece
type king struct {
white bool
killed bool
}
func (this *king) canMove(board board, currLocation, newLocation location) error {}
func (this *king) getPieceType() PieceType {}
func (this *king) isKilled() bool {}
func (this *king) isWhite() bool {}
Queen Piece
type queen struct {
white bool
killed bool
}
func (this *queen) canMove(board board, currLocation, newLocation location) error {}
func (this *queen) getPieceType() PieceType {}
func (this *queen) isKilled() bool {}
func (this *queen) isWhite() bool {}
Rook Piece
type rook struct {
white bool
killed bool
}
func (this *rook) canMove(board board, currLocation, newLocation location) error {}
func (this *rook) getPieceType() PieceType {}
func (this *rook) isKilled() bool {}
func (this *rook) isWhite() bool {}
Bishop Piece
type bishop struct {
white bool
killed bool
}
func (this *bishop) canMove(board board, currLocation, newLocation location) error {}
func (this *bishop) getPieceType() PieceType {}
func (this *bishop) isKilled() bool {}
func (this *bishop) isWhite() bool {}
Knight Piece
type knight struct {
white bool
killed bool
}
func (this *knight) canMove(board board, currLocation, newLocation location) error {}
func (this *knight) getPieceType() PieceType {}
func (this *knight) isKilled() bool {}
func (this *knight) isWhite() bool {}
Pawn Piece
type pawn struct {
white bool
killed bool
}
func (this *pawn) canMove(board board, currLocation, newLocation location) error {}
func (this *pawn) getPieceType() PieceType {}
func (this *pawn) isKilled() bool {}
func (this *pawn) isWhite() bool {}
Location
type location struct {
i int
j int
}
Code
Let’s look at the code. The below code is not fully working functional code as per the chess algorithm and moves, but it will give you an idea
game.go
package main
import "fmt"
type game struct {
board *board
firstPlayer iPlayer
secondPlayer iPlayer
firstPlayerTurn bool
gameStatus GameStatus
moves []move
}
func initGame(b *board, p1, p2 iPlayer) *game {
game := &game{
board: b,
firstPlayer: p1,
secondPlayer: p2,
firstPlayerTurn: true,
gameStatus: GameInProgress,
}
return game
}
func (this *game) play() error {
var move move
var err error
for {
if this.firstPlayerTurn {
move = this.firstPlayer.getNextMove(*this.board)
if move.resign {
this.setGameStatus(true, true, false)
}
if move.drawOffer {
if this.secondPlayer.agreeDraw(*this.board) {
this.setGameStatus(false, true, false)
break
}
}
err := this.board.makeMove(move)
if err != nil {
return err
}
} else {
move = this.firstPlayer.getNextMove(*this.board)
if move.resign {
this.setGameStatus(true, true, false)
}
if move.drawOffer {
if this.secondPlayer.agreeDraw(*this.board) {
this.setGameStatus(false, true, false)
break
}
}
err = this.board.makeMove(move)
if err != nil {
return err
}
}
this.moves = append(this.moves, move)
win, draw, stalemate := this.checkGameStatus()
this.setGameStatus(win, draw, stalemate)
if this.gameStatus != GameInProgress {
break
}
}
return nil
}
func (this *game) checkGameStatus() (win bool, whiteWon bool, stalemate bool) {
return true, true, true
}
func (g *game) setGameStatus(win bool, whiteWon bool, stalemate bool) {
if win {
if whiteWon {
if g.firstPlayer.isWhite() {
g.gameStatus = FirstPlayerWin
return
} else {
g.gameStatus = SecondPlayerWin
return
}
}
}
if stalemate {
g.gameStatus = Stalemate
}
g.gameStatus = GameDraw
return
}
func (g *game) printResult() {
switch g.gameStatus {
case GameInProgress:
fmt.Println("Game in Progress")
case GameDraw:
fmt.Println("Game Drawn")
case Stalemate:
fmt.Println("Stalemate")
case FirstPlayerWin:
fmt.Println("First Player Win")
case SecondPlayerWin:
fmt.Println("Second Player Win")
default:
fmt.Println("Invalid Game Status")
}
g.board.printBoard()
}
gamestatus.go
package main
type GameStatus uint8
const (
GameInProgress GameStatus = iota
GameDraw
Stalemate
FirstPlayerWin
SecondPlayerWin
)
board.go
package main
type board struct {
square [][]cell
dimension int
}
func (this *board) printBoard() {
}
func (this *board) makeMove(move move) error {
err := move.piece.canMove(*this, move.currLocation, move.newLocation)
if err != nil {
return err
}
//Mark the piece at new location as as the current piece
this.square[move.newLocation.i][move.newLocation.j].piece = move.piece
//Mark the piece at current location as nil
this.square[move.currLocation.i][move.currLocation.j].piece = nil
return nil
}
cell.go
package main
type cell struct {
location location
piece piece
}
move.go
package main
type move struct {
currLocation location
newLocation location
piece piece
resign bool
drawOffer bool
}
iPlayer.go
package main
type iPlayer interface {
isWhite() bool
getNextMove(board) move
agreeDraw(board) bool
getID() int
}
humanPlayer.go
package main
type humanPlayer struct {
white bool
id int
}
func (this humanPlayer) getID() int {
return this.id
}
func (this humanPlayer) isWhite() bool {
return this.white
}
func (this humanPlayer) getNextMove(board board) move {
currLocation := location{
i: 1,
j: 0,
}
newLocation := location{
i: 2,
j: 0,
}
return move{
currLocation: currLocation,
piece: board.square[1][0].piece,
newLocation: newLocation,
}
}
func (this humanPlayer) agreeDraw(board board) bool {
return false
}
computerPlayer.go
package main
type computerPlayer struct {
white bool
id int
}
func (this computerPlayer) isWhite() bool {
return this.white
}
func (this computerPlayer) getID() int {
return this.id
}
func (this computerPlayer) getNextMove(board board) move {
currLocation := location{
i: 1,
j: 0,
}
newLocation := location{
i: 2,
j: 0,
}
return move{
currLocation: currLocation,
piece: board.square[1][0].piece,
newLocation: newLocation,
}
}
func (this computerPlayer) agreeDraw(board board) bool {
return false
}
piece.go
package main
type piece interface {
canMove(board, location, location) error
getPieceType() PieceType
isKilled() bool
isWhite() bool
}
pieceType.go
package main
type PieceType uint8
const (
King PieceType = iota
Queen
Rook
Bishop
Knight
Pawn
)
king.go
package main
type king struct {
white bool
killed bool
}
func (this *king) canMove(board board, currLocation, newLocation location) error {
return nil
}
func (this *king) getPieceType() PieceType {
return King
}
func (this *king) isKilled() bool {
return this.killed
}
func (this *king) isWhite() bool {
return this.white
}
queen.go
package main
type queen struct {
white bool
killed bool
}
func (this *queen) canMove(board board, currLocation, newLocation location) error {
return nil
}
func (this *queen) getPieceType() PieceType {
return Queen
}
func (this *queen) isKilled() bool {
return this.killed
}
func (this *queen) isWhite() bool {
return this.white
}
rook.go
package main
type rook struct {
white bool
killed bool
}
func (this *rook) canMove(board board, currLocation, newLocation location) error {
return nil
}
func (this *rook) getPieceType() PieceType {
return Pawn
}
func (this *rook) isKilled() bool {
return this.killed
}
func (this *rook) isWhite() bool {
return this.white
}
bishop.go
package main
type bishop struct {
white bool
killed bool
}
func (this *bishop) canMove(board board, currLocation, newLocation location) error {
return nil
}
func (this *bishop) getPieceType() PieceType {
return Pawn
}
func (this *bishop) isKilled() bool {
return this.killed
}
func (this *bishop) isWhite() bool {
return this.white
}
knight.go
package main
type knight struct {
white bool
killed bool
}
func (this *knight) canMove(board board, currLocation, newLocation location) error {
return nil
}
func (this *knight) getPieceType() PieceType {
return Knight
}
func (this *knight) isKilled() bool {
return this.killed
}
func (this *knight) isWhite() bool {
return this.white
}
pawn.go
package main
type pawn struct {
white bool
killed bool
}
func (this *pawn) canMove(board board, currLocation, newLocation location) error {
return nil
}
func (this *pawn) getPieceType() PieceType {
return Pawn
}
func (this *pawn) isKilled() bool {
return this.killed
}
func (this *pawn) isWhite() bool {
return this.white
}
location.go
package main
type location struct {
i int
j int
}
main.go
package main
func main() {
whiteKing, whiteQueen, whiteRooks, whiteBishops, whiteKnights, whitePawns := makePieces(true)
blackKing, blackQueen, blackRooks, blackBishops, blackKnights, blackPawns := makePieces(true)
cells := make([][]cell, 8)
for i := 0; i < 8; i++ {
cells[i] = make([]cell, 8)
}
//Fill White Pieces in the first row
cells[0][0] = cell{location: location{i: 0, j: 0}, piece: whiteRooks[0]}
cells[0][1] = cell{location: location{i: 0, j: 1}, piece: whiteKnights[0]}
cells[0][2] = cell{location: location{i: 0, j: 2}, piece: whiteBishops[0]}
cells[0][3] = cell{location: location{i: 0, j: 3}, piece: whiteKing}
cells[0][4] = cell{location: location{i: 0, j: 4}, piece: whiteQueen}
cells[0][5] = cell{location: location{i: 0, j: 5}, piece: whiteBishops[1]}
cells[0][6] = cell{location: location{i: 0, j: 6}, piece: whiteKnights[1]}
cells[0][7] = cell{location: location{i: 0, j: 7}, piece: whiteRooks[1]}
//Fill White Pawns in the second row
for i := 0; i < 8; i++ {
cells[1][i] = cell{location: location{i: 0, j: 7}, piece: whitePawns[i]}
}
//Fill Black Pieces in the first row
cells[7][0] = cell{location: location{i: 7, j: 0}, piece: blackRooks[0]}
cells[7][1] = cell{location: location{i: 7, j: 1}, piece: blackKnights[0]}
cells[7][2] = cell{location: location{i: 7, j: 2}, piece: blackBishops[0]}
cells[7][3] = cell{location: location{i: 7, j: 3}, piece: blackKing}
cells[7][4] = cell{location: location{i: 7, j: 4}, piece: blackQueen}
cells[7][5] = cell{location: location{i: 7, j: 5}, piece: blackBishops[1]}
cells[7][6] = cell{location: location{i: 7, j: 6}, piece: blackKnights[1]}
cells[7][7] = cell{location: location{i: 7, j: 7}, piece: blackRooks[1]}
//Fill Black Pawns in the second row
for i := 0; i < 8; i++ {
cells[6][i] = cell{location: location{i: 0, j: 7}, piece: blackPawns[i]}
}
board := &board{
square: cells,
dimension: 8,
}
player1 := humanPlayer{
white: true,
id: 1,
}
player2 := computerPlayer{
white: false,
id: 1,
}
game := initGame(board, player1, player2)
game.play()
game.printResult()
}
func makePieces(isWhite bool) (*king, *queen, [2]*rook, [2]*bishop, [2]*knight, [8]*pawn) {
king := &king{
white: isWhite,
}
queen := &queen{
white: isWhite,
}
rooks := [2]*rook{}
for i := 0; i < 2; i++ {
r := &rook{
white: true,
}
rooks[i] = r
}
bishops := [2]*bishop{}
for i := 0; i < 2; i++ {
b := &bishop{
white: true,
}
bishops[i] = b
}
knights := [2]*knight{}
for i := 0; i < 2; i++ {
k := &knight{
white: true,
}
knights[i] = k
}
pawns := [8]*pawn{}
for i := 0; i < 8; i++ {
p := &pawn{
white: isWhite,
}
pawns[i] = p
}
return king, queen, rooks, bishops, knights, pawns
}
Conclusion
This is all about the low-level design of the chess game. We hope you have liked this article. Please share feedback in the comments