mirror of
https://github.com/tiyn/yeschess.git
synced 2025-04-01 14:47:44 +02:00
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.
This commit is contained in:
parent
d5ac25a642
commit
a3c48fd50c
136
src/chess.nim
136
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
|
||||
|
@ -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()
|
||||
|
Loading…
x
Reference in New Issue
Block a user