mirror of
https://github.com/chesskit-app/chesskit-engine.git
synced 2026-05-19 15:50:35 +00:00
Update to Stockfish 16.1 and Lc0 0.30 (#12)
### Engine Upgrades * Updated to Stockfish 16.1 (see chesskit-app/Stockfish#3). * Now requires two `.nnue` files to be bundled with the app, see CHANGEGLOG. * Updated to Lc0 0.30 (see chesskit-app/Lc0#5). ### Command Execution Updates * Replaced `UCI::execute_command` with native `stdin` calls. * This will allow for much faster updates in the future since the engine code has not been modified. ### Other Changes * `Engine.start()` now requires a completion handler to be provided. * `completion` is called when engine initialization is complete. resolves #11
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
|
||||
name: checks
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: master
|
||||
pull_request:
|
||||
branches: master
|
||||
|
||||
jobs:
|
||||
check:
|
||||
uses: chesskit-app/workflows/.github/workflows/check-swift-package.yaml@master
|
||||
secrets: inherit
|
||||
with:
|
||||
test_bundle: ChessKitEnginePackageTests
|
||||
@@ -1,14 +0,0 @@
|
||||
name: ChessKitEngine Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
uses: chesskit-app/workflows/.github/workflows/test-swift-package.yml@master
|
||||
secrets: inherit
|
||||
with:
|
||||
test_bundle: ChessKitEnginePackageTests
|
||||
@@ -0,0 +1,3 @@
|
||||
retain_public: true
|
||||
targets:
|
||||
- ChessKitEngine
|
||||
+25
-2
@@ -1,3 +1,26 @@
|
||||
# [unreleased]
|
||||
|
||||
#### Engine Upgrades
|
||||
* Update to [*Stockfish 16.1*](https://stockfishchess.org/blog/2024/stockfish-16-1/).
|
||||
* ⚠️ Stockfish now requires `EvalFile` and `EvalFileSmall` options to be set after launch, with a path to the `*.nnue` files provided.
|
||||
* Currently `chesskit-engine` assumes [`nn-baff1ede1f90.nnue`](https://tests.stockfishchess.org/nns?network_name=baff1ede1f90&user=), [`nn-b1a57edbea57.nnue`](https://tests.stockfishchess.org/nns?network_name=b1a57edbea57&user=) are available in your app's `Bundle.main`.
|
||||
* Click the file names in the previous line to access the download pages.
|
||||
* Any other files can be added via `.setoption(id:value:)` engine commands.
|
||||
* Update to [*LeelaChessZero 0.30*](https://github.com/LeelaChessZero/lc0/releases/tag/v0.30.0).
|
||||
* ⚠️ Lc0 requires `WeightsFile` options to be set after launch, with a path to a neural network file provided.
|
||||
* Currently `chesskit-engine` assumes `192x15_network` is available in your app's `Bundle.main`.
|
||||
* Network files can be downloaded from [lczero.org](https://lczero.org/play/bestnets/).
|
||||
* Any other files can be added via `.setoption(id:value:)` engine commands.
|
||||
* Currently there are some performance issues using `lc0` in an app; this is being investigated but any contributions (via PRs or issues) are appreciated.
|
||||
|
||||
#### Improvements
|
||||
* `Engine.start()` now takes a `completion` handler.
|
||||
* This is called once the engine has finished initializing.
|
||||
* Engine commands (i.e. setting options or requesting evaluations) should not be sent until this completion handler is called.
|
||||
* `EngineMessenger` now sends commands to the engines via `stdin`, see [Issue #11](https://github.com/chesskit-app/chesskit-engine/issues/11).
|
||||
* This will allow for much simpler upgrades to existing engines, as well as the inclusion of new engines in the future.
|
||||
* Special thanks [@dehlen](https://github.com/dehlen).
|
||||
|
||||
# ChessKitEngine 0.3.0
|
||||
Released Wednesday, March 27, 2024.
|
||||
|
||||
@@ -32,7 +55,7 @@ Released Wednesday, April 26, 2023.
|
||||
#### New Features
|
||||
* Add [`LeelaChessZero (lc0)` engine](https://lczero.org)
|
||||
* Currently comes bundled with a neural network weights file `192x15_network`
|
||||
|
||||
|
||||
#### Improvements
|
||||
* `Engine` initializer no longer has a default `engineType` (previously `.stockfish`)
|
||||
* Type must be specified using `Engine(type: <engine type>)`
|
||||
@@ -63,7 +86,7 @@ Released Friday, April 14, 2023.
|
||||
Released Friday, April 14, 2023.
|
||||
|
||||
* Fix build issue related to missing `ChessKitEngine_Cxx` target
|
||||
|
||||
|
||||
# ChessKitEngine 0.1.0
|
||||
Released Friday, April 14, 2023.
|
||||
|
||||
|
||||
+2
-4
@@ -40,8 +40,7 @@ let package = Package(
|
||||
name: "ChessKitEngineTests",
|
||||
dependencies: ["ChessKitEngine"],
|
||||
resources: [
|
||||
.copy("EngineTests/Resources/192x15_network"),
|
||||
.copy("EngineTests/Resources/nn-1337b1adec5b.nnue")
|
||||
.copy("EngineTests/Resources/192x15_network")
|
||||
]
|
||||
)
|
||||
],
|
||||
@@ -70,7 +69,6 @@ package.targets.first { $0.name == "ChessKitEngineCore" }?.exclude = [
|
||||
"Engines/lc0/subprojects/eigen-3.4.0/test",
|
||||
"Engines/lc0/subprojects/eigen-3.4.0/unsupported",
|
||||
"Engines/lc0/third_party",
|
||||
"Engines/lc0/src/main.cc",
|
||||
"Engines/lc0/src/utils/filesystem.win32.cc",
|
||||
"Engines/lc0/src/chess/board_test.cc",
|
||||
"Engines/lc0/src/chess/position_test.cc",
|
||||
@@ -85,9 +83,9 @@ package.targets.first { $0.name == "ChessKitEngineCore" }?.exclude = [
|
||||
"Engines/lc0/src/trainingdata/",
|
||||
"Engines/lc0/src/neural/cuda/",
|
||||
"Engines/lc0/src/neural/dx/",
|
||||
"Engines/lc0/src/neural/metal/",
|
||||
"Engines/lc0/src/neural/onednn/",
|
||||
"Engines/lc0/src/neural/onnx/",
|
||||
"Engines/lc0/src/neural/opencl/",
|
||||
"Engines/lc0/src/neural/metal/",
|
||||
"Engines/lc0/src/neural/network_tf_cc.cc"
|
||||
]
|
||||
|
||||
@@ -1,23 +1,31 @@
|
||||
# ♟️🤖 ChessKitEngine
|
||||
|
||||
[](https://github.com/chesskit-app/chesskit-engine/actions/workflows/test-chesskit-engine.yml) [](https://codecov.io/gh/chesskit-app/chesskit-engine)
|
||||
[](https://github.com/chesskit-app/chesskit-engine/actions/workflows/checks.yaml) [](https://codecov.io/gh/chesskit-app/chesskit-engine)
|
||||
|
||||
A Swift package for the following chess engines:
|
||||
|
||||
[<img src="https://stockfishchess.org/images/logo/icon_512x512.png" width="50" />](https://stockfishchess.org) [<img src="https://lczero.org/images/logo.svg" width="50" />](https://lczero.org)
|
||||
<table>
|
||||
<tr>
|
||||
<td valign="center">
|
||||
<a href="https://stockfishchess.org"><img src="https://stockfishchess.org/images/logo/icon_512x512.png" width="50" /></a>
|
||||
</td>
|
||||
<td valign="center">
|
||||
<a href="https://lczero.org"><img src="https://lczero.org/images/logo.svg" width="50" /></a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
`ChessKitEngine` implements the [Universal Chess Interface protocol](https://backscattering.de/chess/uci/2006-04.txt) for communication between [chess engines](https://en.wikipedia.org/wiki/Chess_engine) and user interfaces built with Swift.
|
||||
`chesskit-engine` implements the [Universal Chess Interface protocol](https://backscattering.de/chess/uci/2006-04.txt) for communication between [chess engines](https://en.wikipedia.org/wiki/Chess_engine) and user interfaces built with Swift.
|
||||
|
||||
For a related Swift package that manages chess logic, see [chesskit-swift](https://github.com/chesskit-app/chesskit-swift).
|
||||
|
||||
## Usage
|
||||
|
||||
* Add a package dependency to your Xcode project or Swift Package:
|
||||
``` swift
|
||||
.package(url: "https://github.com/chesskit-app/chesskit-engine", from: "0.2.0")
|
||||
```
|
||||
1. Add `chesskit-engine` as a dependency
|
||||
* In an [app built in Xcode](https://developer.apple.com/documentation/xcode/adding-package-dependencies-to-your-app),
|
||||
* or [as a dependency to another Swift Package](https://www.swift.org/documentation/package-manager/#importing-dependencies).
|
||||
|
||||
* Next you can import `ChessKitEngine` to use it in your Swift code:
|
||||
2. Next, import `ChessKitEngine` to use it in Swift code:
|
||||
``` swift
|
||||
import ChessKitEngine
|
||||
|
||||
@@ -25,6 +33,8 @@ import ChessKitEngine
|
||||
|
||||
```
|
||||
|
||||
⚠️ Be sure to check the [Neural Networks](https://github.com/chesskit-app/chesskit-engine/tree/master?tab=readme-ov-file#neural-networks) section below for important setup details.
|
||||
|
||||
## Features
|
||||
|
||||
* Initialize an engine and set response handler
|
||||
@@ -32,13 +42,15 @@ import ChessKitEngine
|
||||
// create Stockfish engine
|
||||
let engine = Engine(type: .stockfish)
|
||||
|
||||
// set response handler
|
||||
// set response handler, called when engine issues responses
|
||||
engine.receiveResponse = { response in
|
||||
print(response)
|
||||
}
|
||||
|
||||
// start listening for engine responses
|
||||
engine.start()
|
||||
engine.start {
|
||||
// engine is ready to go!
|
||||
}
|
||||
```
|
||||
|
||||
* Send [UCI protocol](https://backscattering.de/chess/uci/2006-04.txt) commands
|
||||
@@ -95,26 +107,41 @@ engine.loggingEnabled = true
|
||||
// verbose while analyzing positions and returning evaluations.
|
||||
```
|
||||
|
||||
* Enable engine neural networks
|
||||
* Copy the relevant file in the `Resources` directory of this repo to your app's bundle, then use the engine-specific commands to provide them to the engine (where `fileURL` is a `String` of the URL of the file).
|
||||
* These must be called in the order shown.
|
||||
* For `Stockfish 15.1` (`nn-1337b1adec5b.nnue`):
|
||||
``` swift
|
||||
engine.send(command: .setoption(id: "EvalFile", value: fileURL))
|
||||
engine.send(command: .setoption(id: "Use NNUE", value: "true"))
|
||||
```
|
||||
* For `LeelaChessZero 0.29` (`192x15_network`):
|
||||
``` swift
|
||||
engine.send(command: .setoption(id: "WeightsFile", value: fileURL))
|
||||
```
|
||||
## Neural Networks
|
||||
Both `Stockfish 16.1` and `LeelaChessZero 0.30` require neural network files to be provided to the engine for computation.
|
||||
In order to keep the package size small and allow for the greatest level of flexibility, these neural network files are **not** bundled with the package. Therefore they must be added to the app (either in the bundle or manually by a user) and then provided to the engine at runtime.
|
||||
|
||||
They can be provided to the engine using the `.setoption(id:value:)` UCI commands included in `chesskit-engine`.
|
||||
|
||||
For example:
|
||||
``` swift
|
||||
// Stockfish
|
||||
engine.send(command: .setoption(id: "EvalFile", value: fileURL))
|
||||
engine.send(command: .setoption(id: "EvalFileSmall", value: smallFileURL))
|
||||
|
||||
// Lc0
|
||||
engine.send(command: .setoption(id: "WeightsFile", value: fileURL))
|
||||
```
|
||||
|
||||
The following details the recommended files for each engine and where to obtain them.
|
||||
|
||||
#### Stockfish
|
||||
* `"EvalFile"`: `nn-b1a57edbea57.nnue` ([download here](https://tests.stockfishchess.org/nns?network_name=b1a57edbea57&user=))
|
||||
* `"EvalFileSmall"`: `nn-baff1ede1f90.nnue` ([download here](https://tests.stockfishchess.org/nns?network_name=baff1ede1f90&user=))
|
||||
* Other files from https://tests.stockfishchess.org can be used if desired.
|
||||
|
||||
#### LeelaChessZero
|
||||
⚠️ There are currently some performance issues with lc0 in `chesskit-engine` ([PR's are welcome!](https://github.com/chesskit-app/chesskit-engine/compare)).
|
||||
* `"WeightsFile"`: `192x15_network` ([download here](https://github.com/chesskit-app/chesskit-engine/tree/0f11891b3c053e12d04c2e9c9d294c4404b006c3/Tests/ChessKitEngineTests/EngineTests/Resources))
|
||||
* Other files can be obtained [here](https://lczero.org/play/bestnets/) or [here](https://training.lczero.org/networks/).
|
||||
|
||||
## Supported Engines
|
||||
|
||||
The following engines are currently supported:
|
||||
| | Engine | Version | License | Options Reference |
|
||||
| :---: | --- | :---: | :---: | :---: |
|
||||
| <img src="https://stockfishchess.org/images/logo/icon_512x512.png" width="25" /> | [Stockfish](https://stockfishchess.org) | [15.1](https://github.com/official-stockfish/Stockfish/tree/sf_15.1) | [GPL v3](https://github.com/official-stockfish/Stockfish/blob/sf_15.1/Copying.txt) | [🔗](https://github.com/official-stockfish/Stockfish/tree/sf_15.1#the-uci-protocol-and-available-options)
|
||||
| <img src="https://lczero.org/images/logo.svg" width="25" /> | [lc0](https://lczero.org) | [0.29](https://github.com/LeelaChessZero/lc0/tree/v0.29.0) | [GPL v3](https://github.com/LeelaChessZero/lc0/blob/v0.29.0/COPYING) | [🔗](https://github.com/LeelaChessZero/lc0/wiki/Lc0-options)
|
||||
| <img src="https://stockfishchess.org/images/logo/icon_512x512.png" width="25" /> | [Stockfish](https://stockfishchess.org) | [16.1](https://github.com/official-stockfish/Stockfish/tree/sf_16.1) | [GPL v3](https://github.com/official-stockfish/Stockfish/blob/sf_16.1/Copying.txt) | [🔗](https://github.com/official-stockfish/Stockfish/wiki/UCI-&-Commands#setoption)
|
||||
| <img src="https://lczero.org/images/logo.svg" width="25" /> | [lc0](https://lczero.org) | [0.30](https://github.com/LeelaChessZero/lc0/tree/v0.30.0) | [GPL v3](https://github.com/LeelaChessZero/lc0/blob/v0.30.0/COPYING) | [🔗](https://github.com/LeelaChessZero/lc0/wiki/Lc0-options)
|
||||
|
||||
## Author
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -6,20 +6,20 @@
|
||||
import ChessKitEngineCore
|
||||
|
||||
public class Engine {
|
||||
|
||||
|
||||
/// The type of the engine.
|
||||
private let type: EngineType
|
||||
|
||||
|
||||
/// Messenger used to communicate with engine.
|
||||
private let messenger: EngineMessenger
|
||||
|
||||
|
||||
/// Whether logging should be enabled.
|
||||
///
|
||||
/// If set to `true`, engine commands and responses
|
||||
/// will be logged to the console. The default value is
|
||||
/// `false`.
|
||||
public var loggingEnabled = false
|
||||
|
||||
|
||||
/// Whether the engine is currently running.
|
||||
///
|
||||
/// - To start the engine, call `start()`.
|
||||
@@ -39,57 +39,69 @@ public class Engine {
|
||||
///
|
||||
public init(type: EngineType) {
|
||||
self.type = type
|
||||
|
||||
messenger = EngineMessenger(engineType: type.objc)
|
||||
|
||||
messenger.responseHandler = { [weak self] response in
|
||||
guard let self else { return }
|
||||
|
||||
DispatchQueue.main.async {
|
||||
if let parsedResponse = EngineResponse(rawValue: response) {
|
||||
self.log(parsedResponse.rawValue)
|
||||
self.receiveResponse(parsedResponse)
|
||||
} else if !response.isEmpty {
|
||||
self.log(response)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
deinit {
|
||||
stop()
|
||||
}
|
||||
|
||||
|
||||
/// Starts the engine.
|
||||
///
|
||||
/// - parameter coreCount: The number of processor cores to use for engine
|
||||
/// calculation. The default value is `nil` which uses the number of
|
||||
/// cores available on the device.
|
||||
/// - parameter multipv: The number of lines the engine should return,
|
||||
/// sent via the `"MultiPV"` UCI option.
|
||||
/// - parameter completion: The completion handler that is called when
|
||||
/// the engine setup is complete. You must wait for this to be called
|
||||
/// before sending further commands to the engine.
|
||||
///
|
||||
/// This must be called before sending any commands
|
||||
/// with `send(command:)`.
|
||||
public func start(coreCount: Int? = nil, multipv: Int = 1) {
|
||||
public func start(coreCount: Int? = nil, multipv: Int = 1, completion: @escaping () -> Void) {
|
||||
messenger.responseHandler = { [weak self] response in
|
||||
guard let self else { return }
|
||||
|
||||
DispatchQueue.main.async {
|
||||
guard let parsed = EngineResponse(rawValue: response) else {
|
||||
if !response.isEmpty {
|
||||
self.log(response)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
self.log(parsed.rawValue)
|
||||
|
||||
guard self.isRunning else {
|
||||
// engine setup loop
|
||||
// <uci> → <uciok> → <isready> → <readok> → complete
|
||||
switch parsed {
|
||||
case .uciok:
|
||||
self.send(command: .isready)
|
||||
case .readyok:
|
||||
self.isRunning = true
|
||||
self.performInitialSetup(
|
||||
coreCount: coreCount ?? ProcessInfo.processInfo.processorCount,
|
||||
multipv: multipv
|
||||
)
|
||||
completion()
|
||||
default:
|
||||
break
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
self.receiveResponse(parsed)
|
||||
}
|
||||
}
|
||||
|
||||
messenger.start()
|
||||
isRunning = true
|
||||
|
||||
// set UCI mode
|
||||
|
||||
// start engine setup loop
|
||||
send(command: .uci)
|
||||
|
||||
// configure engine-specific options
|
||||
type.setupCommands.forEach(send)
|
||||
|
||||
// configure common engine options
|
||||
send(command: .setoption(
|
||||
id: "Threads",
|
||||
value: "\(max((coreCount ?? ProcessInfo.processInfo.processorCount) - 1, 1))"
|
||||
))
|
||||
send(command: .setoption(id: "MultiPV", value: "\(multipv)"))
|
||||
|
||||
// start analyzing
|
||||
send(command: .isready)
|
||||
send(command: .ucinewgame)
|
||||
}
|
||||
|
||||
|
||||
/// Stops the engine.
|
||||
///
|
||||
/// Call this to stop all engine calculation and clean up.
|
||||
@@ -97,14 +109,15 @@ public class Engine {
|
||||
/// sending any more commands with `send(command:)`.
|
||||
public func stop() {
|
||||
guard isRunning else { return }
|
||||
|
||||
|
||||
send(command: .stop)
|
||||
send(command: .quit)
|
||||
messenger.stop()
|
||||
|
||||
|
||||
isRunning = false
|
||||
initialSetupComplete = false
|
||||
}
|
||||
|
||||
|
||||
/// Sends a command to the engine.
|
||||
///
|
||||
/// - parameter command: The command to send.
|
||||
@@ -113,17 +126,17 @@ public class Engine {
|
||||
/// validity. While the engine is processing commands or
|
||||
/// thinking, any responses will be returned via `receiveResponse`.
|
||||
public func send(command: EngineCommand) {
|
||||
guard isRunning else {
|
||||
guard isRunning || [.uci, .isready].contains(command) else {
|
||||
log("Engine is not running, call start() first.")
|
||||
return
|
||||
}
|
||||
|
||||
queue.sync {
|
||||
self.log(command.rawValue)
|
||||
self.messenger.sendCommand(command.rawValue)
|
||||
log(command.rawValue)
|
||||
messenger.sendCommand(command.rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Closure that is called when engine responses are received.
|
||||
///
|
||||
/// - parameter response: The response received from the engine.
|
||||
@@ -133,17 +146,33 @@ public class Engine {
|
||||
public var receiveResponse: (_ response: EngineResponse) -> Void = {
|
||||
_ in
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
// MARK: - Private
|
||||
|
||||
extension Engine {
|
||||
|
||||
/// Logs `message` if `loggingEnabled` is `true`.
|
||||
private func log(_ message: String) {
|
||||
if loggingEnabled {
|
||||
Logging.print(message)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private var initialSetupComplete = false
|
||||
|
||||
/// Sets initial engine options.
|
||||
private func performInitialSetup(coreCount: Int, multipv: Int) {
|
||||
guard !initialSetupComplete else { return }
|
||||
|
||||
// configure engine-specific options
|
||||
type.setupCommands.forEach(send)
|
||||
|
||||
// configure common engine options
|
||||
send(command: .setoption(
|
||||
id: "Threads",
|
||||
value: "\(max(coreCount - 1, 1))"
|
||||
))
|
||||
send(command: .setoption(id: "MultiPV", value: "\(multipv)"))
|
||||
|
||||
initialSetupComplete = true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
/// Possible engine commands based on the
|
||||
/// [Universal Chess Interface (UCI)](https://backscattering.de/chess/uci/2006-04.txt).
|
||||
///
|
||||
public enum EngineCommand {
|
||||
public enum EngineCommand: Equatable {
|
||||
|
||||
/// `"debug [ on | off ]"`
|
||||
///
|
||||
|
||||
@@ -7,37 +7,37 @@
|
||||
/// [Universal Chess Interface (UCI)](https://backscattering.de/chess/uci/2006-04.txt).
|
||||
///
|
||||
public enum EngineResponse {
|
||||
|
||||
|
||||
/// `"id name <x>"`, `"id author <x>"`
|
||||
///
|
||||
/// See [UCI protocol documentation](https://backscattering.de/chess/uci/2006-04.txt)
|
||||
/// for more information.
|
||||
case id(ID)
|
||||
|
||||
|
||||
/// `"uciok"`
|
||||
///
|
||||
/// See [UCI protocol documentation](https://backscattering.de/chess/uci/2006-04.txt)
|
||||
/// for more information.
|
||||
case uciok
|
||||
|
||||
|
||||
/// `"readyok"`
|
||||
///
|
||||
/// See [UCI protocol documentation](https://backscattering.de/chess/uci/2006-04.txt)
|
||||
/// for more information.
|
||||
case readyok
|
||||
|
||||
|
||||
/// `"bestmove <move1> [ ponder <move2> ]"`
|
||||
///
|
||||
/// See [UCI protocol documentation](https://backscattering.de/chess/uci/2006-04.txt)
|
||||
/// for more information.
|
||||
case bestmove(move: String, ponder: String?)
|
||||
|
||||
|
||||
/// `"info"`
|
||||
///
|
||||
/// See [UCI protocol documentation](https://backscattering.de/chess/uci/2006-04.txt)
|
||||
/// for more information.
|
||||
case info(Info)
|
||||
|
||||
|
||||
}
|
||||
|
||||
extension EngineResponse: Equatable {}
|
||||
@@ -45,35 +45,35 @@ extension EngineResponse: Equatable {}
|
||||
extension EngineResponse: RawRepresentable {
|
||||
public init?(rawValue: String) {
|
||||
let parsed = EngineResponseParser.parse(response: rawValue)
|
||||
|
||||
|
||||
guard let parsed else {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
self = parsed
|
||||
}
|
||||
|
||||
|
||||
public var rawValue: String {
|
||||
switch self {
|
||||
case let .id(id):
|
||||
switch id {
|
||||
case let .name(name):
|
||||
return "<id> <name> \(name)"
|
||||
"<id> <name> \(name)"
|
||||
case let .author(author):
|
||||
return "<id> <author> \(author)"
|
||||
"<id> <author> \(author)"
|
||||
}
|
||||
case .uciok:
|
||||
return "<uciok>"
|
||||
"<uciok>"
|
||||
case .readyok:
|
||||
return "<readyok>"
|
||||
"<readyok>"
|
||||
case let .bestmove(move, ponder):
|
||||
if let ponder {
|
||||
return "<bestmove> \(move) <ponder> \(ponder)"
|
||||
"<bestmove> \(move) <ponder> \(ponder)"
|
||||
} else {
|
||||
return "<bestmove> \(move)"
|
||||
"<bestmove> \(move)"
|
||||
}
|
||||
case let .info(info):
|
||||
return "<info>\(info)"
|
||||
"<info>\(info)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
// MARK: - Info
|
||||
|
||||
public extension EngineResponse {
|
||||
|
||||
|
||||
struct Info {
|
||||
public var depth: Int?
|
||||
public var seldepth: Int?
|
||||
@@ -25,7 +25,7 @@ public extension EngineResponse {
|
||||
public var string: String?
|
||||
public var refutation: [String]?
|
||||
public var currline: CurrLine?
|
||||
|
||||
|
||||
/// Possible arguments for the `<info>` command.
|
||||
enum Argument: String, CaseIterable {
|
||||
case depth
|
||||
@@ -45,7 +45,7 @@ public extension EngineResponse {
|
||||
case string
|
||||
case refutation
|
||||
case currline
|
||||
|
||||
|
||||
enum ArgType {
|
||||
/// Single token follows the argument.
|
||||
case single
|
||||
@@ -59,7 +59,7 @@ public extension EngineResponse {
|
||||
/// The argument is of type `(Int, [String])`
|
||||
case currentLine
|
||||
}
|
||||
|
||||
|
||||
var type: ArgType {
|
||||
switch self {
|
||||
case .depth: return .single
|
||||
@@ -82,18 +82,13 @@ public extension EngineResponse {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
subscript(arg: Argument) -> Any? {
|
||||
get { self[arg.rawValue] }
|
||||
set { self[arg.rawValue] = newValue }
|
||||
}
|
||||
|
||||
|
||||
/// Allows subscript access to `Info` properties
|
||||
/// to allow for easier `String` parsing.
|
||||
subscript(arg: String) -> Any? {
|
||||
get {
|
||||
guard let arg = Argument(rawValue: arg) else { return nil }
|
||||
|
||||
|
||||
switch arg {
|
||||
case .depth: return depth
|
||||
case .seldepth: return seldepth
|
||||
@@ -116,7 +111,7 @@ public extension EngineResponse {
|
||||
}
|
||||
set {
|
||||
guard let arg = Argument(rawValue: arg) else { return }
|
||||
|
||||
|
||||
switch arg {
|
||||
case .depth: depth = newValue as? Int
|
||||
case .seldepth: seldepth = newValue as? Int
|
||||
@@ -139,7 +134,7 @@ public extension EngineResponse {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
extension EngineResponse.Info: Equatable {}
|
||||
@@ -147,75 +142,75 @@ extension EngineResponse.Info: Equatable {}
|
||||
extension EngineResponse.Info: CustomStringConvertible {
|
||||
public var description: String {
|
||||
var result = ""
|
||||
|
||||
|
||||
if let depth = depth {
|
||||
result += " <depth> \(depth)"
|
||||
}
|
||||
|
||||
|
||||
if let seldepth = seldepth {
|
||||
result += " <seldepth> \(seldepth)"
|
||||
}
|
||||
|
||||
|
||||
if let time = time {
|
||||
result += " <time> \(time)"
|
||||
}
|
||||
|
||||
|
||||
if let nodes = nodes {
|
||||
result += " <nodes> \(nodes)"
|
||||
}
|
||||
|
||||
|
||||
if let pv = pv, !pv.isEmpty {
|
||||
result += " <pv> \(pv.joined(separator: " "))"
|
||||
}
|
||||
|
||||
|
||||
if let multipv = multipv {
|
||||
result += " <multipv> \(multipv)"
|
||||
}
|
||||
|
||||
|
||||
if let score = score {
|
||||
result += " <score>\(score)"
|
||||
}
|
||||
|
||||
|
||||
if let currmove = currmove {
|
||||
result += " <currmove> \(currmove)"
|
||||
}
|
||||
|
||||
|
||||
if let currmovenumber = currmovenumber {
|
||||
result += " <currmovenumber> \(currmovenumber)"
|
||||
}
|
||||
|
||||
|
||||
if let hashfull = hashfull {
|
||||
result += " <hashfull> \(hashfull)"
|
||||
}
|
||||
|
||||
|
||||
if let nps = nps {
|
||||
result += " <nps> \(nps)"
|
||||
}
|
||||
|
||||
|
||||
if let tbhits = tbhits {
|
||||
result += " <tbhits> \(tbhits)"
|
||||
}
|
||||
|
||||
|
||||
if let sbhits = sbhits {
|
||||
result += " <sbhits> \(sbhits)"
|
||||
}
|
||||
|
||||
|
||||
if let cpuload = cpuload {
|
||||
result += " <cpuload> \(cpuload)"
|
||||
}
|
||||
|
||||
|
||||
if let string = string {
|
||||
result += " <string> \(string)"
|
||||
}
|
||||
|
||||
|
||||
if let refutation = refutation {
|
||||
result += " <refutation> \(refutation.joined(separator: " "))"
|
||||
}
|
||||
|
||||
|
||||
if let currline = currline {
|
||||
result += " <currline>\(currline)"
|
||||
}
|
||||
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
@@ -234,7 +229,7 @@ public extension EngineResponse.Info {
|
||||
public var lowerbound: Bool?
|
||||
/// The score is just an upper bound.
|
||||
public var upperbound: Bool?
|
||||
|
||||
|
||||
/// Allows subscript access to `Score` properties
|
||||
/// to allow for easier `String` parsing.
|
||||
subscript(member: String) -> Any? {
|
||||
@@ -258,29 +253,29 @@ public extension EngineResponse.Info {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
extension EngineResponse.Info.Score: CustomStringConvertible {
|
||||
public var description: String {
|
||||
var result = ""
|
||||
|
||||
|
||||
if let cp = cp {
|
||||
result += " <cp> \(cp)"
|
||||
}
|
||||
|
||||
|
||||
if let mate = mate {
|
||||
result += " <mate> \(mate)"
|
||||
}
|
||||
|
||||
|
||||
if let lowerbound = lowerbound, lowerbound {
|
||||
result += " <lowerbound>"
|
||||
}
|
||||
|
||||
|
||||
if let upperbound = upperbound, upperbound {
|
||||
result += " <upperbound>"
|
||||
}
|
||||
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
@@ -299,15 +294,15 @@ extension EngineResponse.Info {
|
||||
extension EngineResponse.Info.CurrLine: CustomStringConvertible {
|
||||
public var description: String {
|
||||
var result = ""
|
||||
|
||||
|
||||
if let cpunr = cpunr {
|
||||
result += " \(cpunr)"
|
||||
}
|
||||
|
||||
|
||||
if !moves.isEmpty {
|
||||
result += " \(moves.joined(separator: " "))"
|
||||
}
|
||||
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,10 +7,10 @@ import ChessKitEngineCore
|
||||
|
||||
/// Possible engines available in `ChessKitEngine`.
|
||||
public enum EngineType: Int {
|
||||
|
||||
|
||||
case stockfish
|
||||
case lc0
|
||||
|
||||
|
||||
/// Internal mapping from Swift to Obj-C type.
|
||||
var objc: EngineType_objc {
|
||||
switch self {
|
||||
@@ -18,7 +18,7 @@ public enum EngineType: Int {
|
||||
case .lc0: return .lc0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// The user-readable name of the engine.
|
||||
public var name: String {
|
||||
switch self {
|
||||
@@ -26,46 +26,56 @@ public enum EngineType: Int {
|
||||
case .lc0: return "LeelaChessZero (Lc0)"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// The current version of the given engine.
|
||||
public var version: String {
|
||||
switch self {
|
||||
case .stockfish: return "15.1"
|
||||
case .lc0: return "0.29"
|
||||
case .stockfish: return "16.1"
|
||||
case .lc0: return "0.30"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Engine-specific options to configure at initialization.
|
||||
var setupCommands: [EngineCommand] {
|
||||
switch self {
|
||||
case .stockfish:
|
||||
[
|
||||
.setoption(id: "Use NNUE", value: "false"),
|
||||
.setoption(id: "UCI_AnalyseMode", value: "true")
|
||||
]
|
||||
let fileOptions = [
|
||||
"EvalFile": "nn-b1a57edbea57",
|
||||
"EvalFileSmall": "nn-baff1ede1f90"
|
||||
].compactMapValues {
|
||||
Bundle.main.url(forResource: $0, withExtension: "nnue")?.path()
|
||||
}
|
||||
|
||||
return fileOptions.map(EngineCommand.setoption)
|
||||
case .lc0:
|
||||
[]
|
||||
let fileOptions = [
|
||||
"WeightsFile": "192x15_network"
|
||||
].compactMapValues {
|
||||
Bundle.main.url(forResource: $0, withExtension: nil)?.path()
|
||||
}
|
||||
|
||||
return fileOptions.map(EngineCommand.setoption)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// MARK: - CaseIterable
|
||||
|
||||
extension EngineType: CaseIterable {
|
||||
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Equatable
|
||||
|
||||
extension EngineType: Equatable {
|
||||
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Identifiable
|
||||
|
||||
extension EngineType: Identifiable {
|
||||
|
||||
|
||||
public var id: Self { self }
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -8,9 +8,12 @@
|
||||
|
||||
@implementation EngineMessenger : NSObject
|
||||
|
||||
dispatch_queue_t _queue;
|
||||
Engine *_engine;
|
||||
NSPipe *_pipe;
|
||||
NSPipe *_readPipe;
|
||||
NSPipe *_writePipe;
|
||||
NSFileHandle *_pipeReadHandle;
|
||||
NSFileHandle *_pipeWriteHandle;
|
||||
|
||||
/// Initializes a new `EngineMessenger` with default engine `Stockfish`.
|
||||
- (id)init {
|
||||
@@ -19,7 +22,7 @@ NSFileHandle *_pipeReadHandle;
|
||||
|
||||
- (id)initWithEngineType: (EngineType_objc) type {
|
||||
self = [super init];
|
||||
|
||||
|
||||
if (self) {
|
||||
switch (type) {
|
||||
case EngineTypeStockfish:
|
||||
@@ -29,10 +32,8 @@ NSFileHandle *_pipeReadHandle;
|
||||
_engine = new Lc0Engine();
|
||||
break;
|
||||
}
|
||||
|
||||
_engine->initialize();
|
||||
}
|
||||
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
@@ -42,42 +43,62 @@ NSFileHandle *_pipeReadHandle;
|
||||
}
|
||||
|
||||
- (void)start {
|
||||
_pipe = [NSPipe pipe];
|
||||
_pipeReadHandle = [_pipe fileHandleForReading];
|
||||
|
||||
dup2([[_pipe fileHandleForWriting] fileDescriptor], fileno(stdout));
|
||||
|
||||
// set up read pipe
|
||||
_readPipe = [NSPipe pipe];
|
||||
_pipeReadHandle = [_readPipe fileHandleForReading];
|
||||
|
||||
dup2([[_readPipe fileHandleForWriting] fileDescriptor], fileno(stdout));
|
||||
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
addObserver:self
|
||||
selector:@selector(readStdout:)
|
||||
name:NSFileHandleReadCompletionNotification
|
||||
object:_pipeReadHandle
|
||||
];
|
||||
|
||||
|
||||
[_pipeReadHandle readInBackgroundAndNotify];
|
||||
|
||||
// set up write pipe
|
||||
_writePipe = [NSPipe pipe];
|
||||
_pipeWriteHandle = [_writePipe fileHandleForWriting];
|
||||
dup2([[_writePipe fileHandleForReading] fileDescriptor], fileno(stdin));
|
||||
|
||||
// create command dispatch queue and start engine
|
||||
_queue = dispatch_queue_create("ck-message-queue", DISPATCH_QUEUE_CONCURRENT);
|
||||
|
||||
dispatch_async(_queue, ^{
|
||||
_engine->initialize();
|
||||
});
|
||||
}
|
||||
|
||||
- (void)stop {
|
||||
[_pipeReadHandle closeFile];
|
||||
|
||||
_pipe = NULL;
|
||||
[_pipeWriteHandle closeFile];
|
||||
|
||||
_readPipe = NULL;
|
||||
_pipeReadHandle = NULL;
|
||||
|
||||
|
||||
_writePipe = NULL;
|
||||
_pipeWriteHandle = NULL;
|
||||
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
}
|
||||
|
||||
- (void)sendCommand: (NSString*) command {
|
||||
_engine->send_command(std::string([command UTF8String]));
|
||||
dispatch_sync(_queue, ^{
|
||||
const char *cmd = [[command stringByAppendingString:@"\n"] UTF8String];
|
||||
write([_pipeWriteHandle fileDescriptor], cmd, strlen(cmd));
|
||||
});
|
||||
}
|
||||
|
||||
# pragma mark Private
|
||||
|
||||
- (void)readStdout: (NSNotification*) notification {
|
||||
[_pipeReadHandle readInBackgroundAndNotify];
|
||||
|
||||
|
||||
NSData *data = [[notification userInfo] objectForKey:NSFileHandleNotificationDataItem];
|
||||
NSArray<NSString *> *output = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] componentsSeparatedByString:@"\n"];
|
||||
|
||||
|
||||
[output enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
[self responseHandler](obj);
|
||||
}];
|
||||
|
||||
@@ -27,14 +27,6 @@ public:
|
||||
/// Any required deinitialization and cleanup should
|
||||
/// be performed here
|
||||
virtual void deinitialize() {};
|
||||
|
||||
/// Sends a command to the engine.
|
||||
/// - parameter cmd: The UCI command to send to the engine.
|
||||
/// See https://backscattering.de/chess/uci/2006-04.txt
|
||||
/// for valid commands.
|
||||
///
|
||||
/// The output from the engine will appear in `stdout`.
|
||||
virtual void send_command(const std::string &cmd) {};
|
||||
};
|
||||
|
||||
#endif /* engine_h */
|
||||
|
||||
@@ -3,31 +3,15 @@
|
||||
// ChessKitEngine
|
||||
//
|
||||
|
||||
#import "lc0+engine.h"
|
||||
#include "lc0+engine.h"
|
||||
|
||||
#import "../lc0/src/chess/board.h"
|
||||
#import "../lc0/src/engine.h"
|
||||
|
||||
using namespace lczero;
|
||||
|
||||
EngineLoop loop;
|
||||
#include "../lc0/src/_main.h"
|
||||
|
||||
void Lc0Engine::initialize() {
|
||||
InitializeMagicBitboards();
|
||||
loop.Initialize();
|
||||
loop.RunLoop();
|
||||
const char* argv[] = { "uci" };
|
||||
_main(sizeof(argv) / sizeof(argv[0]), argv);
|
||||
}
|
||||
|
||||
void Lc0Engine::deinitialize() {
|
||||
|
||||
}
|
||||
|
||||
void Lc0Engine::send_command(const std::string &cmd) {
|
||||
auto command = loop.ParseCommand(cmd);
|
||||
|
||||
try {
|
||||
loop.DispatchCommand(command.first, command.second);
|
||||
} catch(std::exception& e) {
|
||||
// ignore unsupported commands
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,15 +6,14 @@
|
||||
#ifndef lc0_engine_h
|
||||
#define lc0_engine_h
|
||||
|
||||
#import "engine.h"
|
||||
#import <string>
|
||||
#include "engine.h"
|
||||
#include <string>
|
||||
|
||||
/// LeelaChessZero (Lc0) implementation of `Engine`.
|
||||
class Lc0Engine: public Engine {
|
||||
public:
|
||||
void initialize();
|
||||
void deinitialize();
|
||||
void send_command(const std::string &cmd);
|
||||
};
|
||||
|
||||
#endif /* lc0_engine_h */
|
||||
|
||||
@@ -3,38 +3,18 @@
|
||||
// ChessKitEngine
|
||||
//
|
||||
|
||||
#import "stockfish+engine.h"
|
||||
|
||||
#import "../Stockfish/src/bitboard.h"
|
||||
#import "../Stockfish/src/endgame.h"
|
||||
#import "../Stockfish/src/evaluate.h"
|
||||
#import "../Stockfish/src/position.h"
|
||||
#import "../Stockfish/src/psqt.h"
|
||||
#import "../Stockfish/src/search.h"
|
||||
#import "../Stockfish/src/thread.h"
|
||||
#import "../Stockfish/src/uci.h"
|
||||
#import "../Stockfish/src/types.h"
|
||||
#include "stockfish+engine.h"
|
||||
#include "../Stockfish/src/_main.h"
|
||||
#include "../Stockfish/src/thread.h"
|
||||
|
||||
using namespace Stockfish;
|
||||
|
||||
void StockfishEngine::initialize() {
|
||||
UCI::init(Options);
|
||||
Tune::init();
|
||||
PSQT::init();
|
||||
Bitboards::init();
|
||||
Position::init();
|
||||
Bitbases::init();
|
||||
Endgames::init();
|
||||
Threads.set(size_t(Stockfish::Options["Threads"]));
|
||||
Search::clear(); // After threads are up
|
||||
Eval::NNUE::init();
|
||||
char empty[] = "";
|
||||
char* argv[] = { empty };
|
||||
_main(1, argv);
|
||||
}
|
||||
|
||||
void StockfishEngine::deinitialize() {
|
||||
Threads.clear();
|
||||
Threads.end();
|
||||
}
|
||||
|
||||
void StockfishEngine::send_command(const std::string &cmd) {
|
||||
UCI::execute_command(cmd);
|
||||
ThreadPool().end();
|
||||
}
|
||||
|
||||
@@ -6,15 +6,14 @@
|
||||
#ifndef stockfish_engine_h
|
||||
#define stockfish_engine_h
|
||||
|
||||
#import "engine.h"
|
||||
#import <string>
|
||||
#include "engine.h"
|
||||
#include <string>
|
||||
|
||||
/// Stockfish implementation of `Engine`.
|
||||
class StockfishEngine: public Engine {
|
||||
public:
|
||||
void initialize();
|
||||
void deinitialize();
|
||||
void send_command(const std::string &cmd);
|
||||
};
|
||||
|
||||
#endif /* stockfish_engine_h */
|
||||
|
||||
Submodule Sources/ChessKitEngineCore/Engines/Stockfish updated: 5b0b0456c1...63092f0208
Submodule Sources/ChessKitEngineCore/Engines/lc0 updated: 9835982e80...cb3dbf6be3
@@ -46,7 +46,6 @@ class BaseEngineTests: XCTestCase {
|
||||
super.setUp()
|
||||
|
||||
engine = Engine(type: engineType)
|
||||
engine.start()
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
@@ -57,16 +56,17 @@ class BaseEngineTests: XCTestCase {
|
||||
|
||||
func testEngineSetup() {
|
||||
let expectation = XCTestExpectation()
|
||||
|
||||
|
||||
engine.start { [self] in
|
||||
engine.send(command: .isready)
|
||||
}
|
||||
|
||||
engine.receiveResponse = {
|
||||
if $0 == .uciok || $0 == .readyok {
|
||||
if $0 == .readyok {
|
||||
expectation.fulfill()
|
||||
}
|
||||
}
|
||||
|
||||
engine.send(command: .uci)
|
||||
engine.send(command: .isready)
|
||||
|
||||
wait(for: [expectation], timeout: 5)
|
||||
}
|
||||
|
||||
|
||||
@@ -11,12 +11,6 @@ final class Lc0Tests: BaseEngineTests {
|
||||
override func setUp() {
|
||||
engineType = .lc0
|
||||
super.setUp()
|
||||
|
||||
let weightsFileURL = Bundle.module
|
||||
.path(forResource: "192x15_network", ofType: nil)!
|
||||
.replacingOccurrences(of: "file://", with: "")
|
||||
|
||||
engine.send(command: .setoption(id: "WeightsFile", value: weightsFileURL))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -11,13 +11,6 @@ final class StockfishTests: BaseEngineTests {
|
||||
override func setUp() {
|
||||
engineType = .stockfish
|
||||
super.setUp()
|
||||
|
||||
let evalFileURL = Bundle.module
|
||||
.path(forResource: "nn-1337b1adec5b", ofType: "nnue")!
|
||||
.replacingOccurrences(of: "file://", with: "")
|
||||
|
||||
engine.send(command: .setoption(id: "Eval File", value: evalFileURL))
|
||||
engine.send(command: .setoption(id: "Use NNUE", value: "true"))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user