diff --git a/README.md b/README.md index bd2d8c8..f8ef19f 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,6 @@ A chess engine is planned. ## Todo - draw by - - 3-fold repitition - 50-move rule ## Usage diff --git a/chess.nim b/chess.nim index 0468598..2ce5cfe 100644 --- a/chess.nim +++ b/chess.nim @@ -1,5 +1,6 @@ import tables from strutils import parseInt +import algorithm type Color* = enum @@ -8,18 +9,26 @@ type Board* = array[0..119, int] ## Board that checks if pieces moved Moved* = array[0..119, bool] + ## Castle rights for each player + CastleRights = tuple + wk: bool + wq: bool + bk: bool + bq: bool ## Game as object of different values Game* = object board*: Board moved: Moved toMove*: Color + previousBoard*: seq[Board] + previousCastleRights*: seq[CastleRights] ## Move as object Move* = object start: int dest: int color: Color prom: int - ## Amount of pieces + ## Amount of pieces of a player Pieces = tuple p: int k: int @@ -222,7 +231,7 @@ proc initMoved(): Moved = proc initGame*(): Game = ## Create and return a Game object. let game = Game(board: initBoard(), moved: initMoved(), - to_move: Color.White) + to_move: Color.White, previousBoard: @[], previousCastleRights: @[]) return game proc initGame*(board: array[0..63, int], color: Color): Game = @@ -235,7 +244,7 @@ proc initGame*(board: array[0..63, int], color: Color): Game = same_piece = (board[ind] != compare[ind]) moved.setField(ind, same_piece) let game = Game(board: board, moved: moved, - to_move: color) + to_move: color, previousBoard: @[], previousCastleRights: @[]) return game proc getMove*(start: int, dest: int, prom: int, color: Color): Move = @@ -294,6 +303,18 @@ proc indToField*(ind: int): string = if FileChar[file] == file_ind: return $file & $line +proc genCastleRights(moved: Moved): CastleRights = + ## Generate rights to castle from given `moved` + let wk = not moved.getField(fieldToInd("e1")) and not moved.getField( + fieldToInd("h1")) + let wq = not moved.getField(fieldToInd("e1")) and not moved.getField( + fieldToInd("a1")) + let bk = not moved.getField(fieldToInd("e8")) and not moved.getField( + fieldToInd("h8")) + let bq = not moved.getField(fieldToInd("e8")) and not moved.getField( + fieldToInd("a8")) + return (wk, wq, bk, bq) + proc notationToMove*(notation: string, color: Color): Move = ## Convert simplified algebraic chess `notation` to a move object, color of player is `color`. try: @@ -709,6 +730,9 @@ proc castling(game: var Game, kstart: int, dest_kingside: bool, except IndexDefect, ValueError: return false +proc getPrevBoard*(game: Game): seq[Board] = + return game.previousBoard + 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` @@ -745,6 +769,10 @@ proc checkedMove*(game: var Game, move: Move): bool {.discardable.} = if ((90 < dest and dest < 99) or (20 < dest and dest < 29)) and game.board.getField(dest) == PawnID * 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()) return true except IndexDefect, ValueError: return false @@ -757,6 +785,21 @@ proc isCheckmate*(game: Game, color: Color): bool = ## Checks if a player of a given `color` in a `game` is checkmate. return game.hasNoMoves(color) and game.isInCheck(color) +proc threeMoveRep(game: Game): bool = + ## Checks if a `rep`-times repitition happened on the last move of the `game`. + var lastState = game.previousBoard[game.previousBoard.high] + var lastCastleRights = game.previousCastleRights[game.previousBoard.high] + var reps = 0 + for stateInd in (game.previousBoard.low)..(game.previousBoard.high): + if (game.previousBoard[stateInd] == lastState and game.previousCastleRights[ + stateInd] == lastCastleRights): + reps = reps + 1 + return reps >= 3 + +proc isDrawClaimable*(game: Game): bool = + ## Checks if a draw is claimable by either player. + return game.threeMoveRep() + proc isStalemate*(game: Game, color: Color): bool = ## Checks if a player of a given `color` in a `game` is stalemate. return (game.hasNoMoves(color) and not game.isInCheck(color)) or diff --git a/game.nim b/game.nim index 2e1656f..7c1add4 100644 --- a/game.nim +++ b/game.nim @@ -5,6 +5,7 @@ import ./chess proc runGame(): void = var game = initGame() + var draw: string game.echoBoard(game.toMove) while not game.isCheckmate(game.toMove) and not game.isStalemate(game.toMove): echo "Make a move" @@ -13,6 +14,12 @@ proc runGame(): void = while not game.checkedMove(notationToMove(move, game.toMove)): move = readLine(stdin) game.echoBoard(game.toMove) + if (game.isDrawClaimable): + echo "Do you want to claim a draw? (y/N)" + draw = readLine(stdin) + if (draw == "y"): + echo "Draw claimed" + break if game.isCheckmate(game.toMove): echo $game.toMove & " was checkmated" if game.isStalemate(game.toMove): diff --git a/test.nim b/test.nim index f592a67..6c29cd5 100644 --- a/test.nim +++ b/test.nim @@ -268,6 +268,31 @@ testSuite GameTest of TestSuite: self.check(not self.game.isStalemate(Color.Black)) self.check(not self.game.isStalemate(Color.White)) + method testIsDrawClaimableThreeFoldRepTrue() = + self.setup() + self.game.checkedMove(notationToMove("g1f3", Color.White)) + self.game.checkedMove(notationToMove("g8f6", Color.Black)) + self.game.checkedMove(notationToMove("f3g1", Color.White)) + self.game.checkedMove(notationToMove("f6g8", Color.Black)) + self.game.checkedMove(notationToMove("g1f3", Color.White)) + self.game.checkedMove(notationToMove("g8f6", Color.Black)) + self.game.checkedMove(notationToMove("f3g1", Color.White)) + self.game.checkedMove(notationToMove("f6g8", Color.Black)) + self.game.checkedMove(notationToMove("g1f3", Color.White)) + self.check(self.game.isDrawClaimable()) + + method testIsDrawClaimableThreeFoldRepFalse() = + self.setup() + self.game.checkedMove(notationToMove("g1f3", Color.White)) + self.game.checkedMove(notationToMove("g8f6", Color.Black)) + self.game.checkedMove(notationToMove("f3g1", Color.White)) + self.game.checkedMove(notationToMove("f6g8", Color.Black)) + self.game.checkedMove(notationToMove("g1f3", Color.White)) + self.game.checkedMove(notationToMove("g8f6", Color.Black)) + self.game.checkedMove(notationToMove("f3g1", Color.White)) + self.game.checkedMove(notationToMove("f6g8", Color.Black)) + self.check(not self.game.isDrawClaimable()) + ## Tests for Pawn moves method testCheckedMovePawnSingleTrue() = self.setup()