From 8b5c2a592e09bb124e4689734cfab31e836998ca Mon Sep 17 00:00:00 2001 From: cdricms <36056008+cdricms@users.noreply.github.com> Date: Thu, 27 Jun 2024 18:09:01 +0200 Subject: [PATCH] Linear + Diagonal moves --- Sources/Engine/Board.swift | 18 +++++----- Sources/Engine/Pieces/Bishop.swift | 22 ++++++------ Sources/Engine/Pieces/CommonMoves.swift | 24 ++++++++++--- Sources/Engine/Pieces/King.swift | 22 ++++++------ Sources/Engine/Pieces/Knight.swift | 43 +++++++++++----------- Sources/Engine/Pieces/Pawn.swift | 35 +++++++++--------- Sources/Engine/Pieces/Piece.swift | 8 ++--- Sources/Engine/Pieces/Queen.swift | 22 ++++++------ Sources/Engine/Pieces/Rook.swift | 22 ++++++------ Sources/Engine/Square.swift | 23 ++++++++++-- Tests/EngineTests/EngineTests.swift | 48 ++++++++++++++++++++++--- 11 files changed, 179 insertions(+), 108 deletions(-) diff --git a/Sources/Engine/Board.swift b/Sources/Engine/Board.swift index c84db00..b24651b 100644 --- a/Sources/Engine/Board.swift +++ b/Sources/Engine/Board.swift @@ -60,17 +60,17 @@ public class Board: CustomStringConvertible { let piece: Piece = switch k { case .Pawn: - Pawn(color: c, on: .init(file: file, rank: rank)) + Pawn(color: c, on: .init(rank: rank, file: file)) case .Knight: - Knight(color: c, on: .init(file: file, rank: rank)) + Knight(color: c, on: .init(rank: rank, file: file)) case .Bishop: - Bishop(color: c, on: .init(file: file, rank: rank)) + Bishop(color: c, on: .init(rank: rank, file: file)) case .Rook: - Rook(color: c, on: .init(file: file, rank: rank)) + Rook(color: c, on: .init(rank: rank, file: file)) case .Queen: - Queen(color: c, on: .init(file: file, rank: rank)) + Queen(color: c, on: .init(rank: rank, file: file)) case .King: - King(color: c, on: .init(file: file, rank: rank)) + King(color: c, on: .init(rank: rank, file: file)) } b[8*r+Int(file)-1].piece = piece case .none: @@ -96,7 +96,7 @@ public class Board: CustomStringConvertible { for i in 0...63 { let index = Int8(i) let square = Square( - position: .init(file: (index % 8) + 1, rank: rank), + position: .init(rank: rank, file: (index % 8) + 1), index: i, color: index % 2 != rank % 2 ? .Black : .White) squares.append(square) @@ -135,7 +135,9 @@ public class Board: CustomStringConvertible { } public subscript(pos: Square.Position) -> Square? { - let i = pos.index + guard let i = pos.index else { + return nil + } if i > squares.count { return nil } diff --git a/Sources/Engine/Pieces/Bishop.swift b/Sources/Engine/Pieces/Bishop.swift index 42de3c4..1c00951 100644 --- a/Sources/Engine/Pieces/Bishop.swift +++ b/Sources/Engine/Pieces/Bishop.swift @@ -1,27 +1,27 @@ -internal final class Bishop: Piece, DiagonalMoves { - internal weak var board: Board? - internal var kind: Kind = .Bishop +final class Bishop: Piece, DiagonalMoves { + weak var board: Board? + var kind: Kind = .Bishop - internal var unicodeRepresentation: String { + var unicodeRepresentation: String { return color == .Black ? "♝" : "♗" } - internal var color: Color + var color: Color - internal var position: Square.Position + var position: Square.Position - internal var pseudoLegalPositions: [Square.Position] { + var pseudoLegalPositions: [Square.Position] { return getDiagonalMoves(from: position) } - internal var legalPositions: [Square.Position] { + var legalPositions: [Square.Position] { return pseudoLegalPositions.filter { isLegal(on: $0) } } - internal func move(to dst: Square.Position) -> Bool { + func move(to dst: Square.Position) -> Bool { return false } - internal func isLegal(on pos: Square.Position) -> Bool { + func isLegal(on pos: Square.Position) -> Bool { if let board = board, let s = board[pos] { if let p = s.piece { if p.color == color { return false } @@ -34,7 +34,7 @@ internal final class Bishop: Piece, DiagonalMoves { return true } - internal init(color: Color, on position: Square.Position) { + init(color: Color, on position: Square.Position) { self.color = color self.position = position } diff --git a/Sources/Engine/Pieces/CommonMoves.swift b/Sources/Engine/Pieces/CommonMoves.swift index 2e96ebe..1f78c6c 100644 --- a/Sources/Engine/Pieces/CommonMoves.swift +++ b/Sources/Engine/Pieces/CommonMoves.swift @@ -1,19 +1,35 @@ -internal protocol DiagonalMoves { +protocol DiagonalMoves { func getDiagonalMoves(from pos: Square.Position) -> [Square.Position] } extension DiagonalMoves { func getDiagonalMoves(from pos: Square.Position) -> [Square.Position] { - return [] + var squares = [Square.Position]() + for i: (Int8, Int8) in [(1, -1), (1, 1), (-1, 1), (-1, -1)] { + var currentSquare = pos + i + while currentSquare.index != nil { + squares.append(currentSquare) + currentSquare += i + } + } + return squares } } -internal protocol LinearMoves { +protocol LinearMoves { func getLinearMoves(from pos: Square.Position) -> [Square.Position] } extension LinearMoves { func getLinearMoves(from pos: Square.Position) -> [Square.Position] { - return [] + var squares = [Square.Position]() + for i: (Int8, Int8) in [(1, 0), (0, 1), (-1, 0), (0, -1)] { + var currentSquare = pos + i + while currentSquare.index != nil { + squares.append(currentSquare) + currentSquare += i + } + } + return squares } } \ No newline at end of file diff --git a/Sources/Engine/Pieces/King.swift b/Sources/Engine/Pieces/King.swift index 9c7d4b7..9b3e29a 100644 --- a/Sources/Engine/Pieces/King.swift +++ b/Sources/Engine/Pieces/King.swift @@ -1,27 +1,27 @@ -internal final class King: Piece { - internal weak var board: Board? - internal var kind: Kind = .King +final class King: Piece { + weak var board: Board? + var kind: Kind = .King - internal var unicodeRepresentation: String { + var unicodeRepresentation: String { return color == .Black ? "♛" : "♕" } - internal var color: Color + var color: Color - internal var position: Square.Position + var position: Square.Position - internal var pseudoLegalPositions: [Square.Position] { + var pseudoLegalPositions: [Square.Position] { return [] } - internal var legalPositions: [Square.Position] { + var legalPositions: [Square.Position] { return pseudoLegalPositions.filter { isLegal(on: $0) } } - internal func move(to dst: Square.Position) -> Bool { + func move(to dst: Square.Position) -> Bool { return false } - internal func isLegal(on pos: Square.Position) -> Bool { + func isLegal(on pos: Square.Position) -> Bool { if let board = board, let s = board[pos] { if let p = s.piece { if p.color == color { return false } @@ -35,7 +35,7 @@ internal final class King: Piece { return true } - internal init(color: Color, on position: Square.Position) { + init(color: Color, on position: Square.Position) { self.color = color self.position = position } diff --git a/Sources/Engine/Pieces/Knight.swift b/Sources/Engine/Pieces/Knight.swift index b015241..76b3f9a 100644 --- a/Sources/Engine/Pieces/Knight.swift +++ b/Sources/Engine/Pieces/Knight.swift @@ -1,40 +1,37 @@ -internal final class Knight: Piece { - internal weak var board: Board? - internal var kind: Kind = .Knight +final class Knight: Piece { + weak var board: Board? + var kind: Kind = .Knight - internal var unicodeRepresentation: String { + var unicodeRepresentation: String { return color == .Black ? "♞" : "♘" } - internal var color: Color + var color: Color - internal var position: Square.Position + var position: Square.Position - internal var pseudoLegalPositions: [Square.Position] { + var pseudoLegalPositions: [Square.Position] { let directions: [Square.Position] = [ - .init(file: position.file + 1, rank: position.rank + 2), - .init(file: position.file - 1, rank: position.rank + 2), - .init(file: position.file + 1, rank: position.rank - 2), - .init(file: position.file - 1, rank: position.rank - 2), - .init(file: position.file + 2, rank: position.rank + 1), - .init(file: position.file - 2, rank: position.rank + 1), - .init(file: position.file + 2, rank: position.rank - 1), - .init(file: position.file - 2, rank: position.rank - 1) + .init(rank: position.rank + 2, file: position.file + 1), + .init(rank: position.rank + 2, file: position.file - 1), + .init(rank: position.rank - 2, file: position.file + 1), + .init(rank: position.rank - 2, file: position.file - 1), + .init(rank: position.rank + 1, file: position.file + 2), + .init(rank: position.rank + 1, file: position.file - 2), + .init(rank: position.rank - 1, file: position.file + 2), + .init(rank: position.rank - 1, file: position.file - 2) ] - return directions.filter { - let index = $0.index - return index < 0 || index > 63 - } + return directions.filter { $0.index != nil } } - internal var legalPositions: [Square.Position] { + var legalPositions: [Square.Position] { return pseudoLegalPositions.filter { isLegal(on: $0) } } - internal func move(to dst: Square.Position) -> Bool { + func move(to dst: Square.Position) -> Bool { return false } - internal func isLegal(on pos: Square.Position) -> Bool { + func isLegal(on pos: Square.Position) -> Bool { if let board = board, let s = board[pos] { if let p = s.piece { if p.color == color { return false } @@ -48,7 +45,7 @@ internal final class Knight: Piece { return true } - internal init(color: Color, on position: Square.Position) { + init(color: Color, on position: Square.Position) { self.color = color self.position = position } diff --git a/Sources/Engine/Pieces/Pawn.swift b/Sources/Engine/Pieces/Pawn.swift index cdcc682..9487a51 100644 --- a/Sources/Engine/Pieces/Pawn.swift +++ b/Sources/Engine/Pieces/Pawn.swift @@ -1,32 +1,29 @@ -internal final class Pawn: Piece { - internal var kind: Kind = .Pawn - internal weak var board: Board? - internal var color: Color - internal var position: Square.Position - internal var pseudoLegalPositions: [Square.Position] { +final class Pawn: Piece { + var kind: Kind = .Pawn + weak var board: Board? + var color: Color + var position: Square.Position + var pseudoLegalPositions: [Square.Position] { let sign: Int8 = color == .Black ? -1 : 1 let rank = Int8(position.rank) let directions: [Square.Position] = [ - .init(file: position.file, rank: rank + 1 * sign), - .init(file: position.file, rank: rank + 2 * sign), - .init(file: position.file + 1, rank: rank + 1 * sign), - .init(file: position.file - 1, rank: rank + 1 * sign) + .init(rank: rank + 1 * sign, file: position.file), + .init(rank: rank + 2 * sign, file: position.file), + .init(rank: rank + 1 * sign, file: position.file + 1), + .init(rank: rank + 1 * sign, file: position.file - 1) ] // TODO: Handle en passant - return directions.filter { - let index = $0.index - return index < 1 || index > 63 - } + return directions.filter { $0.index != nil } } - internal var legalPositions: [Square.Position] { + var legalPositions: [Square.Position] { pseudoLegalPositions.filter { isLegal(on: $0) } } - internal var unicodeRepresentation: String { + var unicodeRepresentation: String { color == .Black ? "♟" : "♙" } - internal func move(to dst: Square.Position) -> Bool { + func move(to dst: Square.Position) -> Bool { guard board != nil else { return false } @@ -45,7 +42,7 @@ internal final class Pawn: Piece { return true } - internal func isLegal(on pos: Square.Position) -> Bool { + func isLegal(on pos: Square.Position) -> Bool { // TODO: Handle "En-Passant" if let board = board, let s = board[pos] { if let p = s.piece { @@ -60,7 +57,7 @@ internal final class Pawn: Piece { return true } - internal init( + init( color: Color, on position: Square.Position ) { self.color = color diff --git a/Sources/Engine/Pieces/Piece.swift b/Sources/Engine/Pieces/Piece.swift index 5eaef3d..881521f 100644 --- a/Sources/Engine/Pieces/Piece.swift +++ b/Sources/Engine/Pieces/Piece.swift @@ -1,7 +1,7 @@ -internal enum Kind: String, CaseIterable { +enum Kind: String, CaseIterable { case Pawn, Knight, Bishop, Rook, Queen, King - internal var value: Int8 { + var value: Int8 { switch self { case .Pawn: 1 case .Bishop: 3 @@ -12,7 +12,7 @@ internal enum Kind: String, CaseIterable { } } - internal static subscript(_ c: Character) -> (Self, Color)? { + static subscript(_ c: Character) -> (Self, Color)? { let v = c.uppercased() guard @@ -39,7 +39,7 @@ internal enum Kind: String, CaseIterable { } } -internal protocol Piece { +protocol Piece { var board: Board? { get } var color: Color { get } var unicodeRepresentation: String { get } diff --git a/Sources/Engine/Pieces/Queen.swift b/Sources/Engine/Pieces/Queen.swift index 437a7ac..9c3d74a 100644 --- a/Sources/Engine/Pieces/Queen.swift +++ b/Sources/Engine/Pieces/Queen.swift @@ -1,28 +1,28 @@ -internal final class Queen: Piece, LinearMoves, DiagonalMoves { - internal weak var board: Board? - internal var kind: Kind = .Queen +final class Queen: Piece, LinearMoves, DiagonalMoves { + weak var board: Board? + var kind: Kind = .Queen - internal var unicodeRepresentation: String { + var unicodeRepresentation: String { return color == .Black ? "♛" : "♕" } - internal var color: Color + var color: Color - internal var position: Square.Position + var position: Square.Position - internal var pseudoLegalPositions: [Square.Position] { + var pseudoLegalPositions: [Square.Position] { return getDiagonalMoves(from: position) + getLinearMoves(from: position) } - internal var legalPositions: [Square.Position] { + var legalPositions: [Square.Position] { return pseudoLegalPositions.filter { isLegal(on: $0) } } - internal func move(to dst: Square.Position) -> Bool { + func move(to dst: Square.Position) -> Bool { return false } - internal func isLegal(on pos: Square.Position) -> Bool { + func isLegal(on pos: Square.Position) -> Bool { if let board = board, let s = board[pos] { if let p = s.piece { if p.color == color { return false } @@ -36,7 +36,7 @@ internal final class Queen: Piece, LinearMoves, DiagonalMoves { return true } - internal init(color: Color, on position: Square.Position) { + init(color: Color, on position: Square.Position) { self.color = color self.position = position } diff --git a/Sources/Engine/Pieces/Rook.swift b/Sources/Engine/Pieces/Rook.swift index 94e4935..01245d6 100644 --- a/Sources/Engine/Pieces/Rook.swift +++ b/Sources/Engine/Pieces/Rook.swift @@ -1,27 +1,27 @@ -internal final class Rook: Piece, LinearMoves { - internal weak var board: Board? - internal var kind: Kind = .Rook +final class Rook: Piece, LinearMoves { + weak var board: Board? + var kind: Kind = .Rook - internal var unicodeRepresentation: String { + var unicodeRepresentation: String { return color == .Black ? "♜" : "♖" } - internal var color: Color + var color: Color - internal var position: Square.Position + var position: Square.Position - internal var pseudoLegalPositions: [Square.Position] { + var pseudoLegalPositions: [Square.Position] { return getLinearMoves(from: position) } - internal var legalPositions: [Square.Position] { + var legalPositions: [Square.Position] { return pseudoLegalPositions.filter { isLegal(on: $0) } } - internal func move(to dst: Square.Position) -> Bool { + func move(to dst: Square.Position) -> Bool { return false } - internal func isLegal(on pos: Square.Position) -> Bool { + func isLegal(on pos: Square.Position) -> Bool { if let board = board, let s = board[pos] { if let p = s.piece { if p.color == color { return false } @@ -35,7 +35,7 @@ internal final class Rook: Piece, LinearMoves { return true } - internal init(color: Color, on position: Square.Position) { + init(color: Color, on position: Square.Position) { self.color = color self.position = position } diff --git a/Sources/Engine/Square.swift b/Sources/Engine/Square.swift index 6336250..1c217a9 100644 --- a/Sources/Engine/Square.swift +++ b/Sources/Engine/Square.swift @@ -1,16 +1,35 @@ public struct Square: Equatable { public struct Position: Equatable { - public let file: Int8 public let rank: Int8 + public let file: Int8 - public var index: Int { + public var index: Int? { + guard (rank > 0 && rank < 9) && (file > 0 && file < 9) else { + return nil + } return Int(8*(8-rank)+file-1) } public static func == (lhs: Position, rhs: Position) -> Bool { return lhs.index == rhs.index } + + public static func + (lhs: Position, rhs: Position) -> Position { + .init(rank: lhs.rank + rhs.rank, file: lhs.file + rhs.file) + } + + public static func + (lhs: Position, rhs: (Int8, Int8)) -> Position { + .init(rank: lhs.rank + rhs.0, file: lhs.file + rhs.1) + } + + public static func += (lhs: inout Position, rhs: Position) { + lhs = lhs + rhs + } + + public static func += (lhs: inout Position, rhs: (Int8, Int8)) { + lhs = lhs + rhs + } } public let position: Position diff --git a/Tests/EngineTests/EngineTests.swift b/Tests/EngineTests/EngineTests.swift index b1c0ccd..146cf2c 100644 --- a/Tests/EngineTests/EngineTests.swift +++ b/Tests/EngineTests/EngineTests.swift @@ -7,16 +7,56 @@ final class EngineTests: XCTestCase { super.setUp() } func testPositionIndex() throws { - var rank = 9 - for i in 0..<64 { + var rank: Int8 = 9 + for i: Int8 in 0..<64 { let file = (i % 8) + 1 if file - 1 == 0 { rank -= 1 } - let pos: Square.Position = .init(file: UInt8(file), rank: UInt8(rank)) - XCTAssertTrue(pos.index == i, "Expected \(i) got \(pos.index)") + let pos: Square.Position = .init(rank: rank, file: file) + XCTAssertTrue(pos.index == Int(i), "Expected \(i) got \(pos.index!)") } } + func testDiagonalMoves() throws { + let b: Bishop = .init(color: .White, on: .init(rank: 4, file: 4)) + let result = [ + Square.Position(rank: 5, file: 3), + Square.Position(rank: 6, file: 2), + Square.Position(rank: 7, file: 1), + Square.Position(rank: 5, file: 5), + Square.Position(rank: 6, file: 6), + Square.Position(rank: 7, file: 7), + Square.Position(rank: 8, file: 8), + Square.Position(rank: 3, file: 5), + Square.Position(rank: 2, file: 6), + Square.Position(rank: 1, file: 7), + Square.Position(rank: 3, file: 3), + Square.Position(rank: 2, file: 2), + Square.Position(rank: 1, file: 1), + ] + XCTAssertEqual(result, b.pseudoLegalPositions) + } + + func testLinearMoves() throws { + let r: Rook = .init(color: .White, on: .init(rank: 4, file: 4)) + let result = [ + Square.Position(rank: 5, file: 4), + Square.Position(rank: 6, file: 4), + Square.Position(rank: 7, file: 4), + Square.Position(rank: 8, file: 4), + Square.Position(rank: 4, file: 5), + Square.Position(rank: 4, file: 6), + Square.Position(rank: 4, file: 7), + Square.Position(rank: 4, file: 8), + Square.Position(rank: 3, file: 4), + Square.Position(rank: 2, file: 4), + Square.Position(rank: 1, file: 4), + Square.Position(rank: 4, file: 3), + Square.Position(rank: 4, file: 2), + Square.Position(rank: 4, file: 1), + ] + XCTAssertEqual(result, r.pseudoLegalPositions) + } // func testBoard() throws { // let board = Board() // }