rohanrhu/chessboardkit
ChessboardKit is a SwiftUI library for rendering chessboards and playing chess on the board. It provides a highly customizable and interactive chessboard component for your SwiftUI applications.
Use for your macOS or iOS/iPadOS app
Move with drag&drop or click/touch
| iOS | macOS | |------------------------------------------|------------------------------------------| | [alt text] | [alt text] |
Chess Features
| Promotion Picker | Suits its container's size | |--------------------------------------------|------------------------------------------| | [iOS Promotion] | [iOS Sizing] |
| Hint | Move Legality Check | |----------------------------------------|----------------------------------------| | [iOS Hint] | [iOS Move] |
Render the chessboard with only FEN string
ChessboardKit renders the chessboard based on the provided FEN string and updates it when the board position changes.
| FEN Rendering | |-------------------------------------------| | [macOS FEN] |
Table of Contents
- Use for your macOS or iOS/iPadOS app - Move with drag\&drop or click/touch - Chess Features - Render the chessboard with only FEN string - Table of Contents - Features - Supported Platforms - Swift Version - Installation - Quick Start - Chess Engine - Usage - Setting Up the Chessboard - Handling Moves - Making Moves - Board Size - Customizing the Chessboard - Advanced Features - FEN Rendering - Sliding Animation for Piece Movements - Flip The Board for Player Perspective - Highlighting Squares - Legal Move Highlighting - Waiting State and Avoiding User Interaction - Pawn Promotion - Fen Validation - Move Legality Check - ChessboardModel - Properties - Methods - Board Position Management - Square Selection - Square Highlighting - Promotion Management - Interaction State - Initialization - Color Schemes - Custom Color Scheme Example - Advanced Example Usage - ❤️ Donations ❤️ - ❤️ Buy QuakeNotch to support me ❤️ - ❤️ Buy MacsyZones to support me ❤️ - Cryptocurrency Donations - Contributing - Acknowledgments - License
Features
- Render chessboards with customizable sizes and styles
- Support for FEN notation to set up board positions
- Interactive piece movement with legality checks
- Legal move highlighting
- Promotion picker for pawn promotions
- Slide animation for piece movements
- Highlighting squares for better user interaction
- Click/touch on source and destination squares
- Drag&drop support for piece movement
- Flip board functionality for player perspective
- Built-in FEN serialization and deserialization
- Fen validation
Supported Platforms
ChessboardKit is designed to work with the following platforms:
- iOS 17+
- macOS 14+
Swift Version
ChessboardKit is written in Swift 5 or later.
Installation
To use ChessboardKit in your project, add it as a dependency in your Package.swift:
.package(url: "https://github.com/rohanrhu/ChessboardKit.git", from: "1.0.0")Then, import the library in your Swift files:
import ChessboardKitAlso, you might want to use the ChessKit library:
import ChessKit[!NOTE]
ChessKitwill be automatically included when you addChessboardKitto your project.
Quick Start
Here is a simple example to get started with ChessboardKit:
import SwiftUI
import ChessboardKit
struct ContentView: View {
@State var chessboardModel = ChessboardModel(fen: "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1")
var body: some View {
Chessboard(chessboardModel: chessboardModel)
.onMove { move, isLegal, from, to, lan, promotionPiece in
print("Move: \(lan)")
}
.frame(width: 300, height: 300)
}
}Chess Engine
ChessboardKit uses @aperechnev's amazing ChessKit chess engine. The ChessboardKit Swift package has it as dependency; when you add ChessboardKit to your project, it will automatically add ChessKit package to your project.
Usage
Setting Up the Chessboard
To set up a chessboard, initialize a ChessboardModel with a FEN string and pass it to the Chessboard view:
@State var chessboardModel = ChessboardModel(fen: "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1")Chessboard(chessboardModel: chessboardModel)Handling Moves
You can handle moves using the onMove callback:
[!NOTE] The
promotionPieceparameter will benilif the move is not a promotion.
Chessboard(chessboardModel: chessboardModel)
.onMove { move, isLegal, from, to, lan, promotionPiece in
print("Move: \(lan)")
}[!IMPORTANT] ChessboardKit doesn't perform the move on chess engine automatically. You need to handle the move logic in your app, such as updating the chess engine state and the chessboard model. Follow the example below to see how to handle moves.
Making Moves
When your onMove callback is called, you need to perform the move with the chess engine and update the chessboard model with the new FEN string:
.onMove { move, isLegal, from, to, lan, promotionPiece in
print("Move: Fen: \(chessboardModel.fen) - Lan: \(lan)")
if !isLegal {
print("Illegal move: \(lan)")
return
}
chessboardModel.game.make(move: move)
chessboardModel.setFen(FenSerialization.default.serialize(position: chessboardModel.game.position), lan: lan)
}[!NOTE] You can see what
chessboardModel.gamehas for doing more.
Board Size
You can customize the board size and appearance:
Chessboard(chessboardModel: chessboardModel)
.frame(width: 400, height: 400)Customizing the Chessboard
The Chessboard view automatically adjusts its size based on the provided frame.
Advanced Features
FEN Rendering
ChessboardKit renders the chessboard based on the provided FEN string. You can get the FEN string by ChessBoardModel.fen or set it by using the ChessboardModel.setFen(_ fen: String, lan: String? = nil) method.
chessboardModel.setFen("New FEN string")or directly set the FEN string:
chessboardModel.fen = "New FEN string"You can also get the current FEN string:
let currentFen = chessboardModel.fenSliding Animation for Piece Movements
When you use the setFen method by providing lan parameter too within SwiftUI's withAnimation, you can animate the chess movements.
withAnimation {
chessboardModel.setFen("New FEN string", lan: "Move LAN string")
}You can also use any animation with the withAnimation block.
withAnimation(.bouncy(duration: 1.)) {
chessboardModel.setFen("New FEN string", lan: "Move LAN string")
}Flip The Board for Player Perspective
You can pass the perspective parameter to your ChessboardModel to flip the board for the player's perspective:
@State var chessboardModel = ChessboardModel(fen: "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", perspective: .white)Highlighting Squares
You can highlight specific squares on the board for better user interaction:
chessboardModel.hint(row: 3, column: 4)To clear all highlighted squares:
chessboardModel.clearHint()Legal Move Highlighting
ChessboardKit automatically highlights legal moves when a piece is selected or dragged. This feature is enabled by default and shows dots on squares where the selected piece can legally move.
To disable automatic legal move highlighting:
@State var chessboardModel = ChessboardModel(fen: "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",
highlightLegalMoves: false)
// ... or set ti here
chessboardModel.highlightLegalMoves = falseThe legal move dots are displayed using the legalMove color from your color scheme, which can be customized (see Color Schemes).
Waiting State and Avoiding User Interaction
Indicate a waiting state on the board:
chessboardModel.beginWaiting()To end the waiting state:
chessboardModel.endWaiting()Pawn Promotion
When it is time for a pawn promotion, the Chessboard view will automatically show a promotion picker and handle the promotion process. When a promotion is made, the onMove callback will receive the promotion piece as a parameter.
Fen Validation
ChessboardKit has a FenValidation class that has a static method validateFen(_ fen: String) -> Bool to validate FEN strings. You can use this method to check if a FEN string is valid before using it in your chessboard.
let isValid = FenValidation.validateFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1")Move Legality Check
If you want Chessboard view to check the legality of moves, you can enable it with the validateMoves state of ChessboardModel.
@State var chessboardModel = ChessboardModel(validateMoves: true)ChessboardModel
ChessboardModel is the core data model that manages the state of the chessboard. It provides properties and methods to interact with the chessboard programmatically.
Properties
fen:String- The current FEN string representation of the board positionsize:CGFloat- The size of the chessboard in pointscolorScheme:ChessboardColorScheme- The color scheme used for the boardperspective:PieceColor- The side of the board (white or black) that appears at the bottomturn:PieceColor- The current player's turn (white or black)validateMoves:Bool- Whether to validate moves according to chess rulesallowOpponentMove:Bool- Whether to allow moves for the opponentinWaiting:Bool- Indicates if the board is in a waiting stateselectedSquare:BoardSquare?- The currently selected square on the boardhintedSquares:Set<BoardSquare>- A set of squares that are highlightedhighlightLegalMoves:Bool- Whether to highlight legal moves when a piece is selected or dragged (default:true)legalMoveSquares:Set<BoardSquare>- A set of squares that represent legal move destinationsshowPromotionPicker:Bool- Whether the promotion picker is currently displayedgame:Game- The underlying chess game objectcurrentMove:Move?andprevMove: Move?- Current and previous move objectspromotionPiece:Piece?- The piece being promoted when a promotion is in progressshouldFlipBoard:Bool- Whether the board should be flipped based on perspectivemovingPiece:(piece: Piece, from: BoardSquare, to: BoardSquare)?- Piece currently being animatedonMove:(Move, Bool, String, String, String, PieceKind?) -> Void- Callback for moves
Methods
ChessboardModel provides various methods to manipulate and interact with the chessboard.
Board Position Management
Methods to set and manage the board position:
// Set a new FEN position with optional animation of opponent's move
func setFen(_ fen: String, lan: String? = nil)Square Selection
Methods to manage the selected squares:
// Deselect the currently selected square
func deselect()Square Highlighting
Methods to highlight squares for visual feedback:
// Highlight squares using various methods
func hint(_ square: BoardSquare)
func hint(_ square: String)
func hint(row: Int, column: Int)
func hint(_ squares: [BoardSquare])
func hint(_ squares: [String])
// Clear all highlighted squares
func clearHint()
// Highlight squares for a specific duration
func hint(_ square: String, for seconds: Double)
func hint(_ squares: [String], for seconds: Double)
func hint(_ squares: [BoardSquare], for seconds: Double)
func hint(_ square: BoardSquare, for seconds: Double)Promotion Management
Methods to handle pawn promotion:
// Manage the promotion picker
func presentPromotionPicker(piece: Piece, sourceSquare: String, targetSquare: String, lan: String)
func absentePromotionPicker()
func togglePromotionPicker()
// Check if a move is a pawn promotion
func isPromotable(piece: Piece, lan: String) -> BoolInteraction State
Methods to control the board's interaction state:
// Control waiting state
func beginWaiting()
func endWaiting()Initialization
You can initialize a ChessboardModel with custom options:
ChessboardModel(
fen: "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", // FEN string
perspective: .white, // Board perspective
colorScheme: .light, // Color scheme
allowOpponentMove: false // Allow opponent moves
)Color Schemes
ChessboardKit provides some default color schemes. Just pass the colorScheme parameter like ChessboardModel(colorScheme: .blue).
You can also make your own color schemes with the ChessboardColorScheme protocol. Here are some examples of default color schemes:
public protocol ChessboardColorScheme: Sendable {
var light: Color { get }
var dark: Color { get }
var label: Color { get }
var selected: Color { get }
var hinted: Color { get }
var legalMove: Color { get }
}Custom Color Scheme Example
public struct CustomColorScheme: ChessboardColorScheme {
public var light: Color = Color.white
public var dark: Color = Color.black
public var label: Color = Color.gray
public var selected: Color = Color.blue
public var hinted: Color = Color.red
public var legalMove: Color = Color(red: 0.30, green: 0.30, blue: 0.30, opacity: 0.4)
}Then, use it like this:
@State var chessboardModel = ChessboardModel(colorScheme: CustomColorScheme())Advanced Example Usage
<details>
<summary>Click to expand the full advanced example code</summary>
```swift
import SwiftUI
import ChessboardKit
import ChessKit
struct ContentView: View {
var body: some View {
VStack {
TestBoard()
}
.ignoresSafeArea()
.preferredColorScheme(.light)
}
}
public struct TestBoard: View {
static let POSITION = "5k2/1P2bn2/8/8/8/3Q4/3K4/8 w - - 0 1"
@State var showError: Bool = false
@State var errorMessage: String = ""
@State var size: CGFloat = 350
@Bindable var chessboardModel = ChessboardModel(fen: POSITION,
perspective: .white,
colorScheme: .light)
@State var fen = POSITION
var backgroundAnimationStartDate = Date()
public var body: some View {
VStack(spacing: 20) {
VStack {}.frame(height: 50)
Text("ChessboardKit Sample")
.font(.title)
.fontWeight(.bold)
Chessboard(chessboardModel: chessboardModel)
.onMove { move, isLegal, from, to, lan, promotionPiece in
print("Move: Fen: \(chessboardModel.fen) - Lan: \(lan)")
if !isLegal {
print("Illegal move: \(lan)")
return
}
chessboardModel.game.make(move: move)
chessboardModel.setFen(FenSerialization.default.serialize(position: chessboardModel.game.position), lan: lan)
}
.frame(width: size, height: size)
.padding(5)
.overlay(RoundedRectangle(cornerRadius: 8).stroke(Color.gray, lineWidth: 1))
VStack(alignment: .leading) {
Text("FEN Notation:")
.fontWeight(.medium)
TextField("Enter FEN", text: $fen)
.padding(10)
.background(
RoundedRectangle(cornerRadius: 8)
.stroke(Color.gray, lineWidth: 1)
)
.onChange(of: fen) { _, newValue in
if !FenValidation.validateFen(newValue) {
showError = true
errorMessage = "Invalid FEN notation."
return
} else {
showError = false
errorMessage = ""
}
chessboardModel.setFen(newValue)
}
.onChange(of: chessboardModel.fen) {
fen = chessboardModel.fen
}
if showError {
Text(errorMessage)
.foregroundColor(.red)
.font(.caption)
}
}
Slider(value: $size, in: 200...350, step: 10) {
Text("Board Size: \(Int(chessboardModel.size))")
} minimumValueLabel: {
Text("200")
} maximumValueLabel: {
Text("350")
}
.padding(.horizontal)
Text("Board Size: \(Int(size))")
.font(.caption)
HStack {
Button {
print(FenSerialization.default.serialize(position: chessboardModel.game.position))
} label: {
Text("Print FEN")
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(8)
}
.buttonStyle(.plain)
Button {
withAnimation {
chessboardModel.fen = Self.POSITION
}
} label: {
Text("Reset")
.padding()
.background(Color.red)
.foregroundColor(.white)
.cornerRadius(8)
.modifier {
if chessboardModel.fen == Self.POSITION {
$0.opacity(0.5)
} else { $0 }
}
}
.disabled(chessboardModel.fen == Self.POSITION)
Button {
chessboardModel.hint("d3", for: 1)
} label: {
Text("Hint")
.padding()
.background(Color.yellow)
.foregroundColor(.black)
.cornerRadius(8)
}
.buttonStyle(.plain)
}
.buttonStyle(.plain)
Button {
chessboardModel.togglePromotionPicker()
} label: {
VStack {
if chessboardModel.showPromotionPicker {
Text("Hide Promotion Picker")
} else {
Text("Show Promotion Picker")
}
}
.padding()
.background(chessboardModel.showPromotionPicker ? Color.red : Color.green)
.foregroundColor(.white)
.cornerRadius(8)
}
.buttonStyle(.plain)
Spacer()
}
.padding()
.background {
GeometryReader { proxy in
ZStack {
TimelineView(.animation) { context in
Color.white
.scaledToFill()
.visualEffect { content, proxy in
content
.colorEffect(ShaderLibrary.circlesBackground(
.boundingRect,
.float(backgroundAnimationStartDate.timeIntervalSinceNow),
.color(Color(hue: 0.0, saturation: 0.0, brightness: 0.935)),
.color(Color(hue: 0.0, saturation: 0.0, brightness: 0.890))
))
}
}
LinearGradient(
stops: [
.init(color: .white.opacity(0.1), location: 0),
.init(color: .white.opacity(0.9), location: 0.33)
],
startPoint: .top,
endPoint: .bottom
)
}
.frame(width: proxy.size.width, height: proxy.size.height)
.clipped()
}
}
}
}
#Preview {
ContentView()
}
```
</details>
This example shows a complete implementation of a ChessboardKit sample with FEN validation, board size adjustment, hints, and a promotion picker.❤️ Donations ❤️
You love ChessboardKit? You can support the development by making a donation. You have the following options to donate:
❤️ Buy QuakeNotch to support me ❤️
You can buy my QuakeNotch app to support me. QuakeNotch is a Quake Terminal and Apple Music Player that lives on your MacBook's notch.
❤️ Buy MacsyZones to support me ❤️
You can buy my MacsyZones app to support me. MacsyZones is a revolutionary window manager for macOS.
Cryptocurrency Donations
| Currency | Address | | ----------------- | ----------------------------------------------------------------------------------------------- | | ETH / USDT / USDC | 0x1D99B2a2D85C34d478dD8519792e82B18f861974 |
Preferably, donating USDT or USDC is recommended but you can donate any of the above currencies. 🥳
Contributing
We welcome contributions to ChessboardKit. Please see the CONTRIBUTING.md file for more information.
Acknowledgments
Special thanks to @aperechnev of amazing ChessKit chess engine library for providing the chess engine used in this library.
License
Copyright (C) 2025, Oğuzhan Eroğlu <rohanrhu2@gmail.com> (<https://meowingcat.io/>)
ChessboardKit is licensed under the MIT License. See the LICENSE file for more information.
Package Metadata
Repository: rohanrhu/chessboardkit
Default branch: main
README: README.md