Added commands to execute interactively

This commit is contained in:
cdricms
2024-06-29 20:32:40 +02:00
parent 90f2988a14
commit 533322c5dd
4 changed files with 167 additions and 34 deletions

View File

@@ -6,6 +6,7 @@ protocol EventDelegate {
func notify(_ event: Event) func notify(_ event: Event)
func movePiece(_ piece: Piece, to dst: Square.Position) throws func movePiece(_ piece: Piece, to dst: Square.Position) throws
func addPieceToTarget(_ piece: Piece, target: Square.Position) func addPieceToTarget(_ piece: Piece, target: Square.Position)
func getSquareInfo(on pos: Square.Position) -> Square?
} }
public class Board: CustomStringConvertible, EventDelegate { public class Board: CustomStringConvertible, EventDelegate {
public typealias Grid = [[Square]] 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) { func notify(_ event: Event) {
} }
@@ -38,10 +89,11 @@ public class Board: CustomStringConvertible, EventDelegate {
halfMoveClock / 2 halfMoveClock / 2
} }
internal var threatenedSquares: [Color: Set<Square.Position>] = [ public internal(set) var threatenedSquares: [Color: Set<Square.Position>] =
.White: [], [
.Black: [], .White: [],
] .Black: [],
]
public internal(set) var history = History() public internal(set) var history = History()
@@ -79,7 +131,7 @@ public class Board: CustomStringConvertible, EventDelegate {
case .destinationSquareUnreachable(let pos): case .destinationSquareUnreachable(let pos):
"The destination square given \(pos.rank) \(pos.file) is unreachable." "The destination square given \(pos.rank) \(pos.file) is unreachable."
case .squareHasNoPiece(let pos): 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): case .destinationIsIllegal(let pos):
"The destination square given \(pos.rank) \(pos.file) is illegal to be moved to." "The destination square given \(pos.rank) \(pos.file) is illegal to be moved to."
case .squareANIsTooLong(let an): 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 { guard let f1 = src.first, let r1 = src.last, r1.isWholeNumber else {
return 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 return
} }
guard guard
@@ -245,7 +297,9 @@ public class Board: CustomStringConvertible, EventDelegate {
do { do {
try setBoard() try setBoard()
for square in squares where square.piece != nil { 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) { } catch Fen.FenError.NotAppropriateLength(let n, let column) {
fatalError("Not appropriate length: \(n) on \(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 = var boardString =
" A B C D E F G H \n \(UnicodeBar.TopHorizontalLine)\n" " A B C D E F G H \n \(UnicodeBar.TopHorizontalLine)\n"
var _rank: UInt8 = 8 var _rank: UInt8 = 8
let def: TerminalColors = .init()
var h1 = def
board.forEach { rank in board.forEach { rank in
boardString += String(_rank) + " \(UnicodeBar.VerticalBar)" boardString += String(_rank) + " \(UnicodeBar.VerticalBar)"
rank.forEach { square in rank.forEach { square in
let p = square.piece?.unicodeRepresentation ?? " " 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" boardString += "\n"
_rank -= 1 _rank -= 1
@@ -274,6 +342,10 @@ public class Board: CustomStringConvertible, EventDelegate {
return boardString return boardString
} }
public var description: String {
text()
}
internal subscript(pos: Square.Position) -> Square? { internal subscript(pos: Square.Position) -> Square? {
guard let i = pos.index else { guard let i = pos.index else {
return nil return nil

View File

@@ -2,7 +2,7 @@ final class King: Piece {
typealias Threats = (Piece?, Piece?) typealias Threats = (Piece?, Piece?)
var threats: Threats = (nil, nil) var threats: Threats = (nil, nil)
override var unicodeRepresentation: String { override var unicodeRepresentation: String {
return color == .Black ? "" : "" return color == .Black ? "" : ""
} }
override var pseudoLegalPositions: [Square.Position] { override var pseudoLegalPositions: [Square.Position] {

View File

@@ -55,7 +55,6 @@ public enum Kind: String, CaseIterable {
public class Piece: Hashable { public class Piece: Hashable {
#warning("TODO: To be removed, handle everything through the delegate") #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 color: Color
public internal(set) var halfMoveCount: UInt8 = 0 public internal(set) var halfMoveCount: UInt8 = 0
public var unicodeRepresentation: String { public var unicodeRepresentation: String {
@@ -73,7 +72,7 @@ public class Piece: Hashable {
internal var delegate: EventDelegate? internal var delegate: EventDelegate?
internal func move(to dst: Square.Position) throws { 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) throw Board.MoveFailure.destinationIsIllegal(pos: dst)
} }
@@ -83,7 +82,7 @@ public class Piece: Hashable {
#warning("This method should be better thought out.") #warning("This method should be better thought out.")
internal func isLegal(on pos: Square.Position) -> Bool { 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 let p = s.piece {
if p.color == color { return false } if p.color == color { return false }
if let king = p as? King { if let king = p as? King {

View File

@@ -1,29 +1,91 @@
import Engine import Engine
let board = Board( enum Command: Equatable {
fen: .init( case quit
fen: "rnbqkb1r/pppppppp/2n5/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1")) case noop
// let board: Board = .init() 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) 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 // Execute every time the timer changes
// board.on(.timer) { time in // 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))