aboutsummaryrefslogtreecommitdiff
path: root/Sources/SwiftChessNeo
diff options
context:
space:
mode:
Diffstat (limited to 'Sources/SwiftChessNeo')
-rw-r--r--Sources/SwiftChessNeo/Bitboard.swift646
-rw-r--r--Sources/SwiftChessNeo/Board.swift696
-rw-r--r--Sources/SwiftChessNeo/CastlingRights.swift371
-rw-r--r--Sources/SwiftChessNeo/Color.swift83
-rw-r--r--Sources/SwiftChessNeo/File.swift190
-rw-r--r--Sources/SwiftChessNeo/Game.swift825
-rw-r--r--Sources/SwiftChessNeo/InternalTypes.swift63
-rw-r--r--Sources/SwiftChessNeo/Move.swift203
-rw-r--r--Sources/SwiftChessNeo/PGN.swift454
-rw-r--r--Sources/SwiftChessNeo/PGNMove.swift219
-rw-r--r--Sources/SwiftChessNeo/Piece.swift297
-rw-r--r--Sources/SwiftChessNeo/Player.swift82
-rw-r--r--Sources/SwiftChessNeo/Rank.swift147
-rw-r--r--Sources/SwiftChessNeo/Sequence+Sage.swift33
-rw-r--r--Sources/SwiftChessNeo/Square.swift428
-rw-r--r--Sources/SwiftChessNeo/Tables.swift114
-rw-r--r--Sources/SwiftChessNeo/Variant.swift46
17 files changed, 4897 insertions, 0 deletions
diff --git a/Sources/SwiftChessNeo/Bitboard.swift b/Sources/SwiftChessNeo/Bitboard.swift
new file mode 100644
index 0000000..f8c3dba
--- /dev/null
+++ b/Sources/SwiftChessNeo/Bitboard.swift
@@ -0,0 +1,646 @@
+//
+// Bitboard.swift
+// Sage
+//
+// Copyright 2016-2017 Nikolai Vazquez
+// Modified by SuperGeroy
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+/// A lookup table of least significant bit indices.
+private let _lsbTable: [Int] = [00, 01, 48, 02, 57, 49, 28, 03,
+ 61, 58, 50, 42, 38, 29, 17, 04,
+ 62, 55, 59, 36, 53, 51, 43, 22,
+ 45, 39, 33, 30, 24, 18, 12, 05,
+ 63, 47, 56, 27, 60, 41, 37, 16,
+ 54, 35, 52, 21, 44, 32, 23, 11,
+ 46, 26, 40, 15, 34, 20, 31, 10,
+ 25, 14, 19, 09, 13, 08, 07, 06]
+
+/// A lookup table of most significant bit indices.
+private let _msbTable: [Int] = [00, 47, 01, 56, 48, 27, 02, 60,
+ 57, 49, 41, 37, 28, 16, 03, 61,
+ 54, 58, 35, 52, 50, 42, 21, 44,
+ 38, 32, 29, 23, 17, 11, 04, 62,
+ 46, 55, 26, 59, 40, 36, 15, 53,
+ 34, 51, 20, 43, 31, 22, 10, 45,
+ 25, 39, 14, 33, 19, 30, 09, 24,
+ 13, 18, 08, 12, 07, 06, 05, 63]
+
+/// A lookup table of bitboards for all squares.
+private let _bitboardTable: [Bitboard] = (0 ..< 64).map { Bitboard(rawValue: 1 << $0) }
+
+/// The De Bruijn multiplier.
+private let _debruijn64: UInt64 = 0x03f79d71b4cb0a89
+
+/// Returns the index of the lsb value.
+private func _index(lsb value: Bitboard) -> Int? {
+ guard value != 0 else {
+ return nil
+ }
+ return _lsbTable[Int((value.rawValue &* _debruijn64) >> 58)]
+}
+
+/// Mask for bits not in File A.
+private let _notFileA: Bitboard = 0xfefefefefefefefe
+
+/// Mask for bits not in Files A and B.
+private let _notFileAB: Bitboard = 0xfcfcfcfcfcfcfcfc
+
+/// Mask for bits not in File H.
+private let _notFileH: Bitboard = 0x7f7f7f7f7f7f7f7f
+
+/// Mask for bits not in Files G and H.
+private let _notFileGH: Bitboard = 0x3f3f3f3f3f3f3f3f
+
+/// A bitmap of sixty-four bits suitable for storing squares for various pieces.
+///
+/// The first bit refers to `Square.A1` the last (64th) bit refers to `Square.H8`.
+///
+/// Due to their compact nature, bitboards can store information such as positions in memory very efficiently. Bitboards
+/// can also be used to answer questions about the state of a `Board` quickly with very few operations.
+///
+/// Bitboards are used internally within `Board` to store positions for all twelve cases of `Piece`.
+///
+/// - see also: [Bitboard (Wikipedia)](https://en.wikipedia.org/wiki/Bitboard ),
+/// [Bitboards (Chess Programming Wiki)](https://chessprogramming.org/Bitboards)
+public struct Bitboard: RawRepresentable, Hashable, CustomStringConvertible {
+
+ /// A bitboard shift direction.
+ public enum ShiftDirection {
+
+ /// North direction.
+ case north
+
+ /// South direction.
+ case south
+
+ /// East direction.
+ case east
+
+ /// West direction.
+ case west
+
+ /// Northeast direction.
+ case northeast
+
+ /// Southeast direction.
+ case southeast
+
+ /// Northwest direction.
+ case northwest
+
+ /// Southwest direction.
+ case southwest
+
+ /// North regardless of Swift version.
+ internal static let _north = ShiftDirection.north
+
+ /// South regardless of Swift version.
+ internal static let _south = ShiftDirection.south
+
+ /// East regardless of Swift version.
+ internal static let _east = ShiftDirection.east
+
+ /// West regardless of Swift version.
+ internal static let _west = ShiftDirection.west
+
+ /// Northeast regardless of Swift version.
+ internal static let _northeast = ShiftDirection.northeast
+
+ /// Southeast regardless of Swift version.
+ internal static let _southeast = ShiftDirection.southeast
+
+ /// Northwest regardless of Swift version.
+ internal static let _northwest = ShiftDirection.northwest
+
+ /// Southwest regardless of Swift version.
+ internal static let _southwest = ShiftDirection.southwest
+
+ }
+
+ /// An iterator for the squares of a `Bitboard`.
+ public struct Iterator: IteratorProtocol {
+
+ fileprivate var _bitboard: Bitboard
+
+ /// Advances and returns the next element of the underlying sequence, or
+ /// `nil` if no next element exists.
+ public mutating func next() -> Square? {
+ return _bitboard.popLSBSquare()
+ }
+
+ }
+
+ /// The empty bitset.
+ public static var allZeros: Bitboard {
+ return Bitboard(rawValue: 0)
+ }
+
+ /// The edges of a board.
+ public static let edges: Bitboard = 0xff818181818181ff
+
+ /// The corresponding value of the "raw" type.
+ ///
+ /// `Self(rawValue: self.rawValue)!` is equivalent to `self`.
+ public var rawValue: UInt64
+
+ /// A textual representation of `self`.
+ public var description: String {
+ let num = String(rawValue, radix: 16)
+ let str = repeatElement("0", count: 16 - num.count).joined(separator: "")
+ return "Bitboard(0x\(str + num))"
+ }
+
+ /// The hash value.
+ public var hashValue: Int {
+ return rawValue.hashValue
+ }
+
+ /// An ASCII art representation of `self`.
+ ///
+ /// The ASCII representation for the starting board's bitboard:
+ ///
+ /// ```
+ /// +-----------------+
+ /// 8 | 1 1 1 1 1 1 1 1 |
+ /// 7 | 1 1 1 1 1 1 1 1 |
+ /// 6 | . . . . . . . . |
+ /// 5 | . . . . . . . . |
+ /// 4 | . . . . . . . . |
+ /// 3 | . . . . . . . . |
+ /// 2 | 1 1 1 1 1 1 1 1 |
+ /// 1 | 1 1 1 1 1 1 1 1 |
+ /// +-----------------+
+ /// a b c d e f g h
+ /// ```
+ public var ascii: String {
+ let edge = " +-----------------+\n"
+ var result = edge
+ let ranks = Rank.all.reversed()
+ for rank in ranks {
+ let strings = File.all.map({ file in self[(file, rank)] ? "1" : "." })
+ let str = strings.joined(separator: " ")
+ result += "\(rank) | \(str) |\n"
+ }
+ result += "\(edge) a b c d e f g h "
+ return result
+ }
+
+ /// The number of bits set in `self`.
+ public var count: Int {
+ var n = rawValue
+ n = n - ((n >> 1) & 0x5555555555555555)
+ n = (n & 0x3333333333333333) + ((n >> 2) & 0x3333333333333333)
+ return Int((((n + (n >> 4)) & 0xF0F0F0F0F0F0F0F) &* 0x101010101010101) >> 56)
+ }
+
+ /// `true` if `self` is empty.
+ public var isEmpty: Bool {
+ return self == 0
+ }
+
+ /// `self` has more than one bit set.
+ public var hasMoreThanOne: Bool {
+ return rawValue & (rawValue &- 1) != 0
+ }
+
+ /// The least significant bit.
+ public var lsb: Bitboard {
+ return Bitboard(rawValue: rawValue & (0 &- rawValue))
+ }
+
+ /// The index for the least significant bit of `self`.
+ public var lsbIndex: Int? {
+ return _index(lsb: lsb)
+ }
+
+ /// The square for the least significant bit of `self`.
+ public var lsbSquare: Square? {
+ return lsbIndex.flatMap({ Square(rawValue: $0) })
+ }
+
+ private var _msbShifted: UInt64 {
+ var x = rawValue
+ x |= x >> 1
+ x |= x >> 2
+ x |= x >> 4
+ x |= x >> 8
+ x |= x >> 16
+ x |= x >> 32
+ return x
+ }
+
+ /// The most significant bit.
+ public var msb: Bitboard {
+ return Bitboard(rawValue: (_msbShifted >> 1) + 1)
+ }
+
+ /// The index for the most significant bit of `self`.
+ public var msbIndex: Int? {
+ guard rawValue != 0 else {
+ return nil
+ }
+ return _msbTable[Int((_msbShifted &* _debruijn64) >> 58)]
+ }
+
+ /// The square for the most significant bit of `self`.
+ public var msbSquare: Square? {
+ return msbIndex.flatMap({ Square(rawValue: $0) })
+ }
+
+ /// Convert from a raw value of `UInt64`.
+ public init(rawValue: UInt64) {
+ self.rawValue = rawValue
+ }
+
+ /// Create an empty bitboard.
+ public init() {
+ rawValue = 0
+ }
+
+ /// Create a starting bitboard for `piece`.
+ public init(startFor piece: Piece) {
+ let value: Bitboard
+ switch piece.kind {
+ case .pawn: value = 0xFF00
+ case .knight: value = 0x0042
+ case .bishop: value = 0x0024
+ case .rook: value = 0x0081
+ case .queen: value = 0x0008
+ case .king: value = 0x0010
+ }
+ self = piece.color.isWhite ? value : value << (piece.kind.isPawn ? 40 : 56)
+ }
+
+ /// Create a bitboard from `squares`.
+ public init<S: Sequence>(squares: S) where S.Iterator.Element == Square {
+ rawValue = squares.reduce(0) { $0 | (1 << UInt64($1.rawValue)) }
+ }
+
+ /// Create a bitboard from `locations`.
+ public init<S: Sequence>(locations: S) where S.Iterator.Element == Location {
+ self.init(squares: locations.map(Square.init(location:)))
+ }
+
+ /// Create a bitboard from the start and end of `move`.
+ public init(move: Move) {
+ self.init(squares: [move.start, move.end])
+ }
+
+ /// Create a bitboard mask for `file`.
+ public init(file: File) {
+ switch file {
+ case .a: rawValue = 0x0101010101010101
+ case .b: rawValue = 0x0202020202020202
+ case .c: rawValue = 0x0404040404040404
+ case .d: rawValue = 0x0808080808080808
+ case .e: rawValue = 0x1010101010101010
+ case .f: rawValue = 0x2020202020202020
+ case .g: rawValue = 0x4040404040404040
+ case .h: rawValue = 0x8080808080808080
+ }
+ }
+
+ /// Create a bitboard mask for `rank`.
+ public init(rank: Rank) {
+ rawValue = 0xFF << (UInt64(rank.index) * 8)
+ }
+
+ /// Create a bitboard mask for `square`.
+ ///
+ /// - complexity: O(1).
+ public init(square: Square) {
+ self = _bitboardTable[square.rawValue]
+ }
+
+ /// The `Bool` value for the bit at `square`.
+ ///
+ /// - complexity: O(1).
+ public subscript(square: Square) -> Bool {
+ get {
+ return intersects(_bitboardTable[square.rawValue])
+ }
+ set {
+ let bit = Bitboard(square: square)
+ if newValue {
+ rawValue |= bit.rawValue
+ } else {
+ rawValue &= ~bit.rawValue
+ }
+ }
+ }
+
+ /// The `Bool` value for the bit at `location`.
+ ///
+ /// - complexity: O(1).
+ public subscript(location: Location) -> Bool {
+ get {
+ return self[Square(location: location)]
+ }
+ set {
+ self[Square(location: location)] = newValue
+ }
+ }
+
+ /// Returns the pawn pushes available for `color` in `self`.
+ internal func _pawnPushes(for color: Color, empty: Bitboard) -> Bitboard {
+ return (color.isWhite ? shifted(toward: ._north) : shifted(toward: ._south)) & empty
+ }
+
+ /// Returns the attacks available to the pawns for `color` in `self`.
+ internal func _pawnAttacks(for color: Color) -> Bitboard {
+ if color.isWhite {
+ return shifted(toward: ._northeast) | shifted(toward: ._northwest)
+ } else {
+ return shifted(toward: ._southeast) | shifted(toward: ._southwest)
+ }
+ }
+
+ /// Returns the attacks available to the knight in `self`.
+ internal func _knightAttacks() -> Bitboard {
+ let x = self
+ let a = ((x << 17) | (x >> 15)) & _notFileA
+ let b = ((x << 10) | (x >> 06)) & _notFileAB
+ let c = ((x << 15) | (x >> 17)) & _notFileH
+ let d = ((x << 06) | (x >> 10)) & _notFileGH
+ return a | b | c | d
+ }
+
+ /// Returns the attacks available to the bishop in `self`.
+ internal func _bishopAttacks(stoppers bitboard: Bitboard = 0) -> Bitboard {
+ return filled(toward: ._northeast, stoppers: bitboard).shifted(toward: ._northeast)
+ | filled(toward: ._northwest, stoppers: bitboard).shifted(toward: ._northwest)
+ | filled(toward: ._southeast, stoppers: bitboard).shifted(toward: ._southeast)
+ | filled(toward: ._southwest, stoppers: bitboard).shifted(toward: ._southwest)
+ }
+
+ /// Returns the attacks available to the rook in `self`.
+ internal func _rookAttacks(stoppers bitboard: Bitboard = 0) -> Bitboard {
+ return filled(toward: ._north, stoppers: bitboard).shifted(toward: ._north)
+ | filled(toward: ._south, stoppers: bitboard).shifted(toward: ._south)
+ | filled(toward: ._east, stoppers: bitboard).shifted(toward: ._east)
+ | filled(toward: ._west, stoppers: bitboard).shifted(toward: ._west)
+ }
+
+ /// Returns the x-ray attacks available to the bishop in `self`.
+ internal func _xrayBishopAttacks(occupied occ: Bitboard, stoppers: Bitboard) -> Bitboard {
+ let attacks = _bishopAttacks(stoppers: occ)
+ return attacks ^ _bishopAttacks(stoppers: (stoppers & attacks) ^ stoppers)
+ }
+
+ /// Returns the x-ray attacks available to the rook in `self`.
+ internal func _xrayRookAttacks(occupied occ: Bitboard, stoppers: Bitboard) -> Bitboard {
+ let attacks = _rookAttacks(stoppers: occ)
+ return attacks ^ _rookAttacks(stoppers: (stoppers & attacks) ^ stoppers)
+ }
+
+ /// Returns the attacks available to the queen in `self`.
+ internal func _queenAttacks(stoppers bitboard: Bitboard = 0) -> Bitboard {
+ return _rookAttacks(stoppers: bitboard) | _bishopAttacks(stoppers: bitboard)
+ }
+
+ /// Returns the attacks available to the king in `self`.
+ internal func _kingAttacks() -> Bitboard {
+ let attacks = shifted(toward: ._east) | shifted(toward: ._west)
+ let bitboard = self | attacks
+ return attacks
+ | bitboard.shifted(toward: ._north)
+ | bitboard.shifted(toward: ._south)
+ }
+
+ /// Returns the attacks available to `piece` in `self`.
+ internal func _attacks(for piece: Piece, stoppers: Bitboard = 0) -> Bitboard {
+ switch piece.kind {
+ case .pawn:
+ return _pawnAttacks(for: piece.color)
+ case .knight:
+ return _knightAttacks()
+ case .bishop:
+ return _bishopAttacks(stoppers: stoppers)
+ case .rook:
+ return _rookAttacks(stoppers: stoppers)
+ case .queen:
+ return _queenAttacks(stoppers: stoppers)
+ case .king:
+ return _kingAttacks()
+ }
+ }
+
+ /// Returns `true` if `self` intersects `other`.
+ public func intersects(_ other: Bitboard) -> Bool {
+ return rawValue & other.rawValue != 0
+ }
+
+ /// Returns `self` flipped horizontally.
+ public func flippedHorizontally() -> Bitboard {
+ let x = 0x5555555555555555 as Bitboard
+ let y = 0x3333333333333333 as Bitboard
+ let z = 0x0F0F0F0F0F0F0F0F as Bitboard
+ var n = self
+ n = ((n >> 1) & x) | ((n & x) << 1)
+ n = ((n >> 2) & y) | ((n & y) << 2)
+ n = ((n >> 4) & z) | ((n & z) << 4)
+ return n
+ }
+
+ /// Returns `self` flipped vertically.
+ public func flippedVertically() -> Bitboard {
+ let x = 0x00FF00FF00FF00FF as Bitboard
+ let y = 0x0000FFFF0000FFFF as Bitboard
+ var n = self
+ n = ((n >> 8) & x) | ((n & x) << 8)
+ n = ((n >> 16) & y) | ((n & y) << 16)
+ n = (n >> 32) | (n << 32)
+ return n
+ }
+
+ /// Returns the bits of `self` filled toward `direction` stopped by `stoppers`.
+ public func filled(toward direction: ShiftDirection, stoppers: Bitboard) -> Bitboard {
+ let empty = ~stoppers
+ var bitboard = self
+ for _ in 0 ..< 7 {
+ bitboard |= empty & bitboard.shifted(toward: direction)
+ }
+ return bitboard
+ }
+
+ /// Returns the bits of `self` shifted once toward `direction`.
+ public func shifted(toward direction: ShiftDirection) -> Bitboard {
+ switch direction {
+ case .north: return self << 8
+ case .south: return self >> 8
+ case .east: return (self << 1) & _notFileA
+ case .northeast: return (self << 9) & _notFileA
+ case .southeast: return (self >> 7) & _notFileA
+ case .west: return (self >> 1) & _notFileH
+ case .southwest: return (self >> 9) & _notFileH
+ case .northwest: return (self << 7) & _notFileH
+ }
+ }
+
+ /// Flips `self` horizontally.
+ public mutating func flipHorizontally() {
+ self = flippedHorizontally()
+ }
+
+ /// Flips `self` vertically.
+ public mutating func flipVertically() {
+ self = flippedVertically()
+ }
+
+ /// Shifts the bits of `self` once toward `direction`.
+ public mutating func shift(toward direction: ShiftDirection) {
+ self = shifted(toward: direction)
+ }
+
+ /// Fills the bits of `self` toward `direction` stopped by `stoppers`.
+ public mutating func fill(toward direction: ShiftDirection, stoppers: Bitboard = 0) {
+ self = filled(toward: direction, stoppers: stoppers)
+ }
+
+ /// Swaps the bits between the two squares.
+ public mutating func swap(_ first: Square, _ second: Square) {
+ (self[first], self[second]) = (self[second], self[first])
+ }
+
+ /// Removes the least significant bit and returns it.
+ public mutating func popLSB() -> Bitboard {
+ let lsb = self.lsb
+ rawValue -= lsb.rawValue
+ return lsb
+ }
+
+ /// Removes the least significant bit and returns its index, if any.
+ public mutating func popLSBIndex() -> Int? {
+ return _index(lsb: popLSB())
+ }
+
+ /// Removes the least significant bit and returns its square, if any.
+ public mutating func popLSBSquare() -> Square? {
+ return popLSBIndex().flatMap({ Square(rawValue: $0) })
+ }
+
+ /// Removes the most significant bit and returns it.
+ public mutating func popMSB() -> Bitboard {
+ let msb = self.msb
+ rawValue -= msb.rawValue
+ return msb
+ }
+
+ /// Removes the most significant bit and returns its index, if any.
+ public mutating func popMSBIndex() -> Int? {
+ guard rawValue != 0 else { return nil }
+ let shifted = _msbShifted
+ rawValue -= (shifted >> 1) + 1
+ return _msbTable[Int((shifted &* _debruijn64) >> 58)]
+ }
+
+ /// Removes the most significant bit and returns its square, if any.
+ public mutating func popMSBSquare() -> Square? {
+ return popMSBIndex().flatMap({ Square(rawValue: $0) })
+ }
+
+ /// Returns the ranks of `self` as eight 8-bit integers.
+ public func ranks() -> [UInt8] {
+ return (0 ..< 8).map { UInt8((rawValue >> ($0 * 8)) & 255) }
+ }
+
+}
+
+extension Bitboard: Sequence {
+
+ /// A value less than or equal to the number of elements in
+ /// the sequence, calculated nondestructively.
+ ///
+ /// - complexity: O(1).
+ public var underestimatedCount: Int {
+ return count
+ }
+
+ /// Returns a Boolean value indicating whether the sequence contains the
+ /// given element.
+ ///
+ /// - complexity: O(1).
+ public func contains(_ element: Square) -> Bool {
+ return self[element]
+ }
+
+ /// Returns an iterator over the squares of the board.
+ public func makeIterator() -> Iterator {
+ return Iterator(_bitboard: self)
+ }
+
+}
+
+extension Bitboard: ExpressibleByIntegerLiteral {
+
+ /// Create an instance initialized to `value`.
+ public init(integerLiteral value: UInt64) {
+ rawValue = value
+ }
+}
+
+/// Returns the intersection of bits set in `lhs` and `rhs`.
+///
+/// - complexity: O(1).
+public func & (lhs: Bitboard, rhs: Bitboard) -> Bitboard {
+ return Bitboard(rawValue: lhs.rawValue & rhs.rawValue)
+}
+
+/// Returns the union of bits set in `lhs` and `rhs`.
+///
+/// - complexity: O(1).
+public func | (lhs: Bitboard, rhs: Bitboard) -> Bitboard {
+ return Bitboard(rawValue: lhs.rawValue | rhs.rawValue)
+}
+
+/// Stores the result of performing a bitwise OR operation on the two given values in the left-hand-side variable.
+public func |= (lhs: inout Bitboard, rhs: Bitboard) {
+ lhs.rawValue |= rhs.rawValue
+}
+
+/// Returns the bits that are set in exactly one of `lhs` and `rhs`.
+///
+/// - complexity: O(1).
+public func ^ (lhs: Bitboard, rhs: Bitboard) -> Bitboard {
+ return Bitboard(rawValue: lhs.rawValue ^ rhs.rawValue)
+}
+
+/// Returns `x ^ ~Self.allZeros`.
+///
+/// - complexity: O(1).
+public prefix func ~ (x: Bitboard) -> Bitboard {
+ return Bitboard(rawValue: ~x.rawValue)
+}
+
+/// Returns the bits of `lhs` shifted right by `rhs`.
+public func >> (lhs: Bitboard, rhs: Bitboard) -> Bitboard {
+ return Bitboard(rawValue: lhs.rawValue >> rhs.rawValue)
+}
+
+/// Returns the bits of `lhs` shifted left by `rhs`.
+public func << (lhs: Bitboard, rhs: Bitboard) -> Bitboard {
+ return Bitboard(rawValue: lhs.rawValue << rhs.rawValue)
+}
+
+/// Shifts the bits of `lhs` right by `rhs`.
+public func >>= (lhs: inout Bitboard, rhs: Bitboard) {
+ lhs.rawValue >>= rhs.rawValue
+}
+
+/// Shifts the bits of `lhs` left by `rhs`.
+public func <<= (lhs: inout Bitboard, rhs: Bitboard) {
+ lhs.rawValue <<= rhs.rawValue
+}
diff --git a/Sources/SwiftChessNeo/Board.swift b/Sources/SwiftChessNeo/Board.swift
new file mode 100644
index 0000000..d9d9b8f
--- /dev/null
+++ b/Sources/SwiftChessNeo/Board.swift
@@ -0,0 +1,696 @@
+//
+// Board.swift
+// Sage
+//
+// Copyright 2016-2017 Nikolai Vazquez
+// Modified by SuperGeroy
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#if os(OSX)
+ import Cocoa
+#elseif os(iOS) || os(tvOS)
+ import UIKit
+#endif
+
+/// A chess board used to map `Square`s to `Piece`s.
+///
+/// Pieces map to separate instances of `Bitboard` which can be retrieved with `bitboard(for:)`.
+public struct Board: Hashable, CustomStringConvertible {
+
+ /// A chess board space.
+ public struct Space: Hashable, CustomStringConvertible {
+
+ /// The occupying chess piece.
+ public var piece: Piece?
+
+ /// The space's file.
+ public var file: File
+
+ /// The space's rank.
+ public var rank: Rank
+
+ /// The space's location on a chess board.
+ public var location: Location {
+ get {
+ return (file, rank)
+ }
+ set {
+ (file, rank) = newValue
+ }
+ }
+
+ /// The space's square on a chess board.
+ public var square: Square {
+ get {
+ return Square(file: file, rank: rank)
+ }
+ set {
+ location = newValue.location
+ }
+ }
+
+ /// The space's color.
+ public var color: Color {
+ return (file.index & 1 != rank.index & 1) ? .white : .black
+ }
+
+ /// The space's name.
+ public var name: String {
+ return "\(file.character)\(rank.rawValue)"
+ }
+
+ /// A textual representation of `self`.
+ public var description: String {
+ return "Space(\(name), \(piece._altDescription))"
+ }
+
+ /// The hash value.
+// public var hashValue: Int {
+// let pieceHash = piece?.hashValue ?? (6 << 1)
+// let fileHash = file.hashValue << 4
+// let rankHash = rank.hashValue << 7
+// return pieceHash + fileHash + rankHash
+// }
+
+ public func hash(into hasher: inout Hasher) {
+ hasher.combine(piece)
+ hasher.combine(file)
+ hasher.combine(rank)
+ }
+
+ /// Create a chess board space with a piece, file, and rank.
+ public init(piece: Piece? = nil, file: File, rank: Rank) {
+ self.init(piece: piece, location: (file, rank))
+ }
+
+ /// Create a chess board space with a piece and location.
+ public init(piece: Piece? = nil, location: Location) {
+ self.piece = piece
+ (file, rank) = location
+ }
+
+ /// Create a chess board space with a piece and square.
+ public init(piece: Piece? = nil, square: Square) {
+ self.piece = piece
+ (file, rank) = square.location
+ }
+
+ /// Clears the piece from the space and returns it.
+ public mutating func clear() -> Piece? {
+ let piece = self.piece
+ self.piece = nil
+ return piece
+ }
+
+ #if os(OSX) || os(iOS) || os(tvOS)
+
+ internal func _view(size: CGFloat) -> _View {
+ #if os(OSX)
+ let rectY = CGFloat(rank.index) * size
+ #else
+ let rectY = CGFloat(7 - rank.index) * size
+ #endif
+ let frame = CGRect(x: CGFloat(file.index) * size,
+ y: rectY,
+ width: size,
+ height: size)
+ var textFrame = CGRect(x: 0, y: 0, width: size, height: size)
+ let fontSize = size * 0.625
+ let view = _View(frame: frame)
+ let str = piece.map({ String($0.specialCharacter(background: color)) }) ?? ""
+ let white = _Color.white
+ let black = _Color.black
+ let bg: _Color = color.isWhite ? white : black
+ let tc: _Color = color.isWhite ? black : white
+ #if os(OSX)
+ view.wantsLayer = true
+ let text = NSText(frame: textFrame)
+ view.layer?.backgroundColor = bg.cgColor
+ text.alignment = .center
+ text.font = .systemFont(ofSize: fontSize)
+ text.isEditable = false
+ text.isSelectable = false
+ text.string = str
+ text.drawsBackground = false
+ text.textColor = tc
+ view.addSubview(text)
+ #else
+ view.backgroundColor = bg
+ let label = UILabel(frame: textFrame)
+ label.textAlignment = .center
+ label.font = .systemFont(ofSize: fontSize)
+ label.text = str
+ label.textColor = tc
+ view.addSubview(label)
+ #endif
+ return view
+ }
+
+ #endif
+
+ }
+
+ /// An iterator for the spaces of a chess board.
+ public struct Iterator: IteratorProtocol {
+
+ let _board: Board
+
+ var _index: Int
+
+ fileprivate init(_ board: Board) {
+ self._board = board
+ self._index = 0
+ }
+
+ /// Advances to the next space on the board and returns it.
+ public mutating func next() -> Board.Space? {
+ guard let square = Square(rawValue: _index) else {
+ return nil
+ }
+ defer { _index += 1 }
+ return _board.space(at: square)
+ }
+
+ }
+
+ /// A board side.
+ public enum Side {
+
+ /// Right side of the board.
+ case kingside
+
+ /// Right side of the board.
+ case queenside
+
+ /// `self` is kingside.
+ public var isKingside: Bool {
+ return self == .kingside
+ }
+
+ /// `self` is queenside.
+ public var isQueenside: Bool {
+ return self == .queenside
+ }
+
+ }
+
+ /// The bitboards of `self`.
+ internal var _bitboards: [Bitboard]
+
+ /// The board's pieces.
+ public var pieces: [Piece] {
+ return self.compactMap({ $0.piece })
+ }
+
+ /// The board's white pieces.
+ public var whitePieces: [Piece] {
+ return pieces.filter({ $0.color.isWhite })
+ }
+
+ /// The board's black pieces.
+ public var blackPieces: [Piece] {
+ return pieces.filter({ $0.color.isBlack })
+ }
+
+ /// A bitboard for the occupied spaces of `self`.
+ public var occupiedSpaces: Bitboard {
+ return _bitboards.reduce(0, |)
+ }
+
+ /// A bitboard for the empty spaces of `self`.
+ public var emptySpaces: Bitboard {
+ return ~occupiedSpaces
+ }
+
+ /// A textual representation of `self`.
+ public var description: String {
+ return "Board(\(fen()))"
+ }
+
+ /// The hash value.
+ public func hash(into hasher: inout Hasher) {
+ _bitboards.forEach { hasher.combine($0) }
+ }
+
+ /// An ASCII art representation of `self`.
+ ///
+ /// The ASCII representation for the starting board:
+ ///
+ /// ```
+ /// +-----------------+
+ /// 8 | r n b q k b n r |
+ /// 7 | p p p p p p p p |
+ /// 6 | . . . . . . . . |
+ /// 5 | . . . . . . . . |
+ /// 4 | . . . . . . . . |
+ /// 3 | . . . . . . . . |
+ /// 2 | P P P P P P P P |
+ /// 1 | R N B Q K B N R |
+ /// +-----------------+
+ /// a b c d e f g h
+ /// ```
+ public var ascii: String {
+ let edge = " +-----------------+\n"
+ var result = edge
+ let reversed = Rank.all.reversed()
+ for rank in reversed {
+ let strings = File.all.map({ file in "\(self[(file, rank)]?.character ?? ".")" })
+ let str = strings.joined(separator: " ")
+ result += "\(rank) | \(str) |\n"
+ }
+ result += "\(edge) a b c d e f g h "
+ return result
+ }
+
+ /// Create a chess board.
+ ///
+ /// - parameter variant: The variant to populate the board for. Won't populate if `nil`. Default is `Standard`.
+ public init(variant: Variant? = .standard) {
+ _bitboards = Array(repeating: 0, count: 12)
+ if let variant = variant {
+ for piece in Piece.all {
+ _bitboards[piece.rawValue] = Bitboard(startFor: piece)
+ }
+ if variant.isUpsideDown {
+ for index in _bitboards.indices {
+ _bitboards[index].flipVertically()
+ }
+ }
+ }
+ }
+
+ /// Create a chess board from a valid FEN string.
+ ///
+ /// - Warning: Only to be used with the board part of a full FEN string.
+ ///
+ /// - see also: [FEN (Wikipedia)](https://en.wikipedia.org/wiki/Forsyth%E2%80%93Edwards_Notation),
+ /// [FEN (Chess Programming Wiki)](https://chessprogramming.org/Forsyth-Edwards+Notation)
+ public init?(fen: String) {
+ func pieces(for string: String) -> [Piece?]? {
+ var pieces: [Piece?] = []
+ for char in string {
+ guard pieces.count < 8 else {
+ return nil
+ }
+ if let piece = Piece(character: char) {
+ pieces.append(piece)
+ } else if let num = Int(String(char)) {
+ guard 1...8 ~= num else { return nil }
+ pieces += Array(repeating: nil, count: num)
+ } else {
+ return nil
+ }
+ }
+ return pieces
+ }
+ guard !fen.contains(" ") else {
+ return nil
+ }
+ let parts = fen.split(separator: "/").map(String.init)
+ let ranks = Rank.all.reversed()
+ guard parts.count == 8 else {
+ return nil
+ }
+ var board = Board(variant: nil)
+ for (rank, part) in zip(ranks, parts) {
+ guard let pieces = pieces(for: part) else {
+ return nil
+ }
+ for (file, piece) in zip(File.all, pieces) {
+ board[(file, rank)] = piece
+ }
+ }
+ self = board
+ }
+
+ /// Create a chess board from arrays of piece characters.
+ ///
+ /// Returns `nil` if a piece can't be initialized from a character. Characters beyond the 8x8 area are ignored.
+ /// Empty spaces are denoted with a whitespace or period.
+ ///
+ /// ```swift
+ /// Board(pieces: [["r", "n", "b", "q", "k", "b", "n", "r"],
+ /// ["p", "p", "p", "p", "p", "p", "p", "p"],
+ /// [" ", " ", " ", " ", " ", " ", " ", " "],
+ /// [" ", " ", " ", " ", " ", " ", " ", " "],
+ /// [" ", " ", " ", " ", " ", " ", " ", " "],
+ /// [" ", " ", " ", " ", " ", " ", " ", " "],
+ /// ["P", "P", "P", "P", "P", "P", "P", "P"],
+ /// ["R", "N", "B", "Q", "K", "B", "N", "R"]])
+ /// ```
+ public init?(pieces: [[Character]]) {
+ self.init(variant: nil)
+ for rankIndex in pieces.indices {
+ guard let rank = Rank(index: rankIndex)?.opposite() else { break }
+ for fileIndex in pieces[rankIndex].indices {
+ guard let file = File(index: fileIndex) else { break }
+ let pieceChar = pieces[rankIndex][fileIndex]
+ if pieceChar != " " && pieceChar != "." {
+ guard let piece = Piece(character: pieceChar) else { return nil }
+ self[(file, rank)] = piece
+ }
+ }
+ }
+ }
+
+ /// Gets and sets a piece at `location`.
+ public subscript(location: Location) -> Piece? {
+ get {
+ return self[Square(location: location)]
+ }
+ set {
+ self[Square(location: location)] = newValue
+ }
+ }
+
+ /// Gets and sets a piece at `square`.
+ public subscript(square: Square) -> Piece? {
+ get {
+ for index in _bitboards.indices {
+ if _bitboards[index][square] {
+ return Piece(value: index)
+ }
+ }
+ return nil
+ }
+ set {
+ for index in _bitboards.indices {
+ _bitboards[index][square] = false
+ }
+ if let piece = newValue {
+ self[piece][square] = true
+ }
+ }
+ }
+
+ /// Gets and sets the bitboard for `piece`.
+ internal subscript(piece: Piece) -> Bitboard {
+ get {
+ return _bitboards[piece.rawValue]
+ }
+ set {
+ _bitboards[piece.rawValue] = newValue
+ }
+ }
+
+ /// Returns `self` flipped horizontally.
+ public func flippedHorizontally() -> Board {
+ var board = self
+ for index in _bitboards.indices {
+ board._bitboards[index].flipHorizontally()
+ }
+ return board
+ }
+
+ /// Returns `self` flipped vertically.
+ public func flippedVertically() -> Board {
+ var board = self
+ for index in _bitboards.indices {
+ board._bitboards[index].flipVertically()
+ }
+ return board
+ }
+
+ /// Clears all the pieces from `self`.
+ public mutating func clear() {
+ self = Board(variant: nil)
+ }
+
+ /// Populates `self` with all of the pieces at their proper locations for the given chess variant.
+ public mutating func populate(for variant: Variant = .standard) {
+ self = Board(variant: variant)
+ }
+
+ /// Flips `self` horizontally.
+ public mutating func flipHorizontally() {
+ self = flippedHorizontally()
+ }
+
+ /// Flips `self` vertically.
+ public mutating func flipVertically() {
+ self = flippedVertically()
+ }
+
+ /// Returns the number of pieces for `color`, or all if `nil`.
+ public func pieceCount(for color: Color? = nil) -> Int {
+ if let color = color {
+ return bitboard(for: color).count
+ } else {
+ return _bitboards.reduce(0) { $0 + $1.count }
+ }
+ }
+
+ /// Returns the number of `piece` in `self`.
+ public func count(of piece: Piece) -> Int {
+ return bitboard(for: piece).count
+ }
+
+ /// Returns the bitboard for `piece`.
+ public func bitboard(for piece: Piece) -> Bitboard {
+ return self[piece]
+ }
+
+ /// Returns the bitboard for `color`.
+ public func bitboard(for color: Color) -> Bitboard {
+ return Piece._hashes(for: color).reduce(0) { $0 | _bitboards[$1] }
+ }
+
+ /// The squares with pinned pieces for `color`.
+ public func pinned(for color: Color) -> Bitboard {
+ guard let kingSquare = squareForKing(for: color) else {
+ return 0
+ }
+ let occupied = occupiedSpaces
+ var pinned = Bitboard()
+ let pieces = bitboard(for: color)
+ let king = bitboard(for: Piece(king: color))
+ let opRQ = bitboard(for: Piece(rook: color.inverse())) | bitboard(for: Piece(queen: color.inverse()))
+ let opBQ = bitboard(for: Piece(bishop: color.inverse())) | bitboard(for: Piece(queen: color.inverse()))
+ for square in king._xrayRookAttacks(occupied: occupied, stoppers: pieces) & opRQ {
+ pinned |= square.between(kingSquare) & pieces
+ }
+ for square in king._xrayBishopAttacks(occupied: occupied, stoppers: pieces) & opBQ {
+ pinned |= square.between(kingSquare) & pieces
+ }
+ return pinned
+ }
+
+ /// Returns the attackers to `square` corresponding to `color`.
+ ///
+ /// - parameter square: The `Square` being attacked.
+ /// - parameter color: The `Color` of the attackers.
+ public func attackers(to square: Square, color: Color) -> Bitboard {
+ let all = occupiedSpaces
+ let attackPieces = Piece._nonQueens(for: color)
+ let playerPieces = Piece._nonQueens(for: color.inverse())
+ let attacks = playerPieces.map({ piece in
+ square.attacks(for: piece, stoppers: all)
+ })
+ let queens = (attacks[2] | attacks[3]) & self[Piece(queen: color)]
+ return zip(attackPieces, attacks).reduce(queens) { $0 | (self[$1.0] & $1.1) }
+ }
+
+ /// Returns the attackers to the king for `color`.
+ ///
+ /// - parameter color: The `Color` of the potentially attacked king.
+ ///
+ /// - returns: A bitboard of all attackers, or 0 if the king does not exist or if there are no pieces attacking the
+ /// king.
+ public func attackersToKing(for color: Color) -> Bitboard {
+ guard let square = squareForKing(for: color) else {
+ return 0
+ }
+ return attackers(to: square, color: color.inverse())
+ }
+
+ /// Returns `true` if the king for `color` is in check.
+ public func kingIsChecked(for color: Color) -> Bool {
+ return attackersToKing(for: color) != 0
+ }
+
+ /// Returns the spaces at `file`.
+ public func spaces(at file: File) -> [Space] {
+ return Rank.all.map { space(at: (file, $0)) }
+ }
+
+ /// Returns the spaces at `rank`.
+ public func spaces(at rank: Rank) -> [Space] {
+ return File.all.map { space(at: ($0, rank)) }
+ }
+
+ /// Returns the space at `location`.
+ public func space(at location: Location) -> Space {
+ return Space(piece: self[location], location: location)
+ }
+
+ /// Returns the square at `location`.
+ public func space(at square: Square) -> Space {
+ return Space(piece: self[square], square: square)
+ }
+
+ /// Removes a piece at `square`, and returns it.
+ @discardableResult
+ public mutating func removePiece(at square: Square) -> Piece? {
+ if let piece = self[square] {
+ self[piece][square] = false
+ return piece
+ } else {
+ return nil
+ }
+ }
+
+ /// Removes a piece at `location`, and returns it.
+ @discardableResult
+ public mutating func removePiece(at location: Location) -> Piece? {
+ return removePiece(at: Square(location: location))
+ }
+
+ /// Swaps the pieces between the two locations.
+ public mutating func swap(_ first: Location, _ second: Location) {
+ swap(Square(location: first), Square(location: second))
+ }
+
+ /// Swaps the pieces between the two squares.
+ public mutating func swap(_ first: Square, _ second: Square) {
+ switch (self[first], self[second]) {
+ case let (firstPiece?, secondPiece?):
+ self[firstPiece].swap(first, second)
+ self[secondPiece].swap(first, second)
+ case let (firstPiece?, nil):
+ self[firstPiece].swap(first, second)
+ case let (nil, secondPiece?):
+ self[secondPiece].swap(first, second)
+ default:
+ break
+ }
+ }
+
+ /// Returns the locations where `piece` exists.
+ public func locations(for piece: Piece) -> [Location] {
+ return bitboard(for: piece).map({ $0.location })
+ }
+
+ /// Returns the squares where `piece` exists.
+ public func squares(for piece: Piece) -> [Square] {
+ return Array(bitboard(for: piece))
+ }
+
+ /// Returns the squares where pieces for `color` exist.
+ public func squares(for color: Color) -> [Square] {
+ return Array(bitboard(for: color))
+ }
+
+ /// Returns the square of the king for `color`, if any.
+ public func squareForKing(for color: Color) -> Square? {
+ return bitboard(for: Piece(king: color)).lsbSquare
+ }
+
+ /// Returns `true` if `self` contains `piece`.
+ public func contains(_ piece: Piece) -> Bool {
+ return !self[piece].isEmpty
+ }
+
+ /// Returns the FEN string for the board.
+ ///
+ /// - see also: [FEN (Wikipedia)](https://en.wikipedia.org/wiki/Forsyth%E2%80%93Edwards_Notation),
+ /// [FEN (Chess Programming Wiki)](https://www.chessprogramming.org/Forsyth-Edwards_Notation)
+ public func fen() -> String {
+ func fen(forRank rank: Rank) -> String {
+ var fen = ""
+ var accumulator = 0
+ for space in spaces(at: rank) {
+ if let piece = space.piece {
+ if accumulator > 0 {
+ fen += String(accumulator)
+ accumulator = 0
+ }
+ fen += String(piece.character)
+ } else {
+ accumulator += 1
+ if space.file == ._h {
+ fen += String(accumulator)
+ }
+ }
+ }
+ return fen
+ }
+ return Rank.all.reversed().map(fen).joined(separator: "/")
+ }
+
+ public func toArray() -> [Board.Space] {
+ var array = [Board.Space]()
+ for space in self {
+ array.append(space)
+ }
+
+ return array.reversed()
+ }
+
+}
+
+extension Board: Sequence {
+
+ /// A value less than or equal to the number of elements in
+ /// the sequence, calculated nondestructively.
+ ///
+ /// - Complexity: O(1).
+ public var underestimatedCount: Int {
+ return 64
+ }
+
+ /// Returns an iterator over the spaces of the board.
+ public func makeIterator() -> Iterator {
+ return Iterator(self)
+ }
+
+}
+
+#if os(OSX) || os(iOS) || os(tvOS)
+
+extension Board: CustomPlaygroundDisplayConvertible {
+
+ /// Returns the `playgroundDescription` for `self`.
+ private var _playgroundDescription: _View {
+ let spaceSize: CGFloat = 80
+ let boardSize = spaceSize * 8
+ let frame = CGRect(x: 0, y: 0, width: boardSize, height: boardSize)
+ let view = _View(frame: frame)
+
+ for space in self {
+ view.addSubview(space._view(size: spaceSize))
+ }
+ return view
+ }
+
+ /// A custom playground description for this instance.
+ public var playgroundDescription: Any {
+ return _playgroundDescription
+ }
+
+}
+
+#endif
+
+/// Returns `true` if both boards are the same.
+public func == (lhs: Board, rhs: Board) -> Bool {
+ return lhs._bitboards == rhs._bitboards
+}
+
+/// Returns `true` if both spaces are the same.
+public func == (lhs: Board.Space, rhs: Board.Space) -> Bool {
+ return lhs.piece == rhs.piece
+ && lhs.file == rhs.file
+ && lhs.rank == rhs.rank
+}
diff --git a/Sources/SwiftChessNeo/CastlingRights.swift b/Sources/SwiftChessNeo/CastlingRights.swift
new file mode 100644
index 0000000..fc22c86
--- /dev/null
+++ b/Sources/SwiftChessNeo/CastlingRights.swift
@@ -0,0 +1,371 @@
+//
+// CastlingRights.swift
+// Sage
+//
+// Copyright 2016-2017 Nikolai Vazquez
+// Modified by SuperGeroy
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+/// Castling rights of a `Game`.
+///
+/// Defines whether a `Color` has the right to castle for a `Board.Side`.
+public struct CastlingRights: CustomStringConvertible {
+
+ /// A castling right.
+ public enum Right: String, CustomStringConvertible {
+
+ /// White can castle kingside.
+ case whiteKingside
+
+ /// White can castle queenside.
+ case whiteQueenside
+
+ /// Black can castle kingside.
+ case blackKingside
+
+ /// Black can castle queenside.
+ case blackQueenside
+
+ /// All rights.
+ public static let all: [Right] = [.whiteKingside, .whiteQueenside, .blackKingside, .blackQueenside]
+
+ /// White rights.
+ public static let white: [Right] = all.filter({ $0.color.isWhite })
+
+ /// Black rights.
+ public static let black: [Right] = all.filter({ $0.color.isBlack })
+
+ /// Kingside rights.
+ public static let kingside: [Right] = all.filter({ $0.side.isKingside })
+
+ /// Queenside rights.
+ public static let queenside: [Right] = all.filter({ $0.side.isQueenside })
+
+ /// The color for `self`.
+ public var color: Color {
+ get {
+ switch self {
+ case .whiteKingside, .whiteQueenside:
+ return .white
+ default:
+ return .black
+ }
+ }
+ set {
+ self = Right(color: newValue, side: side)
+ }
+ }
+
+ /// The board side for `self`.
+ public var side: Board.Side {
+ get {
+ switch self {
+ case .whiteKingside, .blackKingside:
+ return .kingside
+ default:
+ return .queenside
+ }
+ }
+ set {
+ self = Right(color: color, side: newValue)
+ }
+ }
+
+ /// The squares expected to be empty for a castle.
+ public var emptySquares: Bitboard {
+ switch self {
+ case .whiteKingside:
+ return 0b01100000
+ case .whiteQueenside:
+ return 0b00001110
+ case .blackKingside:
+ return 0b01100000 << 56
+ case .blackQueenside:
+ return 0b00001110 << 56
+ }
+ }
+
+ /// The castle destination square of a king.
+ public var castleSquare: Square {
+ switch self {
+ case .whiteKingside:
+ return .g1
+ case .whiteQueenside:
+ return .c1
+ case .blackKingside:
+ return .g8
+ case .blackQueenside:
+ return .c8
+ }
+ }
+
+ /// The character for `self`.
+ public var character: Character {
+ switch self {
+ case .whiteKingside: return "K"
+ case .whiteQueenside: return "Q"
+ case .blackKingside: return "k"
+ case .blackQueenside: return "q"
+ }
+ }
+
+ /// A textual representation of `self`.
+ public var description: String {
+ return rawValue
+ }
+
+ fileprivate var _bit: Int {
+ switch self {
+ case .whiteKingside: return 0b0001
+ case .whiteQueenside: return 0b0010
+ case .blackKingside: return 0b0100
+ case .blackQueenside: return 0b1000
+ }
+ }
+
+ /// Create a `Right` from `color` and `side`.
+ public init(color: Color, side: Board.Side) {
+ switch (color, side) {
+ case (.white, .kingside): self = .whiteKingside
+ case (.white, .queenside): self = .whiteQueenside
+ case (.black, .kingside): self = .blackKingside
+ case (.black, .queenside): self = .blackQueenside
+ }
+ }
+
+ /// Create a `Right` from a `Character`.
+ public init?(character: Character) {
+ switch character {
+ case "K": self = .whiteKingside
+ case "Q": self = .whiteQueenside
+ case "k": self = .blackKingside
+ case "q": self = .blackQueenside
+ default: return nil
+ }
+ }
+
+ }
+
+ /// An iterator over the members of `CastlingRights`.
+ public struct Iterator: IteratorProtocol {
+
+ fileprivate var _base: SetIterator<Right>
+
+ /// Advance to the next element and return it, or `nil` if no next element exists.
+ public mutating func next() -> Right? {
+ return _base.next()
+ }
+
+ }
+
+ /// All castling rights.
+ public static let all = CastlingRights(Right.all)
+
+ /// White castling rights.
+ public static let white = CastlingRights(Right.white)
+
+ /// Black castling rights.
+ public static let black = CastlingRights(Right.black)
+
+ /// Kingside castling rights.
+ public static let kingside = CastlingRights(Right.kingside)
+
+ /// Queenside castling rights.
+ public static let queenside = CastlingRights(Right.queenside)
+
+ /// The rights.
+ fileprivate var _rights: Set<Right>
+
+ /// A textual representation of `self`.
+ public var description: String {
+ if !_rights.isEmpty {
+ return String(_rights.map({ $0.character }).sorted())
+ } else {
+ return "-"
+ }
+ }
+
+ /// Creates empty rights.
+ public init() {
+ _rights = Set()
+ }
+
+ /// Creates a `CastlingRights` from a `String`.
+ ///
+ /// - returns: `nil` if `string` is empty or invalid.
+ public init?(string: String) {
+ guard !string.isEmpty else {
+ return nil
+ }
+ if string == "-" {
+ _rights = Set()
+ } else {
+ var rights = Set<Right>()
+ for char in string {
+ guard let right = Right(character: char) else {
+ return nil
+ }
+ rights.insert(right)
+ }
+ _rights = rights
+ }
+ }
+
+ /// Creates castling rights for `color`.
+ public init(color: Color) {
+ self = color.isWhite ? .white : .black
+ }
+
+ /// Creates castling rights for `side`.
+ public init(side: Board.Side) {
+ self = side.isKingside ? .kingside : .queenside
+ }
+
+ /// Creates a set of rights from a sequence.
+ public init<S: Sequence>(_ sequence: S) where S.Iterator.Element == Right {
+ if let set = sequence as? Set<Right> {
+ _rights = set
+ } else {
+ _rights = Set(sequence)
+ }
+ }
+
+ /// Returns `true` if `self` can castle for `color`.
+ public func canCastle(for color: Color) -> Bool {
+ return !self.intersection(CastlingRights(color: color)).isEmpty
+ }
+
+ /// Returns `true` if `self` can castle for `side`.
+ public func canCastle(for side: Board.Side) -> Bool {
+ return !self.intersection(CastlingRights(side: side)).isEmpty
+ }
+
+}
+
+extension CastlingRights: Sequence {
+
+ /// Returns an iterator over the members.
+ public func makeIterator() -> Iterator {
+ return Iterator(_base: _rights.makeIterator())
+ }
+
+}
+
+extension CastlingRights: SetAlgebra {
+
+ /// A Boolean value that indicates whether the set has no elements.
+ public var isEmpty: Bool {
+ return _rights.isEmpty
+ }
+
+ /// Returns a Boolean value that indicates whether the given element exists
+ /// in the set.
+ public func contains(_ member: Right) -> Bool {
+ return _rights.contains(member)
+ }
+
+ /// Returns a new set with the elements of both this and the given set.
+ public func union(_ other: CastlingRights) -> CastlingRights {
+ return CastlingRights(_rights.union(other._rights))
+ }
+
+ /// Returns a new set with the elements that are common to both this set and
+ /// the given set.
+ public func intersection(_ other: CastlingRights) -> CastlingRights {
+ return CastlingRights(_rights.intersection(other._rights))
+ }
+
+ /// Returns a new set with the elements that are either in this set or in the
+ /// given set, but not in both.
+ public func symmetricDifference(_ other: CastlingRights) -> CastlingRights {
+ return CastlingRights(_rights.symmetricDifference(other._rights))
+ }
+
+ /// Inserts the given element in the set if it is not already present.
+ @discardableResult
+ public mutating func insert(_ newMember: Right) -> (inserted: Bool, memberAfterInsert: Right) {
+ return _rights.insert(newMember)
+ }
+
+ /// Removes the given element and any elements subsumed by the given element.
+ @discardableResult
+ public mutating func remove(_ member: Right) -> Right? {
+ return _rights.remove(member)
+ }
+
+ /// Inserts the given element into the set unconditionally.
+ @discardableResult
+ public mutating func update(with newMember: Right) -> Right? {
+ return _rights.update(with: newMember)
+ }
+
+ /// Adds the elements of the given set to the set.
+ public mutating func formUnion(_ other: CastlingRights) {
+ _rights.formUnion(other._rights)
+ }
+
+ /// Removes the elements of this set that aren't also in the given set.
+ public mutating func formIntersection(_ other: CastlingRights) {
+ _rights.formIntersection(other._rights)
+ }
+
+ /// Removes the elements of the set that are also in the given set and
+ /// adds the members of the given set that are not already in the set.
+ public mutating func formSymmetricDifference(_ other: CastlingRights) {
+ _rights.formSymmetricDifference(other._rights)
+ }
+
+ /// Returns a new set containing the elements of this set that do not occur
+ /// in the given set.
+ public func subtracting(_ other: CastlingRights) -> CastlingRights {
+ return CastlingRights(_rights.subtracting(other._rights))
+ }
+
+ /// Returns a Boolean value that indicates whether the set is a subset of
+ /// another set.
+ public func isSubset(of other: CastlingRights) -> Bool {
+ return _rights.isSubset(of: other._rights)
+ }
+
+ /// Returns a Boolean value that indicates whether the set has no members in
+ /// common with the given set.
+ public func isDisjoint(with other: CastlingRights) -> Bool {
+ return _rights.isDisjoint(with: other._rights)
+ }
+
+ /// Returns a Boolean value that indicates whether the set is a superset of
+ /// the given set.
+ public func isSuperset(of other: CastlingRights) -> Bool {
+ return _rights.isSuperset(of: other._rights)
+ }
+
+ /// Removes the elements of the given set from this set.
+ public mutating func subtract(_ other: CastlingRights) {
+ _rights.subtract(other)
+ }
+
+}
+
+extension CastlingRights: Hashable {
+ /// The hash value.
+ public func hash(into hasher: inout Hasher) {
+ hasher.combine(_rights)
+ }
+}
+
+/// Returns `true` if both have the same rights.
+public func == (lhs: CastlingRights, rhs: CastlingRights) -> Bool {
+ return lhs._rights == rhs._rights
+}
diff --git a/Sources/SwiftChessNeo/Color.swift b/Sources/SwiftChessNeo/Color.swift
new file mode 100644
index 0000000..39d63a4
--- /dev/null
+++ b/Sources/SwiftChessNeo/Color.swift
@@ -0,0 +1,83 @@
+//
+// Color.swift
+// Sage
+//
+// Copyright 2016-2017 Nikolai Vazquez
+// Modified by SuperGeroy
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+/// A chess color.
+public enum Color: String, CustomStringConvertible {
+
+ /// White chess color.
+ case white
+
+ /// Black chess color.
+ case black
+
+ /// White color regardless of Swift version.
+ internal static let _white = Color.white
+
+ /// Black color regardless of Swift version.
+ internal static let _black = Color.black
+
+ /// An array of all colors.
+ public static let all: [Color] = [.white, .black]
+
+ /// Whether the color is white or not.
+ public var isWhite: Bool {
+ return self == ._white
+ }
+
+ /// Whether the color is black or not.
+ public var isBlack: Bool {
+ return self == ._black
+ }
+
+ /// A textual representation of `self`.
+ public var description: String {
+ return rawValue
+ }
+
+ /// The numeric representation of `self`. `White` is "0", `Black` is "1".
+ public var numericValue: Int {
+ return self.isWhite ? 0 : 1
+ }
+
+ /// The lowercase character for the color. `White` is "w", `Black` is "b".
+ public var character: Character {
+ return self.isWhite ? "w" : "b"
+ }
+
+ /// Create a color from a character of any case.
+ public init?(character: Character) {
+ switch character {
+ case "W", "w": self = ._white
+ case "B", "b": self = ._black
+ default: return nil
+ }
+ }
+
+ /// Returns the inverse of `self`.
+ public func inverse() -> Color {
+ return self.isWhite ? ._black : ._white
+ }
+
+ /// Inverts the color of `self`.
+ public mutating func invert() {
+ self = inverse()
+ }
+
+}
diff --git a/Sources/SwiftChessNeo/File.swift b/Sources/SwiftChessNeo/File.swift
new file mode 100644
index 0000000..158eee7
--- /dev/null
+++ b/Sources/SwiftChessNeo/File.swift
@@ -0,0 +1,190 @@
+//
+// File.swift
+// Sage
+//
+// Copyright 2016-2017 Nikolai Vazquez
+// Modified by SuperGeroy
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+/// A chess board file.
+///
+/// Files refer to the eight columns of a chess board, beginning with A and ending with H from left to right.
+public enum File: Int, Comparable, CustomStringConvertible {
+
+ /// A direction in file.
+ public enum Direction {
+
+ /// Left direction.
+ case left
+
+ /// Right direction.
+ case right
+
+ }
+
+ /// File "A".
+ case a = 1
+
+ /// File "B".
+ case b = 2
+
+ /// File "C".
+ case c = 3
+
+ /// File "D".
+ case d = 4
+
+ /// File "E".
+ case e = 5
+
+ /// File "F".
+ case f = 6
+
+ /// File "G".
+ case g = 7
+
+ /// File "H".
+ case h = 8
+
+ /// A regardless of Swift version.
+ internal static let _a = File.a
+
+ /// B regardless of Swift version.
+ internal static let _b = File.b
+
+ /// C regardless of Swift version.
+ internal static let _c = File.c
+
+ /// D regardless of Swift version.
+ internal static let _d = File.d
+
+ /// E regardless of Swift version.
+ internal static let _e = File.e
+
+ /// F regardless of Swift version.
+ internal static let _f = File.f
+
+ /// G regardless of Swift version.
+ internal static let _g = File.g
+
+ /// H regardless of Swift version.
+ internal static let _h = File.h
+
+}
+
+extension File {
+
+ /// An array of all files.
+ public static let all: [File] = [.a, .b, .c, .d, .e, .f, .g, .h]
+
+ /// The column index of `self`.
+ public var index: Int {
+ return rawValue - 1
+ }
+
+ /// A textual representation of `self`.
+ public var description: String {
+ return String(character)
+ }
+
+ /// The character value of `self`.
+ public var character: Character {
+ switch self {
+ case .a: return "a"
+ case .b: return "b"
+ case .c: return "c"
+ case .d: return "d"
+ case .e: return "e"
+ case .f: return "f"
+ case .g: return "g"
+ case .h: return "h"
+ }
+ }
+
+ /// Create an instance from a character value.
+ public init?(_ character: Character) {
+ switch character {
+ case "A", "a": self = .a
+ case "B", "b": self = .b
+ case "C", "c": self = .c
+ case "D", "d": self = .d
+ case "E", "e": self = .e
+ case "F", "f": self = .f
+ case "G", "g": self = .g
+ case "H", "h": self = .h
+ default: return nil
+ }
+ }
+
+ /// Create a `File` from a zero-based column index.
+ public init?(index: Int) {
+ self.init(rawValue: index + 1)
+ }
+
+ /// Returns a rank from advancing `self` by `value`.
+ public func advanced(by value: Int) -> File? {
+ return File(rawValue: rawValue + value)
+ }
+
+ /// The next file after `self`.
+ public func next() -> File? {
+ return File(rawValue: (rawValue + 1))
+ }
+
+ /// The previous file to `self`.
+ public func previous() -> File? {
+ return File(rawValue: (rawValue - 1))
+ }
+
+ /// The opposite file of `self`.
+ public func opposite() -> File {
+ return File(rawValue: 9 - rawValue)!
+ }
+
+ /// The files from `self` to `other`.
+ public func to(_ other: File) -> [File] {
+ return _to(other)
+ }
+
+ /// The files between `self` and `other`.
+ public func between(_ other: File) -> [File] {
+ return _between(other)
+ }
+
+}
+
+extension File: ExpressibleByExtendedGraphemeClusterLiteral { }
+
+extension File {
+
+ /// Create an instance initialized to `value`.
+ public init(unicodeScalarLiteral value: Character) {
+ guard let file = File(value) else {
+ fatalError("File value not within \"A\" and \"H\" or \"a\" and \"h\", inclusive")
+ }
+ self = file
+ }
+
+ /// Create an instance initialized to `value`.
+ public init(extendedGraphemeClusterLiteral value: Character) {
+ self.init(unicodeScalarLiteral: value)
+ }
+
+}
+
+/// Returns `true` if one file is further left than the other.
+public func < (lhs: File, rhs: File) -> Bool {
+ return lhs.rawValue < rhs.rawValue
+}
diff --git a/Sources/SwiftChessNeo/Game.swift b/Sources/SwiftChessNeo/Game.swift
new file mode 100644
index 0000000..b70c0bb
--- /dev/null
+++ b/Sources/SwiftChessNeo/Game.swift
@@ -0,0 +1,825 @@
+//
+// Game.swift
+// Sage
+//
+// Copyright 2016-2017 Nikolai Vazquez
+// Modified by SuperGeroy
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+/// A chess game.
+public final class Game {
+
+ public var PGN: PGN?
+
+ /// A chess game outcome.
+ public enum Outcome: Hashable, CustomStringConvertible {
+
+ /// A win for a `Color`.
+ case win(Color)
+
+ /// A draw.
+ case draw
+
+ /// Draw.
+ internal static let _draw = Outcome.draw
+
+ /// Win.
+ internal static func _win(_ color: Color) -> Outcome {
+ return .win(color)
+ }
+
+ /// The hash value.
+ public func hash(into hasher: inout Hasher) {
+ switch self {
+ case .win(let color):
+ hasher.combine(0)
+ hasher.combine(color)
+ case .draw:
+ hasher.combine(1)
+ }
+ }
+
+ /// A textual representation of `self`.
+ public var description: String {
+ if let color = winColor {
+ return color.isWhite ? "1-0" : "0-1"
+ } else {
+ return "1/2-1/2"
+ }
+ }
+
+ /// The color for the winning player.
+ public var winColor: Color? {
+ guard case let .win(color) = self else { return nil }
+ return color
+ }
+
+ /// `self` is a win.
+ public var isWin: Bool {
+ if case .win = self { return true } else { return false }
+ }
+
+ /// `self` is a draw.
+ public var isDraw: Bool {
+ return !isWin
+ }
+
+ /// Create an outcome from `string`. Ignores whitespace.
+ public init?(_ string: String) {
+ let stripped = string.split(separator: " ").map(String.init).joined(separator: "")
+ switch stripped {
+ case "1-0":
+ self = ._win(.white)
+ case "0-1":
+ self = ._win(.black)
+ case "1/2-1/2":
+ self = ._draw
+ case "½-½":
+ self = .draw
+ default:
+ return nil
+ }
+ }
+
+ /// The point value for a player. Can be 1 for win, 0.5 for draw, or 0 for loss.
+ public func value(for playerColor: Color) -> Double {
+ return winColor.map({ $0 == playerColor ? 1 : 0 }) ?? 0.5
+ }
+
+ }
+
+ /// A game position.
+ public struct Position: Equatable, CustomStringConvertible {
+
+ /// The board for the position.
+ public var board: Board
+
+ /// The active player turn.
+ public var playerTurn: PlayerTurn
+
+ /// The castling rights.
+ public var castlingRights: CastlingRights
+
+ /// The en passant target location.
+ public var enPassantTarget: Square?
+
+ /// The halfmove number.
+ public var halfmoves: UInt
+
+ /// The fullmove clock.
+ public var fullmoves: UInt
+
+ /// A textual representation of `self`.
+ public var description: String {
+ return "Position(\(fen()))"
+ }
+
+ /// Create a position.
+ public init(board: Board = Board(),
+ playerTurn: PlayerTurn = .white,
+ castlingRights: CastlingRights = .all,
+ enPassantTarget: Square? = nil,
+ halfmoves: UInt = 0,
+ fullmoves: UInt = 1) {
+ self.board = board
+ self.playerTurn = playerTurn
+ self.castlingRights = castlingRights
+ self.enPassantTarget = enPassantTarget
+ self.halfmoves = halfmoves
+ self.fullmoves = fullmoves
+ }
+
+ /// Create a position from a valid FEN string.
+ ///
+ /// - see also: [FEN (Wikipedia)](https://en.wikipedia.org/wiki/Forsyth%E2%80%93Edwards_Notation ),
+ /// [FEN (Chess Programming Wiki)](https://chessprogramming.org/Forsyth-Edwards_Notation)
+ public init?(fen: String) {
+ let parts = fen.split(separator: " ").map(String.init)
+ guard
+ parts.count == 6,
+ let board = Board(fen: parts[0]),
+ parts[1].count == 1,
+ let playerTurn = parts[1].first.flatMap(Color.init),
+ let rights = CastlingRights(string: parts[2]),
+ let halfmoves = UInt(parts[4]),
+ let fullmoves = UInt(parts[5]),
+ fullmoves > 0 else {
+ return nil
+ }
+ var target: Square? = nil
+ let targetStr = parts[3]
+ if targetStr.count == 2 {
+ guard let square = Square(targetStr) else {
+ return nil
+ }
+ target = square
+ } else {
+ guard targetStr == "-" else {
+ return nil
+ }
+ }
+ self.init(board: board,
+ playerTurn: playerTurn,
+ castlingRights: rights,
+ enPassantTarget: target,
+ halfmoves: halfmoves,
+ fullmoves: fullmoves)
+ }
+
+ internal func _validationError() -> PositionError? {
+ for color in Color.all {
+ guard board.count(of: Piece(king: color)) == 1 else {
+ return .wrongKingCount(color)
+ }
+ }
+ for right in castlingRights {
+ let color = right.color
+ let king = Piece(king: color)
+ guard board.bitboard(for: king) == Bitboard(startFor: king) else {
+ return .missingKing(right)
+ }
+ let rook = Piece(rook: color)
+ let square = Square(file: right.side.isKingside ? ._h : ._a,
+ rank: Rank(startFor: color))
+ guard board.bitboard(for: rook)[square] else {
+ return .missingRook(right)
+ }
+ }
+ if let target = enPassantTarget {
+ guard target.rank == (playerTurn.isWhite ? 6 : 3) else {
+ return .wrongEnPassantTargetRank(target.rank)
+ }
+ if let piece = board[target] {
+ return .nonEmptyEnPassantTarget(target, piece)
+ }
+ let pawnSquare = Square(file: target.file, rank: playerTurn.isWhite ? 5 : 4)
+ guard board[pawnSquare] == Piece(pawn: playerTurn.inverse()) else {
+ return .missingEnPassantPawn(pawnSquare)
+ }
+ let startSquare = Square(file: target.file, rank: playerTurn.isWhite ? 7 : 2)
+ if let piece = board[startSquare] {
+ return .nonEmptyEnPassantSquare(startSquare, piece)
+ }
+ }
+ return nil
+ }
+
+ /// Returns the FEN string for the position.
+ ///
+ /// - see also: [FEN (Wikipedia)](https://en.wikipedia.org/wiki/Forsyth%E2%80%93Edwards_Notation ),
+ /// [FEN (Chess Programming Wiki)](https://chessprogramming.org/Forsyth-Edwards_Notation)
+ public func fen() -> String {
+ return board.fen()
+ + " \(playerTurn.isWhite ? "w" : "b") \(castlingRights) "
+ + (enPassantTarget.map({ "\($0 as Square)".lowercased() }) ?? "-")
+ + " \(halfmoves) \(fullmoves)"
+ }
+
+ }
+
+ /// An error in position validation.
+ public enum PositionError: Error {
+
+ /// Found number other than 1 for king count.
+ case wrongKingCount(Color)
+
+ /// King missing for castling right.
+ case missingKing(CastlingRights.Right)
+
+ /// Rook missing for castling right.
+ case missingRook(CastlingRights.Right)
+
+ /// Wrong rank for en passant target.
+ case wrongEnPassantTargetRank(Rank)
+
+ /// Non empty en passant target square.
+ case nonEmptyEnPassantTarget(Square, Piece)
+
+ /// Pawn missing for previous en passant.
+ case missingEnPassantPawn(Square)
+
+ /// Piece found at start of en passant move.
+ case nonEmptyEnPassantSquare(Square, Piece)
+
+ }
+
+ /// An error in move execution.
+ ///
+ /// Thrown by the `execute(move:promotion:)` or `execute(uncheckedMove:promotion:)` method for a `Game` instance.
+ public enum ExecutionError: Error {
+
+ /// Missing piece at a square.
+ case missingPiece(Square)
+
+ /// Attempted illegal move.
+ case illegalMove(Move, Color, Board)
+
+ /// Could not promote with a piece kind.
+ case invalidPromotion(Piece.Kind)
+
+ /// The error message.
+ public var message: String {
+ switch self {
+ case let .missingPiece(square):
+ return "Missing piece: \(square)"
+ case let .illegalMove(move, color, board):
+ return "Illegal move: \(move) for \(color) on \(board)"
+ case let .invalidPromotion(pieceKind):
+ return "Invalid promoton: \(pieceKind)"
+ }
+ }
+
+ }
+
+ /// A player turn.
+ public typealias PlayerTurn = Color
+
+ /// All of the conducted moves in the game.
+ private var _moveHistory: [(move: Move,
+ piece: Piece,
+ capture: Piece?,
+ enPassantTarget: Square?,
+ kingAttackers: Bitboard,
+ halfmoves: UInt,
+ rights: CastlingRights)]
+
+ /// All of the undone moves in the game.
+ private var _undoHistory: [(move: Move, promotion: Piece.Kind?, kingAttackers: Bitboard)]
+
+ /// The game's board.
+ public private(set) var board: Board
+
+ /// The current player's turn.
+ public private(set) var playerTurn: PlayerTurn
+
+ /// The castling rights.
+ public private(set) var castlingRights: CastlingRights
+
+ /// The white player.
+ public var whitePlayer: Player
+
+ /// The black player.
+ public var blackPlayer: Player
+
+ /// The game's variant.
+ public let variant: Variant
+
+ /// Attackers to the current player's king.
+ private var attackersToKing: Bitboard
+
+ /// The current player's king is in check.
+ public var kingIsChecked: Bool {
+ return attackersToKing != 0
+ }
+
+ /// The current player's king is checked by two or more pieces.
+ public var kingIsDoubleChecked: Bool {
+ return attackersToKing.count > 1
+ }
+
+ /// All of the moves played in the game.
+ public var playedMoves: [Move] {
+ return _moveHistory.map({ $0.move })
+ }
+
+ /// The amount of plies (half moves) executed.
+ public var moveCount: Int {
+ return _moveHistory.count
+ }
+
+ /// The current fullmove number.
+ public private(set) var fullmoves: UInt
+
+ /// The current halfmove clock (used for 50-moves draw rule).
+ ///
+ /// Counts no-pawn moves with no capture.
+ public private(set) var halfmoves: UInt
+
+ /// The target move location for an en passant.
+ public private(set) var enPassantTarget: Square?
+
+ /// The captured piece for the last move.
+ public var captureForLastMove: Piece? {
+ return _moveHistory.last?.capture
+ }
+
+ /// The current position for `self`.
+ public var position: Position {
+ return Position(board: board,
+ playerTurn: playerTurn,
+ castlingRights: castlingRights,
+ enPassantTarget: enPassantTarget,
+ halfmoves: halfmoves,
+ fullmoves: fullmoves)
+ }
+
+ /// The outcome for `self` if no moves are available.
+ public var outcome: Outcome? {
+ let moves = _availableMoves(considerHalfmoves: false)
+ if moves.isEmpty {
+ return kingIsChecked ? ._win(playerTurn.inverse()) : ._draw
+ } else if halfmoves >= 100 {
+ return ._draw
+ } else {
+ return nil
+ }
+ }
+
+ /// The game has no more available moves.
+ public var isFinished: Bool {
+ return availableMoves().isEmpty
+ }
+
+ /// Create a game from another.
+ private init(game: Game) {
+ self._moveHistory = game._moveHistory
+ self._undoHistory = game._undoHistory
+ self.board = game.board
+ self.playerTurn = game.playerTurn
+ self.castlingRights = game.castlingRights
+ self.whitePlayer = game.whitePlayer
+ self.blackPlayer = game.blackPlayer
+ self.variant = game.variant
+ self.attackersToKing = game.attackersToKing
+ self.halfmoves = game.halfmoves
+ self.fullmoves = game.fullmoves
+ self.enPassantTarget = game.enPassantTarget
+ self.PGN = game.PGN
+ }
+
+ /// Creates a new chess game.
+ ///
+ /// - parameter whitePlayer: The game's white player. Default is a nameless human.
+ /// - parameter blackPlayer: The game's black player. Default is a nameless human.
+ /// - parameter variant: The game's chess variant. Default is standard.
+ public init(whitePlayer: Player = Player(),
+ blackPlayer: Player = Player(),
+ variant: Variant = .standard) {
+ self._moveHistory = []
+ self._undoHistory = []
+ self.board = Board(variant: variant)
+ self.playerTurn = .white
+ self.castlingRights = .all
+ self.whitePlayer = whitePlayer
+ self.blackPlayer = blackPlayer
+ self.variant = variant
+ self.attackersToKing = 0
+ self.halfmoves = 0
+ self.fullmoves = 1
+ }
+
+ /// Creates a chess game from a `Position`.
+ ///
+ /// - parameter position: The position to start off from.
+ /// - parameter whitePlayer: The game's white player. Default is a nameless human.
+ /// - parameter blackPlayer: The game's black player. Default is a nameless human.
+ /// - parameter variant: The game's chess variant. Default is standard.
+ ///
+ /// - throws: `PositionError` if the position is invalid.
+ public init(position: Position,
+ whitePlayer: Player = Player(),
+ blackPlayer: Player = Player(),
+ variant: Variant = .standard) throws {
+ if let error = position._validationError() {
+ throw error
+ }
+ self._moveHistory = []
+ self._undoHistory = []
+ self.board = position.board
+ self.playerTurn = position.playerTurn
+ self.castlingRights = position.castlingRights
+ self.whitePlayer = whitePlayer
+ self.blackPlayer = blackPlayer
+ self.variant = variant
+ self.enPassantTarget = position.enPassantTarget
+ self.attackersToKing = position.board.attackersToKing(for: position.playerTurn)
+ self.halfmoves = position.halfmoves
+ self.fullmoves = position.fullmoves
+ }
+
+ /// Creates a chess game with `moves`.
+ ///
+ /// - parameter moves: The moves to execute.
+ /// - parameter whitePlayer: The game's white player. Default is a nameless human.
+ /// - parameter blackPlayer: The game's black player. Default is a nameless human.
+ /// - parameter variant: The game's chess variant. Default is standard.
+ ///
+ /// - throws: `ExecutionError` if any move from `moves` is illegal.
+ public convenience init(moves: [Move],
+ whitePlayer: Player = Player(),
+ blackPlayer: Player = Player(),
+ variant: Variant = .standard) throws {
+ self.init(whitePlayer: whitePlayer, blackPlayer: blackPlayer, variant: variant)
+ for move in moves {
+ try execute(move: move)
+ }
+ }
+
+ /// Returns a copy of `self`.
+ ///
+ /// - complexity: O(1).
+ public func copy() -> Game {
+ return Game(game: self)
+ }
+
+ /// Returns the captured pieces for a color, or for all if color is `nil`.
+ public func capturedPieces(for color: Color? = nil) -> [Piece] {
+ let pieces = _moveHistory.compactMap({ $0.capture })
+ if let color = color {
+ return pieces.filter({ $0.color == color })
+ } else {
+ return pieces
+ }
+ }
+
+ /// Returns the moves bitboard currently available for the piece at `square`, if any.
+ private func _movesBitboardForPiece(at square: Square, considerHalfmoves: Bool) -> Bitboard {
+ if considerHalfmoves && halfmoves >= 100 {
+ return 0
+ }
+ guard let piece = board[square] else { return 0 }
+ guard piece.color == playerTurn else { return 0 }
+ if kingIsDoubleChecked {
+ guard piece.kind.isKing else {
+ return 0
+ }
+ }
+
+ let playerBitboard = board.bitboard(for: playerTurn)
+ let enemyBitboard = board.bitboard(for: playerTurn.inverse())
+ let allBitboard = playerBitboard | enemyBitboard
+ let emptyBitboard = ~allBitboard
+ let squareBitboard = Bitboard(square: square)
+
+ var movesBitboard: Bitboard = 0
+ let attacks = square.attacks(for: piece, stoppers: allBitboard)
+
+ if piece.kind.isPawn {
+ let enPassant = enPassantTarget.map({ Bitboard(square: $0) }) ?? 0
+ let pushes = squareBitboard._pawnPushes(for: playerTurn,
+ empty: emptyBitboard)
+ let doublePushes = (squareBitboard & Bitboard(startFor: piece))
+ ._pawnPushes(for: playerTurn, empty: emptyBitboard)
+ ._pawnPushes(for: playerTurn, empty: emptyBitboard)
+ movesBitboard |= pushes | doublePushes
+ | (attacks & enemyBitboard)
+ | (attacks & enPassant)
+ } else {
+ movesBitboard |= attacks & ~playerBitboard
+ }
+
+ if piece.kind.isKing && squareBitboard == Bitboard(startFor: piece) && !kingIsChecked {
+ rightLoop: for right in castlingRights {
+ let emptySquares = right.emptySquares
+ guard right.color == playerTurn && allBitboard & emptySquares == 0 else {
+ continue
+ }
+ for square in emptySquares {
+ guard board.attackers(to: square, color: piece.color.inverse()).isEmpty else {
+ continue rightLoop
+ }
+ }
+ movesBitboard |= Bitboard(square: right.castleSquare)
+ }
+ }
+
+ let player = playerTurn
+ for moveSquare in movesBitboard {
+ try! _execute(uncheckedMove: square >>> moveSquare, promotion: { ._queen })
+ if board.attackersToKing(for: player) != 0 {
+ movesBitboard[moveSquare] = false
+ }
+ undoMove()
+ _undoHistory.removeLast()
+ }
+
+ return movesBitboard
+ }
+
+ /// Returns the moves currently available for the piece at `square`, if any.
+ private func _movesForPiece(at square: Square, considerHalfmoves flag: Bool) -> [Move] {
+ return _movesBitboardForPiece(at: square, considerHalfmoves: flag).moves(from: square)
+ }
+
+ /// Returns the available moves for the current player.
+ private func _availableMoves(considerHalfmoves flag: Bool) -> [Move] {
+ let moves = Square.all.map({ _movesForPiece(at: $0, considerHalfmoves: flag) })
+ return Array(moves.joined())
+ }
+
+ /// Returns the available moves for the current player.
+ public func availableMoves() -> [Move] {
+ return _availableMoves(considerHalfmoves: true)
+ }
+
+ /// Returns the moves bitboard currently available for the piece at `square`.
+ public func movesBitboardForPiece(at square: Square) -> Bitboard {
+ return _movesBitboardForPiece(at: square, considerHalfmoves: true)
+ }
+
+ /// Returns the moves bitboard currently available for the piece at `location`.
+ public func movesBitboardForPiece(at location: Location) -> Bitboard {
+ return movesBitboardForPiece(at: Square(location: location))
+ }
+
+ /// Returns the moves currently available for the piece at `square`.
+ public func movesForPiece(at square: Square) -> [Move] {
+ return _movesForPiece(at: square, considerHalfmoves: true)
+ }
+
+ /// Returns the moves currently available for the piece at `location`.
+ public func movesForPiece(at location: Location) -> [Move] {
+ return movesForPiece(at: Square(location: location))
+ }
+
+ /// Returns `true` if the move is legal.
+ public func isLegal(move: Move) -> Bool {
+ let moves = movesBitboardForPiece(at: move.start)
+ return Bitboard(square: move.end).intersects(moves)
+ }
+
+ @inline(__always)
+ private func _execute(uncheckedMove move: Move, promotion: () -> Piece.Kind) throws {
+ guard let piece = board[move.start] else {
+ throw ExecutionError.missingPiece(move.start)
+ }
+ var endPiece = piece
+ var capture = board[move.end]
+ var captureSquare = move.end
+ let rights = castlingRights
+ if piece.kind.isPawn {
+ if move.end.rank == Rank(endFor: playerTurn) {
+ let promotion = promotion()
+ guard promotion.canPromote() else {
+ throw ExecutionError.invalidPromotion(promotion)
+ }
+ endPiece = Piece(kind: promotion, color: playerTurn)
+ } else if move.end == enPassantTarget {
+ capture = Piece(pawn: playerTurn.inverse())
+ captureSquare = Square(file: move.end.file, rank: move.start.rank)
+ }
+ } else if piece.kind.isRook {
+ switch move.start {
+ case .a1: castlingRights.remove(.whiteQueenside)
+ case .h1: castlingRights.remove(.whiteKingside)
+ case .a8: castlingRights.remove(.blackQueenside)
+ case .h8: castlingRights.remove(.blackKingside)
+ default:
+ break
+ }
+ } else if piece.kind.isKing {
+ for option in castlingRights where option.color == playerTurn {
+ castlingRights.remove(option)
+ }
+ if move.isCastle(for: playerTurn) {
+ let (old, new) = move._castleSquares()
+ let rook = Piece(rook: playerTurn)
+ board[rook][old] = false
+ board[rook][new] = true
+ }
+ }
+ if let capture = capture, capture.kind.isRook {
+ switch move.end {
+ case .a1 where playerTurn.isBlack: castlingRights.remove(.whiteQueenside)
+ case .h1 where playerTurn.isBlack: castlingRights.remove(.whiteKingside)
+ case .a8 where playerTurn.isWhite: castlingRights.remove(.blackQueenside)
+ case .h8 where playerTurn.isWhite: castlingRights.remove(.blackKingside)
+ default:
+ break
+ }
+ }
+
+ _moveHistory.append((move, piece, capture, enPassantTarget, attackersToKing, halfmoves, rights))
+ if let capture = capture {
+ board[capture][captureSquare] = false
+ }
+ if capture == nil && !piece.kind.isPawn {
+ halfmoves += 1
+ } else {
+ halfmoves = 0
+ }
+ board[piece][move.start] = false
+ board[endPiece][move.end] = true
+ playerTurn.invert()
+ }
+
+ /// Executes `move` without checking its legality, updating the state for `self`.
+ ///
+ /// - warning: Can cause unwanted effects. Should only be used with moves that are known to be legal.
+ ///
+ /// - parameter move: The move to be executed.
+ /// - parameter promotion: A closure returning a promotion piece kind if a pawn promotion occurs.
+ ///
+ /// - throws: `ExecutionError` if no piece exists at `move.start` or if `promotion` is invalid.
+ public func execute(uncheckedMove move: Move, promotion: () -> Piece.Kind) throws {
+ try _execute(uncheckedMove: move, promotion: promotion)
+ let piece = board[move.end]!
+ if piece.kind.isPawn && abs(move.rankChange) == 2 {
+ enPassantTarget = Square(file: move.start.file, rank: piece.color.isWhite ? 3 : 6)
+ } else {
+ enPassantTarget = nil
+ }
+ if kingIsChecked {
+ attackersToKing = 0
+ } else {
+ attackersToKing = board.attackersToKing(for: playerTurn)
+ }
+
+ fullmoves = 1 + (UInt(moveCount) / 2)
+ _undoHistory = []
+ }
+
+ /// Executes `move` without checking its legality, updating the state for `self`.
+ ///
+ /// - warning: Can cause unwanted effects. Should only be used with moves that are known to be legal.
+ ///
+ /// - parameter move: The move to be executed.
+ /// - parameter promotion: A piece kind for a pawn promotion.
+ ///
+ /// - throws: `ExecutionError` if no piece exists at `move.start` or if `promotion` is invalid.
+ public func execute(uncheckedMove move: Move, promotion: Piece.Kind) throws {
+ try execute(uncheckedMove: move, promotion: { promotion })
+ }
+
+ /// Executes `move` without checking its legality, updating the state for `self`.
+ ///
+ /// - warning: Can cause unwanted effects. Should only be used with moves that are known to be legal.
+ ///
+ /// - parameter move: The move to be executed.
+ ///
+ /// - throws: `ExecutionError` if no piece exists at `move.start`.
+ public func execute(uncheckedMove move: Move) throws {
+ try execute(uncheckedMove: move, promotion: ._queen)
+ }
+
+ /// Executes `move`, updating the state for `self`.
+ ///
+ /// - parameter move: The move to be executed.
+ /// - parameter promotion: A closure returning a promotion piece kind if a pawn promotion occurs.
+ ///
+ /// - throws: `ExecutionError` if `move` is illegal or if `promotion` is invalid.
+ public func execute(move: Move, promotion: () -> Piece.Kind) throws {
+ guard isLegal(move: move) else {
+ throw ExecutionError.illegalMove(move, playerTurn, board)
+ }
+ try execute(uncheckedMove: move, promotion: promotion)
+ }
+
+ /// Executes `move`, updating the state for `self`.
+ ///
+ /// - parameter move: The move to be executed.
+ /// - parameter promotion: A piece kind for a pawn promotion.
+ ///
+ /// - throws: `ExecutionError` if `move` is illegal or if `promotion` is invalid.
+ public func execute(move: Move, promotion: Piece.Kind) throws {
+ try execute(move: move, promotion: { promotion })
+ }
+
+ /// Executes `move`, updating the state for `self`.
+ ///
+ /// - parameter move: The move to be executed.
+ ///
+ /// - throws: `ExecutionError` if `move` is illegal.
+ public func execute(move: Move) throws {
+ try execute(move: move, promotion: ._queen)
+ }
+
+ public func execute(move: PGNMove) throws {
+ let parsedMove = try PGNParser.parse(move: move, in: position)
+ try execute(move: parsedMove, promotion: { move.promotionPiece ?? ._queen })
+ }
+
+ /// Returns the last move on the move stack, if any.
+ public func moveToUndo() -> Move? {
+ return _moveHistory.last?.move
+ }
+
+ /// Returns the last move on the undo stack, if any.
+ public func moveToRedo() -> Move? {
+ return _undoHistory.last?.move
+ }
+
+ /// Undoes the previous move and returns it, if any.
+ private func _undoMove() -> Move? {
+ guard let (move, piece, capture, enPassantTarget, attackers, halfmoves, rights) = _moveHistory.popLast() else {
+ return nil
+ }
+ var captureSquare = move.end
+ var promotionKind: Piece.Kind? = nil
+ if piece.kind.isPawn {
+ if move.end == enPassantTarget {
+ captureSquare = Square(file: move.end.file, rank: move.start.rank)
+ } else if move.end.rank == Rank(endFor: playerTurn.inverse()), let promotion = board[move.end] {
+ promotionKind = promotion.kind
+ board[promotion][move.end] = false
+ }
+ } else if piece.kind.isKing && abs(move.fileChange) == 2 {
+ let (old, new) = move._castleSquares()
+ let rook = Piece(rook: playerTurn.inverse())
+ board[rook][old] = true
+ board[rook][new] = false
+ }
+ if let capture = capture {
+ board[capture][captureSquare] = true
+ }
+ _undoHistory.append((move, promotionKind, attackers))
+ board[piece][move.end] = false
+ board[piece][move.start] = true
+ playerTurn.invert()
+ self.enPassantTarget = enPassantTarget
+ self.attackersToKing = attackers
+ self.fullmoves = 1 + (UInt(moveCount) / 2)
+ self.halfmoves = halfmoves
+ self.castlingRights = rights
+ return move
+ }
+
+ /// Redoes the previous undone move and returns it, if any.
+ private func _redoMove() -> Move? {
+ guard let (move, promotion, attackers) = _undoHistory.popLast() else {
+ return nil
+ }
+ try! _execute(uncheckedMove: move, promotion: { promotion ?? ._queen })
+ attackersToKing = attackers
+ return move
+ }
+
+ /// Undoes the previous move and returns it, if any.
+ @discardableResult
+ public func undoMove() -> Move? {
+ return _undoMove()
+ }
+
+ /// Redoes the previous undone move and returns it, if any.
+ @discardableResult
+ public func redoMove() -> Move? {
+ return _redoMove()
+ }
+
+}
+
+/// Returns `true` if the outcomes are the same.
+public func == (lhs: Game.Outcome, rhs: Game.Outcome) -> Bool {
+ return lhs.winColor == rhs.winColor
+}
+
+/// Returns `true` if the positions are the same.
+public func == (lhs: Game.Position, rhs: Game.Position) -> Bool {
+ return lhs.playerTurn == rhs.playerTurn
+ && lhs.castlingRights == rhs.castlingRights
+ && lhs.halfmoves == rhs.halfmoves
+ && lhs.fullmoves == rhs.fullmoves
+ && lhs.enPassantTarget == rhs.enPassantTarget
+ && lhs.board == rhs.board
+}
diff --git a/Sources/SwiftChessNeo/InternalTypes.swift b/Sources/SwiftChessNeo/InternalTypes.swift
new file mode 100644
index 0000000..c418c64
--- /dev/null
+++ b/Sources/SwiftChessNeo/InternalTypes.swift
@@ -0,0 +1,63 @@
+//
+// InternalTypes.swift
+// Sage
+//
+// Copyright 2016-2017 Nikolai Vazquez
+// Modified by SuperGeroy
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#if os(OSX)
+ import Cocoa
+ internal typealias _View = NSView
+ internal typealias _Color = NSColor
+#elseif os(iOS) || os(tvOS)
+ import UIKit
+ internal typealias _View = UIView
+ internal typealias _Color = UIColor
+#endif
+
+internal extension Optional {
+
+ var _altDescription: String {
+ return self.map({ String(describing: $0) }) ?? "nil"
+ }
+
+}
+
+extension RawRepresentable where RawValue == Int, Self: Comparable {
+
+ internal func _to(_ other: Self) -> [Self] {
+ if other > self {
+ return (rawValue...other.rawValue).compactMap(Self.init(rawValue:))
+ } else if other < self {
+ let values = (other.rawValue...rawValue).reversed()
+ return values.compactMap(Self.init(rawValue:))
+ } else {
+ return [self]
+ }
+ }
+
+ internal func _between(_ other: Self) -> [Self] {
+ if other > self {
+ return (rawValue + 1 ..< other.rawValue).compactMap(Self.init(rawValue:))
+ } else if other < self {
+ let values = (other.rawValue + 1 ..< rawValue).reversed()
+ return values.compactMap(Self.init(rawValue:))
+ } else {
+ return []
+ }
+ }
+
+}
diff --git a/Sources/SwiftChessNeo/Move.swift b/Sources/SwiftChessNeo/Move.swift
new file mode 100644
index 0000000..efc88d7
--- /dev/null
+++ b/Sources/SwiftChessNeo/Move.swift
@@ -0,0 +1,203 @@
+//
+// Move.swift
+// Sage
+//
+// Copyright 2016-2017 Nikolai Vazquez
+// Modified by SuperGeroy
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+/// A chess move from a start `Square` to an end `Square`.
+public struct Move: Hashable, CustomStringConvertible {
+
+ /// The move's start square.
+ public var start: Square
+
+ /// The move's end square.
+ public var end: Square
+
+ /// The move's change in file.
+ public var fileChange: Int {
+ return end.file.rawValue - start.file.rawValue
+ }
+
+ /// The move's change in rank.
+ public var rankChange: Int {
+ return end.rank.rawValue - start.rank.rawValue
+ }
+
+ /// The move is a real change in location.
+ public var isChange: Bool {
+ return start != end
+ }
+
+ /// The move is diagonal.
+ public var isDiagonal: Bool {
+ let fileChange = self.fileChange
+ return fileChange != 0 && abs(fileChange) == abs(rankChange)
+ }
+
+ /// The move is horizontal.
+ public var isHorizontal: Bool {
+ return start.file != end.file && start.rank == end.rank
+ }
+
+ /// The move is vertical.
+ public var isVertical: Bool {
+ return start.file == end.file && start.rank != end.rank
+ }
+
+ /// The move is horizontal or vertical.
+ public var isAxial: Bool {
+ return isHorizontal || isVertical
+ }
+
+ /// The move is leftward.
+ public var isLeftward: Bool {
+ return end.file < start.file
+ }
+
+ /// The move is rightward.
+ public var isRightward: Bool {
+ return end.file > start.file
+ }
+
+ /// The move is downward.
+ public var isDownward: Bool {
+ return end.rank < start.rank
+ }
+
+ /// The move is upward.
+ public var isUpward: Bool {
+ return end.rank > start.rank
+ }
+
+ /// The move is a knight jump two spaces horizontally and one space vertically, or two spaces vertically and one
+ /// space horizontally.
+ public var isKnightJump: Bool {
+ let fileChange = abs(self.fileChange)
+ let rankChange = abs(self.rankChange)
+ return (fileChange == 2 && rankChange == 1)
+ || (rankChange == 2 && fileChange == 1)
+ }
+
+ /// The move's direction in file, if any.
+ public var fileDirection: File.Direction? {
+ if self.isLeftward {
+ return .left
+ } else if self.isRightward {
+ return .right
+ } else {
+ return .none
+ }
+ }
+
+ /// The move's direction in rank, if any.
+ public var rankDirection: Rank.Direction? {
+ if self.isUpward {
+ return .up
+ } else if self.isDownward {
+ return .down
+ } else {
+ return .none
+ }
+ }
+
+ /// A textual representation of `self`.
+ public var description: String {
+ return "\(start) >>> \(end)"
+ }
+
+ /// The hash value.
+ public func hash(into hasher: inout Hasher) {
+ hasher.combine(start)
+ hasher.combine(end)
+ }
+
+ /// Create a move with start and end squares.
+ public init(start: Square, end: Square) {
+ self.start = start
+ self.end = end
+ }
+
+ /// Create a move with start and end locations.
+ public init(start: Location, end: Location) {
+ self.start = Square(location: start)
+ self.end = Square(location: end)
+ }
+
+ /// A castle move for `color` in `direction`.
+ public init(castle color: Color, direction: File.Direction) {
+ let rank: Rank = color.isWhite ? 1 : 8
+ self = Move(start: Square(file: .e, rank: rank), end: Square(file: direction == .left ? .c : .g, rank: rank))
+ }
+
+ /// Returns the castle squares for a rook.
+ internal func _castleSquares() -> (old: Square, new: Square) {
+ let rank = start.rank
+ let movedLeft = self.isLeftward
+ let old = Square(file: movedLeft ? .a : .h, rank: rank)
+ let new = Square(file: movedLeft ? .d : .f, rank: rank)
+ return (old, new)
+ }
+
+ /// Returns a move with the end and start of `self` reversed.
+ public func reversed() -> Move {
+ return Move(start: end, end: start)
+ }
+
+ /// Returns the result of rotating `self` 180 degrees.
+ public func rotated() -> Move {
+ let start = Square(file: self.start.file.opposite(),
+ rank: self.start.rank.opposite())
+ let end = Square(file: self.end.file.opposite(),
+ rank: self.end.rank.opposite())
+ return start >>> end
+ }
+
+ /// Returns `true` if `self` is castle move for `color`.
+ ///
+ /// - parameter color: The color to check the rank against. If `nil`, the rank can be either 1 or 8. The default
+ /// value is `nil`.
+ public func isCastle(for color: Color? = nil) -> Bool {
+ let startRank = start.rank
+ if let color = color {
+ guard startRank == Rank(startFor: color) else { return false }
+ } else {
+ guard startRank == 1 || startRank == 8 else { return false }
+ }
+ let endFile = end.file
+ return startRank == end.rank
+ && start.file == ._e
+ && (endFile == ._c || endFile == ._g)
+ }
+
+}
+
+infix operator >>>
+
+/// Returns `true` if both moves are the same.
+public func == (lhs: Move, rhs: Move) -> Bool {
+ return lhs.start == rhs.start && lhs.end == rhs.end
+}
+
+/// Returns a `Move` from the two squares.
+public func >>> (start: Square, end: Square) -> Move {
+ return Move(start: start, end: end)
+}
+
+/// Returns a `Move` from the two locations.
+public func >>> (start: Location, rhs: Location) -> Move {
+ return Square(location: start) >>> Square(location: rhs)
+}
diff --git a/Sources/SwiftChessNeo/PGN.swift b/Sources/SwiftChessNeo/PGN.swift
new file mode 100644
index 0000000..4150347
--- /dev/null
+++ b/Sources/SwiftChessNeo/PGN.swift
@@ -0,0 +1,454 @@
+//
+// PGN.swift
+// Sage
+//
+// Copyright 2016-2017 Nikolai Vazquez
+// Modified by SuperGeroy
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+/// Portable game notation data.
+///
+/// - seealso: [Portable Game Notation (Wikipedia)](https://en.wikipedia.org/wiki/Portable_Game_Notation),
+/// [PGN Specification](https://www.chessclub.com/user/help/PGN-spec)
+
+import Foundation
+
+public struct PGN: Equatable {
+
+ /// PGN tag.
+ public enum Tag: String, CustomStringConvertible {
+
+ /// Event tag.
+ case event = "Event"
+
+ /// Site tag.
+ case site = "Site"
+
+ /// Date tag.
+ case date = "Date"
+
+ /// Round tag.
+ case round = "Round"
+
+ /// White tag.
+ case white = "White"
+
+ /// Black tag.
+ case black = "Black"
+
+ /// Result tag.
+ case result = "Result"
+
+ /// Annotator tag.
+ case annotator = "Annotator"
+
+ /// Ply (moves) count tag.
+ case plyCount = "PlyCount"
+
+ /// TimeControl tag.
+ case timeControl = "TimeControl"
+
+ /// Time tag.
+ case time = "Time"
+
+ /// Termination tag.
+ case termination = "Termination"
+
+ /// Playing mode tag.
+ case mode = "Mode"
+
+ /// FEN tag.
+ case fen = "FEN"
+
+ /// White player's title tag.
+ case whiteTitle = "WhiteTitle"
+
+ /// Black player's title tag.
+ case blackTitle = "BlackTitle"
+
+ /// White player's elo rating tag.
+ case whiteElo = "WhiteElo"
+
+ /// Black player's elo rating tag.
+ case blackElo = "BlackElo"
+
+ /// White player's United States Chess Federation rating tag.
+ case whiteUSCF = "WhiteUSCF"
+
+ /// Black player's United States Chess Federation rating tag.
+ case blackUSCF = "BlackUSCF"
+
+ /// White player's network or email address tag.
+ case whiteNA = "WhiteNA"
+
+ /// Black player's network or email address tag.
+ case blackNA = "BlackNA"
+
+ /// White player's type tag; either human or program.
+ case whiteType = "WhiteType"
+
+ /// Black player's type tag; either human or program.
+ case blackType = "BlackType"
+
+ /// The starting date tag of the event.
+ case eventDate = "EventDate"
+
+ /// Tag for the name of the sponsor of the event.
+ case eventSponsor = "EventSponsor"
+
+ /// The playing section tag of a tournament.
+ case section = "Section"
+
+ /// Tag for the stage of a multistage event.
+ case stage = "Stage"
+
+ /// The board number tag in a team event or in a simultaneous exhibition.
+ case board = "Board"
+
+ /// The traditional opening name tag.
+ case opening = "Opening"
+
+ /// Tag used to further refine the opening tag.
+ case variation = "Variation"
+
+ /// Used to further refine the variation tag.
+ case subVariation = "SubVariation"
+
+ /// Tag used for an opening designation from the five volume *Encyclopedia of Chess Openings*.
+ case eco = "ECO"
+
+ /// Tag used for an opening designation from the *New in Chess* database.
+ case nic = "NIC"
+
+ /// Tag similar to the Time tag but given according to the Universal Coordinated Time standard.
+ case utcTime = "UTCTime"
+
+ /// Tag similar to the Date tag but given according to the Universal Coordinated Time standard.
+ case utcDate = "UTCDate"
+
+ /// Tag for the "set-up" status of the game.
+ case setUp = "SetUp"
+
+ /// A textual representation of `self`.
+ public var description: String {
+ return rawValue
+ }
+
+ }
+
+ /// An error thrown by `PGN.init(parse:)`.
+ public enum ParseError: Error {
+
+ /// Unexpected quote found in move text.
+ case unexpectedQuote(String)
+
+ /// Unexpected closing brace found outside of comment.
+ case unexpectedClosingBrace(String)
+
+ /// No closing brace for comment.
+ case noClosingBrace(String)
+
+ /// No closing quote for tag value.
+ case noClosingQuote(String)
+
+ /// No closing bracket for tag pair.
+ case noClosingBracket(String)
+
+ /// Wrong number of tokens for tag pair.
+ case tagPairTokenCount([String])
+
+ /// Incorrect count of parenthesis for recursive annotation variation.
+ case parenthesisCountForRAV(String)
+
+ }
+
+ /// The tag pairs for `self`.
+ public var tagPairs: [String: String]
+
+ /// The moves in standard algebraic notation.
+ public var moves: [String]
+
+ /// The game outcome.
+ public var outcome: Game.Outcome? {
+ get {
+ let resultTag = Tag.result
+ return self[resultTag].flatMap(Game.Outcome.init)
+ }
+ set {
+ let resultTag = Tag.result
+ self[resultTag] = newValue?.description
+ }
+ }
+
+ /// Create PGN with `tagPairs` and `moves`.
+ public init(tagPairs: [String: String] = [:], moves: [String] = []) {
+ self.tagPairs = tagPairs
+ self.moves = moves
+ }
+
+ /// Create PGN with `tagPairs` and `moves`.
+ public init(tagPairs: [Tag: String], moves: [String] = []) {
+ self.init(moves: moves)
+ for (tag, value) in tagPairs {
+ self[tag] = value
+ }
+ }
+
+ /// Create PGN by parsing `string`.
+ ///
+ /// - throws: `ParseError` if an error occured while parsing.
+ public init(parse string: String) throws {
+ self.init()
+ if string.isEmpty { return }
+ for line in string._splitByNewlines() {
+ if line.first == "[" {
+ let commentsStripped = try line._commentsStripped(strings: true)
+ let (tag, value) = try commentsStripped._tagPair()
+ tagPairs[tag] = value
+ } else if line.first != "%" {
+ let commentsStripped = try line._commentsStripped(strings: false)
+ // Lichess PGNs have an extra comment after a few days
+ if (commentsStripped.count == 0 ) {
+ continue
+ }
+ let (moves, outcome) = try commentsStripped._moves()
+ self.moves += moves
+ if let outcome = outcome {
+ self.outcome = outcome
+ }
+ }
+ }
+ }
+
+ /// Get or set the value for `tag`.
+ public subscript(tag: Tag) -> String? {
+ get {
+ return tagPairs[tag.rawValue]
+ }
+ set {
+ tagPairs[tag.rawValue] = newValue
+ }
+ }
+
+ /// Returns `self` in export string format.
+ /// // PGN(tagPairs: ["Black": "Spassky, Boris V.", "Event": "F/S Return Match", "Round": "29", "Result": "½-½", "Site": "Belgrade, Serbia Yugoslavia|JUG", "Date": "1992.11.04", "White": "Fischer, Robert J."
+ public func exported() -> String {
+ var result = ""
+ var tagPairs = self.tagPairs
+ let sevenTags = [("Event", "?"),
+ ("Site", "?"),
+ ("Date", "????.??.??"),
+ ("Round", "?"),
+ ("White", "?"),
+ ("Black", "?"),
+ ("Result", "*")]
+ let orderedTags = ["Event", "Site", "Date", "Round", "White", "Black", "Result"]
+ for tag in orderedTags {
+ if let value = tagPairs.removeValue(forKey: tag) {
+ result += "[\(tag) \"\(value)\"]\n"
+ } else {
+ if let defaultValue = sevenTags.first(where: { $0.0 == tag })?.1 {
+ result += "[\(tag) \"\(defaultValue)\"]\n"
+ } else {
+ // Handle cases where the tag is not in 'sevenTags'
+ }
+ }
+ }
+ for (tag, value) in tagPairs {
+ result += "[\(tag) \"\(value)\"]\n"
+ }
+ let strideTo = stride(from: 0, to: moves.endIndex, by: 2)
+ var moveLine = ""
+ for num in strideTo {
+ let moveNumber = (num + 2) / 2
+ var moveString = "\(moveNumber). \(moves[num])"
+ if num + 1 < moves.endIndex {
+ moveString += " \(moves[num + 1])"
+ }
+ if moveString.count + moveLine.count < 80 {
+ if !moveLine.isEmpty {
+ moveString = " \(moveString)"
+ }
+ moveLine += moveString
+ } else {
+ result += "\n\(moveLine)"
+ moveLine = moveString
+ }
+ }
+ if !moveLine.isEmpty {
+ result += "\n\(moveLine)"
+ }
+ if let outcomeString = outcome?.description {
+ if moveLine.isEmpty {
+ result += "\n\(outcomeString)"
+ } else if outcomeString.count + moveLine.count < 80 {
+ result += " \(outcomeString)"
+ } else {
+ result += "\n\(outcomeString)"
+ }
+ }
+ return result
+ }
+
+}
+
+private extension Character {
+
+ static let newlines: Set<Character> = ["\u{000A}", "\u{000B}", "\u{000C}", "\u{000D}",
+ "\u{0085}", "\u{2028}", "\u{2029}"]
+
+ static let whitespaces: Set<Character> = ["\u{0020}", "\u{00A0}", "\u{1680}", "\u{180E}", "\u{2000}",
+ "\u{2001}", "\u{2002}", "\u{2003}", "\u{2004}", "\u{2005}",
+ "\u{2006}", "\u{2007}", "\u{2008}", "\u{2009}", "\u{200A}",
+ "\u{200B}", "\u{202F}", "\u{205F}", "\u{3000}", "\u{FEFF}"]
+
+ static let digits: Set<Character> = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
+
+ var isDigit: Bool {
+ return Character.digits.contains(self)
+ }
+
+}
+
+private extension String {
+
+ var _lastIndex: Index {
+ return index(before: endIndex)
+ }
+
+ @inline(__always)
+ func _split(by set: Set<Character>) -> [String] {
+ return split(whereSeparator: set.contains).map(String.init)
+ }
+
+ @inline(__always)
+ func _splitByNewlines() -> [String] {
+ return _split(by: Character.newlines)
+ }
+
+ @inline(__always)
+ func _splitByWhitespaces() -> [String] {
+ return _split(by: Character.whitespaces)
+ }
+
+ @inline(__always)
+ func _tagPair() throws -> (String, String) {
+ guard last == "]" else {
+ throw PGN.ParseError.noClosingBracket(self)
+ }
+ let startIndex = index(after: self.startIndex)
+ let endIndex = index(before: self.endIndex)
+ let tokens = String(self[startIndex ..< endIndex])._split(by: ["\""])
+ guard tokens.count == 2 else {
+ throw PGN.ParseError.tagPairTokenCount(tokens)
+ }
+ let tagParts = tokens[0]._splitByWhitespaces()
+ guard tagParts.count == 1 else {
+ throw PGN.ParseError.tagPairTokenCount(tagParts)
+ }
+ return (tagParts[0], tokens[1])
+ }
+
+ @inline(__always)
+ func _moves() throws -> (moves: [String], outcome: Game.Outcome?) {
+ var stripped = ""
+ var ravDepth = 0
+ var startIndex = self.startIndex
+ let lastIndex = _lastIndex
+ for (index, character) in zip(indices, self) {
+ if character == "(" {
+ if ravDepth == 0 {
+ stripped += self[startIndex ..< index]
+ }
+ ravDepth += 1
+ } else if character == ")" {
+ ravDepth -= 1
+ if ravDepth == 0 {
+ startIndex = self.index(after: index)
+ }
+ } else if index == lastIndex && ravDepth == 0 {
+ stripped += self[startIndex ... index]
+ }
+ }
+ guard ravDepth == 0 else {
+ throw PGN.ParseError.parenthesisCountForRAV(self)
+ }
+ let tokens = stripped._split(by: [" ", "."])
+ let moves = tokens.filter({ $0.first?.isDigit == false }).map { $0.replacingOccurrences(of: "?", with: "").replacingOccurrences(of: "!", with: "")}
+ let outcome = tokens.last.flatMap(Game.Outcome.init)
+ return (moves, outcome)
+ }
+
+ @inline(__always)
+ func _commentsStripped(strings consideringStrings: Bool) throws -> String {
+ var stripped = ""
+ var startIndex = self.startIndex
+ let lastIndex = _lastIndex
+ var afterEscape = false
+ var inString = false
+ var inComment = false
+ for (index, character) in zip(indices, self) {
+ if character == "\\" {
+ afterEscape = true
+ continue
+ }
+ if character == "\"" {
+ if !inComment {
+ guard consideringStrings else {
+ throw PGN.ParseError.unexpectedQuote(self)
+ }
+ if !inString {
+ inString = true
+ } else if !afterEscape {
+ inString = false
+ }
+ }
+ } else if !inString {
+ if character == ";" && !inComment {
+ stripped += self[startIndex ..< index]
+ break
+ } else if character == "{" && !inComment {
+ inComment = true
+ stripped += self[startIndex ..< index]
+ } else if character == "}" {
+ guard inComment else {
+ throw PGN.ParseError.unexpectedClosingBrace(self)
+ }
+ inComment = false
+ startIndex = self.index(after: index)
+ }
+ }
+ if index >= startIndex && index == lastIndex && !inComment {
+ stripped += self[startIndex ... index]
+ }
+ afterEscape = false
+ }
+ guard !inString else {
+ throw PGN.ParseError.noClosingQuote(self)
+ }
+ guard !inComment else {
+ throw PGN.ParseError.noClosingBrace(self)
+ }
+
+ return stripped
+ }
+
+}
+
+/// Returns a Boolean value indicating whether two values are equal.
+public func == (lhs: PGN, rhs: PGN) -> Bool {
+ return lhs.tagPairs == rhs.tagPairs
+ && lhs.moves == rhs.moves
+}
diff --git a/Sources/SwiftChessNeo/PGNMove.swift b/Sources/SwiftChessNeo/PGNMove.swift
new file mode 100644
index 0000000..3be103b
--- /dev/null
+++ b/Sources/SwiftChessNeo/PGNMove.swift
@@ -0,0 +1,219 @@
+//
+// PGNMove.swift
+// Sage
+//
+// Created by Kajetan Dąbrowski on 19/10/2016.
+// Copyright © 2016 Nikolai Vazquez. All rights reserved.
+//
+
+import Foundation
+
+/// A PGN move representation in a string.
+public struct PGNMove: RawRepresentable, ExpressibleByStringLiteral {
+
+
+ /// PGN Move parsing error
+ ///
+ /// - invalidMove: The move is invalid
+ public enum ParseError: Error {
+ case invalidMove(String)
+ }
+
+ public typealias RawValue = String
+ public typealias StringLiteralType = String
+ public typealias ExtendedGraphemeClusterLiteralType = String
+ public typealias UnicodeScalarLiteralType = String
+ public let rawValue: String
+
+ static private let pattern = "^(?:([NBRQK]?)([a-h]?)([1-8]?)(x?)([a-h])([1-8])((?:=[NBRQ])?)|(O-O)|(O-O-O))([+#]?)$"
+ static private let regex: NSRegularExpression! = try? NSRegularExpression(pattern: pattern, options: [])
+
+ private var match: NSTextCheckingResult?
+ private var unwrappedMatch: NSTextCheckingResult {
+ guard let unwrappedMatch = match else {
+ fatalError("PGNMove not possible. Check move.isPossible before checking other properties")
+ }
+ return unwrappedMatch
+ }
+
+ public init?(rawValue: String) {
+ self.rawValue = rawValue
+ parse()
+ if !isPossible { return nil }
+ }
+
+ public init(stringLiteral value: StringLiteralType) {
+ rawValue = value
+ parse()
+ }
+
+ public init(unicodeScalarLiteral value: UnicodeScalarLiteralType) {
+ rawValue = value
+ parse()
+ }
+
+ public init(extendedGraphemeClusterLiteral value: ExtendedGraphemeClusterLiteralType) {
+ rawValue = value
+ parse()
+ }
+
+ mutating private func parse() {
+ let matches = PGNMove.regex.matches(in: rawValue, options: [], range: fullRange)
+ match = matches.filter { $0.range.length == self.fullRange.length }.first
+ }
+
+ private var fullRange: NSRange {
+ return NSRange(location: 0, length: rawValue.count)
+ }
+
+ /// Indicates whether the move is possible.
+ public var isPossible: Bool {
+ return match != nil
+ }
+
+ /// Indicated whether the pgn represents a capture
+ public var isCapture: Bool {
+ return unwrappedMatch.range(at: 4).length > 0
+ }
+
+ /// Indicates whether the move represents a promotion
+ public var isPromotion: Bool {
+ return unwrappedMatch.range(at: 7).length > 0
+ }
+
+ /// Indicates whether the move is castle
+ public var isCastle: Bool {
+ return isCastleKingside || isCastleQueenside
+ }
+
+ /// Indicates whether the move is castle kingside
+ public var isCastleKingside: Bool {
+ return unwrappedMatch.range(at: 8).length > 0
+ }
+
+ /// Indicates whether the move is castle queenside
+ public var isCastleQueenside: Bool {
+ return unwrappedMatch.range(at: 9).length > 0
+ }
+
+ /// Indicates whether the move represents a check
+ public var isCheck: Bool {
+ return unwrappedMatch.range(at: 10).length > 0
+ }
+
+ /// Indicates whether the move represents a checkmate
+ public var isCheckmate: Bool {
+ return stringAt(rangeIndex: 10) == "#"
+ }
+
+ /// A piece kind that is moved by the move
+ public var piece: Piece.Kind {
+ let pieceLetter = stringAt(rangeIndex: 1)
+ guard let piece = PGNMove.pieceFor(letter: pieceLetter) else {
+ fatalError("Invalid piece")
+ }
+ return piece
+ }
+
+ /// The rank to move to
+ public var rank: Rank {
+ let rankSymbol = stringAt(rangeIndex: 6)
+ guard let raw = Int(rankSymbol), let rank = Rank(rawValue: raw) else { fatalError("Could not get rank") }
+ return rank
+ }
+
+ /// The file to move to
+ public var file: File {
+ guard let fileSymbol = stringAt(rangeIndex: 5).first,
+ let file = File(fileSymbol) else { fatalError("Could not get file") }
+ return file
+ }
+
+ /// The rank to move from.
+ /// For example in the move 'Nf3' there is no source rank, since PGNMove is out of board context.
+ /// However, if you specify the move like 'N4d2' the move will represent the knight from the fourth rank.
+ public var sourceRank: Rank? {
+ let sourceRankSymbol = stringAt(rangeIndex: 3)
+ if sourceRankSymbol == "" { return nil }
+ guard let sourceRankRaw = Int(sourceRankSymbol),
+ let sourceRank = Rank(rawValue: sourceRankRaw) else { fatalError("Could not get source rank") }
+ return sourceRank
+ }
+
+ /// The file to move from.
+ /// For example in the move 'Nf3' there is no source file, since PGNMove is out of board context.
+ /// However, if you specify the move like 'Nfd2' the move will represent the knight from the d file.
+ public var sourceFile: File? {
+ let sourceFileSymbol = stringAt(rangeIndex: 2)
+ if sourceFileSymbol == "" { return nil }
+ guard let sourceFileRaw = sourceFileSymbol.first,
+ let sourceFile = File(sourceFileRaw) else { fatalError("Could not get source file") }
+ return sourceFile
+ }
+
+ /// Represents a piece that the move wants to promote to
+ public var promotionPiece: Piece.Kind? {
+ if !isPromotion { return nil }
+ guard let pieceLetter = stringAt(rangeIndex: 7).last,
+ let piece = PGNMove.pieceFor(letter: String(pieceLetter)) else { fatalError("Could not get promotion piece") }
+ return piece
+ }
+
+ private static func pieceFor(letter: String) -> Piece.Kind? {
+ switch letter {
+ case "N":
+ return ._knight
+ case "B":
+ return ._bishop
+ case "K":
+ return ._king
+ case "Q":
+ return ._queen
+ case "R":
+ return ._rook
+ case "":
+ return ._pawn
+ default:
+ return nil
+ }
+ }
+
+ private func stringAt(rangeIndex: Int) -> String {
+ guard let match = match else { fatalError() }
+ let range = match.range(at: rangeIndex)
+ let substring = (rawValue as NSString).substring(with: range)
+ return substring
+ }
+}
+
+public struct PGNParser {
+
+
+ /// Parses the move in context of the game position
+ ///
+ /// - parameter move: Move that needs to be parsed
+ /// - parameter position: position to parse in
+ ///
+ /// - throws: Errors if move is invalid, or if it cannot be executed in this position, or if it's ambiguous.
+ ///
+ /// - returns: Parsed move that can be applied to a game (containing source and destination squares)
+ public static func parse(move: PGNMove, in position: Game.Position) throws -> Move {
+ if !move.isPossible { throw PGNMove.ParseError.invalidMove(move.rawValue) }
+ let colorToMove = position.playerTurn
+ if move.isCastleKingside { return Move(castle: colorToMove, direction: .right) }
+ if move.isCastleQueenside { return Move(castle: colorToMove, direction: .left) }
+
+ let piece = Piece(kind: move.piece, color: colorToMove)
+ let destinationSquare: Square = Square(file: move.file, rank: move.rank)
+ let game = try Game(position: position)
+ var possibleMoves = game.availableMoves().filter { return $0.end == destinationSquare }.filter { move -> Bool in
+ game.board.locations(for: piece).contains(where: { move.start.location == $0 })
+ }
+
+ if let sourceFile = move.sourceFile { possibleMoves = possibleMoves.filter { $0.start.file == sourceFile } }
+ if let sourceRank = move.sourceRank { possibleMoves = possibleMoves.filter { $0.start.rank == sourceRank } }
+
+ if possibleMoves.count != 1 { throw PGNMove.ParseError.invalidMove(move.rawValue) }
+ return possibleMoves.first!
+ }
+}
diff --git a/Sources/SwiftChessNeo/Piece.swift b/Sources/SwiftChessNeo/Piece.swift
new file mode 100644
index 0000000..492ad87
--- /dev/null
+++ b/Sources/SwiftChessNeo/Piece.swift
@@ -0,0 +1,297 @@
+//
+// Piece.swift
+// Sage
+//
+// Copyright 2016-2017 Nikolai Vazquez
+// Modified by SuperGeroy
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+/// A chess piece.
+public struct Piece: Hashable, CustomStringConvertible {
+
+ /// A piece kind.
+ public enum Kind: Int {
+
+ /// Pawn piece kind.
+ case pawn
+
+ /// Knight piece kind.
+ case knight
+
+ /// Bishop piece kind.
+ case bishop
+
+ /// Rook piece kind.
+ case rook
+
+ /// Queen piece kind.
+ case queen
+
+ /// King piece kind.
+ case king
+
+ /// Pawn
+ internal static let _pawn = Kind.pawn
+
+ /// Knight
+ internal static let _knight = Kind.knight
+
+ /// Bishop
+ internal static let _bishop = Kind.bishop
+
+ /// Rook
+ internal static let _rook = Kind.rook
+
+ /// Queen
+ internal static let _queen = Kind.queen
+
+ /// King
+ internal static let _king = Kind.king
+
+ /// An array of all piece kinds.
+ public static let all: [Kind] = [.pawn, .knight, .bishop, .rook, .queen, .king]
+
+ /// The piece kind's name.
+ public var name: String {
+ switch self {
+ case .pawn: return "Pawn"
+ case .knight: return "Knight"
+ case .bishop: return "Bishop"
+ case .rook: return "Rook"
+ case .queen: return "Queen"
+ case .king: return "King"
+ }
+ }
+
+ /// The piece kind's relative value. Can be used to determine how valuable a piece or combination of pieces is.
+ public var relativeValue: Double {
+ switch self {
+ case .pawn: return 1
+ case .knight: return 3
+ case .bishop: return 3.25
+ case .rook: return 5
+ case .queen: return 9
+ case .king: return .infinity
+ }
+ }
+
+ /// The piece is `Pawn`.
+ public var isPawn: Bool {
+ return self == ._pawn
+ }
+
+ /// The piece `Knight`.
+ public var isKnight: Bool {
+ return self == ._knight
+ }
+
+ /// The piece is `Bishop`.
+ public var isBishop: Bool {
+ return self == ._bishop
+ }
+
+ /// The piece is `Rook`.
+ public var isRook: Bool {
+ return self == ._rook
+ }
+
+ /// The piece is `Queen`.
+ public var isQueen: Bool {
+ return self == ._queen
+ }
+
+ /// The piece is `King`.
+ public var isKing: Bool {
+ return self == ._king
+ }
+
+ /// Returns `true` if `self` can be a promotion for a pawn.
+ public func canPromote() -> Bool {
+ return !(isPawn || isKing)
+ }
+
+ /// Returns `true` if `self` can be a promotion for `other`.
+ public func canPromote(_ other: Kind) -> Bool {
+ return canPromote() ? other.isPawn : false
+ }
+
+ }
+
+ internal static let _whiteHashes: [Int] = whitePieces.map({ $0.rawValue })
+
+ internal static let _blackHashes: [Int] = blackPieces.map({ $0.rawValue })
+
+ internal static func _hashes(for color: Color) -> [Int] {
+ return color.isWhite ? _whiteHashes : _blackHashes
+ }
+
+ internal static let _whiteNonQueens: [Piece] = whitePieces.filter({ !$0.kind.isQueen })
+
+ internal static let _blackNonQueens: [Piece] = blackPieces.filter({ !$0.kind.isQueen })
+
+ internal static func _nonQueens(for color: Color) -> [Piece] {
+ return color.isWhite ? _whiteNonQueens : _blackNonQueens
+ }
+
+ /// An array of all pieces.
+ public static let all: [Piece] = {
+ return [.white, .black].reduce([]) { pieces, color in
+ return pieces + Kind.all.map({ Piece(kind: $0, color: color) })
+ }
+ }()
+
+ /// An array of all white pieces.
+ public static let whitePieces: [Piece] = all.filter({ $0.color.isWhite })
+
+ /// An array of all black pieces.
+ public static let blackPieces: [Piece] = all.filter({ $0.color.isBlack })
+
+ /// Returns an array of all pieces for `color`.
+ public static func pieces(for color: Color) -> [Piece] {
+ return color.isWhite ? whitePieces : blackPieces
+ }
+
+ /// The piece's kind.
+ public var kind: Kind
+
+ /// The piece's color.
+ public var color: Color
+
+ /// The character for the piece. Uppercase if white or lowercase if black.
+ public var character: Character {
+ switch kind {
+ case .pawn: return color.isWhite ? "P" : "p"
+ case .knight: return color.isWhite ? "N" : "n"
+ case .bishop: return color.isWhite ? "B" : "b"
+ case .rook: return color.isWhite ? "R" : "r"
+ case .queen: return color.isWhite ? "Q" : "q"
+ case .king: return color.isWhite ? "K" : "k"
+ }
+ }
+
+ /// A textual representation of `self`.
+ public var description: String {
+ return "\(kind.name)(\(color))"
+ }
+
+ /// The piece's raw value.
+ public var rawValue: Int {
+ return (kind.rawValue << 1) | color.numericValue
+ }
+
+ /// Create a piece from an integer value.
+ internal init?(value: Int) {
+ guard let kind = Kind(rawValue: value >> 1) else {
+ return nil
+ }
+ self.init(kind: kind, color: value & 1 == 0 ? .white : .black)
+ }
+
+ /// Create a piece from `kind` and `color`.
+ public init(kind: Kind, color: Color) {
+ self.kind = kind
+ self.color = color
+ }
+
+ /// Create a pawn piece with `color`.
+ public init(pawn color: Color) {
+ self.init(kind: ._pawn, color: color)
+ }
+
+ /// Create a knight piece with `color`.
+ public init(knight color: Color) {
+ self.init(kind: ._knight, color: color)
+ }
+
+ /// Create a bishop piece with `color`.
+ public init(bishop color: Color) {
+ self.init(kind: ._bishop, color: color)
+ }
+
+ /// Create a rook piece with `color`.
+ public init(rook color: Color) {
+ self.init(kind: ._rook, color: color)
+ }
+
+ /// Create a queen piece with `color`.
+ public init(queen color: Color) {
+ self.init(kind: ._queen, color: color)
+ }
+
+ /// Create a king piece with `color`.
+ public init(king color: Color) {
+ self.init(kind: ._king, color: color)
+ }
+
+ /// Create a piece from a character.
+ public init?(character: Character) {
+ switch character {
+ case "P": self.init(pawn: .white)
+ case "p": self.init(pawn: .black)
+ case "N": self.init(knight: .white)
+ case "n": self.init(knight: .black)
+ case "B": self.init(bishop: .white)
+ case "b": self.init(bishop: .black)
+ case "R": self.init(rook: .white)
+ case "r": self.init(rook: .black)
+ case "Q": self.init(queen: .white)
+ case "q": self.init(queen: .black)
+ case "K": self.init(king: .white)
+ case "k": self.init(king: .black)
+ default:
+ return nil
+ }
+ }
+
+ /// Returns `true` if `self` can be a promotion for `other`.
+ public func canPromote(_ other: Piece) -> Bool {
+ return kind.canPromote(other.kind) && color == other.color
+ }
+
+ /// Returns `true` if `self` can be a promotion for `color`.
+ public func canPromote(_ color: Color) -> Bool {
+ return kind.canPromote() ? self.color == color : false
+ }
+
+ /// The special character for the piece.
+ public func specialCharacter(background color: Color = .white) -> Character {
+ switch kind {
+ case .pawn: return color == self.color ? "♙" : "♟"
+ case .knight: return color == self.color ? "♘" : "♞"
+ case .bishop: return color == self.color ? "♗" : "♝"
+ case .rook: return color == self.color ? "♖" : "♜"
+ case .queen: return color == self.color ? "♕" : "♛"
+ case .king: return color == self.color ? "♔" : "♚"
+ }
+ }
+
+ public func unicodeCharacter() -> Character {
+ switch kind {
+ case .pawn: return "♟"
+ case .knight: return "♞"
+ case .bishop: return "♝"
+ case .rook: return "♜"
+ case .queen: return "♛"
+ case .king: return "♚"
+ }
+ }
+
+}
+
+/// Returns `true` if both pieces are the same.
+public func == (lhs: Piece, rhs: Piece) -> Bool {
+ return lhs.kind == rhs.kind
+ && lhs.color == rhs.color
+}
diff --git a/Sources/SwiftChessNeo/Player.swift b/Sources/SwiftChessNeo/Player.swift
new file mode 100644
index 0000000..45f3145
--- /dev/null
+++ b/Sources/SwiftChessNeo/Player.swift
@@ -0,0 +1,82 @@
+//
+// Player.swift
+// Sage
+//
+// Copyright 2016-2017 Nikolai Vazquez
+// Modified by SuperGeroy
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+/// A chess game player.
+public struct Player: Equatable, CustomStringConvertible {
+
+ /// A player kind.
+ public enum Kind: String, CustomStringConvertible {
+
+ /// Human player kind.
+ case human = "Human"
+
+ /// Computer player kind.
+ case computer = "Computer"
+
+ /// Boolean indicating `self` is a human.
+ public var isHuman: Bool {
+ return self == .human
+ }
+
+ /// Boolean indicating `self` is a computer.
+ public var isComputer: Bool {
+ return self == .computer
+ }
+
+ /// A textual representation of this instance.
+ public var description: String {
+ return rawValue
+ }
+
+ }
+
+ /// The player's kind.
+ public var kind: Kind
+
+ /// The player's name.
+ public var name: String?
+
+ /// The player's elo rating.
+ public var elo: UInt?
+
+ /// A textual representation of this instance.
+ public var description: String {
+ return "Player(kind: \(kind), name: \(name._altDescription), elo: \(elo._altDescription))"
+ }
+
+ /// Create a player with `kind` and `name`.
+ ///
+ /// - parameter kind: The player's kind. Default is human.
+ /// - parameter name: The player's name. Default is `nil`.
+ /// - parameter elo: The player's elo rating. Default is `nil`.
+ public init(kind: Kind = .human, name: String? = nil, elo: UInt? = nil) {
+ self.kind = kind
+ self.name = name
+ self.elo = elo
+ }
+
+}
+
+/// Returns `true` if the players are the same.
+public func == (lhs: Player, rhs: Player) -> Bool {
+ return lhs.kind == rhs.kind
+ && lhs.name == rhs.name
+ && lhs.elo == rhs.elo
+}
diff --git a/Sources/SwiftChessNeo/Rank.swift b/Sources/SwiftChessNeo/Rank.swift
new file mode 100644
index 0000000..9998e41
--- /dev/null
+++ b/Sources/SwiftChessNeo/Rank.swift
@@ -0,0 +1,147 @@
+//
+// Rank.swift
+// Sage
+//
+// Copyright 2016-2017 Nikolai Vazquez
+// Modified by SuperGeroy
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+/// A chess board rank.
+///
+/// `Rank`s refer to the eight rows of a chess board, beginning with 1 at the bottom and ending with 8 at the top.
+public enum Rank: Int, Comparable, CustomStringConvertible {
+
+ /// A direction in rank.
+ public enum Direction {
+
+ /// Up direction.
+ case up
+
+ /// Down direction.
+ case down
+
+ }
+
+ /// Rank 1.
+ case one = 1
+
+ /// Rank 2.
+ case two = 2
+
+ /// Rank 3.
+ case three = 3
+
+ /// Rank 4.
+ case four = 4
+
+ /// Rank 5.
+ case five = 5
+
+ /// Rank 6.
+ case six = 6
+
+ /// Rank 7.
+ case seven = 7
+
+ /// Rank 8.
+ case eight = 8
+
+}
+
+extension Rank {
+
+ /// An array of all ranks.
+ public static let all: [Rank] = [1, 2, 3, 4, 5, 6, 7, 8]
+
+ /// The row index of `self`.
+ public var index: Int {
+ return rawValue - 1
+ }
+
+ /// A textual representation of `self`.
+ public var description: String {
+ return String(rawValue)
+ }
+
+ /// Create an instance from an integer value.
+ public init?(_ value: Int) {
+ self.init(rawValue: value)
+ }
+
+ /// Create a `Rank` from a zero-based row index.
+ public init?(index: Int) {
+ self.init(rawValue: index + 1)
+ }
+
+ /// Creates the starting `Rank` for the color.
+ public init(startFor color: Color) {
+ self = color.isWhite ? 1 : 8
+ }
+
+ /// Creates the ending `Rank` for the color.
+ public init(endFor color: Color) {
+ self = color.isWhite ? 8 : 1
+ }
+
+ /// Returns a rank from advancing `self` by `value` with respect to `color`.
+ public func advanced(by value: Int, for color: Color = .white) -> Rank? {
+ return Rank(rawValue: rawValue + (color.isWhite ? value : -value))
+ }
+
+ /// The next rank after `self`.
+ public func next() -> Rank? {
+ return Rank(rawValue: (rawValue + 1))
+ }
+
+ /// The previous rank to `self`.
+ public func previous() -> Rank? {
+ return Rank(rawValue: (rawValue - 1))
+ }
+
+ /// The opposite rank of `self`.
+ public func opposite() -> Rank {
+ return Rank(rawValue: 9 - rawValue)!
+ }
+
+ /// The files from `self` to `other`.
+ public func to(_ other: Rank) -> [Rank] {
+ return _to(other)
+ }
+
+ /// The files between `self` and `other`.
+ public func between(_ other: Rank) -> [Rank] {
+ return _between(other)
+ }
+
+}
+
+extension Rank: ExpressibleByIntegerLiteral { }
+
+extension Rank {
+
+ /// Create an instance initialized to `value`.
+ public init(integerLiteral value: Int) {
+ guard let rank = Rank(rawValue: value) else {
+ fatalError("Rank value not within 1 and 8, inclusive")
+ }
+ self = rank
+ }
+
+}
+
+/// Returns `true` if one rank is higher than the other.
+public func < (lhs: Rank, rhs: Rank) -> Bool {
+ return lhs.rawValue < rhs.rawValue
+}
diff --git a/Sources/SwiftChessNeo/Sequence+Sage.swift b/Sources/SwiftChessNeo/Sequence+Sage.swift
new file mode 100644
index 0000000..19aaa67
--- /dev/null
+++ b/Sources/SwiftChessNeo/Sequence+Sage.swift
@@ -0,0 +1,33 @@
+//
+// Sequence+Sage.swift
+// Sage
+//
+// Copyright 2016-2017 Nikolai Vazquez
+// Modified by SuperGeroy
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+extension Sequence where Iterator.Element == Square {
+
+ /// Returns moves from `square` to the squares in `self`.
+ public func moves(from square: Square) -> [Move] {
+ return self.map({ square >>> $0 })
+ }
+
+ /// Returns moves from the squares in `self` to `square`.
+ public func moves(to square: Square) -> [Move] {
+ return self.map({ $0 >>> square })
+ }
+
+}
diff --git a/Sources/SwiftChessNeo/Square.swift b/Sources/SwiftChessNeo/Square.swift
new file mode 100644
index 0000000..8dbc17c
--- /dev/null
+++ b/Sources/SwiftChessNeo/Square.swift
@@ -0,0 +1,428 @@
+//
+// Square.swift
+// Sage
+//
+// Copyright 2016-2017 Nikolai Vazquez
+// Modified by SuperGeroy
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+/// A pair of a chess board `File` and `Rank`.
+public typealias Location = (file: File, rank: Rank)
+
+/// A chess board square.
+///
+/// A `Square` can be one of sixty-four possible values, ranging from `A1` to `H8`.
+public enum Square: Int, CustomStringConvertible {
+
+ /// A1 square.
+ case a1
+
+ /// B1 square.
+ case b1
+
+ /// C1 square.
+ case c1
+
+ /// D1 square.
+ case d1
+
+ /// E1 square.
+ case e1
+
+ /// F1 square.
+ case f1
+
+ /// G1 square.
+ case g1
+
+ /// H1 square.
+ case h1
+
+ /// A2 square.
+ case a2
+
+ /// B2 square.
+ case b2
+
+ /// C2 square.
+ case c2
+
+ /// D2 square.
+ case d2
+
+ /// E2 square.
+ case e2
+
+ /// F2 square.
+ case f2
+
+ /// G2 square.
+ case g2
+
+ /// H2 square.
+ case h2
+
+ /// A3 square.
+ case a3
+
+ /// B3 square.
+ case b3
+
+ /// C3 square.
+ case c3
+
+ /// D3 square.
+ case d3
+
+ /// E3 square.
+ case e3
+
+ /// F3 square.
+ case f3
+
+ /// G3 square.
+ case g3
+
+ /// H3 square.
+ case h3
+
+ /// A4 square.
+ case a4
+
+ /// B4 square.
+ case b4
+
+ /// C4 square.
+ case c4
+
+ /// D4 square.
+ case d4
+
+ /// E4 square.
+ case e4
+
+ /// F4 square.
+ case f4
+
+ /// G4 square.
+ case g4
+
+ /// H4 square.
+ case h4
+
+ /// A5 square.
+ case a5
+
+ /// B5 square.
+ case b5
+
+ /// C5 square.
+ case c5
+
+ /// D5 square.
+ case d5
+
+ /// E5 square.
+ case e5
+
+ /// F5 square.
+ case f5
+
+ /// G5 square.
+ case g5
+
+ /// H5 square.
+ case h5
+
+ /// A6 square.
+ case a6
+
+ /// B6 square.
+ case b6
+
+ /// C6 square.
+ case c6
+
+ /// D6 square.
+ case d6
+
+ /// E6 square.
+ case e6
+
+ /// F6 square.
+ case f6
+
+ /// G6 square.
+ case g6
+
+ /// H6 square.
+ case h6
+
+ /// A7 square.
+ case a7
+
+ /// B7 square.
+ case b7
+
+ /// C7 square.
+ case c7
+
+ /// D7 square.
+ case d7
+
+ /// E7 square.
+ case e7
+
+ /// F7 square.
+ case f7
+
+ /// G7 square.
+ case g7
+
+ /// H7 square.
+ case h7
+
+ /// A8 square.
+ case a8
+
+ /// B8 square.
+ case b8
+
+ /// C8 square.
+ case c8
+
+ /// D8 square.
+ case d8
+
+ /// E8 square.
+ case e8
+
+ /// F8 square.
+ case f8
+
+ /// G8 square.
+ case g8
+
+ /// H8 square.
+ case h8
+
+}
+
+extension Square {
+
+ /// An array of all squares.
+ public static let all: [Square] = (0 ..< 64).compactMap(Square.init(rawValue:))
+
+ /// The file of `self`.
+ public var file: File {
+ get {
+ return File(index: rawValue & 7)!
+ }
+ set(newFile) {
+ self = Square(file: newFile, rank: rank)
+ }
+ }
+
+ /// The rank of `self`.
+ public var rank: Rank {
+ get {
+ return Rank(index: rawValue >> 3)!
+ }
+ set(newRank) {
+ self = Square(file: file, rank: newRank)
+ }
+ }
+
+ /// The location of `self`.
+ public var location: Location {
+ get {
+ return (file, rank)
+ }
+ set(newLocation) {
+ self = Square(location: newLocation)
+ }
+ }
+
+ /// The square's color.
+ public var color: Color {
+ return (file.index & 1 != rank.index & 1) ? ._white : ._black
+ }
+
+ /// A textual representation of `self`.
+ public var description: String {
+ return "\(file)\(rank)"
+ }
+
+ /// Create a square from `file` and `rank`.
+ public init(file: File, rank: Rank) {
+ self.init(rawValue: file.index + (rank.index << 3))!
+ }
+
+ /// Create a square from `location`.
+ public init(location: Location) {
+ self.init(file: location.file, rank: location.rank)
+ }
+
+ /// Create a square from `file` and `rank`. Returns `nil` if either is `nil`.
+ public init?(file: File?, rank: Rank?) {
+ guard let file = file, let rank = rank else {
+ return nil
+ }
+ self.init(file: file, rank: rank)
+ }
+
+ /// Create a square from `string`.
+ public init?(_ string: String) {
+ guard string.count == 2 else {
+ return nil
+ }
+ guard let file = File(string.first!) else {
+ return nil
+ }
+ guard let rank = Int(String(string.last!)).flatMap(Rank.init(_:)) else {
+ return nil
+ }
+ self.init(file: file, rank: rank)
+ }
+
+ /// Returns the squares between `self` and `other`.
+ public func between(_ other: Square) -> Bitboard {
+ return _betweenTable[_triangleIndex(self, other)]
+ }
+
+ /// Returns the squares along the line with `other`.
+ public func line(with other: Square) -> Bitboard {
+ return _lineTable[_triangleIndex(self, other)]
+ }
+
+ /// Returns `true` if `self` is between `start` and `end`.
+ public func isBetween(start: Square, end: Square) -> Bool {
+ return start.between(end)[self]
+ }
+
+ /// Returns `true` if `self` is aligned with `first` and `second`.
+ public func isAligned(with first: Square, and second: Square) -> Bool {
+ return line(with: first)[second]
+ || line(with: second)[first]
+ || (self == first && self == second)
+ }
+
+ /// Returns `true` if `self` is aligned with `first` and `rest`.
+ public func isAligned(with first: Square, _ rest: Square...) -> Bool {
+ var line = self == first ? Bitboard(square: self) : self.line(with: first)
+ for square in rest where square != self {
+ if line == Bitboard(square: self) {
+ line = self.line(with: square)
+ }
+ guard line[square] else {
+ return false
+ }
+ }
+ return !line.isEmpty
+ }
+
+ /// Returns `true` if `self` is aligned with `squares`.
+ public func isAligned<S: Sequence>(with squares: S) -> Bool where S.Iterator.Element == Square {
+ var line: Bitboard? = nil
+ let bitboard = Bitboard(square: self)
+ for square in squares {
+ if let lineBitboard = line {
+ if lineBitboard == bitboard {
+ line = self.line(with: square)
+ } else {
+ guard lineBitboard[square] else {
+ return false
+ }
+ }
+ } else if square == self {
+ line = bitboard
+ } else {
+ line = self.line(with: square)
+ }
+ }
+ return line?.isEmpty == false
+ }
+
+ /// Returns a bitboard mask of attacks for a king at `self`.
+ public func kingAttacks() -> Bitboard {
+ return _kingAttackTable[rawValue]
+ }
+
+ /// Returns a bitboard mask of attacks for a knight at `self`.
+ public func knightAttacks() -> Bitboard {
+ return _knightAttackTable[rawValue]
+ }
+
+ /// Returns a bitboard mask of attacks for a piece at `self`.
+ ///
+ /// - parameter piece: The piece for the attacks.
+ /// - parameter stoppers: The pieces stopping a sliding move. The returned bitboard includes the stopped space.
+ ///
+ /// - seealso: `attackMoves(for:stoppers:)`
+ public func attacks(for piece: Piece, stoppers: Bitboard = 0) -> Bitboard {
+ switch piece.kind {
+ case .king:
+ return kingAttacks()
+ case .knight:
+ return knightAttacks()
+ case .pawn:
+ return _pawnAttackTable(for: piece.color)[rawValue]
+ default:
+ return Bitboard(square: self)._attacks(for: piece, stoppers: stoppers)
+ }
+ }
+
+ /// Returns an array of attack moves for a piece at `self`.
+ ///
+ /// - seealso: `attacks(for:stoppers:)`
+ public func attackMoves(for piece: Piece, stoppers: Bitboard = 0) -> [Move] {
+ return attacks(for: piece, stoppers: stoppers).moves(from: self)
+ }
+
+ /// Returns moves from the squares in `squares` to `self`.
+ public func moves<S: Sequence>(from squares: S) -> [Move] where S.Iterator.Element == Square {
+ return squares.moves(to: self)
+ }
+
+ /// Returns moves from `self` to the squares in `squares`.
+ public func moves<S: Sequence>(to squares: S) -> [Move] where S.Iterator.Element == Square {
+ return squares.moves(from: self)
+ }
+
+}
+
+extension Square: ExpressibleByStringLiteral { }
+
+extension Square {
+
+ /// Create an instance initialized to `value`.
+ public init(stringLiteral value: String) {
+ guard let square = Square(value) else {
+ fatalError("Invalid string for square: \"\(value)\"")
+ }
+ self = square
+ }
+
+ /// Create an instance initialized to `value`.
+ public init(unicodeScalarLiteral value: String) {
+ self.init(stringLiteral: value)
+ }
+
+ /// Create an instance initialized to `value`.
+ public init(extendedGraphemeClusterLiteral value: String) {
+ self.init(stringLiteral: value)
+ }
+
+}
diff --git a/Sources/SwiftChessNeo/Tables.swift b/Sources/SwiftChessNeo/Tables.swift
new file mode 100644
index 0000000..afad90d
--- /dev/null
+++ b/Sources/SwiftChessNeo/Tables.swift
@@ -0,0 +1,114 @@
+//
+// Tables.swift
+// Sage
+//
+// Copyright 2016-2017 Nikolai Vazquez
+// Modified by SuperGeroy
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+/// Returns the pawn attack table for `color`.
+internal func _pawnAttackTable(for color: Color) -> [Bitboard] {
+ if color.isWhite {
+ return _whitePawnAttackTable
+ } else {
+ return _blackPawnAttackTable
+ }
+}
+
+/// A lookup table of all white pawn attack bitboards.
+internal let _whitePawnAttackTable = Square.all.map { square in
+ return Bitboard(square: square)._pawnAttacks(for: ._white)
+}
+
+/// A lookup table of all black pawn attack bitboards.
+internal let _blackPawnAttackTable = Square.all.map { square in
+ return Bitboard(square: square)._pawnAttacks(for: ._black)
+}
+
+/// A lookup table of all king attack bitboards.
+internal let _kingAttackTable = Square.all.map { square in
+ return Bitboard(square: square)._kingAttacks()
+}
+
+/// A lookup table of all knight attack bitboards.
+internal let _knightAttackTable = Square.all.map { square in
+ return Bitboard(square: square)._knightAttacks()
+}
+
+/// Returns the squares between `start` and `end`.
+private func _between(_ start: Square, _ end: Square) -> Bitboard {
+ let start = UInt64(start.hashValue)
+ let end = UInt64(end.hashValue)
+ let max = UInt64.max
+ let a2a7: UInt64 = 0x0001010101010100
+ let b2g7: UInt64 = 0x0040201008040200
+ let h1b7: UInt64 = 0x0002040810204080
+
+ let between = (max << start) ^ (max << end)
+ let file = (end & 7) &- (start & 7)
+ let rank = ((end | 7) &- start) >> 3
+
+ var line = ((file & 7) &- 1) & a2a7
+ line += 2 &* (((rank & 7) &- 1) >> 58)
+ line += (((rank &- file) & 15) &- 1) & b2g7
+ line += (((rank &+ file) & 15) &- 1) & h1b7
+ line = line &* (between & (0 &- between))
+
+ return Bitboard(rawValue: line & between)
+}
+
+/// Returns the triangle index for `start` and `end`.
+internal func _triangleIndex(_ start: Square, _ end: Square) -> Int {
+ var a = start.hashValue
+ var b = end.hashValue
+ var d = a &- b
+ d &= d >> 31
+ b = b &+ d
+ a = a &- d
+ b = b &* (b ^ 127)
+ return (b >> 1) + a
+}
+
+/// A lookup table of squares between two squares.
+internal let _betweenTable: [Bitboard] = {
+ var table = [Bitboard](repeating: 0, count: 2080)
+ for start in Square.all {
+ for end in Square.all {
+ let index = _triangleIndex(start, end)
+ table[index] = _between(start, end)
+ }
+ }
+ return table
+}()
+
+/// A lookup table of lines for two squares.
+internal let _lineTable: [Bitboard] = {
+ var table = [Bitboard](repeating: 0, count: 2080)
+ for start in Square.all {
+ for end in Square.all {
+ let startBB = Bitboard(square: start)
+ let endBB = Bitboard(square: end)
+ let index = _triangleIndex(start, end)
+ let rookAttacks = startBB._rookAttacks()
+ let bishopAttacks = startBB._bishopAttacks()
+ if rookAttacks[end] {
+ table[index] = startBB | endBB | (rookAttacks & endBB._rookAttacks())
+ } else if bishopAttacks[end] {
+ table[index] = startBB | endBB | (bishopAttacks & endBB._bishopAttacks())
+ }
+ }
+ }
+ return table
+}()
diff --git a/Sources/SwiftChessNeo/Variant.swift b/Sources/SwiftChessNeo/Variant.swift
new file mode 100644
index 0000000..70f7b71
--- /dev/null
+++ b/Sources/SwiftChessNeo/Variant.swift
@@ -0,0 +1,46 @@
+//
+// Variant.swift
+// Sage
+//
+// Copyright 2016-2017 Nikolai Vazquez
+// Modified by SuperGeroy
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+/// A chess variant that defines how a `Board` is populated or how a `Game` is played.
+public enum Variant {
+
+ /// Standard chess.
+ case standard
+
+ /// Upside down chess where the piece colors swap starting squares.
+ case upsideDown
+
+ /// Standard regardless of Swift version.
+ internal static let _standard = Variant.standard
+
+ /// UpsideDown regardless of Swift version.
+ internal static let _upsideDown = Variant.upsideDown
+
+ /// `self` is standard variant.
+ public var isStandard: Bool {
+ return self == ._standard
+ }
+
+ /// `self` is upside down variant.
+ public var isUpsideDown: Bool {
+ return self == ._upsideDown
+ }
+
+}