FischerCore is a Swift library that encapsulates the core logic and data structures necessary for building chess games.
Named in honor of the legendary chess grandmaster Bobby Fischer, this library provides a comprehensive foundation for creating, managing, and enforcing the rules of chess.
It also powers the MindChess project as its chess logic core: https://nsstudent.dev/MindChessLanding/
FischerCore: a Swift library target with board, bitboard, move, game, FEN, SAN, UCI, and PGN support.fischer-cli: an interactive terminal executable for exploring the engine without writing an app.- Rule-enforced move execution, including castling, en passant, promotion, check detection, legal move generation, undo, and redo.
- FEN parsing and serialization for
Board,Position, andGame. - Helpers for physical-board and app workflows, including FEN placement extraction, board-placement move resolution, UCI execution, history navigation, structured SAN history, stable renderable piece identity, and game outcome lookup.
- PGN parsing into structured game models, column elements, and move trees.
- Rich PGN comment support, including text, arrows, highlighted squares, clock time, elapsed move time, and engine evaluation annotations.
- SAN and UCI helpers for converting and executing moves.
- Core value types are
Sendablewhere their stored state is value-safe, so they can move cleanly through Swift concurrency boundaries.
You can use FischerCore in your project by adding it as a dependency in your Package.swift file:
.package(url: "https://github.com/NSStudent/fischer-core.git", from: "0.1.0")Then, add "FischerCore" to your target's dependencies:
.target(
name: "YourTarget",
dependencies: [
.product(name: "FischerCore", package: "fischer-core")
]
)The package exposes both a library product and an executable product:
.product(name: "FischerCore", package: "fischer-core")swift run fischer-cliThe API reference and detailed documentation for FischerCore is available at:
👉 https://nsstudent.dev/fischer-core/documentation/fischercore/
You can also generate the DocC documentation locally:
swift package generate-documentation --target FischerCoreTo preview the documentation in Xcode, open the package and choose Product > Build Documentation.
Create a game from the standard starting position:
import FischerCore
var game = Game()
try game.execute(san: "e4")
try game.execute(san: "e5")
try game.execute(san: "Nf3")
print(game.position.fen())
print(try game.sanRepresentation())Create a game from FEN:
let game = try Game(with: "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1")
print(game.board.ascii())
print(game.availableMoves())Work directly with moves:
var game = Game()
let move = Square.e2 >>> Square.e4
if game.isLegal(move: move) {
try game.execute(move: move)
}Undo and redo moves:
var game = Game()
try game.execute(san: "d4")
try game.execute(san: "d5")
let undone = game.undoMove()
let redone = game.redoMove()Generate legal moves for a position:
let moves = game.availableMoves()
let knightMoves = game.movesBitboardForPiece(at: .g1)Board represents piece placement, while Position represents the full chess state:
- Piece placement
- Side to move
- Castling rights
- En passant target
- Halfmove clock
- Fullmove number
let position = Position(fen: "8/8/8/8/8/8/8/4K3 w - - 0 1")
let fen = position?.fen()Useful board helpers include:
board.ascii()for terminal-friendly board rendering.board.fen()for FEN placement serialization.board.bitboard(for:)for fast piece/color queries.board.attackers(to:color:)andboard.attackersToKing(for:)for attack detection.board.pinned(for:)for pinned-piece lookup.FEN.placement(from:)to extract the piece-placement field from either a full FEN string or a placement-only string.
let placement = FEN.placement(
from: "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1"
)
// "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR"FischerCore includes convenience APIs for common app integrations, especially physical-board, training, replay, and SwiftUI-style views.
Resolve a board-placement change into the legal move that produced it:
var game = Game()
let nextPlacement = "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR"
if let resolved = game.resolvedMove(toPlacement: nextPlacement) {
try game.execute(resolvedMove: resolved)
}The placement resolver also handles promotions by trying every legal promotion piece and returning the matching ResolvedMove:
let game = try Game(with: "8/k6P/8/8/8/8/K6p/8 w - - 0 1")
let nextPlacement = "7N/k7/8/8/8/8/K6p/8"
let resolved = game.resolvedMove(toPlacement: nextPlacement)
print(resolved?.promotion as Any) // Optional(.knight)Run UCI moves directly when integrating with engines, logs, or network protocols:
var game = Game()
try game.execute(uci: "e2e4")
try game.execute(uci: "e7e8q")Navigate through played and undone moves without manually looping over undoMove() and redoMove():
game.jumpToMove(8)
game.rewindToStart()
game.fastForward()Use playedSANMoves() when a UI needs structured move-list rows instead of one SAN string:
let rows = try game.playedSANMoves()
for row in rows {
print(row.moveNumber, row.color, row.san, row.move, row.promotion as Any)
}Render board pieces with stable identities for SwiftUI diffing and animations:
ForEach(game.boardPieces) { boardPiece in
PieceView(piece: boardPiece.piece)
.position(position(for: boardPiece.square))
}Look up the identity for a specific square when a custom renderer needs direct access:
let pieceID = game.pieceID(at: .e4)Check the finished result directly:
if let outcome = game.outcome {
print(outcome)
}FischerCore supports both SAN and UCI-oriented workflows.
Execute SAN directly:
var game = Game()
try game.execute(san: "e4")
try game.execute(san: "Nf6")Convert UCI to SAN in the current game state:
var game = Game()
let sanMove = try game.sanMove(from: "e2e4")
try game.execute(move: sanMove)Convert a sequence of UCI moves:
let sans = try Game().sanMoveList(from: ["e2e4", "e7e5", "g1f3"])Parse UCI move values:
let value = try UCIMoveValueParser().parse("e7e8q")
print(value.start)
print(value.end)
print(value.promotion)Parse PGN text into structured games:
let pgn = """
[Event "Example"]
[Result "*"]
1. e4 e5 2. Nf3 Nc6 *
"""
let games = try PGNReader().parse(pgn)
let firstGame = games.firstParse into the classic PGNGame model:
let game = try PGNGameParser().parse(pgn)
print(game.tags)
print(game.elements)
print(game.result)Load a parsed PGN game into a rule-enforced Game:
let pgnGame = try PGNGameParser().parse(pgn)
let gameAtStart = try Game(loading: pgnGame)
let gameAtEnd = try Game(loading: pgnGame, moveToEnd: true)Convert a sequence of FEN positions into a PGN game:
let pgnGame = try PGNGame(fenPositions: [
"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",
"rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1"
])PGN comments preserve structured annotations:
- Text comments
- Colored arrows (
[%cal ...]) - Colored square highlights (
[%csl ...]) - Clock time (
[%clk ...]) - Elapsed move time (
[%emt ...]) - Engine evaluations (
[%eval ...])
The FischerCore command line interface can be used to explore and test the package from a terminal. It is an interactive Noora-powered prompt, so you select commands from a menu instead of typing command aliases manually.
swift run fischer-cliOn startup, the CLI prints the initial board. Each loop then asks you to select one of these commands:
| Command | Description |
|---|---|
board |
Print the current position as an ASCII chess board. |
move |
Prompt for one or more SAN moves separated by spaces, then execute them in order. |
position |
Print the current position as a full FEN string. |
reset |
Reset to the starting position, or enter a custom FEN position. |
clear |
Clear the visible terminal area. |
help |
Print the available command list. |
quit |
End the FischerCore CLI session. |
Example move input when the CLI asks for SAN notation:
e4 e5 Nf3 Nc6 Bb5
Example custom FEN input when using reset:
rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1
CLI behavior notes:
- Empty reset input restores the standard starting position.
- Invalid FEN input keeps the current game and prints an error.
- Invalid SAN input prints an error for that move.
- Successful moves report the SAN move and the resolved start/end squares.
Run the test suite:
swift testBuild the library and CLI:
swift buildGenerate DocC documentation:
swift package generate-documentation --target FischerCoreThe package currently depends on:
- swift-parsing for parser composition.
- Noora for the interactive CLI prompts.
- swift-docc-plugin for local documentation generation.
This library is based on the code of other well-crafted chess engines and bitboard libraries in Swift. Notably, it builds upon the work in Sage by @nvzqz, adapting and updating it to be compatible with the current state of the Swift language and modern development practices.
Special thanks to Point-Free for their fantastic swift-parsing library, which greatly simplified the implementation of our PGN parser.
The following features are planned to improve the functionality and completeness of FischerCore:
-
BasicPGNParser - Tests with some TWIC pgn files
- Interactive CLI with board, move, position, reset, clear, help, and quit commands
- FEN-to-PGN reconstruction from position sequences
- UCI-to-SAN conversion helpers
- App workflow helpers for physical-board FEN placement resolution, UCI execution, structured SAN history, move-history navigation, and outcome lookup
-
Sendableconformances for value-safe core model types - Add performance benchmarks for parsers and move generation.
- Improve FEN parsing using a unified parser approach.
The following core features are already available:
Bitboard & tables: Dense bitboard representation with precomputed attack masks (king, knight, pawn, lines) for fast move queries and between/line lookup tables.Game: Rule-enforced move execution (castling, en passant, promotion), move history with undo/redo, outcome detection, threefold/50-move counters, and FEN-based initialization.App helpers: Resolve a target board placement into a legal move, execute resolved moves or UCI strings, jump through move history, and expose structured SAN rows for move-list UIs.Moves:SANMove<->Movebridge so PGN moves can be executed insideGame.UI identity: StableGame.BoardPieceandGame.PieceIDvalues for rendering pieces without relying on string tokens.UCI: UCI move value parsing and UCI-to-SAN conversion against the current game state.PGN parsing: Full game parsing intoPGN,PGNGame,PGNElement, andMoveTreePGNwith tags, variations, NAG evaluations, and rich comments.PGN reconstruction: Build SAN move text andPGNGamevalues from FEN position sequences.Comment types: Text, arrows, highlighted squares, clock time ([%clk ...]), elapsed move time ([%emt ...]), and engine evaluation ([%eval ...]) comments are parsed and preserved.
