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
master
TiynGER 4 years ago
parent c4f7e3b98d
commit c5cf2fd737

@ -22,3 +22,8 @@ You can simply run the tests with `nim c -r test.nim`.
Documentation is written into the code via DocGen. Documentation is written into the code via DocGen.
For this reason it is not saved in this repository. 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` 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.

@ -1,6 +1,5 @@
import tables import tables
from strutils import parseInt from strutils import parseInt
import algorithm
type type
Color* = enum Color* = enum
@ -134,22 +133,6 @@ let
"h": 0 "h": 0
}.newTable }.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 = proc checkInsufficientMaterial(board: Board): bool =
## Checks for combinations of pieces on a `board`, where no checkmate can be forced ## Checks for combinations of pieces on a `board`, where no checkmate can be forced
var wp = 0 var wp = 0
@ -252,7 +235,7 @@ proc initGame*(board: array[0..63, int], color: Color): Game =
var same_piece: bool var same_piece: bool
for ind in board.low..board.high: for ind in board.low..board.high:
same_piece = (board[ind] != compare[ind]) same_piece = (board[ind] != compare[ind])
moved.setField(ind, same_piece) moved[ind] = same_piece
let game = Game(board: board, moved: moved, let game = Game(board: board, moved: moved,
to_move: color, previousBoard: @[], previousCastleRights: @[], to_move: color, previousBoard: @[], previousCastleRights: @[],
fiftyMoveCounter: 0) fiftyMoveCounter: 0)
@ -324,204 +307,209 @@ proc genCastleRights(moved: Moved): CastleRights =
proc notationToMove*(notation: string, color: Color): Move = proc notationToMove*(notation: string, color: Color): Move =
## Convert simplified algebraic chess `notation` to a move object, color of player is `color`. ## Convert simplified algebraic chess `notation` to a move object, color of player is `color`.
try: var move: Move
var move: Move var start = fieldToInd(notation[0..1])
var start = fieldToInd(notation[0..1]) var dest = fieldToInd(notation[2..3])
var dest = fieldToInd(notation[2..3]) move = getMove(start, dest, color)
move = getMove(start, dest, color) if (len(notation) > 4):
if (len(notation) > 4): var promStr = $notation[4]
var promStr = $notation[4] var prom: int
var prom: int case promStr:
case promStr: of "Q":
of "Q": prom = WQueen * ord(color)
prom = WQueen * ord(color) of "R":
of "R": prom = WRook * ord(color)
prom = WRook * ord(color) of "B":
of "B": prom = WBishop * ord(color)
prom = WBishop * ord(color) of "N":
of "N": prom = WKnight * ord(color)
prom = WKnight * ord(color) move = getMove(start, dest, prom, color)
move = getMove(start, dest, prom, color) return move
return move
except IndexError:
var move: Move
return move
proc genBishopDests(game: Game, field: int, color: Color): seq[int] = 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`. ## Generate possible destinations for a bishop with specific `color` located at index `field` of `game`.
## Returns a sequence of possible indices to move to. ## Returns a sequence of possible indices to move to.
try: if (not field in game.board.low..game.board.high):
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:
return @[] 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] = 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`. ## Generate possible destinations for a rook with specific `color` located at index `field` of `game`.
## Returns a sequence of possible indices to move to. ## Returns a sequence of possible indices to move to.
try: if (not field in game.board.low..game.board.high):
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:
return @[] 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] = 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`. ## Generate possible destinations for a queen with specific `color` located at index `field` of `game`.
## Returns a sequence of possible indices to move to. ## Returns a sequence of possible indices to move to.
try: if (not field in game.board.low..game.board.high):
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:
return @[] 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] = 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` ## 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. ## Returns a sequence of possible indices to move to.
try: if (not field in game.board.low..game.board.high):
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:
return @[] 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] = 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`. ## Generate possible destinations for a king with specific `color` located at index `field` of `game`.
## Returns a sequence of possible indices to move to. ## Returns a sequence of possible indices to move to.
try: if (not field in game.board.low..game.board.high):
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:
return @[] 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] = 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`. ## Generate possible destinations for a knight with specific `color` located at index `field` of `game`.
## Returns a sequence of possible indices to move to. ## Returns a sequence of possible indices to move to.
try: if (not field in game.board.low..game.board.high):
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:
return @[] 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] = 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`. ## 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. ## Returns a sequence of possible indices to move to.
try: if (not field in game.board.low..game.board.high):
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:
return @[] 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] = 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`. ## 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. ## Returns a sequence of possible indices to move to.
try: if (not field in game.board.low..game.board.high):
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:
return @[] 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] = 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`. ## Generate possible destinations for a pawn with specific `color` located at index `field` of `game`.
## Returns a sequence of possible indices to move to. ## Returns a sequence of possible indices to move to.
try: if (not field in game.board.low..game.board.high):
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:
return @[] 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], proc pieceOn(game: Game, color: Color, sequence: seq[int],
pieceID: int): bool = 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. ## Moves a piece if possible from `start` position to `dest` position.
## Doesnt check boundaries, checks, movement. ## Doesnt check boundaries, checks, movement.
## returns true if the piece moved, else false ## returns true if the piece moved, else false
try: let piece = game.board[start]
let piece = game.board[start] game.board[start] = 0
if game.board.setField(start, 0): game.board[dest] = piece
if game.board.setField(dest, piece): game.moved[start] = true
game.moved.setField(start, true) game.moved[dest] = true
game.moved.setField(dest, true) return true
return true
else:
game.board.setField(start, piece)
except IndexDefect, ValueError:
return false
proc moveLeadsToCheck(game: Game, start: int, dest: int, proc moveLeadsToCheck(game: Game, start: int, dest: int,
color: Color): bool = 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`. ## Removes every en passant of given `color` from the `game`.
for field in board.low..board.high: for field in board.low..board.high:
if board[field] == ord(color) * WEnPassant: if board[field] == ord(color) * WEnPassant:
board.setField(field, 0) board[field] = 0
proc genLegalKnightMoves(game: Game, field: int, color: Color): seq[Move] = proc genLegalKnightMoves(game: Game, field: int, color: Color): seq[Move] =
## Generates all legal knight moves starting from `field` in a `game` for a `color`. ## 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`. ## 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. ## `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` ## This process checks for the legality of the move and performs the switch of `game.to_move`
try: if game.toMove != color:
if game.toMove != color: return false
return false var kdest = kstart
var kdest = kstart var rstart: int
var rstart: int var rdest: 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): if (dest_kingside):
kdest = kstart + (E+E) check = check or game.isAttacked(kstart, color)
rstart = kstart + (E+E+E) check = check or game.isAttacked(kstart+(E), color)
rdest = rstart + (W+W) check = check or game.isAttacked(kstart+(E+E), color)
else: else:
rstart = kstart + (W+W+W+W) check = check or game.isAttacked(kstart, color)
rdest = rstart + (E+E+E) check = check or game.isAttacked(kstart+(W), color)
kdest = kstart + (W+W) check = check or game.isAttacked(kstart+(W+W), color)
if not game.moved[kstart] and not game.moved[rstart]: if check:
var check = false return false
if (dest_kingside): game.uncheckedMove(kstart, kdest)
check = check or game.isAttacked(kstart, color) game.uncheckedMove(rstart, rdest)
check = check or game.isAttacked(kstart+(E), color) game.toMove = Color(ord(game.toMove)*(-1))
check = check or game.isAttacked(kstart+(E+E), color) return true
else: 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
except IndexDefect, ValueError:
return false
proc getPrevBoard*(game: Game): seq[Board] = proc getPrevBoard*(game: Game): seq[Board] =
return game.previousBoard return game.previousBoard
@ -743,53 +723,50 @@ proc getPrevBoard*(game: Game): seq[Board] =
proc checkedMove*(game: var Game, move: Move): bool {.discardable.} = 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`. ## 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` ## This process checks for the legality of the move and performs the switch of `game.to_move`
try: let start = move.start
let start = move.start let dest = move.dest
let dest = move.dest let color = move.color
let color = move.color let prom = move.prom
let prom = move.prom if (game.toMove != color or start == -1 or dest == -1):
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:
return false 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 = proc hasNoMoves(game: Game, color: Color): bool =
## Checks if a player of a given `color` has no legal moves in a `game`. ## Checks if a player of a given `color` has no legal moves in a `game`.

@ -1588,5 +1588,22 @@ testSuite GameTest of TestSuite:
else: else:
self.check(not test) 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: when isMainModule:
einheit.runTests() einheit.runTests()

Loading…
Cancel
Save