From Concept to Code: Domain-Driven Design with F# in a Chess Game Engine

Explore the implementation of domain-driven design principles in F# through the creation of a chess game engine, utilizing discriminated unions and records for robust domain modeling.

10 novembre 2024

Published

Hugo Mufraggi

Author

3 min read
From Concept to Code: Domain-Driven Design with F# in a Chess Game Engine

Creating A Chess Engine In F# Using Domain Driven Design Principles

This is my second article about F#. Today, I’ll introduce you to domain modeling in F#.

Case Study

I’ll try to make a Chess Game system. In the short term, I want to create just a chess engine, and in the long term, maybe use the game engine alongside an API.

Domain Chess Game Engine

First, I’ll define all my Entities, and then I’ll define all the possible actions.

I’ll leverage discriminated types — a very powerful feature of F#. Discriminated unions allow you to define a type that can be one of several distinct cases. This is useful when you want to represent a value that can have multiple possible states and ensure that the value is always valid. Discriminated unions are defined using the type keyword, followed by the name of the type and a list of possible cases, separated by the | symbol. Each case can optionally have associated data of any type.

Entities

Chess Board

First, I’ll define my Column, Row, and Position.

type File = A | B | C | D | E | F | G | H
type Rank = One | Two | Three | Four | Five | Six | Seven | Eight

type Position = File * Rank

The Position is a tuple of File and Rank. In practice, you can define a Position easily like this:

let myPosition: Position = (D, Four)

Pieces

What is a Piece? For me, it combines two things: the Color and the PieceType.

type Color = White | Black
type PieceType = King | Queen | Rook | Bishop | Knight | Pawn
type Piece = Color * PieceType

I can easily define a black Rook like this:

let blackRook: Piece = (Black, Rook)

Move and GameState

The Move type is quite simple to define. It includes a starting From position, an ending To position, and a Piece. I won’t use a tuple here to make the type more explicit. Instead, I’ll define it as a record, allowing me to specify From and To fields clearly, reducing the risk of errors.

type Move = { From: Position
              To: Position
              Piece: Piece }

For the GameState, I’m not entirely sure about the final structure. I think my GameState is currently incomplete; in the future, I might add states like the different options for castling (e.g., CastleKingSide or CastleQueenSide).

type GameStatus =
    | NotStarted
    | InProgress
    | Check of Color
    | Checkmate of Color
    | Stalemate
    | Draw
f

type GameState = {
    Board: Map<Position, Piece>
    CurrentTurn: Color
    Status: GameStatus
    MoveHistory: Move list
    CapturedPieces: Piece list
}

The Board type is a perfect example of the power of F#’s type system. I use a simple Map with Position as the key and Piece I can represent each position and its occupying piece and color as the value.

Conclusion

In the following article, we’ll dive into the gameplay loop and the functions that support the core logic of the chess engine. We’ll see how to handle turns, validate moves, and manage the game state as pieces interact on the board. If you don’t want to miss Part 2, subscribe to stay updated!