mirror of
				https://github.com/tiyn/yeschess.git
				synced 2025-10-31 18:51:16 +01:00 
			
		
		
		
	chess/game: claimable draw at 3-fold repitition added
This commit is contained in:
		| @@ -8,7 +8,6 @@ A chess engine is planned. | ||||
| ## Todo | ||||
|  | ||||
| - draw by | ||||
|   - 3-fold repitition | ||||
|   - 50-move rule | ||||
|  | ||||
| ## Usage | ||||
|   | ||||
							
								
								
									
										49
									
								
								chess.nim
									
									
									
									
									
								
							
							
						
						
									
										49
									
								
								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 | ||||
|   | ||||
							
								
								
									
										7
									
								
								game.nim
									
									
									
									
									
								
							
							
						
						
									
										7
									
								
								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): | ||||
|   | ||||
							
								
								
									
										25
									
								
								test.nim
									
									
									
									
									
								
							
							
						
						
									
										25
									
								
								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() | ||||
|   | ||||
		Reference in New Issue
	
	Block a user