Implement short-circuiting, iterative SEE (bench 5697813)
This commit is contained in:
2
.gitmodules
vendored
2
.gitmodules
vendored
@@ -1,3 +1,3 @@
|
||||
[submodule "networks"]
|
||||
path = networks
|
||||
url = git@git.nocturn9x.space/heimdall-engine/networks
|
||||
url = git@git.nocturn9x.space:heimdall-engine/networks
|
||||
|
||||
@@ -87,6 +87,18 @@ func highestSquare*(self: Bitboard): Square {.inline.} =
|
||||
result = Square(self.countLeadingZeroBits().uint8 xor 0x3f)
|
||||
|
||||
|
||||
func lowestBit*(self: Bitboard): Bitboard {.inline.} =
|
||||
## Returns the least significant bit of the bitboard
|
||||
result = self and Bitboard(-cast[int64](self))
|
||||
|
||||
|
||||
func resetLSB*(self: Bitboard): Bitboard {.inline.} =
|
||||
## Resets the least significant bit of the given
|
||||
## bitboard (only makes sense if used with popLSB
|
||||
## earlier)
|
||||
result = self and Bitboard(-cast[int64](self - 1))
|
||||
|
||||
|
||||
func getFileMask*(file: int): Bitboard {.inline.} = Bitboard(0x101010101010101'u64) shl file.uint64
|
||||
func getRankMask*(rank: int): Bitboard {.inline.} = Bitboard(0xff) shl uint64(8 * rank)
|
||||
func toBitboard*(square: SomeInteger): Bitboard {.inline.} = Bitboard(1'u64) shl square.uint64
|
||||
|
||||
@@ -26,7 +26,6 @@ import heimdall/pieces
|
||||
import heimdall/moves
|
||||
import heimdall/position
|
||||
import heimdall/rays
|
||||
import heimdall/see
|
||||
import heimdall/datagen/marlinformat
|
||||
|
||||
|
||||
@@ -638,34 +637,6 @@ const drawnFens = [("4k3/2b5/8/8/8/5B2/8/4K3 w - - 0 1", false), # KBvKB (curr
|
||||
]
|
||||
|
||||
|
||||
const seeFens = [("4R3/2r3p1/5bk1/1p1r3p/p2PR1P1/P1BK1P2/1P6/8 b - - 0 1", createMove("h5", "g4", Capture), 0),
|
||||
("4R3/2r3p1/5bk1/1p1r1p1p/p2PR1P1/P1BK1P2/1P6/8 b - - 0 1", createMove("h5", "g4", Capture), 0),
|
||||
("4r1k1/5pp1/nbp4p/1p2p2q/1P2P1b1/1BP2N1P/1B2QPPK/3R4 b - - 0 1", createMove("g4", "f3", Capture), Knight.getStaticPieceScore() - Bishop.getStaticPieceScore()),
|
||||
("2r1r1k1/pp1bppbp/3p1np1/q3P3/2P2P2/1P2B3/P1N1B1PP/2RQ1RK1 b - - 0 1", createMove("d6", "e5", Capture) , Pawn.getStaticPieceScore()),
|
||||
("7r/5qpk/p1Qp1b1p/3r3n/BB3p2/5p2/P1P2P2/4RK1R w - - 0 1", createMove("e1", "e8"), 0),
|
||||
("6rr/6pk/p1Qp1b1p/2n5/1B3p2/5p2/P1P2P2/4RK1R w - - 0 1", createMove("e1", "e8"), -Rook.getStaticPieceScore()),
|
||||
("7r/5qpk/2Qp1b1p/1N1r3n/BB3p2/5p2/P1P2P2/4RK1R w - - 0 1", createMove("e1", "e8"), -Rook.getStaticPieceScore()),
|
||||
("6RR/4bP2/8/8/5r2/3K4/5p2/4k3 w - - 0 1", createMove("f7", "f8", PromoteToQueen), Bishop.getStaticPieceScore() - Pawn.getStaticPieceScore()),
|
||||
("6RR/4bP2/8/8/5r2/3K4/5p2/4k3 w - - 0 1", createMove("f7", "f8", PromoteToKnight), Knight.getStaticPieceScore() - Pawn.getStaticPieceScore()),
|
||||
("7R/4bP2/8/8/1q6/3K4/5p2/4k3 w - - 0 1", createMove("f7", "f8", PromoteToRook), -Pawn.getStaticPieceScore()),
|
||||
("8/4kp2/2npp3/1Nn5/1p2PQP1/7q/1PP1B3/4KR1r b - - 0 1", createMove("h1", "f1", Capture), 0),
|
||||
("8/4kp2/2npp3/1Nn5/1p2P1P1/7q/1PP1B3/4KR1r b - - 0 1", createMove("h1", "f1", Capture), 0),
|
||||
("2r2r1k/6bp/p7/2q2p1Q/3PpP2/1B6/P5PP/2RR3K b - - 0 1", createMove("c5", "c1", Capture), 2 * Rook.getStaticPieceScore() - Queen.getStaticPieceScore()),
|
||||
("r2qk1nr/pp2ppbp/2b3p1/2p1p3/8/2N2N2/PPPP1PPP/R1BQR1K1 w qk - 0 1", createMove("f3", "e5", Capture), Pawn.getStaticPieceScore()),
|
||||
("6r1/4kq2/b2p1p2/p1pPb3/p1P2B1Q/2P4P/2B1R1P1/6K1 w - - 0 1", createMove("f4", "e5", Capture), 0),
|
||||
("3q2nk/pb1r1p2/np6/3P2Pp/2p1P3/2R4B/PQ3P1P/3R2K1 w - h6 0 1", createMove("g5", "h6", EnPassant), 0),
|
||||
("3q2nk/pb1r1p2/np6/3P2Pp/2p1P3/2R1B2B/PQ3P1P/3R2K1 w - h6 0 1", createMove("g5", "h6", EnPassant), Pawn.getStaticPieceScore()),
|
||||
("2r4r/1P4pk/p2p1b1p/7n/BB3p2/2R2p2/P1P2P2/4RK2 w - - 0 1", createMove("c3", "c8", Capture), Rook.getStaticPieceScore()),
|
||||
("2r5/1P4pk/p2p1b1p/5b1n/BB3p2/2R2p2/P1P2P2/4RK2 w - - 0 1", createMove("c3", "c8", Capture), Rook.getStaticPieceScore()),
|
||||
("2r4k/2r4p/p7/2b2p1b/4pP2/1BR5/P1R3PP/2Q4K w - - 0 1", createMove("c3", "c5", Capture), Bishop.getStaticPieceScore()),
|
||||
("8/pp6/2pkp3/4bp2/2R3b1/2P5/PP4B1/1K6 w - - 0 1", createMove("g2", "c6", Capture), Pawn.getStaticPieceScore() - Bishop.getStaticPieceScore()),
|
||||
("4q3/1p1pr1k1/1B2rp2/6p1/p3PP2/P3R1P1/1P2R1K1/4Q3 b - - 0 1", createMove("e6", "e4", Capture), Pawn.getStaticPieceScore()-Rook.getStaticPieceScore()),
|
||||
("4q3/1p1pr1kb/1B2rp2/6p1/p3PP2/P3R1P1/1P2R1K1/4Q3 b - - 0 1", createMove("h7", "e4", Capture), Pawn.getStaticPieceScore()),
|
||||
("r1q1r1k1/pb1nppbp/1p3np1/1Pp1N3/3pNP2/B2P2PP/P3P1B1/2R1QRK1 w - c6 0 11", createMove("b5", "c6", EnPassant), Pawn.getStaticPieceScore()),
|
||||
("r3k2r/p1ppqpb1/Bn2pnp1/3PN3/1p2P3/2N2Q2/PPPB1PpP/R3K2R w QKqk - 0 2", createMove("a6", "f1"), Pawn.getStaticPieceScore() - Bishop.getStaticPieceScore())
|
||||
]
|
||||
|
||||
|
||||
proc basicTests* =
|
||||
|
||||
# Test the FEN parser
|
||||
@@ -693,11 +664,6 @@ proc basicTests* =
|
||||
for (fen, isDrawn) in drawnFens:
|
||||
doAssert newChessboardFromFEN(fen).isInsufficientMaterial() == isDrawn, &"draw check failed for {fen} (expected {isDrawn})"
|
||||
|
||||
# Test SEE scores
|
||||
for (fen, move, expected) in seeFens:
|
||||
let res = loadFEN(fen).see(move)
|
||||
doAssert res == expected, &"SEE test failed for {fen} ({move}): expected {expected}, got {res}"
|
||||
|
||||
var board = newDefaultChessboard()
|
||||
# Ensure correct number of pieces
|
||||
testPieceCount(board, Pawn, White, 8)
|
||||
|
||||
@@ -95,6 +95,7 @@ func getMaterial*(self: Position): int {.inline.} =
|
||||
self.getBitboard(Rook).countSquares() * 5 +
|
||||
self.getBitboard(Queen).countSquares() * 9
|
||||
|
||||
|
||||
func getOccupancyFor*(self: Position, color: PieceColor): Bitboard {.inline.} =
|
||||
## Get the occupancy bitboard for every piece of the given color
|
||||
result = self.colors[color]
|
||||
@@ -111,6 +112,12 @@ proc getPawnAttackers*(self: Position, square: Square, attacker: PieceColor): Bi
|
||||
return self.getBitboard(Pawn, attacker) and getPawnAttackers(attacker, square)
|
||||
|
||||
|
||||
proc getPawnAttackers*(self: Position, square: Square, attacker: PieceColor, occupancy: Bitboard): Bitboard {.inline.} =
|
||||
## Returns the locations of the pawns attacking the given square
|
||||
## with the given occupancy
|
||||
return (self.getBitboard(Pawn, attacker) and occupancy) and getPawnAttackers(attacker, square)
|
||||
|
||||
|
||||
proc getKingAttacker*(self: Position, square: Square, attacker: PieceColor): Bitboard {.inline.} =
|
||||
## Returns the location of the king if it is attacking the given square
|
||||
result = Bitboard(0)
|
||||
@@ -124,18 +131,34 @@ proc getKingAttacker*(self: Position, square: Square, attacker: PieceColor): Bit
|
||||
return king
|
||||
|
||||
|
||||
proc getKingAttacker*(self: Position, square: Square, attacker: PieceColor, occupancy: Bitboard): Bitboard {.inline.} =
|
||||
## Returns the location of the king if it is attacking the given square
|
||||
## given the provided occupancy
|
||||
result = Bitboard(0)
|
||||
let king = self.getBitboard(King, attacker) and occupancy
|
||||
if king == 0:
|
||||
# The king is not included in the occupancy
|
||||
return
|
||||
if (getKingMoves(king.toSquare()) and square.toBitboard()) != 0:
|
||||
return king
|
||||
|
||||
|
||||
func getKnightAttackers*(self: Position, square: Square, attacker: PieceColor): Bitboard {.inline.} =
|
||||
## Returns the locations of the knights attacking the given square
|
||||
return getKnightMoves(square) and self.getBitboard(Knight, attacker)
|
||||
return getKnightMoves(square) and self.getBitboard(Knight, attacker)
|
||||
|
||||
|
||||
proc getSlidingAttackers*(self: Position, square: Square, attacker: PieceColor): Bitboard {.inline.} =
|
||||
func getKnightAttackers*(self: Position, square: Square, attacker: PieceColor, occupancy: Bitboard): Bitboard {.inline.} =
|
||||
## Returns the locations of the knights attacking the given square
|
||||
return getKnightMoves(square) and (self.getBitboard(Knight) and occupancy)
|
||||
|
||||
|
||||
proc getSlidingAttackers*(self: Position, square: Square, attacker: PieceColor, occupancy: Bitboard): Bitboard {.inline.} =
|
||||
## Returns the locations of the sliding pieces attacking the given square
|
||||
let
|
||||
queens = self.getBitboard(Queen, attacker)
|
||||
rooks = self.getBitboard(Rook, attacker) or queens
|
||||
bishops = self.getBitboard(Bishop, attacker) or queens
|
||||
occupancy = self.getOccupancy()
|
||||
|
||||
result = getBishopMoves(square, occupancy) and (bishops or queens)
|
||||
result = result or getRookMoves(square, occupancy) and (rooks or queens)
|
||||
@@ -147,7 +170,26 @@ proc getAttackersTo*(self: Position, square: Square, attacker: PieceColor): Bitb
|
||||
result = self.getPawnAttackers(square, attacker)
|
||||
result = result or self.getKingAttacker(square, attacker)
|
||||
result = result or self.getKnightAttackers(square, attacker)
|
||||
result = result or self.getSlidingAttackers(square, attacker)
|
||||
result = result or self.getSlidingAttackers(square, attacker, self.getOccupancy())
|
||||
|
||||
|
||||
proc getAttackersTo*(self: Position, square: Square, attacker: PieceColor, occupancy: Bitboard): Bitboard {.inline.} =
|
||||
## Computes the attackers bitboard for the given square from
|
||||
## the given side using the provided occupancy bitboard instead
|
||||
## of the one in the position
|
||||
result = self.getPawnAttackers(square, attacker, occupancy)
|
||||
result = result or self.getKingAttacker(square, attacker, occupancy)
|
||||
result = result or self.getKnightAttackers(square, attacker, occupancy)
|
||||
result = result or self.getSlidingAttackers(square, attacker, occupancy)
|
||||
|
||||
|
||||
proc getAttackersTo*(self: Position, square: Square, occupancy: Bitboard): Bitboard {.inline.} =
|
||||
## Computes the attackers bitboard for the given square for both
|
||||
## sides using the provided occupancy bitboard
|
||||
result = self.getPawnAttackers(square, White, occupancy) or self.getPawnAttackers(square, Black, occupancy)
|
||||
result = result or self.getKingAttacker(square, White, occupancy) or self.getKingAttacker(square, Black, occupancy)
|
||||
result = result or self.getKnightAttackers(square, White, occupancy) or self.getKnightAttackers(square, Black, occupancy)
|
||||
result = result or self.getSlidingAttackers(square, White, occupancy) or self.getSlidingAttackers(square, Black, occupancy)
|
||||
|
||||
|
||||
proc isOccupancyAttacked*(self: Position, square: Square, occupancy: Bitboard): bool {.inline.} =
|
||||
@@ -167,12 +209,12 @@ proc isOccupancyAttacked*(self: Position, square: Square, occupancy: Bitboard):
|
||||
nonSideToMove = self.sideToMove.opposite()
|
||||
knights = self.getBitboard(Knight, nonSideToMove)
|
||||
|
||||
if (getKnightMoves(square) and knights) != 0:
|
||||
if (getKnightMoves(square) and knights and occupancy) != 0:
|
||||
return true
|
||||
|
||||
let king = self.getBitboard(King, nonSideToMove)
|
||||
|
||||
if (getKingMoves(square) and king) != 0:
|
||||
if (getKingMoves(square) and king and occupancy) != 0:
|
||||
return true
|
||||
|
||||
let
|
||||
@@ -187,7 +229,7 @@ proc isOccupancyAttacked*(self: Position, square: Square, occupancy: Bitboard):
|
||||
if (getRookMoves(square, occupancy) and rooks) != 0:
|
||||
return true
|
||||
|
||||
if self.getPawnAttackers(square, nonSideToMove) != 0:
|
||||
if self.getPawnAttackers(square, nonSideToMove, occupancy) != 0:
|
||||
return true
|
||||
|
||||
|
||||
|
||||
@@ -324,13 +324,12 @@ proc getEstimatedMoveScore(self: SearchManager, hashMove: Move, move: Move, ply:
|
||||
|
||||
# Good/bad tacticals
|
||||
if move.isTactical():
|
||||
let seeScore = self.board.positions[^1].see(move)
|
||||
# Prioritize good exchanges (see > 0)
|
||||
result += seeScore
|
||||
let winning = self.board.positions[^1].see(move, 1)
|
||||
if move.isCapture():
|
||||
# Add capthist score
|
||||
result += self.getHistoryScore(sideToMove, move)
|
||||
if seeScore < 0:
|
||||
if not winning:
|
||||
# Prioritize good exchanges (see > 0)
|
||||
if move.isCapture(): # TODO: En passant!
|
||||
# Prioritize attacking our opponent's
|
||||
# most valuable pieces
|
||||
@@ -692,13 +691,13 @@ proc qsearch(self: var SearchManager, ply: int, alpha, beta: Score): Score =
|
||||
alpha = max(alpha, staticEval)
|
||||
bestMove = hashMove
|
||||
for move in self.pickMoves(hashMove, ply, qsearch=true):
|
||||
let seeScore = self.board.position.see(move)
|
||||
let winning = self.board.position.see(move, 1)
|
||||
# Skip bad captures (gains 52.9 +/- 25.2)
|
||||
if seeScore < 0:
|
||||
if not winning:
|
||||
continue
|
||||
# Qsearch futility pruning: similar to FP in regular search, but we skip moves
|
||||
# that gain no material instead of just moves that don't improve alpha
|
||||
if not self.board.inCheck() and staticEval + self.parameters.qsearchFpEvalMargin <= alpha and seeScore < 1:
|
||||
if not self.board.inCheck() and staticEval + self.parameters.qsearchFpEvalMargin <= alpha and not winning:
|
||||
continue
|
||||
let kingSq = self.board.getBitboard(King, self.board.sideToMove).toSquare()
|
||||
self.stack[ply].move = move
|
||||
@@ -979,9 +978,8 @@ proc search(self: var SearchManager, depth, ply: int, alpha, beta: Score, isPV:
|
||||
continue
|
||||
if not root and isNotMated and depth <= self.parameters.seePruningMaxDepth and (move.isQuiet() or move.isCapture() or move.isEnPassant()):
|
||||
# SEE pruning: prune moves with a bad SEE score
|
||||
let seeScore = self.board.positions[^1].see(move)
|
||||
let margin = -depth * (if move.isQuiet(): self.parameters.seePruningQuietMargin else: self.parameters.seePruningCaptureMargin)
|
||||
if seeScore < margin:
|
||||
if not self.board.positions[^1].see(move, margin):
|
||||
inc(i)
|
||||
continue
|
||||
var singular = 0
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
# limitations under the License.
|
||||
|
||||
## Implementation of Static Exchange Evaluation
|
||||
import std/algorithm
|
||||
|
||||
|
||||
import heimdall/position
|
||||
import heimdall/pieces
|
||||
import heimdall/board
|
||||
|
||||
|
||||
const PIECE_SCORES: array[PieceKind.Pawn..PieceKind.King, int] = [100, 450, 450, 650, 1250, 100000]
|
||||
|
||||
|
||||
func getStaticPieceScore*(kind: PieceKind): int =
|
||||
## Returns a static score for the given piece
|
||||
@@ -28,23 +28,7 @@ func getStaticPieceScore*(kind: PieceKind): int =
|
||||
## as well as general usage of SEE much more
|
||||
## sane, because if SEE(move) == 0 then we know
|
||||
## the capture sequence is balanced
|
||||
case kind:
|
||||
of Pawn:
|
||||
return 100
|
||||
of Knight:
|
||||
return 450
|
||||
of Bishop:
|
||||
return 450
|
||||
of Rook:
|
||||
return 650
|
||||
of Queen:
|
||||
return 1250
|
||||
of King:
|
||||
# The king has a REALLY large value so
|
||||
# that capturing it is always losing
|
||||
return 100000
|
||||
else:
|
||||
return 0
|
||||
return PIECE_SCORES[kind]
|
||||
|
||||
|
||||
func getStaticPieceScore*(piece: Piece): int {.inline.} =
|
||||
@@ -56,99 +40,90 @@ func getStaticPieceScore*(piece: Piece): int {.inline.} =
|
||||
return piece.kind.getStaticPieceScore()
|
||||
|
||||
|
||||
func pickLeastValuableAttacker(position: Position, attackers: Bitboard): Square {.inline.} =
|
||||
## Returns the square in the given position containing the lowest
|
||||
## value piece in the given attackers bitboard
|
||||
if attackers == 0:
|
||||
return nullSquare()
|
||||
|
||||
var attacks: array[16, tuple[score: int, square: Square]]
|
||||
var count = 0
|
||||
for i, attacker in attackers:
|
||||
attacks[i] = (position.getPiece(attacker).getStaticPieceScore(), attacker)
|
||||
inc(count)
|
||||
|
||||
proc orderer(a, b: tuple[score: int, square: Square]): int {.closure.} =
|
||||
return cmp(a.score, b.score)
|
||||
|
||||
|
||||
attacks.toOpenArray(0, count - 1).sort(orderer)
|
||||
return attacks[0].square
|
||||
|
||||
|
||||
proc see(position: var Position, square: Square): int =
|
||||
## Recursive implementation of static exchange evaluation
|
||||
|
||||
let sideToMove = position.sideToMove
|
||||
let attackers = position.getAttackersTo(square, sideToMove)
|
||||
if attackers == 0:
|
||||
func gain(position: Position, move: Move): int =
|
||||
## Returns how much a single move gains in terms
|
||||
## of static material value
|
||||
if move.isCastling():
|
||||
return 0
|
||||
if move.isEnPassant():
|
||||
return getStaticPieceScore(Pawn)
|
||||
|
||||
result = getStaticPieceScore(position.getPiece(move.targetSquare))
|
||||
if move.isPromotion():
|
||||
result += getStaticPieceScore(move.getPromotionType().promotionToPiece()) - getStaticPieceScore(Pawn)
|
||||
|
||||
|
||||
func popLeastValuable(position: Position, occupancy: var Bitboard, attackers: Bitboard, stm: PieceColor): PieceKind =
|
||||
## Returns the piece type in the given position containing the lowest
|
||||
## value victim in the given attackers bitboard
|
||||
|
||||
for kind in PieceKind.all():
|
||||
let board = attackers and position.getBitboard(kind)
|
||||
|
||||
if board != 0:
|
||||
occupancy = occupancy xor board.lowestBit()
|
||||
return kind
|
||||
|
||||
return PieceKind.Empty
|
||||
|
||||
|
||||
proc see*(position: Position, move: Move, threshold: int): bool =
|
||||
## Statically evaluates a sequence of exchanges
|
||||
## starting from the given one and returns whether
|
||||
## the exchange can beat the given (positive!) threshold.
|
||||
## A sequence of moves leading to a losing capture (score < 0)
|
||||
## will short-circuit and return false regardless of the value
|
||||
## of the threshold
|
||||
|
||||
# Yoinked from Stormphrax
|
||||
|
||||
var score = gain(position, move) - threshold
|
||||
if score < 0:
|
||||
return false
|
||||
|
||||
var next = if move.isPromotion(): move.getPromotionType().promotionToPiece() else: position.getPiece(move.startSquare).kind
|
||||
score -= next.getStaticPieceScore()
|
||||
|
||||
if score > 0:
|
||||
return true
|
||||
|
||||
let
|
||||
attacker = position.pickLeastValuableAttacker(attackers)
|
||||
attackerPiece = position.getPiece(attacker)
|
||||
queens = position.getBitboard(Queen)
|
||||
bishops = queens or position.getBitboard(Bishop)
|
||||
rooks = queens or position.getBitboard(Rook)
|
||||
|
||||
var
|
||||
victimPiece = position.getPiece(square)
|
||||
victim = victimPiece.getStaticPieceScore()
|
||||
occupancy = position.getOccupancy() xor move.startSquare.toBitboard() xor move.targetSquare.toBitboard()
|
||||
stm = position.sideToMove.opposite()
|
||||
attackers = position.getAttackersTo(move.targetSquare, occupancy)
|
||||
|
||||
|
||||
if victimPiece != nullPiece():
|
||||
position.removePiece(square)
|
||||
position.movePiece(attacker, square)
|
||||
# En passant capture
|
||||
if attackerPiece.kind == Pawn and square == position.enPassantSquare:
|
||||
let
|
||||
epTarget = position.enPassantSquare.toBitboard()
|
||||
epPawn = epTarget.backwardRelativeTo(sideToMove).toSquare()
|
||||
if position.getPiece(epPawn) != nullPiece():
|
||||
victimPiece = position.getPiece(epPawn)
|
||||
victim = victimPiece.getStaticPieceScore()
|
||||
position.removePiece(epPawn)
|
||||
while true:
|
||||
let friendlyAttackers = attackers and position.getOccupancyFor(stm)
|
||||
|
||||
# Capture with promotion
|
||||
if attackerPiece.kind == Pawn and getRankMask(rankFromSquare(square)) == attackerPiece.color.getEighthRank():
|
||||
# SEE is meant to simulate the best possible sequence of moves, so we always
|
||||
# promote to a queen
|
||||
position.removePiece(square)
|
||||
position.spawnPiece(square, Piece(kind: Queen, color: sideToMove))
|
||||
result = Queen.getStaticPieceScore() - Pawn.getStaticPieceScore()
|
||||
position.sideToMove = position.sideToMove.opposite()
|
||||
# We don't want to lose material, so the maximum score is
|
||||
# zero
|
||||
result = max(0, result + victim - position.see(square))
|
||||
|
||||
|
||||
proc see*(position: Position, move: Move): int =
|
||||
## Statically evaluates a sequence of exchanges
|
||||
## starting from the given one
|
||||
var position = position
|
||||
var capturedPiece = position.getPiece(move.targetSquare)
|
||||
if move.isCapture():
|
||||
position.removePiece(move.targetSquare)
|
||||
if move.isEnPassant():
|
||||
let
|
||||
epTarget = position.enPassantSquare.toBitboard()
|
||||
epPawn = epTarget.backwardRelativeTo(position.sideToMove).toSquare()
|
||||
capturedPiece = Piece(kind: Pawn, color: position.sideToMove.opposite())
|
||||
position.removePiece(epPawn)
|
||||
if move.isPromotion():
|
||||
position.removePiece(move.startSquare)
|
||||
var promoted = Piece(color: position.sideToMove)
|
||||
case move.getPromotionType():
|
||||
of PromoteToKnight:
|
||||
promoted.kind = Knight
|
||||
of PromoteToBishop:
|
||||
promoted.kind = Bishop
|
||||
of PromoteToRook:
|
||||
promoted.kind = Rook
|
||||
of PromoteToQueen:
|
||||
promoted.kind = Queen
|
||||
else:
|
||||
discard # Unreachable
|
||||
if friendlyAttackers == 0:
|
||||
break
|
||||
|
||||
position.spawnPiece(move.targetSquare, promoted)
|
||||
result += promoted.getStaticPieceScore() - Pawn.getStaticPieceScore()
|
||||
if position.getPiece(move.targetSquare) == nullPiece():
|
||||
position.movePiece(move.startSquare, move.targetSquare)
|
||||
position.sideToMove = position.sideToMove.opposite()
|
||||
result += capturedPiece.getStaticPieceScore() - position.see(move.targetSquare)
|
||||
next = position.popLeastValuable(occupancy, friendlyAttackers, stm)
|
||||
|
||||
# Diagonal/orthogonal captures can add new diagonal/orthogonal attackers,
|
||||
# so handle this
|
||||
if next in [PieceKind.Pawn, PieceKind.Queen, PieceKind.Bishop]:
|
||||
attackers = attackers or (getBishopMoves(move.targetSquare, occupancy) and bishops)
|
||||
elif next in [PieceKind.Rook, PieceKind.Queen]:
|
||||
attackers = attackers or (getRookMoves(move.targetSquare, occupancy) and rooks)
|
||||
|
||||
attackers = attackers and occupancy
|
||||
|
||||
score = -score - 1 - next.getStaticPieceScore()
|
||||
stm = stm.opposite()
|
||||
|
||||
if score >= 0:
|
||||
if next == PieceKind.King and (attackers and position.getOccupancyFor(stm)) != 0:
|
||||
# Can't capture with the king if the other side has defenders on the
|
||||
# target square
|
||||
stm = stm.opposite()
|
||||
# We beat the threshold, hooray!
|
||||
break
|
||||
|
||||
return position.sideToMove != stm
|
||||
Reference in New Issue
Block a user