From c5cf2fd737cd5101bd8ec7001206feb4c2f8f9f6 Mon Sep 17 00:00:00 2001 From: TiynGER Date: Wed, 16 Dec 2020 03:18:24 +0100 Subject: [PATCH] chess: refactoring setters and try Setters are not needed as all the assigning gets handled inside the file. All Setters were removed. The try-statements were used to excessively and were changed for manual checking --- README.md | 5 + chess.nim | 495 ++++++++++++++++++++++++++---------------------------- test.nim | 17 ++ 3 files changed, 258 insertions(+), 259 deletions(-) diff --git a/README.md b/README.md index 8c9877d..22ca731 100644 --- a/README.md +++ b/README.md @@ -22,3 +22,8 @@ You can simply run the tests with `nim c -r test.nim`. Documentation is written into the code via DocGen. For this reason it is not saved in this repository. To extract it into html run `nim doc --project --index:on --outdir:htmldocs game.nim` + +### Board Representation + +Due to easier off the board checking a +[10x12](https://www.chessprogramming.org/10x12_Board) board is used. diff --git a/chess.nim b/chess.nim index 06203e2..dcd6205 100644 --- a/chess.nim +++ b/chess.nim @@ -1,6 +1,5 @@ import tables from strutils import parseInt -import algorithm type Color* = enum @@ -134,22 +133,6 @@ let "h": 0 }.newTable -proc setField(board: var Board, field: int, val: int): bool {.discardable.} = - try: - if (val in PieceChar): - board[field] = val - return true - return false - except Exception: - return false - -proc setField(moved: var Moved, field: int, val: bool): bool {.discardable.} = - try: - moved[field] = val - return true - except Exception: - return false - proc checkInsufficientMaterial(board: Board): bool = ## Checks for combinations of pieces on a `board`, where no checkmate can be forced var wp = 0 @@ -252,7 +235,7 @@ proc initGame*(board: array[0..63, int], color: Color): Game = var same_piece: bool for ind in board.low..board.high: same_piece = (board[ind] != compare[ind]) - moved.setField(ind, same_piece) + moved[ind] = same_piece let game = Game(board: board, moved: moved, to_move: color, previousBoard: @[], previousCastleRights: @[], fiftyMoveCounter: 0) @@ -324,204 +307,209 @@ proc genCastleRights(moved: Moved): CastleRights = proc notationToMove*(notation: string, color: Color): Move = ## Convert simplified algebraic chess `notation` to a move object, color of player is `color`. - try: - var move: Move - var start = fieldToInd(notation[0..1]) - var dest = fieldToInd(notation[2..3]) - move = getMove(start, dest, color) - if (len(notation) > 4): - var promStr = $notation[4] - var prom: int - case promStr: - of "Q": - prom = WQueen * ord(color) - of "R": - prom = WRook * ord(color) - of "B": - prom = WBishop * ord(color) - of "N": - prom = WKnight * ord(color) - move = getMove(start, dest, prom, color) - return move - except IndexError: - var move: Move - return move + var move: Move + var start = fieldToInd(notation[0..1]) + var dest = fieldToInd(notation[2..3]) + move = getMove(start, dest, color) + if (len(notation) > 4): + var promStr = $notation[4] + var prom: int + case promStr: + of "Q": + prom = WQueen * ord(color) + of "R": + prom = WRook * ord(color) + of "B": + prom = WBishop * ord(color) + of "N": + prom = WKnight * ord(color) + move = getMove(start, dest, prom, color) + return move proc genBishopDests(game: Game, field: int, color: Color): seq[int] = ## Generate possible destinations for a bishop with specific `color` located at index `field` of `game`. ## Returns a sequence of possible indices to move to. - try: - var res = newSeq[int]() - var dest: int - var target: int - for move in Bishop_Moves: - dest = field+move - target = game.board[dest] - while (target != 999 and (ord(color) * target <= 0) or target == - WEnPassant or target == -WEnPassant): - res.add(dest) - if (ord(color) * target < 0 and ord(color) * target > -WEnPassant): - break - dest = dest+move - target = game.board[dest] - return res - except IndexDefect: + if (not field in game.board.low..game.board.high): return @[] + var res = newSeq[int]() + var dest: int + var target: int + for move in Bishop_Moves: + dest = field+move + if (not dest in game.board.low..game.board.high): + continue + target = game.board[dest] + while (target != 999 and (ord(color) * target <= 0) or target == + WEnPassant or target == -WEnPassant): + res.add(dest) + if (ord(color) * target < 0 and ord(color) * target > -WEnPassant): + break + dest = dest+move + target = game.board[dest] + return res proc genRookDests(game: Game, field: int, color: Color): seq[int] = ## Generate possible destinations for a rook with specific `color` located at index `field` of `game`. ## Returns a sequence of possible indices to move to. - try: - var res = newSeq[int]() - var dest: int - var target: int - for move in Rook_Moves: - dest = field+move - target = game.board[dest] - while (target != 999 and (ord(color) * target <= 0) or target == - WEnPassant or target == -WEnPassant): - res.add(dest) - if (ord(color) * target < 0 and ord(color) * target > -WEnPassant): - break - dest = dest+move - target = game.board[dest] - return res - except IndexDefect: + if (not field in game.board.low..game.board.high): return @[] + var res = newSeq[int]() + var dest: int + var target: int + for move in Rook_Moves: + dest = field+move + if (not dest in game.board.low..game.board.high): + continue + target = game.board[dest] + while (target != 999 and (ord(color) * target <= 0) or target == + WEnPassant or target == -WEnPassant): + res.add(dest) + if (ord(color) * target < 0 and ord(color) * target > -WEnPassant): + break + dest = dest+move + target = game.board[dest] + return res proc genQueenDests(game: Game, field: int, color: Color): seq[int] = ## Generate possible destinations for a queen with specific `color` located at index `field` of `game`. ## Returns a sequence of possible indices to move to. - try: - var res = newSeq[int]() - var dest: int - var target: int - for move in Queen_Moves: - dest = field+move - target = game.board[dest] - while (target != 999 and (ord(color) * target <= 0) or target == - WEnPassant or target == -WEnPassant): - res.add(dest) - if (ord(color) * target < 0 and ord(color) * target > -WEnPassant): - break - dest = dest+move - target = game.board[dest] - return res - except IndexDefect: + if (not field in game.board.low..game.board.high): return @[] + var res = newSeq[int]() + var dest: int + var target: int + for move in Queen_Moves: + dest = field+move + if (not dest in game.board.low..game.board.high): + continue + target = game.board[dest] + while (target != 999 and (ord(color) * target <= 0) or target == + WEnPassant or target == -WEnPassant): + res.add(dest) + if (ord(color) * target < 0 and ord(color) * target > -WEnPassant): + break + dest = dest+move + target = game.board[dest] + return res proc genKingCastleDest(game: Game, field: int, color: Color): seq[int] = ## Generate possible castle destinations for a king with specific `color` located at index `field` of `game` ## Returns a sequence of possible indices to move to. - try: - var res = newSeq[int]() - var dest: int - var target: int - var half_dest: int - var half_target: int - for castle in King_Moves_White_Castle: - dest = field + castle - target = game.board[dest] - half_dest = field + (int)castle/2 - half_target = game.board[half_dest] - if (target == 999 or (target != 0)): - continue - if (half_target == 999 or (half_target != 0)): - continue - res.add(dest) - return res - except IndexDefect: + if (not field in game.board.low..game.board.high): return @[] + var res = newSeq[int]() + var dest: int + var target: int + var half_dest: int + var half_target: int + for castle in King_Moves_White_Castle: + dest = field + castle + if (not dest in game.board.low..game.board.high): + continue + target = game.board[dest] + half_dest = field + (int)castle/2 + half_target = game.board[half_dest] + if (target == 999 or (target != 0)): + continue + if (half_target == 999 or (half_target != 0)): + continue + res.add(dest) + return res proc genKingDests(game: Game, field: int, color: Color): seq[int] = ## Generate possible destinations for a king with specific `color` located at index `field` of `game`. ## Returns a sequence of possible indices to move to. - try: - var res = newSeq[int]() - var dest: int - var target: int - for move in King_Moves: - dest = field + move - target = game.board[dest] - if (target == 999 or (ord(color) * target > 0 and ord(color) * target != WEnPassant)): - continue - res.add(dest) - res.add(game.genKingCastleDest(field, color)) - return res - except IndexDefect: + if (not field in game.board.low..game.board.high): return @[] + var res = newSeq[int]() + var dest: int + var target: int + for move in King_Moves: + dest = field + move + if (not dest in game.board.low..game.board.high): + continue + target = game.board[dest] + if (target == 999 or (ord(color) * target > 0 and ord(color) * target != WEnPassant)): + continue + res.add(dest) + res.add(game.genKingCastleDest(field, color)) + return res proc genKnightDests(game: Game, field: int, color: Color): seq[int] = ## Generate possible destinations for a knight with specific `color` located at index `field` of `game`. ## Returns a sequence of possible indices to move to. - try: - var res = newSeq[int]() - var dest: int - var target: int - for move in Knight_Moves: - dest = field + move - target = game.board[dest] - if (target == 999 or (ord(color) * target > 0 and ord(color) * target != WEnPassant)): - continue - res.add(dest) - return res - except IndexDefect: + if (not field in game.board.low..game.board.high): return @[] + var res = newSeq[int]() + var dest: int + var target: int + for move in Knight_Moves: + dest = field + move + if (not dest in game.board.low..game.board.high): + continue + target = game.board[dest] + if (target == 999 or (ord(color) * target > 0 and ord(color) * target != WEnPassant)): + continue + res.add(dest) + return res proc genPawnAttackDests(game: Game, field: int, color: Color): seq[int] = ## Generate possible attack destinations for a pawn with specific `color` located at index `field` of `game`. ## Returns a sequence of possible indices to move to. - try: - var res = newSeq[int]() - var dest: int - var target: int - for attacks in Pawn_Moves_White_Attack: - dest = field + (attacks * ord(color)) - target = game.board[dest] - if (target == 999 or ord(color) * target >= 0): - continue - res.add(dest) - return res - except IndexDefect: + if (not field in game.board.low..game.board.high): return @[] + var res = newSeq[int]() + var dest: int + var target: int + for attacks in Pawn_Moves_White_Attack: + dest = field + (attacks * ord(color)) + if (not dest in game.board.low..game.board.high): + continue + target = game.board[dest] + if (target == 999 or ord(color) * target >= 0): + continue + res.add(dest) + return res proc genPawnDoubleDests(game: Game, field: int, color: Color): seq[int] = ## Generate possible double destinations for a pawn with specific `color` located at index `field` of `game`. ## Returns a sequence of possible indices to move to. - try: - var res = newSeq[int]() - var dest: int - var target: int - for doubles in Pawn_Moves_White_Double: - dest = field + doubles * ord(color) - target = game.board[dest] - if (game.moved[field] or (target != 0) or ( - game.board[dest+(S*ord(color))] != 0)): - continue - res.add(dest) - return res - except IndexDefect: + if (not field in game.board.low..game.board.high): return @[] + var res = newSeq[int]() + var dest: int + var target: int + for doubles in Pawn_Moves_White_Double: + dest = field + doubles * ord(color) + if (not dest in game.board.low..game.board.high): + continue + target = game.board[dest] + if (game.moved[field] or (target != 0) or ( + game.board[dest+(S*ord(color))] != 0)): + continue + res.add(dest) + return res proc genPawnDests(game: Game, field: int, color: Color): seq[int] = ## Generate possible destinations for a pawn with specific `color` located at index `field` of `game`. ## Returns a sequence of possible indices to move to. - try: - var res = newSeq[int]() - var dest: int - var target: int - for move in Pawn_Moves_White: - dest = field + move * ord(color) - target = game.board[dest] - if (target != 0 and target != ord(color) * WEnPassant): - continue - res.add(dest) - res.add(game.genPawnAttackDests(field, color)) - res.add(game.genPawnDoubleDests(field, color)) - return res - except IndexDefect: + if (not field in game.board.low..game.board.high): return @[] + var res = newSeq[int]() + var dest: int + var target: int + for move in Pawn_Moves_White: + dest = field + move * ord(color) + if (not dest in game.board.low..game.board.high): + continue + target = game.board[dest] + if (target != 0 and target != ord(color) * WEnPassant): + continue + res.add(dest) + res.add(game.genPawnAttackDests(field, color)) + res.add(game.genPawnDoubleDests(field, color)) + return res proc pieceOn(game: Game, color: Color, sequence: seq[int], pieceID: int): bool = @@ -560,17 +548,12 @@ proc uncheckedMove(game: var Game, start: int, dest: int): bool {.discardable.} ## Moves a piece if possible from `start` position to `dest` position. ## Doesnt check boundaries, checks, movement. ## returns true if the piece moved, else false - try: - let piece = game.board[start] - if game.board.setField(start, 0): - if game.board.setField(dest, piece): - game.moved.setField(start, true) - game.moved.setField(dest, true) - return true - else: - game.board.setField(start, piece) - except IndexDefect, ValueError: - return false + let piece = game.board[start] + game.board[start] = 0 + game.board[dest] = piece + game.moved[start] = true + game.moved[dest] = true + return true proc moveLeadsToCheck(game: Game, start: int, dest: int, color: Color): bool = @@ -583,7 +566,7 @@ proc removeEnPassant(board: var Board, color: Color): void = ## Removes every en passant of given `color` from the `game`. for field in board.low..board.high: if board[field] == ord(color) * WEnPassant: - board.setField(field, 0) + board[field] = 0 proc genLegalKnightMoves(game: Game, field: int, color: Color): seq[Move] = ## Generates all legal knight moves starting from `field` in a `game` for a `color`. @@ -703,39 +686,36 @@ proc castling(game: var Game, kstart: int, dest_kingside: bool, ## Tries to castle in a given `game` with the king of a given `color` from `start`. ## `dest_kingside` for kingside castling, else castling is queenside. ## This process checks for the legality of the move and performs the switch of `game.to_move` - try: - if game.toMove != color: - return false - var kdest = kstart - var rstart: int - var rdest: int + if game.toMove != color: + return false + var kdest = kstart + var rstart: int + var rdest: int + if (dest_kingside): + kdest = kstart + (E+E) + rstart = kstart + (E+E+E) + rdest = rstart + (W+W) + else: + rstart = kstart + (W+W+W+W) + rdest = rstart + (E+E+E) + kdest = kstart + (W+W) + if not game.moved[kstart] and not game.moved[rstart]: + var check = false if (dest_kingside): - kdest = kstart + (E+E) - rstart = kstart + (E+E+E) - rdest = rstart + (W+W) + check = check or game.isAttacked(kstart, color) + check = check or game.isAttacked(kstart+(E), color) + check = check or game.isAttacked(kstart+(E+E), color) else: - rstart = kstart + (W+W+W+W) - rdest = rstart + (E+E+E) - kdest = kstart + (W+W) - if not game.moved[kstart] and not game.moved[rstart]: - var check = false - if (dest_kingside): - check = check or game.isAttacked(kstart, color) - check = check or game.isAttacked(kstart+(E), color) - check = check or game.isAttacked(kstart+(E+E), color) - else: - check = check or game.isAttacked(kstart, color) - check = check or game.isAttacked(kstart+(W), color) - check = check or game.isAttacked(kstart+(W+W), color) - if check: - return false - game.uncheckedMove(kstart, kdest) - game.uncheckedMove(rstart, rdest) - game.toMove = Color(ord(game.toMove)*(-1)) - return true - return false - except IndexDefect, ValueError: - return false + check = check or game.isAttacked(kstart, color) + check = check or game.isAttacked(kstart+(W), color) + check = check or game.isAttacked(kstart+(W+W), color) + if check: + return false + game.uncheckedMove(kstart, kdest) + game.uncheckedMove(rstart, rdest) + game.toMove = Color(ord(game.toMove)*(-1)) + return true + return false proc getPrevBoard*(game: Game): seq[Board] = return game.previousBoard @@ -743,53 +723,50 @@ proc getPrevBoard*(game: Game): seq[Board] = proc checkedMove*(game: var Game, move: Move): bool {.discardable.} = ## Tries to make a move in a given `game` with the piece of a given `color` from `start` to `dest`. ## This process checks for the legality of the move and performs the switch of `game.to_move` - try: - let start = move.start - let dest = move.dest - let color = move.color - let prom = move.prom - if game.toMove != color: - return false - var sequence = newSeq[Move]() - let piece = game.board[start] - var createEnPassant = false - var capturedEnPassant = false - var fiftyMoveRuleReset = false - var move: Move - move = getMove(start, dest, color) - if (piece == WPawn * ord(color)): - createEnPassant = dest in game.genPawnDoubleDests(start, color) - capturedEnPassant = (game.board[dest] == -1 * ord(color) * WEnPassant) - fiftyMoveRuleReset = true - if (game.board[move.dest] != 0): - fiftyMoveRuleReset = true - sequence.add(game.genLegalMoves(start, color)) - if (move in sequence): - game.board.removeEnPassant(color) - if (piece == WKing * ord(color) and (start - dest == (W+W))): - return game.castling(start, true, color) - elif (piece == WKing * ord(color) and (start - dest == (E+E))): - return game.castling(start, false, color) - else: - game.uncheckedMove(start, dest) - game.toMove = Color(ord(game.toMove)*(-1)) - if createEnPassant: - game.board.setField(dest-(N*ord(color)), WEnPassant * ord(color)) - if capturedEnPassant: - game.board.setField(dest-(N*ord(color)), 0) - if ((90 < dest and dest < 99) or (20 < dest and dest < 29)) and - game.board[dest] == WPawn * ord(color): - game.board.setField(dest, prom) - var prevBoard = game.previousBoard - var prevCastle = game.previousCastleRights - game.previousBoard.add(game.board) - game.previousCastleRights.add(game.moved.genCastleRights()) - game.fiftyMoveCounter = game.fiftyMoveCounter + 1 - if fiftyMoveRuleReset: - game.fiftyMoveCounter = 0 - return true - except IndexDefect, ValueError: + let start = move.start + let dest = move.dest + let color = move.color + let prom = move.prom + if (game.toMove != color or start == -1 or dest == -1): return false + var sequence = newSeq[Move]() + let piece = game.board[start] + var createEnPassant = false + var capturedEnPassant = false + var fiftyMoveRuleReset = false + var move: Move + move = getMove(start, dest, color) + if (piece == WPawn * ord(color)): + createEnPassant = dest in game.genPawnDoubleDests(start, color) + capturedEnPassant = (game.board[dest] == -1 * ord(color) * WEnPassant) + fiftyMoveRuleReset = true + if (game.board[move.dest] != 0): + fiftyMoveRuleReset = true + sequence.add(game.genLegalMoves(start, color)) + if (move in sequence): + game.board.removeEnPassant(color) + if (piece == WKing * ord(color) and (start - dest == (W+W))): + return game.castling(start, true, color) + elif (piece == WKing * ord(color) and (start - dest == (E+E))): + return game.castling(start, false, color) + else: + game.uncheckedMove(start, dest) + game.toMove = Color(ord(game.toMove)*(-1)) + if createEnPassant: + game.board[dest-(N*ord(color))] = WEnPassant * ord(color) + if capturedEnPassant: + game.board[dest-(N*ord(color))] = 0 + if ((90 < dest and dest < 99) or (20 < dest and dest < 29)) and + game.board[dest] == WPawn * ord(color): + game.board[dest] = prom + var prevBoard = game.previousBoard + var prevCastle = game.previousCastleRights + game.previousBoard.add(game.board) + game.previousCastleRights.add(game.moved.genCastleRights()) + game.fiftyMoveCounter = game.fiftyMoveCounter + 1 + if fiftyMoveRuleReset: + game.fiftyMoveCounter = 0 + return true proc hasNoMoves(game: Game, color: Color): bool = ## Checks if a player of a given `color` has no legal moves in a `game`. diff --git a/test.nim b/test.nim index 9d48e29..543afdc 100644 --- a/test.nim +++ b/test.nim @@ -1588,5 +1588,22 @@ testSuite GameTest of TestSuite: else: self.check(not test) + method testcheckedMoveFaultyInput() = + var test: bool + self.setup() + let startPos = self.game + test = self.game.checkedMove(notationToMove("aaaa", Color.White)) + self.check(not test) + test = (self.game == startPos) + self.check(test) + test = self.game.checkedMove(notationToMove("1bb6&111", Color.White)) + self.check(not test) + test = (self.game == startPos) + self.check(test) + test = self.game.checkedMove(notationToMove("e1g1sdfa", Color.White)) + self.check(not test) + test = (self.game == startPos) + self.check(test) + when isMainModule: einheit.runTests()