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 // } // } // }