aboutsummaryrefslogtreecommitdiff
path: root/Tests/SwiftChessNeoTests/SwiftChessNeoTests.swift
diff options
context:
space:
mode:
authorNavan Chauhan <navanchauhan@gmail.com>2024-04-17 12:00:05 -0600
committerNavan Chauhan <navanchauhan@gmail.com>2024-04-17 12:00:05 -0600
commit424fe090aa919d7ef70720d663bd280d09092bdf (patch)
treee8d54467b3f3cb77ed778d1fadbc826a2e6aab7a /Tests/SwiftChessNeoTests/SwiftChessNeoTests.swift
initial commit
Diffstat (limited to 'Tests/SwiftChessNeoTests/SwiftChessNeoTests.swift')
-rw-r--r--Tests/SwiftChessNeoTests/SwiftChessNeoTests.swift539
1 files changed, 539 insertions, 0 deletions
diff --git a/Tests/SwiftChessNeoTests/SwiftChessNeoTests.swift b/Tests/SwiftChessNeoTests/SwiftChessNeoTests.swift
new file mode 100644
index 0000000..310a53c
--- /dev/null
+++ b/Tests/SwiftChessNeoTests/SwiftChessNeoTests.swift
@@ -0,0 +1,539 @@
+import XCTest
+@testable import SwiftChessNeo
+
+final class Sage2Tests: XCTestCase {
+
+ func testBoardInitializer() {
+ XCTAssertEqual(Board(variant: .standard), Board())
+ XCTAssertNotEqual(Board(variant: nil), Board())
+ }
+
+ func testBoardEquality() {
+ XCTAssertEqual(Board(), Board())
+ XCTAssertEqual(Board(variant: nil), Board(variant: nil))
+ XCTAssertNotEqual(Board(), Board(variant: nil))
+ var board = Board(variant: .standard)
+ board.removePiece(at: .a1)
+ XCTAssertNotEqual(Board(), board)
+ }
+
+ func testBoardPopulate() {
+ let board = Board()
+ XCTAssertEqual(board.pieces.count, 32)
+ XCTAssertEqual(board.whitePieces.count, 16)
+ XCTAssertEqual(board.blackPieces.count, 16)
+ for file in File.all { for rank in Rank.all {
+ if let piece = board[(file, rank)] {
+ let color = piece.color
+ XCTAssertTrue((color.isWhite ? [1, 2] : [7, 8]).contains(rank))
+ if piece.kind.isPawn {
+ XCTAssertTrue([2, 7].contains(rank))
+ } else {
+ XCTAssertTrue([1, 8].contains(rank))
+ }
+ switch piece.kind {
+ case .pawn:
+ break
+ case .knight:
+ XCTAssertTrue([.b, .g].contains(file))
+ case .bishop:
+ XCTAssertTrue([.c, .f].contains(file))
+ case .rook:
+ XCTAssertTrue([.a, .h].contains(file))
+ case .queen:
+ XCTAssertEqual(file, File.d)
+ case .king:
+ XCTAssertEqual(file, File.e)
+ }
+ } else {
+ XCTAssertTrue([3, 4, 5, 6].contains(rank))
+ }
+ } }
+ XCTAssertTrue(Board(variant: nil).pieces.isEmpty)
+ }
+
+ func testBoardFromCharacters() {
+ let board = Board(pieces: [["r", "n", "b", "q", "k", "b", "n", "r"],
+ ["p", "p", "p", "p", "p", "p", "p", "p"],
+ [" ", " ", " ", " ", " ", " ", " ", " "],
+ [" ", " ", " ", " ", " ", " ", " ", " "],
+ [" ", " ", " ", " ", " ", " ", " ", " "],
+ [" ", " ", " ", " ", " ", " ", " ", " "],
+ ["P", "P", "P", "P", "P", "P", "P", "P"],
+ ["R", "N", "B", "Q", "K", "B", "N", "R"]])
+ XCTAssertEqual(board, Board())
+ }
+
+ func testBoardSequence() {
+ let board = Board()
+ let spaces = Array(board)
+ let pieces = spaces.flatMap({ $0.piece })
+ let whitePieces = pieces.filter({ $0.color.isWhite })
+ let blackPieces = pieces.filter({ $0.color.isBlack })
+ XCTAssertEqual(spaces.count, 64)
+ XCTAssertEqual(pieces.count, 32)
+ XCTAssertEqual(whitePieces.count, 16)
+ XCTAssertEqual(blackPieces.count, 16)
+ }
+
+ func testBoardSubscript() {
+ var board = Board()
+ #if swift(>=3)
+ XCTAssertEqual(Piece(pawn: .white), board[.a2])
+ XCTAssertEqual(Piece(king: .black), board[.e8])
+ let piece = Piece(pawn: .black)
+ #else
+ XCTAssertEqual(Piece(pawn: .White), board[.A2])
+ XCTAssertEqual(Piece(king: .Black), board[.E8])
+ let piece = Piece(pawn: .Black)
+ #endif
+ let location = ("A", 3) as Location
+ XCTAssertNil(board[location])
+ board[location] = piece
+ XCTAssertEqual(board[location], piece)
+ board[location] = nil
+ XCTAssertNil(board[location])
+ }
+
+ func testBoardSwap() {
+ let start = Board()
+ var board = start
+ let location1 = ("D", 1) as Location
+ let location2 = ("F", 2) as Location
+ board.swap(location1, location2)
+ XCTAssertEqual(start[location1], board[location2])
+ XCTAssertEqual(start[location2], board[location1])
+ }
+
+ func testAllFiles() {
+ XCTAssertEqual(File.all, ["a", "b", "c", "d", "e", "f", "g", "h"])
+ }
+
+ func testAllRanks() {
+ XCTAssertEqual(Rank.all, [1, 2, 3, 4, 5, 6, 7, 8])
+ }
+
+ func testFileOpposite() {
+ let all = File.all
+ #if swift(>=3)
+ let reversed = all.reversed()
+ #else
+ let reversed = all.reverse()
+ #endif
+ for (a, b) in zip(all, reversed) {
+ XCTAssertEqual(a.opposite(), b)
+ }
+ }
+
+ func testRankOpposite() {
+ let all = Rank.all
+ #if swift(>=3)
+ let reversed = all.reversed()
+ #else
+ let reversed = all.reverse()
+ #endif
+ for (a, b) in zip(all, reversed) {
+ XCTAssertEqual(a.opposite(), b)
+ }
+ }
+
+ func testFileTo() {
+ #if swift(>=3)
+ XCTAssertEqual(File.a.to(.h), File.all)
+ XCTAssertEqual(File.a.to(.a), [File.a])
+ #else
+ XCTAssertEqual(File.A.to(.H), File.all)
+ XCTAssertEqual(File.A.to(.A), [File.A])
+ #endif
+ }
+
+ func testRankTo() {
+ #if swift(>=3)
+ XCTAssertEqual(Rank.one.to(.eight), Rank.all)
+ XCTAssertEqual(Rank.one.to(.one), [Rank.one])
+ #else
+ XCTAssertEqual(Rank.One.to(.Eight), Rank.all)
+ XCTAssertEqual(Rank.One.to(.One), [Rank.One])
+ #endif
+ }
+
+ func testFileBetween() {
+ #if swift(>=3)
+ XCTAssertEqual(File.c.between(.f), [.d, .e])
+ XCTAssertEqual(File.c.between(.d), [])
+ XCTAssertEqual(File.c.between(.c), [])
+ #else
+ XCTAssertEqual(File.C.between(.F), [.D, .E])
+ XCTAssertEqual(File.C.between(.D), [])
+ XCTAssertEqual(File.C.between(.C), [])
+ #endif
+ }
+
+ func testRankBetween() {
+ #if swift(>=3)
+ XCTAssertEqual(Rank.two.between(.five), [.three, .four])
+ XCTAssertEqual(Rank.two.between(.three), [])
+ XCTAssertEqual(Rank.two.between(.two), [])
+ #else
+ XCTAssertEqual(Rank.Two.between(.Five), [.Three, .Four])
+ XCTAssertEqual(Rank.Two.between(.Three), [])
+ XCTAssertEqual(Rank.Two.between(.Two), [])
+ #endif
+ }
+
+ func testFileFromCharacter() {
+ for u in 65...72 {
+ #if swift(>=3)
+ let scalar = UnicodeScalar(u)!
+ #else
+ let scalar = UnicodeScalar(u)
+ #endif
+ XCTAssertNotNil(File(Character(scalar)))
+ }
+ for u in 97...104 {
+ #if swift(>=3)
+ let scalar = UnicodeScalar(u)!
+ #else
+ let scalar = UnicodeScalar(u)
+ #endif
+ XCTAssertNotNil(File(Character(scalar)))
+ }
+ }
+
+ func testRankFromNumber() {
+ for n in 1...8 {
+ XCTAssertNotNil(Rank(n))
+ }
+ }
+
+ func testMoveEquality() {
+ #if swift(>=3)
+ let move = Move(start: .a1, end: .c3)
+ XCTAssertEqual(move, move)
+ XCTAssertEqual(move, Move(start: .a1, end: .c3))
+ XCTAssertNotEqual(move, Move(start: .a1, end: .b1))
+ #else
+ let move = Move(start: .A1, end: .C3)
+ XCTAssertEqual(move, move)
+ XCTAssertEqual(move, Move(start: .A1, end: .C3))
+ XCTAssertNotEqual(move, Move(start: .A1, end: .B1))
+ #endif
+ }
+
+ func testMoveRotation() {
+ #if swift(>=3)
+ let move = Move(start: .a1, end: .c6)
+ let rotated = move.rotated()
+ XCTAssertEqual(rotated.start, Square.h8)
+ XCTAssertEqual(rotated.end, Square.f3)
+ #else
+ let move = Move(start: .A1, end: .C6)
+ let rotated = move.rotated()
+ XCTAssertEqual(rotated.start, Square.H8)
+ XCTAssertEqual(rotated.end, Square.F3)
+ #endif
+ }
+
+ func testMoveOperator() {
+ for file in File.all { for rank in Rank.all {
+ let start = Square(file: file, rank: rank)
+ let end = Square(file: file.opposite(), rank: rank.opposite())
+ XCTAssertEqual(Move(start: start, end: end), start >>> end)
+ } }
+ }
+
+ func testGameRandomMoves() throws {
+ let game = Game()
+ while let move = game.availableMoves().random() {
+ let enemyColor = game.playerTurn.inverse()
+ let enemyKingSpace = game.board.squareForKing(for: enemyColor)
+ guard move.end != enemyKingSpace else {
+ let error = "Attempted attack to king for \(enemyColor): \(move.formatted())"
+ + "\nPosition: " + game.position.fen().debugDescription
+ + "\nMoves: \(game.playedMoves.formatted())"
+ XCTFail(error)
+ return
+ }
+ try game.execute(move: move)
+ }
+ guard let outcome = game.outcome else {
+ XCTFail("Expected outcome for complete game")
+ return
+ }
+ if let color = outcome.winColor {
+ guard game.kingIsChecked && game.board.kingIsChecked(for: color.inverse()) else {
+ XCTFail("\(color.inverse()) should be in check if \(color) wins")
+ return
+ }
+ }
+ }
+
+ func testGameFromMoves() throws {
+ let game = Game()
+ while let move = game.availableMoves().random() {
+ try game.execute(uncheckedMove: move)
+ }
+ do {
+ let moves = game.playedMoves
+ let other = try Game(moves: moves)
+ XCTAssertEqual(other.board, game.board)
+ XCTAssertEqual(other.playedMoves, moves)
+ } catch {
+ #if swift(>=3)
+ let str = String(describing: error)
+ #else
+ let str = String(error)
+ #endif
+ XCTFail(str)
+ }
+ }
+
+ func testGameDoubleStep() throws {
+ let game = Game()
+ for file in File.all {
+ let move = Move(start: Square(location: (file, 2)), end: Square(location: (file, 5)))
+ XCTAssertThrowsError(try game.execute(move: move)) { error in
+ #if swift(>=3)
+ guard case Game.ExecutionError.illegalMove = error else {
+ XCTFail("Expected MoveExecutionError.IllegalMove, got \(error)")
+ return
+ }
+ #else
+ guard case Game.ExecutionError.IllegalMove = error else {
+ XCTFail("Expected MoveExecutionError.IllegalMove, got \(error)")
+ return
+ }
+ #endif
+ }
+ }
+ for file in File.all {
+ try game.execute(move: Move(start: Square(location: (file, 2)), end: Square(location: (file, 4))))
+ try game.execute(move: Move(start: Square(location: (file, 7)), end: Square(location: (file, 5))))
+ }
+ }
+
+ func testGameEnPassant() {
+ let game = Game()
+ do {
+ #if swift(>=3)
+ try game.execute(move: Move(start: .c2, end: .c4))
+ try game.execute(move: Move(start: .c7, end: .c6))
+ try game.execute(move: Move(start: .c4, end: .c5))
+ try game.execute(move: Move(start: .d7, end: .d5))
+ try game.execute(move: Move(start: .c5, end: .d6))
+ #else
+ try game.execute(move: Move(start: .C2, end: .C4))
+ try game.execute(move: Move(start: .C7, end: .C6))
+ try game.execute(move: Move(start: .C4, end: .C5))
+ try game.execute(move: Move(start: .D7, end: .D5))
+ try game.execute(move: Move(start: .C5, end: .D6))
+ #endif
+ } catch {
+ #if swift(>=3)
+ let str = String(describing: error)
+ #else
+ let str = String(error)
+ #endif
+ XCTFail(str)
+ }
+ }
+
+ func testGameWhiteKingSideCastlingRightsAfterRookCapture() {
+ let startFen = "r3k2r/8/8/8/8/8/8/R3K2R b KQkq - 0 1"
+ let startPosition = Game.Position(fen: startFen)!
+ let game = try! Game(position: startPosition)
+ #if swift(>=3)
+ let move = Move(start: .h8, end: .h1)
+ #else
+ let move = Move(start: .H8, end: .H1)
+ #endif
+ try! game.execute(move: move)
+ #if swift(>=3)
+ XCTAssertFalse(game.castlingRights.contains(.whiteKingside))
+ #else
+ XCTAssertFalse(game.castlingRights.contains(.WhiteKingside))
+ #endif
+ }
+
+ func testGameWhiteQueenSideCastlingRightsAfterRookCapture() {
+ let startFen = "r3k2r/8/8/8/8/8/8/R3K2R b KQkq - 0 1"
+ let startPosition = Game.Position(fen: startFen)!
+ let game = try! Game(position: startPosition)
+ #if swift(>=3)
+ let move = Move(start: .a8, end: .a1)
+ #else
+ let move = Move(start: .A8, end: .A1)
+ #endif
+ try! game.execute(move: move)
+ #if swift(>=3)
+ XCTAssertFalse(game.castlingRights.contains(.whiteQueenside))
+ #else
+ XCTAssertFalse(game.castlingRights.contains(.WhiteQueenside))
+ #endif
+ }
+
+ func testGameBlackKingSideCastlingRightsAfterRookCapture() {
+ let startFen = "r3k2r/8/8/8/8/8/8/R3K2R w KQkq - 0 1"
+ let startPosition = Game.Position(fen: startFen)!
+ let game = try! Game(position: startPosition)
+ #if swift(>=3)
+ let move = Move(start: .h1, end: .h8)
+ #else
+ let move = Move(start: .H1, end: .H8)
+ #endif
+ try! game.execute(move: move)
+ #if swift(>=3)
+ XCTAssertFalse(game.castlingRights.contains(.blackKingside))
+ #else
+ XCTAssertFalse(game.castlingRights.contains(.BlackKingside))
+ #endif
+ }
+
+ func testGameBlackQueenSideCastlingRightsAfterRookCapture() {
+ let startFen = "r3k2r/8/8/8/8/8/8/R3K2R w KQkq - 0 1"
+ let startPosition = Game.Position(fen: startFen)!
+ let game = try! Game(position: startPosition)
+ #if swift(>=3)
+ let move = Move(start: .a1, end: .a8)
+ #else
+ let move = Move(start: .A1, end: .A8)
+ #endif
+ try! game.execute(move: move)
+ #if swift(>=3)
+ XCTAssertFalse(game.castlingRights.contains(.blackQueenside))
+ #else
+ XCTAssertFalse(game.castlingRights.contains(.BlackQueenside))
+ #endif
+ }
+
+ func testGameUndoAndRedo() throws {
+ let game = Game()
+ let startBoard = game.board
+ var endBoard = startBoard
+ var moves = [Move]()
+
+ while let move = game.availableMoves().random() {
+ try game.execute(uncheckedMove: move)
+ moves.append(move)
+ endBoard = game.board
+ }
+ #if swift(>=3)
+ var redoMoves = moves.reversed() as [Move]
+ #else
+ var redoMoves = moves.reverse() as [Move]
+ #endif
+
+ while let move = game.moveToUndo() {
+ XCTAssertEqual(move, game.undoMove())
+ XCTAssertEqual(move, moves.popLast())
+ }
+ XCTAssert(moves.isEmpty)
+ XCTAssertEqual(game.board, startBoard)
+
+ while let move = game.moveToRedo() {
+ XCTAssertEqual(move, game.redoMove())
+ XCTAssertEqual(move, redoMoves.popLast())
+ }
+ XCTAssertEqual(game.board, endBoard)
+ }
+
+ func testPGNParsingAndExporting() throws {
+ let immortalGame = String()
+ + "[Event \"London\"]\n"
+ + "[Site \"London\"]\n"
+ + "[Date \"1851.??.??\"]\n"
+ + "[EventDate \"?\"]\n"
+ + "[Round \"?\"]\n"
+ + "[Result \"1-0\"]\n"
+ + "[White \"Adolf Anderssen\"]\n"
+ + "[Black \"Kieseritzky\"]\n"
+ + "[ECO \"C33\"]\n"
+ + "[WhiteElo \"?\"]\n"
+ + "[BlackElo \"?\"]\n"
+ + "[PlyCount \"45\"]\n"
+ + "\n"
+ + "1.e4 e5 2.f4 exf4 3.Bc4 Qh4+ 4.Kf1 b5 5.Bxb5 Nf6 6.Nf3 Qh6 7.d3 Nh5 8.Nh4 Qg5\n"
+ + "9.Nf5 c6 10.g4 Nf6 11.Rg1 cxb5 12.h4 Qg6 13.h5 Qg5 14.Qf3 Ng8 15.Bxf4 Qf6\n"
+ + "16.Nc3 Bc5 17.Nd5 Qxb2 18.Bd6 Bxg1 19. e5 Qxa1+ 20. Ke2 Na6 21.Nxg7+ Kd8\n"
+ + "22.Qf6+ Nxf6 23.Be7# 1-0\n"
+
+ let returnGame = String()
+ + "[Event \"F/S Return Match\"]\n"
+ + "[Site \"Belgrade, Serbia Yugoslavia|JUG\"]\n"
+ + "[Date \"1992.11.04\"]\n"
+ + "[Round \"29\"]\n"
+ + "[White \"Fischer, Robert J.\"]\n"
+ + "[Black \"Spassky, Boris V.\"]\n"
+ + "[Result \"1/2-1/2\"]\n"
+ + "\n"
+ + "1. e4 e5 2. Nf3 Nc6 3. Bb5 a6 {This opening is called the Ruy Lopez.}\n"
+ + "4. Ba4 Nf6 5. O-O Be7 6. Re1 b5 7. Bb3 d6 8. c3 O-O 9. h3 Nb8 10. d4 Nbd7\n"
+ + "11. c4 c6 12. cxb5 axb5 13. Nc3 Bb7 14. Bg5 b4 15. Nb1 h6 16. Bh4 c5 17. dxe5\n"
+ + "Nxe4 18. Bxe7 Qxe7 19. exd6 Qf6 20. Nbd2 Nxd6 21. Nc4 Nxc4 22. Bxc4 Nb6\n"
+ + "23. Ne5 Rae8 24. Bxf7+ Rxf7 25. Nxf7 Rxe1+ 26. Qxe1 Kxf7 27. Qe3 Qg5 28. Qxg5\n"
+ + "hxg5 29. b3 Ke6 30. a3 Kd6 31. axb4 cxb4 32. Ra5 Nd5 33. f3 Bc8 34. Kf2 Bf5\n"
+ + "35. Ra7 g6 36. Ra6+ Kc5 37. Ke1 Nf4 38. g3 Nxh3 39. Kd2 Kb5 40. Rd6 Kc5 41. Ra6\n"
+ + "Nf2 42. g4 Bd3 43. Re6 1/2-1/2\n"
+
+ let immortalGamePGN = try PGN(parse: immortalGame)
+ XCTAssertEqual(immortalGamePGN.moves.count, 45)
+ XCTAssertEqual(immortalGamePGN.outcome, Game.Outcome._win(._white))
+ let returnGamePGN = try PGN(parse: returnGame)
+ XCTAssertEqual(returnGamePGN.moves.count, 85)
+ XCTAssertEqual(returnGamePGN.outcome, Game.Outcome._draw)
+
+ let immortalGameExportedPGN = try PGN(parse: immortalGamePGN.exported())
+ let returnGameExportedPGN = try PGN(parse: returnGamePGN.exported())
+ XCTAssertEqual(immortalGameExportedPGN, immortalGamePGN)
+ XCTAssertEqual(returnGameExportedPGN, returnGamePGN)
+ XCTAssertNotEqual(immortalGameExportedPGN, returnGamePGN)
+ XCTAssertNotEqual(returnGameExportedPGN, immortalGamePGN)
+ }
+
+ }
+
+ extension Int {
+
+ static func random(from value: Int) -> Int {
+ #if os(OSX) || os(iOS) || os(watchOS) || os(tvOS)
+ return Int(arc4random_uniform(UInt32(value)))
+ #elseif os(Linux)
+ srand(.init(time(nil)))
+ return Int(rand() % .init(value))
+ #else
+ fatalError("Unknown OS")
+ #endif
+ }
+
+ }
+
+ extension Array {
+
+ func random() -> Element? {
+ return !self.isEmpty ? self[.random(from: count)] : nil
+ }
+
+ }
+
+ extension Move {
+
+ func formatted() -> String {
+ let result = ".\(start) >>> .\(end)"
+ #if swift(>=3)
+ return result.lowercased()
+ #else
+ return result.uppercaseString
+ #endif
+ }
+
+ }
+
+ extension Sequence where Iterator.Element == Move {
+
+ func formatted() -> String {
+ let values = map { $0.formatted() }
+ let string = values.joined(separator: ", ")
+ return "[" + string + "]"
+ }
+
+ }