diff --git a/Sources/Engine/Board.swift b/Sources/Engine/Board.swift index 05e6f62..d067dae 100644 --- a/Sources/Engine/Board.swift +++ b/Sources/Engine/Board.swift @@ -6,6 +6,7 @@ protocol EventDelegate { func notify(_ event: Event) func movePiece(_ piece: Piece, to dst: Square.Position) throws func addPieceToTarget(_ piece: Piece, target: Square.Position) + func getSquareInfo(on pos: Square.Position) -> Square? } public class Board: CustomStringConvertible, EventDelegate { public typealias Grid = [[Square]] @@ -21,6 +22,56 @@ public class Board: CustomStringConvertible, EventDelegate { } } + public struct TerminalColors: CustomStringConvertible, Hashable { + public enum Foreground: String { + case def = "39" + case black = "30" + case red = "31" + case green = "32" + case yellow = "33" + case blue = "34" + case magenta = "35" + case cyan = "36" + case white = "37" + } + public enum Background: String { + case def = "49" + case black = "40" + case red = "41" + case green = "42" + case yellow = "43" + case blue = "44" + case magenta = "45" + case cyan = "46" + case white = "47" + } + + public var foregroundColor: Foreground? = .def + public var backgroundColor: Background? = .def + + public init() {} + + public init(fg: Foreground?, bg: Background) { + foregroundColor = fg + backgroundColor = bg + } + + public var description: String { + var res = "\u{001B}[" + if let fc = foregroundColor { + res += fc.rawValue + if backgroundColor != nil { + res += ";" + } + } + if let bc = backgroundColor { + res += bc.rawValue + } + return res + "m" + } + + } + func notify(_ event: Event) { } @@ -38,10 +89,11 @@ public class Board: CustomStringConvertible, EventDelegate { halfMoveClock / 2 } - internal var threatenedSquares: [Color: Set] = [ - .White: [], - .Black: [], - ] + public internal(set) var threatenedSquares: [Color: Set] = + [ + .White: [], + .Black: [], + ] public internal(set) var history = History() @@ -79,7 +131,7 @@ public class Board: CustomStringConvertible, EventDelegate { case .destinationSquareUnreachable(let pos): "The destination square given \(pos.rank) \(pos.file) is unreachable." case .squareHasNoPiece(let pos): - "The square \(pos.rank) \(pos.file) doesn't have any piece to be moved." + "The square \(pos.rank) \(pos.file) doesn't have any piece." case .destinationIsIllegal(let pos): "The destination square given \(pos.rank) \(pos.file) is illegal to be moved to." case .squareANIsTooLong(let an): @@ -106,7 +158,7 @@ public class Board: CustomStringConvertible, EventDelegate { guard let f1 = src.first, let r1 = src.last, r1.isWholeNumber else { return } - guard let f2 = dst.first, let r2 = src.last, r2.isWholeNumber else { + guard let f2 = dst.first, let r2 = dst.last, r2.isWholeNumber else { return } guard @@ -245,7 +297,9 @@ public class Board: CustomStringConvertible, EventDelegate { do { try setBoard() for square in squares where square.piece != nil { - square.piece?.getLegalPosition() + let p = square.piece! + p.getLegalPosition() + threatenedSquares[p.color]! += Set(p.legalPositions) } } catch Fen.FenError.NotAppropriateLength(let n, let column) { fatalError("Not appropriate length: \(n) on \(column)") @@ -254,15 +308,29 @@ public class Board: CustomStringConvertible, EventDelegate { } } - public var description: String { + public func text(with colors: [TerminalColors: [Square.Position]]? = nil) + -> String + { var boardString = " A B C D E F G H \n \(UnicodeBar.TopHorizontalLine)\n" var _rank: UInt8 = 8 + let def: TerminalColors = .init() + var h1 = def board.forEach { rank in boardString += String(_rank) + " \(UnicodeBar.VerticalBar)" rank.forEach { square in let p = square.piece?.unicodeRepresentation ?? " " - boardString += " \(p) \(UnicodeBar.VerticalBar)" + if let c = colors { + for (color, s) in c { + h1 = def + if (s.contains { $0 == square.position }) { + h1 = color + break + } + } + } + boardString += + "\(h1) \(p) \(def)\(UnicodeBar.VerticalBar)" } boardString += "\n" _rank -= 1 @@ -274,6 +342,10 @@ public class Board: CustomStringConvertible, EventDelegate { return boardString } + public var description: String { + text() + } + internal subscript(pos: Square.Position) -> Square? { guard let i = pos.index else { return nil diff --git a/Sources/Engine/Pieces/King.swift b/Sources/Engine/Pieces/King.swift index b95af31..a820ca7 100644 --- a/Sources/Engine/Pieces/King.swift +++ b/Sources/Engine/Pieces/King.swift @@ -2,7 +2,7 @@ final class King: Piece { typealias Threats = (Piece?, Piece?) var threats: Threats = (nil, nil) override var unicodeRepresentation: String { - return color == .Black ? "♛" : "♕" + return color == .Black ? "♚" : "♔" } override var pseudoLegalPositions: [Square.Position] { diff --git a/Sources/Engine/Pieces/Piece.swift b/Sources/Engine/Pieces/Piece.swift index c90bbfd..7ba3de8 100644 --- a/Sources/Engine/Pieces/Piece.swift +++ b/Sources/Engine/Pieces/Piece.swift @@ -55,7 +55,6 @@ public enum Kind: String, CaseIterable { public class Piece: Hashable { #warning("TODO: To be removed, handle everything through the delegate") - internal weak var board: Board? public internal(set) var color: Color public internal(set) var halfMoveCount: UInt8 = 0 public var unicodeRepresentation: String { @@ -73,7 +72,7 @@ public class Piece: Hashable { internal var delegate: EventDelegate? internal func move(to dst: Square.Position) throws { - if !(legalPositions.contains { $0 == dst }) { + guard (legalPositions.contains { $0 == dst }) else { throw Board.MoveFailure.destinationIsIllegal(pos: dst) } @@ -83,7 +82,7 @@ public class Piece: Hashable { #warning("This method should be better thought out.") internal func isLegal(on pos: Square.Position) -> Bool { - if let board = board, let s = board[pos] { + if let s = delegate?.getSquareInfo(on: pos) { if let p = s.piece { if p.color == color { return false } if let king = p as? King { diff --git a/Sources/exe/main.swift b/Sources/exe/main.swift index 2b8acc0..b479d59 100644 --- a/Sources/exe/main.swift +++ b/Sources/exe/main.swift @@ -1,29 +1,91 @@ import Engine -let board = Board( - fen: .init( - fen: "rnbqkb1r/pppppppp/2n5/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1")) -// let board: Board = .init() +enum Command: Equatable { + case quit + case noop + case print + case move(from: String, to: String) + case history(command: String? = nil) + case highlightThreatened(color: Color) + + init?(rawValue: String) { + let args = rawValue.lowercased().split(separator: " ") + guard !args.isEmpty else { + return nil + } + + switch args[0] { + case "quit", "q": + self = .quit + case "noop", "n": + self = .noop + case "print", "p": + self = .print + case "move" where args.count == 3, "m" where args.count == 3: + self = .move(from: String(args[1]), to: String(args[2])) + case "history" where args.count == 2, "h" where args.count == 2: + self = .history(command: String(args[1])) + case "history", "h": + self = .history() + case "highlight" where args.count == 2, "hi" where args.count == 2: + let color: Color = + switch args[1] { + case "w", "white": + .White + case "b", "black": + .Black + default: + .White + } + self = .highlightThreatened(color: color) + default: + return nil + } + } +} +let board: Board = .init() print(board) +var command: Command = .noop + +while command != .quit { + if let line = readLine(), let c = Command(rawValue: line.lowercased()) { + command = c + switch command { + case .print: print(board) + case .noop, .quit: continue + case .move(let from, let to): + do { + try board.move(src: from, dst: to) + } catch { + print(error) + continue + } + case .history(let command): + if let c = command { + if let fmc = Int(c), + fmc > -1 && fmc < board.history.values.count + { + print(board.history.values[fmc]) + } else if c == "count" { + print(board.history.values.count) + } else { + print(board.history.values) + } + } else { + print(board.history.values) + } + case .highlightThreatened(let color): + if let ts = board.threatenedSquares[color] { + let color = Board.TerminalColors(fg: .def, bg: .red) + print(ts.count) + print(board.text(with: [color: Array(ts)])) + } + } + } +} + // Execute every time the timer changes // board.on(.timer) { time in // } - -do { - try board.move(src: "e4", dst: "f6") -} catch { - print(error) -} -do { - try board.move(src: .init(rank: 2, file: 5), dst: .init(rank: 4, file: 5)) -} catch { - print(error) -} - -print(board) -print(board.fen) -print(board.history.values) - -// let square = board.getSquareInfo(on: .init(rank: 2, file: 7))