Fix bugs with pawn movegen and add promotions
This commit is contained in:
parent
e50cfb9d64
commit
86265c68f0
|
@ -46,21 +46,21 @@ func `!=`*(a: Bitboard, b: SomeInteger): bool {.inline.} = a.uint64 != b.uint64
|
|||
|
||||
|
||||
func getFileMask*(file: int): Bitboard = Bitboard(0x101010101010101'u64) shl file.uint64
|
||||
func getRankMask*(rank: int): Bitboard = Bitboard(uint64.high()) shl uint64(8 * (rank + 1))
|
||||
func squareToBitboard*(square: SomeInteger): Bitboard = Bitboard(1'u64) shl square.uint64
|
||||
func squareToBitboard*(square: Square): Bitboard = squareToBitboard(square.int8)
|
||||
func getRankMask*(rank: int): Bitboard = Bitboard(0xff) shl uint64(8 * rank)
|
||||
func toBitboard*(square: SomeInteger): Bitboard = Bitboard(1'u64) shl square.uint64
|
||||
func toBitboard*(square: Square): Bitboard = toBitboard(square.int8)
|
||||
|
||||
proc bitboardToSquare*(b: Bitboard): Square = Square(b.uint64.countTrailingZeroBits())
|
||||
proc toSquare*(b: Bitboard): Square = Square(b.uint64.countTrailingZeroBits())
|
||||
func createMove*(startSquare: Bitboard, targetSquare: Square, flags: varargs[MoveFlag]): Move =
|
||||
result = createMove(startSquare.bitboardToSquare(), targetSquare, flags)
|
||||
result = createMove(startSquare.toSquare(), targetSquare, flags)
|
||||
|
||||
|
||||
func createMove*(startSquare: Square, targetSquare: Bitboard, flags: varargs[MoveFlag]): Move =
|
||||
result = createMove(startSquare, targetSquare.bitboardToSquare(), flags)
|
||||
result = createMove(startSquare, targetSquare.toSquare(), flags)
|
||||
|
||||
|
||||
func createMove*(startSquare, targetSquare: Bitboard, flags: varargs[MoveFlag]): Move =
|
||||
result = createMove(startSquare.bitboardToSquare(), targetSquare.bitboardToSquare(), flags)
|
||||
result = createMove(startSquare.toSquare(), targetSquare.toSquare(), flags)
|
||||
|
||||
|
||||
func toBin*(x: Bitboard, b: Positive = 64): string = toBin(BiggestInt(x), b)
|
||||
|
@ -73,7 +73,7 @@ iterator items*(self: Bitboard): Square =
|
|||
## are set
|
||||
var bits = self
|
||||
while bits != 0:
|
||||
yield bits.bitboardToSquare()
|
||||
yield bits.toSquare()
|
||||
bits = bits and bits - 1
|
||||
|
||||
|
||||
|
@ -165,10 +165,10 @@ func getDirectionMask*(bitboard: Bitboard, color: PieceColor, direction: Directi
|
|||
return bitboard shl 8
|
||||
of Backward:
|
||||
return bitboard shr 8
|
||||
of ForwardLeft:
|
||||
return bitboard shl 9
|
||||
of ForwardRight:
|
||||
return bitboard shl 7
|
||||
of ForwardLeft:
|
||||
return bitboard shr 9
|
||||
of BackwardRight:
|
||||
return bitboard shr 9
|
||||
of BackwardLeft:
|
||||
|
@ -179,34 +179,35 @@ func getDirectionMask*(bitboard: Bitboard, color: PieceColor, direction: Directi
|
|||
discard
|
||||
|
||||
|
||||
func getLastRank*(color: PieceColor): Bitboard = (if color == White: getRankMask(0) else: getRankMask(7))
|
||||
|
||||
func getDirectionMask*(square: Square, color: PieceColor, direction: Direction): Bitboard =
|
||||
## Get a bitmask for the given direction for a piece
|
||||
## of the given color located at the given square
|
||||
result = getDirectionMask(squareToBitboard(square), color, direction)
|
||||
result = getDirectionMask(toBitboard(square), color, direction)
|
||||
|
||||
|
||||
func forwardRelativeTo*(self: Bitboard, side: PieceColor): Bitboard = getDirectionMask(self, side, Forward)
|
||||
func doubleForwardRelativeTo*(self: Bitboard, side: PieceColor): Bitboard = self.forwardRelativeTo(side).forwardRelativeTo(side)
|
||||
|
||||
func backwardRelativeTo*(self: Bitboard, side: PieceColor): Bitboard = getDirectionMask(self, side, Backward)
|
||||
func doubleBackwardRelativeTo*(self: Bitboard, side: PieceColor): Bitboard = self.backwardRelativeTo(side).backwardRelativeTo(side)
|
||||
|
||||
|
||||
# We mask off the first and last ranks/files for
|
||||
# We mask off the first and last ranks for
|
||||
# left and right movements respectively to
|
||||
# avoid weird wraparounds
|
||||
func topRightRelativeTo*(self: Bitboard, side: PieceColor): Bitboard =
|
||||
func forwardRightRelativeTo*(self: Bitboard, side: PieceColor): Bitboard =
|
||||
getDirectionMask(self, side, ForwardRight) and not getFileMask(7)
|
||||
|
||||
|
||||
func topLeftRelativeTo*(self: Bitboard, side: PieceColor): Bitboard =
|
||||
func forwardLeftRelativeTo*(self: Bitboard, side: PieceColor): Bitboard =
|
||||
getDirectionMask(self, side, ForwardLeft) and not getFileMask(0)
|
||||
|
||||
|
||||
func bottomRightRelativeTo*(self: Bitboard, side: PieceColor): Bitboard =
|
||||
let lastRank = if side == White: getRankMask(0) else: getRankMask(7)
|
||||
getDirectionMask(self, side, BackwardRight) and not lastRank
|
||||
getDirectionMask(self, side, BackwardRight) and not getFileMask(7)
|
||||
|
||||
|
||||
func bottomLeftRelativeTo*(self: Bitboard, side: PieceColor): Bitboard =
|
||||
let lastRank = if side == White: getRankMask(0) else: getRankMask(7)
|
||||
getDirectionMask(self, side, BackwardLeft) and not getRankMask(7)
|
||||
getDirectionMask(self, side, BackwardLeft) and not getFileMask(0)
|
|
@ -85,9 +85,8 @@ proc updateBoard*(self: ChessBoard)
|
|||
|
||||
|
||||
# A bunch of getters
|
||||
func getActiveColor*(self: ChessBoard): PieceColor {.inline.} =
|
||||
## Returns the currently active color
|
||||
## (turn of who has to move)
|
||||
func getSideToMove*(self: ChessBoard): PieceColor {.inline.} =
|
||||
## Returns the currently side to move
|
||||
return self.position.turn
|
||||
|
||||
|
||||
|
@ -141,17 +140,6 @@ func getKingStartingSquare(color: PieceColor): Square {.inline.} =
|
|||
discard
|
||||
|
||||
|
||||
func getLastRank(color: PieceColor): int {.inline.} =
|
||||
## Retrieves the square of the last
|
||||
## rank relative to the given color
|
||||
case color:
|
||||
of White:
|
||||
return 0
|
||||
of Black:
|
||||
return 7
|
||||
else:
|
||||
return -1
|
||||
|
||||
func kingSideRook(color: PieceColor): Square {.inline.} = (if color == White: makeSquare(7, 7) else: makeSquare(0, 7))
|
||||
func queenSideRook(color: PieceColor): Square {.inline.} = (if color == White: makeSquare(7, 0) else: makeSquare(0, 0))
|
||||
func longCastleKing(color: PieceColor): Square {.inline.} = (if color == White: makeSquare(7, 2) else: makeSquare(0, 5))
|
||||
|
@ -369,7 +357,7 @@ proc newChessboardFromFEN*(fen: string): ChessBoard =
|
|||
raise newException(ValueError, "invalid FEN: too many fields in FEN string")
|
||||
inc(index)
|
||||
# result.updateAttackedSquares()
|
||||
#[if result.inCheck(result.getActiveColor().opposite):
|
||||
#[if result.inCheck(result.getSideToMove().opposite):
|
||||
# Opponent king cannot be captured on the next move
|
||||
raise newException(ValueError, "invalid position: opponent king can be captured")]#
|
||||
if result.position.pieces.white.king == Bitboard(0) or result.position.pieces.black.king == Bitboard(0):
|
||||
|
@ -509,7 +497,7 @@ func getKing(self: ChessBoard, color: PieceColor = None): Square {.inline.} =
|
|||
## color (if it is None, the active color is used)
|
||||
var color = color
|
||||
if color == None:
|
||||
color = self.getActiveColor()
|
||||
color = self.getSideToMove()
|
||||
case color:
|
||||
of White:
|
||||
return Square(self.position.pieces.white.king.uint64.countTrailingZeroBits())
|
||||
|
@ -541,54 +529,61 @@ proc getOccupancy(self: ChessBoard): Bitboard =
|
|||
|
||||
proc generatePawnMovements(self: ChessBoard, moves: var MoveList) =
|
||||
## Helper of generatePawnMoves for generating all non-capture
|
||||
## pawn movems
|
||||
## and non-promotion pawn moves
|
||||
let
|
||||
sideToMove = self.getActiveColor()
|
||||
sideToMove = self.getSideToMove()
|
||||
pawns = self.getBitboard(Pawn, sideToMove)
|
||||
# Can't move to these squares
|
||||
occupancy = not self.getOccupancy()
|
||||
offsets = if sideToMove == White: (-8, -16) else: (8, 16)
|
||||
# Single pushes
|
||||
# We can only move to squares that are *not* occupied by another piece.
|
||||
# We also cannot move to the last rank, as that will result in a promotion
|
||||
# and is handled elsewhere
|
||||
occupancy = not (self.getOccupancy() or sideToMove.getLastRank())
|
||||
# Single push
|
||||
for square in pawns.forwardRelativeTo(sideToMove) and occupancy:
|
||||
moves.add(createMove((square - offsets[0]), square))
|
||||
# Double pushes
|
||||
for square in pawns.doubleForwardRelativeTo(sideToMove) and occupancy:
|
||||
moves.add(createMove((square - offsets[1]), square, DoublePush))
|
||||
moves.add(createMove(square.toBitboard().backwardRelativeTo(sideToMove), square))
|
||||
# Double push
|
||||
let rank = if sideToMove == White: getRankMask(6) else: getRankMask(1) # Only pawns on their starting rank can double push
|
||||
for square in (pawns and rank).doubleForwardRelativeTo(sideToMove) and occupancy:
|
||||
moves.add(createMove(square.toBitboard().doubleBackwardRelativeTo(sideToMove), square, DoublePush))
|
||||
|
||||
|
||||
proc generatePawnCaptures(self: ChessBoard, moves: var MoveList) =
|
||||
## Helper of generatePawnMoves for generating all capture
|
||||
## pawn moves
|
||||
let
|
||||
sideToMove = self.getActiveColor()
|
||||
sideToMove = self.getSideToMove()
|
||||
nonSideToMove = sideToMove.opposite()
|
||||
pawns = self.getBitboard(Pawn, sideToMove)
|
||||
# We can only capture enemy pieces
|
||||
enemyPieces = self.getOccupancyFor(nonSideToMove)
|
||||
offsets = if sideToMove == White: (9, 7) else: (-7, -9)
|
||||
rightMovement = pawns.topRightRelativeTo(sideToMove)
|
||||
leftMovement = pawns.topLeftRelativeTo(sideToMove)
|
||||
rightMovement = pawns.forwardRightRelativeTo(sideToMove)
|
||||
leftMovement = pawns.forwardLeftRelativeTo(sideToMove)
|
||||
epTarget = self.getEnPassantTarget()
|
||||
let epBitboard = if (epTarget != nullSquare()): epTarget.squareToBitboard() else: Bitboard(0)
|
||||
let epBitboard = if (epTarget != nullSquare()): epTarget.toBitboard() else: Bitboard(0)
|
||||
# Top right attacks
|
||||
for square in rightMovement and enemyPieces:
|
||||
moves.add(createMove((square - offsets[0]), square, Capture))
|
||||
# Top left attacks
|
||||
moves.add(createMove(square.toBitboard().bottomLeftRelativeTo(sideToMove), square, Capture))
|
||||
for square in leftMovement and enemyPieces:
|
||||
moves.add(createMove((square - offsets[1]), square, Capture))
|
||||
moves.add(createMove(square.toBitboard().bottomRightRelativeTo(sideToMove), square, Capture))
|
||||
# Special case for en passant
|
||||
let
|
||||
epLeft = epBitboard and leftMovement
|
||||
epRight = epBitboard and rightMovement
|
||||
if epLeft != 0:
|
||||
moves.add(createMove(epBitboard.topLeftRelativeTo(nonSideToMove), epBitboard, EnPassant))
|
||||
moves.add(createMove(epBitboard.forwardLeftRelativeTo(nonSideToMove), epBitboard, EnPassant))
|
||||
elif epRight != 0:
|
||||
moves.add(createMove(epBitboard.topRightRelativeTo(nonSideToMove), epBitboard, EnPassant))
|
||||
moves.add(createMove(epBitboard.forwardRightRelativeTo(nonSideToMove), epBitboard, EnPassant))
|
||||
|
||||
|
||||
proc generatePawnPromotions(self: ChessBoard, moves: var MoveList) =
|
||||
## Helper of generatePawnMoves for generating all pawn promotion
|
||||
## moves
|
||||
let
|
||||
sideToMove = self.getSideToMove()
|
||||
pawns = self.getBitboard(Pawn, sideToMove)
|
||||
occupancy = not self.getOccupancy()
|
||||
for square in pawns.forwardRelativeTo(sideToMove) and occupancy and sideToMove.getLastRank():
|
||||
for promotion in [PromoteToBishop, PromoteToKnight, PromoteToQueen, PromoteToRook]:
|
||||
moves.add(createMove(square.toBitboard().backwardRelativeTo(sideToMove), square, promotion))
|
||||
|
||||
|
||||
proc generatePawnMoves(self: ChessBoard, moves: var MoveList) =
|
||||
|
@ -597,6 +592,7 @@ proc generatePawnMoves(self: ChessBoard, moves: var MoveList) =
|
|||
|
||||
self.generatePawnMovements(moves)
|
||||
self.generatePawnCaptures(moves)
|
||||
self.generatePawnPromotions(moves)
|
||||
|
||||
|
||||
proc generateSlidingMoves(self: ChessBoard, square: Square): seq[Move] =
|
||||
|
@ -882,7 +878,7 @@ proc doMove(self: ChessBoard, move: Move) =
|
|||
inc(fullMoveCount)
|
||||
|
||||
if move.isDoublePush():
|
||||
enPassantTarget = move.targetSquare.squareToBitboard().backwardRelativeTo(piece.color).bitboardToSquare()
|
||||
enPassantTarget = move.targetSquare.toBitboard().backwardRelativeTo(piece.color).toSquare()
|
||||
|
||||
# Castling check: have the rooks moved?
|
||||
if piece.kind == Rook:
|
||||
|
@ -944,7 +940,7 @@ proc doMove(self: ChessBoard, move: Move) =
|
|||
self.position = Position(plyFromRoot: self.position.plyFromRoot + 1,
|
||||
halfMoveClock: halfMoveClock,
|
||||
fullMoveCount: fullMoveCount,
|
||||
turn: self.getActiveColor().opposite,
|
||||
turn: self.getSideToMove().opposite,
|
||||
castlingAvailable: castlingAvailable,
|
||||
enPassantSquare: enPassantTarget,
|
||||
pieces: self.position.pieces
|
||||
|
@ -971,7 +967,7 @@ proc doMove(self: ChessBoard, move: Move) =
|
|||
|
||||
if move.isEnPassant():
|
||||
# Make the en passant pawn disappear
|
||||
self.removePiece(move.targetSquare.squareToBitboard().backwardRelativeTo(piece.color).bitboardToSquare(), attack=false)
|
||||
self.removePiece(move.targetSquare.toBitboard().backwardRelativeTo(piece.color).toSquare(), attack=false)
|
||||
|
||||
if move.isCapture():
|
||||
# Get rid of captured pieces
|
||||
|
@ -1032,8 +1028,8 @@ proc updateBoard*(self: ChessBoard) =
|
|||
self.grid[sq] = Piece(color: White, kind: Queen)
|
||||
for sq in self.position.pieces.black.queens:
|
||||
self.grid[sq] = Piece(color: Black, kind: Queen)
|
||||
self.grid[self.position.pieces.white.king.bitboardToSquare()] = Piece(color: White, kind: King)
|
||||
self.grid[self.position.pieces.black.king.bitboardToSquare()] = Piece(color: Black, kind: King)
|
||||
self.grid[self.position.pieces.white.king.toSquare()] = Piece(color: White, kind: King)
|
||||
self.grid[self.position.pieces.black.king.toSquare()] = Piece(color: Black, kind: King)
|
||||
|
||||
|
||||
proc unmakeMove*(self: ChessBoard) =
|
||||
|
@ -1180,7 +1176,7 @@ proc toFEN*(self: ChessBoard): string =
|
|||
result &= "/"
|
||||
result &= " "
|
||||
# Active color
|
||||
result &= (if self.getActiveColor() == White: "w" else: "b")
|
||||
result &= (if self.getSideToMove() == White: "w" else: "b")
|
||||
result &= " "
|
||||
# Castling availability
|
||||
let castleWhite = self.position.castlingAvailable.white
|
||||
|
@ -1245,10 +1241,10 @@ proc perft*(self: ChessBoard, ply: int, verbose: bool = false, divide: bool = fa
|
|||
|
||||
for move in moves:
|
||||
if verbose:
|
||||
let canCastle = self.canCastle(self.getActiveColor())
|
||||
let canCastle = self.canCastle(self.getSideToMove())
|
||||
echo &"Ply (from root): {self.position.plyFromRoot}"
|
||||
echo &"Move: {move.startSquare.toAlgebraic()}{move.targetSquare.toAlgebraic()}, from ({move.startSquare.rank}, {move.startSquare.file}) to ({move.targetSquare.rank}, {move.targetSquare.file})"
|
||||
echo &"Turn: {self.getActiveColor()}"
|
||||
echo &"Turn: {self.getSideToMove()}"
|
||||
echo &"Piece: {self.grid[move.startSquare].kind}"
|
||||
echo &"Flags: {move.getFlags()}"
|
||||
echo &"In check: {(if self.inCheck(): \"yes\" else: \"no\")}"
|
||||
|
@ -1274,7 +1270,7 @@ proc perft*(self: ChessBoard, ply: int, verbose: bool = false, divide: bool = fa
|
|||
# Opponent king is in check
|
||||
inc(result.checks)
|
||||
if verbose:
|
||||
let canCastle = self.canCastle(self.getActiveColor())
|
||||
let canCastle = self.canCastle(self.getSideToMove())
|
||||
echo "\n"
|
||||
echo &"Opponent in check: {(if self.inCheck(): \"yes\" else: \"no\")}"
|
||||
echo &"Opponent can castle:\n - King side: {(if canCastle.king: \"yes\" else: \"no\")}\n - Queen side: {(if canCastle.queen: \"yes\" else: \"no\")}"
|
||||
|
@ -1420,7 +1416,7 @@ proc handleMoveCommand(board: ChessBoard, command: seq[string]): Move {.discarda
|
|||
|
||||
var move = createMove(startSquare, targetSquare, flags)
|
||||
let piece = board.getPiece(move.startSquare)
|
||||
if piece.kind == King and move.startSquare == board.getActiveColor().getKingStartingSquare():
|
||||
if piece.kind == King and move.startSquare == board.getSideToMove().getKingStartingSquare():
|
||||
if move.targetSquare == longCastleKing(piece.color):
|
||||
move.flags = move.flags or CastleLong.uint16
|
||||
elif move.targetSquare == shortCastleKing(piece.color):
|
||||
|
@ -1582,7 +1578,7 @@ proc main: int =
|
|||
of "undo", "u":
|
||||
board.unmakeMove()
|
||||
of "turn":
|
||||
echo &"Active color: {board.getActiveColor()}"
|
||||
echo &"Active color: {board.getSideToMove()}"
|
||||
of "ep":
|
||||
let target = board.getEnPassantTarget()
|
||||
if target != nullSquare():
|
||||
|
@ -1600,9 +1596,9 @@ proc main: int =
|
|||
continue
|
||||
#[of "castle":
|
||||
let canCastle = board.canCastle()
|
||||
echo &"Castling rights for {($board.getActiveColor()).toLowerAscii()}:\n - King side: {(if canCastle.king: \"yes\" else: \"no\")}\n - Queen side: {(if canCastle.queen: \"yes\" else: \"no\")}"
|
||||
echo &"Castling rights for {($board.getSideToMove()).toLowerAscii()}:\n - King side: {(if canCastle.king: \"yes\" else: \"no\")}\n - Queen side: {(if canCastle.queen: \"yes\" else: \"no\")}"
|
||||
of "check":
|
||||
echo &"{board.getActiveColor()} king in check: {(if board.inCheck(): \"yes\" else: \"no\")}"
|
||||
echo &"{board.getSideToMove()} king in check: {(if board.inCheck(): \"yes\" else: \"no\")}"
|
||||
]#
|
||||
else:
|
||||
echo &"Unknown command '{cmd[0]}'. Type 'help' for more information."
|
||||
|
@ -1719,18 +1715,18 @@ when isMainModule:
|
|||
testPieceBitboard(blackQueens, blackQueenSquares)
|
||||
testPieceBitboard(blackKing, blackKingSquares)
|
||||
|
||||
b = newChessboardFromFEN("B7/P1P1P1P1/1P1P1P1P/7k/8/8/8/3K4 w - - 0 1")
|
||||
var m = MoveList()
|
||||
b.generatePawnMoves(m)
|
||||
echo &"Pawn moves for {b.getActiveColor()} at {b.toFEN()}: "
|
||||
echo &"Pawn moves for {b.getSideToMove()} at {b.toFEN()}: "
|
||||
for move in m:
|
||||
echo " - ", move.startSquare, move.targetSquare
|
||||
b.doMove(createMove("a2", "a3"))
|
||||
echo " - ", move.startSquare, move.targetSquare, " ", move.getFlags()
|
||||
#[b.doMove(createMove("d1", "c1"))
|
||||
m.clear()
|
||||
b.generatePawnMoves(m)
|
||||
echo &"Pawn moves for {b.getActiveColor()} at {b.toFEN()}: "
|
||||
echo &"Pawn moves for {b.getSideToMove()} at {b.toFEN()}: "
|
||||
for move in m:
|
||||
echo " - ", move.startSquare, move.targetSquare
|
||||
b.doMove(createMove("a7", "a5"))
|
||||
echo b.pretty()
|
||||
echo " - ", move.startSquare, move.targetSquare, " ", move.getFlags()
|
||||
echo b.pretty()]#
|
||||
# setControlCHook(proc () {.noconv.} = quit(0))
|
||||
# quit(main())
|
||||
|
|
|
@ -39,7 +39,9 @@ func `!=`*(a, b: Square): bool {.inline.} = a.int8 != b.int8
|
|||
func `-`*(a, b: Square): Square {.inline.} = Square(a.int8 - b.int8)
|
||||
func `-`*(a: Square, b: SomeInteger): Square {.inline.} = Square(a.int8 - b.int8)
|
||||
func `-`*(a: SomeInteger, b: Square): Square {.inline.} = Square(a.int8 - b.int8)
|
||||
|
||||
func `+`*(a, b: Square): Square {.inline.} = Square(a.int8 + b.int8)
|
||||
func `+`*(a: Square, b: SomeInteger): Square {.inline.} = Square(a.int8 + b.int8)
|
||||
func `+`*(a: SomeInteger, b: Square): Square {.inline.} = Square(a.int8 + b.int8)
|
||||
|
||||
func colFromSquare*(square: Square): int8 = square.int8 mod 8 + 1
|
||||
func rowFromSquare*(square: Square): int8 = square.int8 div 8 + 1
|
||||
|
|
|
@ -19,7 +19,7 @@ when isMainModule:
|
|||
while true:
|
||||
canCastle = board.canCastle()
|
||||
echo &"{board.pretty()}"
|
||||
echo &"Turn: {board.getActiveColor()}"
|
||||
echo &"Turn: {board.getSideToMove()}"
|
||||
echo &"Moves: {board.getMoveCount()} full, {board.getHalfMoveCount()} half"
|
||||
echo &"Can castle:\n - King side: {(if canCastle.king: \"yes\" else: \"no\")}\n - Queen side: {(if canCastle.queen: \"yes\" else: \"no\")}"
|
||||
stdout.write(&"En passant target: ")
|
||||
|
|
Loading…
Reference in New Issue