Skip to content

Commit

Permalink
Enable Swift 6 language mode (#46)
Browse files Browse the repository at this point in the history
* Entire package now uses Swift 6 language mode.
* Increased thread-safety for `Node` modifications in `MoveTree` to
ensure sendability
  • Loading branch information
pdil authored Oct 3, 2024
2 parents f37fa69 + 98af208 commit 8c1f40d
Show file tree
Hide file tree
Showing 8 changed files with 44 additions and 30 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
# [unreleased]

### New Features
* `BoardDelegate` now notifies when king is in check, and provides the color of the checked king, see [Issue #38](https://github.com/chesskit-app/chesskit-swift/issues/38).
* `Move.checkState` and `Move.disambiguation` are now publicly accessible, see [Issue #38](https://github.com/chesskit-app/chesskit-swift/issues/38).

### Technical Changes
* Enable Swift 6 language mode package-wide.
* `Game` and `MoveTree` are now `Sendable` types.

# ChessKit 0.12.1
Released Wednesday, September 11, 2024.

Expand Down
20 changes: 4 additions & 16 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// swift-tools-version: 5.9
// swift-tools-version: 6.0

import PackageDescription

Expand All @@ -12,22 +12,10 @@ let package = Package(
.watchOS(.v6)
],
products: [
.library(
name: "ChessKit",
targets: ["ChessKit"]
)
.library(name: "ChessKit", targets: ["ChessKit"])
],
targets: [
.target(
name: "ChessKit",
dependencies: [],
swiftSettings: [
.enableExperimentalFeature("StrictConcurrency")
]
),
.testTarget(
name: "ChessKitTests",
dependencies: ["ChessKit"]
)
.target(name: "ChessKit"),
.testTarget(name: "ChessKitTests", dependencies: ["ChessKit"])
]
)
2 changes: 2 additions & 0 deletions Sources/ChessKit/Board.swift
Original file line number Diff line number Diff line change
Expand Up @@ -220,8 +220,10 @@ public struct Board: Sendable {
let checkState = self.checkState(for: move.piece.color)
processedMove.checkState = checkState

// draw by repetition
positionHashCounts[position.hashValue, default: 0] += 1

// board state notification
if checkState == .checkmate {
delegate?.didEnd(with: .win(move.piece.color))
} else if checkState == .stalemate {
Expand Down
2 changes: 1 addition & 1 deletion Sources/ChessKit/Game.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import Foundation
/// This object is the entry point for interacting with a full
/// chess game within `ChessKit`. It provides methods for
/// making moves and publishes the played moves in an observable way.
public struct Game: Hashable {
public struct Game: Hashable, Sendable {

// MARK: - Properties

Expand Down
2 changes: 2 additions & 0 deletions Sources/ChessKit/MoveTree/MoveTree+Index.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ extension MoveTree {
/// Object that represents the index of a node in the move tree.
public struct Index: Hashable, Sendable {

/// Variation number corresponding to the main variation of the tree.
public static let mainVariation = 0

/// The move number.
Expand Down Expand Up @@ -86,6 +87,7 @@ extension MoveTree {

}

// MARK: - Comparable
extension MoveTree.Index: Comparable {
public static func < (lhs: Self, rhs: Self) -> Bool {
if lhs.variation == rhs.variation {
Expand Down
22 changes: 16 additions & 6 deletions Sources/ChessKit/MoveTree/MoveTree.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
// ChessKit
//

import Foundation

/// A tree-like data structure that represents the moves of a chess game.
///
/// The tree maintains the move order including variations and
/// provides index-based access for any element in the tree.
public struct MoveTree: Hashable {
public struct MoveTree: Hashable, Sendable {

/// The index of the root of the move tree.
///
Expand All @@ -27,6 +29,10 @@ public struct MoveTree: Hashable {
Array(dictionary.keys)
}

/// Lock to restrict modification of tree nodes
/// to ensure `Sendable` conformance for ``Node``.
private static let nodeLock = NSLock()

/// Adds a move to the move tree.
///
/// - parameter move: The move to add to the tree.
Expand Down Expand Up @@ -67,7 +73,9 @@ public struct MoveTree: Hashable {
}
}

dictionary[newIndex] = newNode
Self.nodeLock.withLock {
dictionary[newIndex] = newNode
}
newNode.index = newIndex

if newIndex.variation == Index.mainVariation {
Expand Down Expand Up @@ -257,8 +265,10 @@ public struct MoveTree: Hashable {
assessment: Move.Assessment = .null,
comment: String = ""
) -> Move? {
dictionary[index]?.move.assessment = assessment
dictionary[index]?.move.comment = comment
Self.nodeLock.withLock {
dictionary[index]?.move.assessment = assessment
dictionary[index]?.move.comment = comment
}
return dictionary[index]?.move
}

Expand Down Expand Up @@ -343,7 +353,7 @@ extension MoveTree: Equatable {
extension MoveTree {

/// Object that represents a node in the move tree.
class Node: Hashable {
class Node: Hashable, @unchecked Sendable {

/// The move for this node.
var move: Move
Expand All @@ -365,7 +375,7 @@ extension MoveTree {
lhs.index == rhs.index && lhs.move == rhs.move
}

// MARK: - Hashable
// MARK: Hashable
func hash(into hasher: inout Hasher) {
hasher.combine(move)
hasher.combine(index)
Expand Down
14 changes: 7 additions & 7 deletions Tests/ChessKitTests/BoardTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ final class BoardTests: XCTestCase {
XCTAssertTrue(board.position.enPassantIsPossible)
}

func testWhitePromotion() {
@MainActor func testWhitePromotion() {
let pawn = Piece(.pawn, color: .white, square: .e7)
let queen = Piece(.queen, color: .white, square: .e8)
var board = Board(position: .init(pieces: [pawn]))
Expand All @@ -58,7 +58,7 @@ final class BoardTests: XCTestCase {
XCTAssertEqual(promotionMove.end, .e8)
}

func testBlackPromotion() {
@MainActor func testBlackPromotion() {
let pawn = Piece(.pawn, color: .black, square: .e2)
let queen = Piece(.queen, color: .black, square: .e1)
var board = Board(position: .init(pieces: [pawn]))
Expand All @@ -82,7 +82,7 @@ final class BoardTests: XCTestCase {
XCTAssertEqual(promotionMove.end, .e1)
}

func testFiftyMoveRule() {
@MainActor func testFiftyMoveRule() {
var board = Board(position: .fiftyMove)
nonisolated(unsafe) var expectation: XCTestExpectation? = self.expectation(description: "Board returns fifty move draw result")

Expand All @@ -102,7 +102,7 @@ final class BoardTests: XCTestCase {
waitForExpectations(timeout: 1.0)
}

func testInsufficientMaterial() {
@MainActor func testInsufficientMaterial() {
var board = Board(position: .init(fen: "k7/b6P/8/8/8/8/8/K7 w - - 0 1")!)
nonisolated(unsafe) var expectation: XCTestExpectation? = self.expectation(description: "Board returns insufficient material draw result")

Expand Down Expand Up @@ -165,7 +165,7 @@ final class BoardTests: XCTestCase {
XCTAssertTrue(board4.position.hasInsufficientMaterial)
}

func testThreefoldRepetition() {
@MainActor func testThreefoldRepetition() {
var board = Board(position: .standard)

board.move(pieceAt: .e2, to: .e4)
Expand Down Expand Up @@ -350,7 +350,7 @@ final class BoardTests: XCTestCase {
XCTAssertNil(move)
}

func testCheckMove() {
@MainActor func testCheckMove() {
var board = Board(position: .init(fen: "k7/7R/8/8/8/8/K7/8 w - - 0 1")!)

nonisolated(unsafe) var expectation: XCTestExpectation? = self.expectation(description: "Board returns check result")
Expand All @@ -371,7 +371,7 @@ final class BoardTests: XCTestCase {
waitForExpectations(timeout: 1.0)
}

func testCheckmateMove() {
@MainActor func testCheckmateMove() {
var board = Board(position: .init(fen: "k7/7R/6R1/8/8/8/K7/8 w - - 0 1")!)

nonisolated(unsafe) var expectation: XCTestExpectation? = self.expectation(description: "Board returns checkmate result")
Expand Down
7 changes: 7 additions & 0 deletions Tests/ChessKitTests/MoveTreeTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ final class MoveTreeTests: XCTestCase {
XCTAssertEqual(moveTree[.minimum.next], e4)
}

func testNodeHashValue() {
var moveTree = MoveTree()
let e4 = Move(san: "e4", position: .standard)
moveTree[.minimum.next] = e4
XCTAssertNotNil(moveTree.dictionary[.minimum.next]?.hashValue)
}

func testSameVariationComparability() {
let wIndex = MoveTree.Index(number: 4, color: .white, variation: 2)
XCTAssertLessThan(wIndex, wIndex.next)
Expand Down

0 comments on commit 8c1f40d

Please sign in to comment.