batman
This commit is contained in:
219
Sources/Engine/Board.swift
Normal file
219
Sources/Engine/Board.swift
Normal file
@@ -0,0 +1,219 @@
|
||||
public struct Fen: CustomStringConvertible {
|
||||
public enum CastlingAvailibility: String {
|
||||
case Neither = "-"
|
||||
case WhiteKingSide = "K"
|
||||
case WhiteQueenSide = "Q"
|
||||
case BlackKingSide = "k"
|
||||
case BlackQueenSide = "q"
|
||||
case WhiteSide = "KQ"
|
||||
case BlackSide = "kq"
|
||||
case Kings = "Kk"
|
||||
case Queens = "Qq"
|
||||
case WKingBQueen = "Kq"
|
||||
case WQueenBKing = "Qk"
|
||||
case All = "KQkq"
|
||||
}
|
||||
private var _fen: String = ""
|
||||
private var value: String {
|
||||
get {
|
||||
return _fen
|
||||
}
|
||||
set {
|
||||
_fen = newValue
|
||||
let splitted = _fen.split(separator: " ")
|
||||
guard splitted.count == 6 else {
|
||||
return
|
||||
}
|
||||
placement = String(splitted[0])
|
||||
activeColor =
|
||||
switch splitted[1] {
|
||||
case "w": .White
|
||||
case "b": .Black
|
||||
default: .White
|
||||
}
|
||||
castlingAvailibility =
|
||||
CastlingAvailibility(
|
||||
rawValue:
|
||||
String(splitted[2])) ?? .Neither
|
||||
enPassant = String(splitted[3])
|
||||
halfMoveClock =
|
||||
if let c = splitted[4].first, c.isWholeNumber {
|
||||
UInt8(String(c)) ?? 0
|
||||
} else {
|
||||
0
|
||||
}
|
||||
fullMoveClock =
|
||||
if let c = splitted[5].first, c.isWholeNumber {
|
||||
UInt8(String(c)) ?? 1
|
||||
} else {
|
||||
1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public private(set) var placement: String = "" // 70 chars
|
||||
public private(set) var activeColor: Color = .White // 1 char
|
||||
public private(set) var castlingAvailibility: CastlingAvailibility = .All
|
||||
// 1 to 4 chars
|
||||
public private(set) var enPassant: String = "-" // 1 or 2 chars
|
||||
public private(set) var halfMoveClock: UInt8 = 0
|
||||
public package(set) var fullMoveClock: UInt8 = 1
|
||||
|
||||
public init(fen value: String) {
|
||||
self.value = value
|
||||
}
|
||||
|
||||
public enum FenError: Error {
|
||||
case InvalidCharacter(c: String, column: UInt8)
|
||||
case NumberTooBig(n: UInt8, column: UInt8)
|
||||
case NumberTooSmall(n: UInt8, column: UInt8)
|
||||
}
|
||||
|
||||
public var description: String {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
public class Board: CustomStringConvertible {
|
||||
public typealias Grid = [[Square]]
|
||||
|
||||
private enum UnicodeBar: String, CustomStringConvertible {
|
||||
case VerticalBar = "│"
|
||||
case TopHorizontalLine = "┌───┬───┬───┬───┬───┬───┬───┬───┐"
|
||||
case MiddleHorizontalLine = " ├───┼───┼───┼───┼───┼───┼───┼───┤"
|
||||
case BottomHorizonLine = " └───┴───┴───┴───┴───┴───┴───┴───┘"
|
||||
|
||||
var description: String {
|
||||
return self.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
private var squares = [Square]()
|
||||
private var board: Grid {
|
||||
var board = Grid()
|
||||
var rank = -1
|
||||
for i in 0...63 {
|
||||
if i % 8 == 0 {
|
||||
board.append([])
|
||||
rank += 1
|
||||
}
|
||||
board[rank].append(squares[i])
|
||||
}
|
||||
return board
|
||||
}
|
||||
|
||||
public private(set) var fen =
|
||||
Fen(fen: "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1")
|
||||
|
||||
public func setBoard() throws -> Grid {
|
||||
var file: UInt8 = 1
|
||||
var rank: UInt8 = 8
|
||||
var index: UInt8 = 0
|
||||
var b: Grid = board
|
||||
for c in fen.placement {
|
||||
if c == "/" {
|
||||
rank -= 1
|
||||
file = 0
|
||||
} else if c.isWholeNumber, let n = UInt8(String(c)) {
|
||||
if n < 1 {
|
||||
throw Fen.FenError.NumberTooSmall(n: n, column: index)
|
||||
} else if n > 8 {
|
||||
throw Fen.FenError.NumberTooBig(n: n, column: index)
|
||||
}
|
||||
|
||||
let f = file
|
||||
for i in f..<(f + n) {
|
||||
file += 1
|
||||
b[rank, i-1]?.piece = nil
|
||||
}
|
||||
file -= 1
|
||||
} else if c.isASCII {
|
||||
switch Kind[c] {
|
||||
case .some(let (k, c)):
|
||||
let piece: Piece =
|
||||
switch k {
|
||||
case .Pawn:
|
||||
Pawn(color: c, on: .init(file: file, rank: rank))
|
||||
default:
|
||||
Pawn(color: c, on: .init(file: file, rank: rank))
|
||||
}
|
||||
b[rank, file-1]?.piece = piece
|
||||
case .none:
|
||||
throw Fen.FenError.InvalidCharacter(
|
||||
c: String(c), column: index)
|
||||
}
|
||||
}
|
||||
|
||||
file += 1
|
||||
index += 1
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
public required init() {
|
||||
var rank: UInt8 = 8
|
||||
for i in 0...63 {
|
||||
let index = UInt8(i)
|
||||
let square = Square(
|
||||
position: .init(file: (index % 8) + 1, rank: rank),
|
||||
index: index,
|
||||
color: index % 2 != rank % 2 ? .Black : .White)
|
||||
squares.append(square)
|
||||
if (index + 1) % 8 == 0 {
|
||||
rank -= 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public var description: String {
|
||||
var boardString =
|
||||
" A B C D E F G H \n \(UnicodeBar.TopHorizontalLine)\n"
|
||||
var _rank: UInt8 = 8
|
||||
board.forEach { rank in
|
||||
boardString += String(_rank) + " \(UnicodeBar.VerticalBar)"
|
||||
rank.forEach { square in
|
||||
let p = square.piece?.unicodeRepresentation ?? " "
|
||||
boardString += " \(p) \(UnicodeBar.VerticalBar)"
|
||||
}
|
||||
boardString += "\n"
|
||||
_rank -= 1
|
||||
if _rank > 0 {
|
||||
boardString += "\(UnicodeBar.MiddleHorizontalLine)\n"
|
||||
}
|
||||
}
|
||||
boardString += "\(UnicodeBar.BottomHorizonLine)"
|
||||
return boardString
|
||||
}
|
||||
|
||||
public subscript(pos: Square.Position) -> Square? {
|
||||
let i = pos.index
|
||||
if i > squares.count {
|
||||
return nil
|
||||
}
|
||||
return squares[i]
|
||||
}
|
||||
}
|
||||
|
||||
extension Board.Grid {
|
||||
public subscript(rank: UInt8, file: UInt8) -> Square? {
|
||||
get {
|
||||
guard 1 > rank && rank < 9 && 1 > file && file < 9 else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return self[(8-Int(rank)) % 8][Int(file) - 1]
|
||||
}
|
||||
set {
|
||||
guard 1 > rank && rank < 9 && 1 > file && file < 9 else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let n = newValue else {
|
||||
return
|
||||
}
|
||||
|
||||
self[(8-Int(rank)) % 8][Int(file) - 1] = n
|
||||
}
|
||||
}
|
||||
}
|
||||
53
Sources/Engine/Pieces/Pawn.swift
Normal file
53
Sources/Engine/Pieces/Pawn.swift
Normal file
@@ -0,0 +1,53 @@
|
||||
package class Pawn: Piece {
|
||||
package var kind: Kind = .Pawn
|
||||
package weak var board: Board?
|
||||
package var color: Color
|
||||
package var position: Square.Position
|
||||
package var pseudoLegalPositions: [Square.Position] {
|
||||
return []
|
||||
}
|
||||
package var legalPositions: [Square.Position] {
|
||||
return pseudoLegalPositions.filter { isLegal(pos: $0) }
|
||||
}
|
||||
|
||||
package var unicodeRepresentation: String {
|
||||
return color == .Black ? "♟" : "♙"
|
||||
}
|
||||
|
||||
package func move(dst: Square.Position) -> Bool {
|
||||
guard board != nil else {
|
||||
return false
|
||||
}
|
||||
|
||||
if !(legalPositions.contains { $0 == dst }) {
|
||||
return false
|
||||
}
|
||||
|
||||
if let board = board, var s = board[position], var d = board[dst] {
|
||||
s.piece = self
|
||||
d.piece = nil
|
||||
}
|
||||
|
||||
position = dst
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
package func isLegal(pos: Square.Position) -> Bool {
|
||||
// TODO: Handle "En-Passant"
|
||||
if let board = board, let s = board[pos] {
|
||||
if let p = s.piece {
|
||||
if p.color == color { return false }
|
||||
}
|
||||
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
package init(
|
||||
color: Color, on position: Square.Position
|
||||
) {
|
||||
self.color = color
|
||||
self.position = position
|
||||
}
|
||||
}
|
||||
48
Sources/Engine/Pieces/Piece.swift
Normal file
48
Sources/Engine/Pieces/Piece.swift
Normal file
@@ -0,0 +1,48 @@
|
||||
public enum Kind: String, CaseIterable {
|
||||
case Pawn, Knight, Bishop, Rook, Queen, King
|
||||
|
||||
public var value: Int8 {
|
||||
switch self {
|
||||
case .Pawn: 1
|
||||
case .Bishop: 3
|
||||
case .Knight: 3
|
||||
case .Rook: 5
|
||||
case .Queen: 9
|
||||
case .King: -1
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static subscript(_ c: Character) -> (Self, Color)? {
|
||||
let v = c.uppercased()
|
||||
|
||||
guard v == "N" || (Self.allCases.contains { String($0.rawValue.first!) == v}) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let kind: Self = switch v {
|
||||
case "P": .Pawn
|
||||
case "N": .Knight
|
||||
case "B": .Bishop
|
||||
case "R": .Rook
|
||||
case "Q": .Queen
|
||||
case "K": .King
|
||||
default: .Pawn
|
||||
}
|
||||
|
||||
let color: Color = c.isUppercase ? .White : .Black
|
||||
|
||||
return (kind, color)
|
||||
}
|
||||
}
|
||||
|
||||
public protocol Piece {
|
||||
var color: Color { get }
|
||||
var unicodeRepresentation: String { get }
|
||||
var kind: Kind { get }
|
||||
var position: Square.Position { get }
|
||||
var pseudoLegalPositions: [Square.Position] { get }
|
||||
var legalPositions: [Square.Position] { get }
|
||||
func move(dst: Square.Position) -> Bool
|
||||
func isLegal(pos: Square.Position) -> Bool
|
||||
}
|
||||
24
Sources/Engine/Square.swift
Normal file
24
Sources/Engine/Square.swift
Normal file
@@ -0,0 +1,24 @@
|
||||
public struct Square: Equatable {
|
||||
|
||||
public struct Position: Equatable {
|
||||
public let file: UInt8
|
||||
public let rank: UInt8
|
||||
|
||||
public var index: Int {
|
||||
return Int(file * rank) - 1
|
||||
}
|
||||
|
||||
public static func == (lhs: Position, rhs: Position) -> Bool {
|
||||
return lhs.rank == rhs.rank && lhs.file == rhs.file
|
||||
}
|
||||
}
|
||||
|
||||
public let position: Position
|
||||
public let index: UInt8
|
||||
public var piece: Piece? = nil
|
||||
public let color: Color
|
||||
|
||||
public static func == (lhs: Square, rhs: Square) -> Bool {
|
||||
return lhs.position == rhs.position
|
||||
}
|
||||
}
|
||||
4
Sources/Engine/utils.swift
Normal file
4
Sources/Engine/utils.swift
Normal file
@@ -0,0 +1,4 @@
|
||||
public enum Color: UInt8 {
|
||||
case Black = 0
|
||||
case White = 1
|
||||
}
|
||||
Reference in New Issue
Block a user