From a3c48fd50c50caf587758110daa6a9f368999ecc Mon Sep 17 00:00:00 2001 From: TiynGER Date: Mon, 10 May 2021 01:30:11 +0200 Subject: [PATCH] chess: chess to fen notation added Now there is a method to turn a chess object into a fen-string. Additionally the enPassant handling was changed. There can be only one enPassant field at a time. So a new attribute was added to the chess object that stores the field for the enPassant capture. If there is no enPassant field it will be set to -1. --- src/chess.nim | 136 ++++++++++++++++++++++++--------------------- src/engineTest.nim | 60 ++------------------ 2 files changed, 80 insertions(+), 116 deletions(-) diff --git a/src/chess.nim b/src/chess.nim index 61f7b66..de70699 100644 --- a/src/chess.nim +++ b/src/chess.nim @@ -23,8 +23,10 @@ type toMove*: Color previousBoard: seq[Board] previousCastleRights: seq[CastleRights] - fiftyMoveCounter: int + halfMoveClock: int + fullMoveCounter*: int castleRights: CastleRights + enPassantSquare: int Move* = object ## `Move` stores all important information for a move. start: int @@ -61,9 +63,6 @@ const ## queen. WKing* = 6 ## \ ## `WKing` is the value assigned to a square in a board with a white king. - WEnPassant = 7 ## \ - ## `WEnPassant` is assigned to a square in a board with an invisible white - ## en passant pawn. BPawn* = -WPawn ## \ ## `BPawn` is the value assigned to a square in a board with a black pawn. BKnight* = -WKnight ## \ @@ -78,9 +77,6 @@ const ## `BQueen` is the value assigned to a square in a board with a black queen. BKing* = -WKing ## \ ## `BKing` is the value assigned to a square in a board with a black king. - BEnPassant = -WEnPassant ## \ - ## `BEnPassant` is assigned to a square in a board with an invisible black - ## en passant pawn. N = 10 ## `N` describes a move a field up the board from whites perspective. S = -N ## `S` describes a move a field down the board from whites perspective. E = 1 ## `E` describes a move a field to the right from whites perspective. @@ -124,14 +120,12 @@ let WRook: "R", WQueen: "Q", WKing: "K", - WEnPassant: " ", BPawn: "p", BKnight: "n", BBishop: "b", BRook: "r", BQueen: "q", BKing: "k", - BEnPassant: " ", }.newTable ## \ ## `PieceChar` describes the representation for the pieceIDs for the cli. FileChar = { @@ -260,39 +254,14 @@ proc initBoard(board: array[64, int]): Board = proc initChess*(): Chess = ## Create and return a Chess object. let chess = Chess(board: initBoard(), - to_move: Color.White, castleRights: (true, true, true, true)) - return chess - -proc initChess(board: array[64, int], color: Color): Chess = - ## Create and return a Chess object based on a position of choice. - ## `board` describes the pieces, `color` the color that is about to move. - let board = initBoard(board) - let compare = initBoard() - var wk: bool - var wq: bool - var bk: bool - var bq: bool - if (board[fieldToInd("e1")] == compare[fieldToInd("e1")]): - if (board[fieldToInd("a1")] == compare[fieldToInd("a1")]): - wq = true - if (board[fieldToInd("h1")] == compare[fieldToInd("h1")]): - wk = true - if (board[fieldToInd("e8")] == compare[fieldToInd("e8")]): - if (board[fieldToInd("a8")] == compare[fieldToInd("a8")]): - bq = true - if (board[fieldToInd("h8")] == compare[fieldToInd("h8")]): - bk = true - let chess = Chess(board: board, - to_move: color, castleRights: (wk, wq, bk, bq)) + to_move: Color.White, castleRights: (true, true, true, true), fullMoveCounter: 1, enPassantSquare: -1) return chess proc initChess*(fen: string): Chess = ## Create and return a Chess object from `fen`. var revPieceChar = toSeq(PieceChar.pairs).map(y => (y[1], y[0])).toTable - revPieceChar[" "] = Empty var fenArr = fen.split(" ") var squares: array[64, int] - var tmp: seq[int] var squaresInd: int for i, subc in fenArr[0].reversed(): if subc == '/': @@ -320,14 +289,60 @@ proc initChess*(fen: string): Chess = castleRights.bk = true if fenArr[2].contains("q"): castleRights.bq = true + var enPassantSquare = -1 if fenArr[3] != "-": - if toMove == Color.White: - board[fieldToInd(fenArr[3])] = BEnPassant - else: - board[fieldToInd(fenArr[3])] = WEnPassant - var fiftyMoveCounter = parseInt(fenArr[4]) + enPassantSquare = fieldToInd(fenArr[3]) + let halfMoveClock = parseInt(fenArr[4]) + let fullMoveCounter = parseInt(fenArr[5]) return Chess(board: board, toMove: toMove, castleRights: castleRights, - fiftyMoveCounter: fiftyMoveCounter) + halfMoveClock: halfMoveClock, fullMoveCounter: fullMoveCounter, enPassantSquare: enPassantSquare) + +proc convertToFen*(chess: Chess): string = + ## Build and return a fen string from a given `chess` object. + var pieces: string + var fen: string + var spaceOcc: int + var fileCounter = 0 + for piece in chess.board.reversed: + if not (piece == Block): + if fileCounter == 8: + if spaceOcc != 0: + pieces &= $spaceOcc + spaceOcc = 0 + pieces &= "/" + fileCounter = 0 + if PieceChar[piece] == " ": + spaceOcc += 1 + else: + if spaceOcc != 0: + pieces &= $spaceOcc + spaceOcc = 0 + pieces &= PieceChar[piece] + fileCounter += 1 + fen &= pieces & " " + if chess.toMove == Color.White: + fen &= "w " + else: + fen &= "b " + var castleR: string + if chess.castleRights.wk: + castleR &= "K" + if chess.castleRights.wq: + castleR &= "Q" + if chess.castleRights.bk: + castleR &= "k" + if chess.castleRights.bq: + castleR &= "q" + if castleR.isEmptyOrWhitespace: + castleR = "-" + fen &= castleR & " " + if chess.enPassantSquare != -1: + fen &= indToField(chess.enPassantSquare) & " " + else: + fen &= "- " + fen &= $chess.halfMoveClock & " " & $chess.fullMoveCounter + return fen + proc echoBoard*(chess: Chess, color: Color) = ## Prints out the given `board` with its pieces as characters and line @@ -363,10 +378,12 @@ proc genPawnAttackDests(chess: Chess, field: int, color: Color): seq[int] = var target: int for attacks in Pawn_Moves_White_Attack: dest = field + (attacks * ord(color)) - if (not dest in chess.board.low..chess.board.high): + if dest == chess.enPassantSquare: + res.add(dest) + if not dest in chess.board.low..chess.board.high: continue target = chess.board[dest] - if (target == Block or ord(color) * target >= 0): + if target == Block or ord(color) * target >= 0: continue res.add(dest) return res @@ -407,7 +424,7 @@ proc genPawnDests(chess: Chess, field: int, color: Color): seq[int] = if (not dest in chess.board.low..chess.board.high): continue target = chess.board[dest] - if (target != 0 and target != ord(color) * WEnPassant): + if (target != 0 and dest != chess.enPassantSquare): continue res.add(dest) res.add(chess.genPawnAttackDests(field, color)) @@ -428,7 +445,7 @@ proc genKnightDests(chess: Chess, field: int, color: Color): seq[int] = if (not dest in chess.board.low..chess.board.high): continue target = chess.board[dest] - if (target == Block or (ord(color) * target > 0 and ord(color) * target != WEnPassant)): + if (target == Block or (ord(color) * target > 0)): continue res.add(dest) return res @@ -448,10 +465,9 @@ proc genSlidePieceDests(chess: Chess, field: int, color: Color, moves: seq[ if (not dest in chess.board.low..chess.board.high): continue target = chess.board[dest] - while (target != Block and (ord(color) * target <= 0) or abs(target) == - WEnPassant): + while (target != Block and (ord(color) * target <= 0)): res.add(dest) - if (ord(color) * target < 0 and ord(color) * target > BEnPassant): + if (ord(color) * target < 0): break dest = dest + move target = chess.board[dest] @@ -514,7 +530,7 @@ proc genKingDests(chess: Chess, field: int, color: Color): seq[int] = if (not dest in chess.board.low..chess.board.high): continue target = chess.board[dest] - if (target == Block or (ord(color) * target > 0 and ord(color) * target != WEnPassant)): + if (target == Block or (ord(color) * target > 0)): continue res.add(dest) res.add(chess.genKingCastleDest(field, color)) @@ -732,12 +748,6 @@ proc castling(chess: var Chess, kstart: int, dest_kingside: bool, return true return false -proc removeEnPassant(board: var Board, color: Color): void = - ## Removes every en passant of given `color` from the `board`. - for field in board.low..board.high: - if board[field] == ord(color) * WEnPassant: - board[field] = 0 - proc checkedMove*(chess: var Chess, move: Move): bool {.discardable.} = ## Tries to make a `move` in a given `chess``. ## This process checks for the legality of the move and performs the switch @@ -757,13 +767,13 @@ proc checkedMove*(chess: var Chess, move: Move): bool {.discardable.} = move = getMove(start, dest, color) if (piece == WPawn * ord(color)): createEnPassant = dest in chess.genPawnDoubleDests(start, color) - capturedEnPassant = (chess.board[dest] == -1 * ord(color) * WEnPassant) + capturedEnPassant = (dest == chess.enPassantSquare) fiftyMoveRuleReset = true if (chess.board[move.dest] != 0): fiftyMoveRuleReset = true sequence.add(chess.genLegalMoves(start, color)) if (move in sequence): - chess.board.removeEnPassant(color) + chess.enPassantSquare = -1 if (piece == WKing * ord(color) and (start - dest == (W+W))): return chess.castling(start, true, color) elif (piece == WKing * ord(color) and (start - dest == (E+E))): @@ -772,19 +782,21 @@ proc checkedMove*(chess: var Chess, move: Move): bool {.discardable.} = chess.uncheckedMove(start, dest) chess.toMove = Color(ord(chess.toMove)*(-1)) if createEnPassant: - chess.board[dest - (N * ord(color))] = WEnPassant * ord(color) + chess.enPassantSquare = dest - (N * ord(color)) if capturedEnPassant: chess.board[dest - (N * ord(color))] = 0 - if ((90 < dest and dest < 99) or (20 < dest and dest < 29)) and + if ((fieldToInd("h8") < dest and dest < fieldToInd("a8")) or (fieldToInd("h1") < dest and dest < fieldToInd("a1"))) and chess.board[dest] == WPawn * ord(color): chess.board[dest] = prom var prevBoard = chess.previousBoard var prevCastle = chess.previousCastleRights chess.previousBoard.add(chess.board) chess.previousCastleRights.add(chess.castleRights) - chess.fiftyMoveCounter = chess.fiftyMoveCounter + 1 + chess.halfMoveClock = chess.halfMoveClock + 1 + if color == Color.Black: + chess.fullMoveCounter += 1 if fiftyMoveRuleReset: - chess.fiftyMoveCounter = 0 + chess.halfMoveClock = 0 return true proc isCheckmate*(chess: Chess, color: Color): bool = @@ -807,7 +819,7 @@ proc threeMoveRep(chess: Chess): bool = proc isDrawClaimable*(chess: Chess): bool = ## Returns true if a draw is claimable by either player. - return chess.threeMoveRep() or chess.fiftyMoveCounter >= 100 + return chess.threeMoveRep() or chess.halfMoveClock >= 100 proc checkInsufficientMaterial(board: Board): bool = ## Checks for combinations of pieces on a `board`, where no checkmate can be diff --git a/src/engineTest.nim b/src/engineTest.nim index e4208d9..deba278 100644 --- a/src/engineTest.nim +++ b/src/engineTest.nim @@ -12,85 +12,37 @@ testSuite ChessTest of TestSuite: self.chess = initChess() method testPieceEvalStalemate() = - self.chess = initChess([ - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, WKing, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, BKing, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0 - ], Color.Black) - var pieceEvaluation = self.chess.pieceEval() + self.chess = initChess("8/8/2k5/8/8/5K2/8/8 b - - 0 1") + var pieceEvaluation = self.chess.evaluate() self.check(pieceEvaluation == 0) method testBestMoveProm() = - self.chess = initChess([ - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, WKing, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, WPawn, 0, BKing, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0 - ], Color.White) + self.chess = initChess("8/2k1P3/8/8/8/5K2/8/8 w - - 0 1") var testBestMove = self.chess.bestMove(2) self.check(testBestMove.start != 0) self.check(indToField(testBestMove.start) == "e7") self.check(indToField(testBestMove.dest) == "e8") - - method testBestMoveStopProm() = - self.chess = initChess([ - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, WKing, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, WPawn, BKing, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0 - ], Color.Black) + self.chess = initChess("8/2k1P3/8/8/8/5K2/8/8 b - - 0 1") var testBestMove = self.chess.bestMove(2) self.check(testBestMove.start != 0) self.check(indToField(testBestMove.start) == "c7") self.check(indToField(testBestMove.dest) == "d7") method testBestMoveTacticBlack() = - self.chess = initChess([ - 0, 0, 0, 0, 0, 0, 0, 0, - 0, WRook, 0, WKing, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, WPawn, 0, 0, 0, 0, 0, - 0, BPawn, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, BRook, 0, 0, 0, BKing, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0 - ], Color.Black) + self.chess = initChess("8/2k3r1/8/6p1/5P2/8/4K1R1/8 b - - 0 1") var testBestMove = self.chess.bestMove(2) self.check(testBestMove.start != 0) self.check(indToField(testBestMove.start) != "g5" or indToField( testBestMove.dest) != "f4") method testBestMoveTacticWhite() = - self.chess = initChess([ - 0, 0, 0, 0, 0, 0, 0, 0, - 0, WRook, 0, WKing, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, WPawn, 0, 0, 0, 0, 0, 0, - 0, 0, BPawn, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, BRook, 0, 0, 0, BKing, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0 - ], Color.White) + self.chess = initChess("8/2k3r1/8/5p2/6P1/8/4K1R1/8 w - - 0 1") var testBestMove = self.chess.bestMove(2) self.check(testBestMove.start != 0) self.check(indToField(testBestMove.start) != "g4" or indToField( testBestMove.dest) != "f5") - when isMainModule: einheit.runTests()