236 lines
5.6 KiB
Swift
236 lines
5.6 KiB
Swift
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
|
|
|
|
public func setBoard() throws {
|
|
var file: UInt8 = 1
|
|
var rank: UInt8 = 8
|
|
var index: UInt8 = 0
|
|
var b: [Square] = squares
|
|
for c in fen.placement {
|
|
let r = (8 - Int(rank)) % 8
|
|
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[8*r+Int(i)].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))
|
|
case .Knight:
|
|
Knight(color: c, on: .init(file: file, rank: rank))
|
|
case .Bishop:
|
|
Bishop(color: c, on: .init(file: file, rank: rank))
|
|
case .Rook:
|
|
Rook(color: c, on: .init(file: file, rank: rank))
|
|
case .Queen:
|
|
Queen(color: c, on: .init(file: file, rank: rank))
|
|
case .King:
|
|
King(color: c, on: .init(file: file, rank: rank))
|
|
}
|
|
b[8*r+Int(file)-1].piece = piece
|
|
case .none:
|
|
throw Fen.FenError.InvalidCharacter(
|
|
c: String(c), column: index)
|
|
}
|
|
}
|
|
|
|
file += 1
|
|
index += 1
|
|
}
|
|
print(b)
|
|
squares = b
|
|
}
|
|
|
|
public required init(fen: Fen =
|
|
.init(fen: "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1")
|
|
) {
|
|
var rank: UInt8 = 8
|
|
self.fen = fen
|
|
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
|
|
}
|
|
}
|
|
do {
|
|
try setBoard()
|
|
} catch {
|
|
}
|
|
}
|
|
|
|
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]
|
|
}
|
|
}
|
|
|
|
// For now useless, learn how to setup properly
|
|
// 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
|
|
// }
|
|
// }
|
|
// }
|