Skip to content

Usage Examples

The following examples demonstrate how to drive the engine. Every snippet uses only the public Swift API.

Setup: loader, engine, and handshake

import Foundation
import SwiftStockfish

func startEngine() async throws -> StockfishEngine {
    let dir = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0]
                .appendingPathComponent("stockfish-nets")

    // Nets first — Stockfish exits the process without them.
    try await StockfishNetworkLoader().ensure(in: dir) { p in
        print("\(p.file): \(p.fractionCompleted.map { "\(Int($0 * 100))%" } ?? "...")")
    }

    guard let engine = StockfishEngine(networkDirectory: dir) else {
        throw NSError(domain: "engine", code: 1)
    }

    // Handshake.
    engine.uci()
    for await line in engine.output {
        if line == "uciok" { engine.isReady() }
        if line == "readyok" { break }
    }
    return engine
}

Best move for a position

func bestMove(for fen: String, depth: Int, engine: StockfishEngine) async -> String? {
    engine.send("position fen \(fen)")
    engine.send("go depth \(depth)")
    for await line in engine.output {
        if line.hasPrefix("bestmove ") {
            return line.split(separator: " ").dropFirst().first.map(String.init)
        }
    }
    return nil
}

let move = await bestMove(
    for: "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",
    depth: 20,
    engine: engine
)

Stream a live evaluation

Parse info lines to drive an evaluation bar or depth indicator:

struct Eval { var depth = 0; var scoreCp: Int?; var mateIn: Int?; var pv: [String] = [] }

func parseInfo(_ line: String) -> Eval? {
    guard line.hasPrefix("info "), line.contains(" pv ") else { return nil }
    var eval = Eval()
    let tokens = line.split(separator: " ").map(String.init)
    var i = 0
    while i < tokens.count {
        switch tokens[i] {
        case "depth": eval.depth = Int(tokens[i + 1]) ?? 0; i += 2
        case "score":
            if tokens[i + 1] == "cp"   { eval.scoreCp = Int(tokens[i + 2]) }
            if tokens[i + 1] == "mate" { eval.mateIn  = Int(tokens[i + 2]) }
            i += 3
        case "pv":
            eval.pv = Array(tokens[(i + 1)...]); i = tokens.count
        default: i += 1
        }
    }
    return eval
}

engine.send("position startpos")
engine.send("go depth 22")
for await line in engine.output {
    if let eval = parseInfo(line) {
        print("d\(eval.depth)", eval.scoreCp.map { "\($0)cp" } ?? "mate \(eval.mateIn ?? 0)")
    }
    if line.hasPrefix("bestmove ") { break }
}

Top-3 candidate moves (MultiPV)

engine.send("setoption name MultiPV value 3")
engine.send("position fen \(fen)")
engine.send("go depth 18")

var lines: [Int: String] = [:]      // multipv index -> first pv move
for await line in engine.output {
    if line.hasPrefix("info "), let idxRange = line.range(of: "multipv ") {
        let after = line[idxRange.upperBound...]
        let idx = Int(after.prefix { $0.isNumber }) ?? 0
        if let pvRange = line.range(of: " pv ") {
            let first = line[pvRange.upperBound...].split(separator: " ").first.map(String.init)
            lines[idx] = first
        }
    }
    if line.hasPrefix("bestmove ") { break }
}
print(lines)   // [1: "e2e4", 2: "d2d4", 3: "g1f3"]

Play with a real clock

engine.send("position startpos moves e2e4 e7e5 g1f3")
engine.send("go wtime 120000 btime 118000 winc 2000 binc 2000")
// Read until "bestmove …" as above.

Bundle the nets at build time

The following script downloads the networks so they can be shipped as application resources, avoiding any runtime network access:

import Foundation
import SwiftStockfish

@main struct FetchNets {
    static func main() async throws {
        let out = URL(fileURLWithPath: CommandLine.arguments[1])
        try await StockfishNetworkLoader().ensure(in: out) { p in
            print(p.file, p.bytesDownloaded)
        }
        print("nets ready in \(out.path)")
    }
}

Tear down

Call shutdown() from a background context to explicitly destroy the engine — it joins the engine and reader threads, frees the bridge, and releases the process-wide lifecycle gate:

// From a background Task or off-main context — never from the main actor.
engine.shutdown()
// or: let all references to `engine` go out of scope (deinit calls shutdown()).

quit() only sends the UCI quit string to the engine's input. It does not join threads, free the bridge, or release the lifecycle gate. Using quit() alone as teardown keeps the gate held and causes the next StockfishEngine(...) to block indefinitely.

Only one engine per process is supported. The bridge enforces this with a lifecycle gate: creating a second engine blocks the calling thread until the first is fully torn down. Create and tear down engines off the main thread/actor, and always shutdown() or release the first engine before creating another.