fix(bot): include quiet promotions in quiescence search
Build & Test (NowChessSystems) TeamCity build finished
Build & Test (NowChessSystems) TeamCity build finished
Quiescence tactical filter only flagged capture-promotions, so a quiet queening on an empty back-rank square was treated as non-tactical and skipped at the search horizon. A bot could therefore miss a winning promotion sitting exactly at the horizon and play another move. All bots (Classical/NNUE/Hybrid) share AlphaBetaSearch and were affected. Treat every promotion as tactical so quiescence always expands it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -468,5 +468,5 @@ final class AlphaBetaSearch(
|
||||
private def isCapture(context: GameContext, move: Move): Boolean = move.moveType match
|
||||
case MoveType.Normal(true) => true
|
||||
case MoveType.EnPassant => true
|
||||
case MoveType.Promotion(_) => context.board.pieceAt(move.to).exists(_.color != context.turn)
|
||||
case MoveType.Promotion(_) => true
|
||||
case _ => false
|
||||
|
||||
@@ -253,6 +253,30 @@ class AlphaBetaSearchTest extends AnyFunSuite with Matchers:
|
||||
val search = AlphaBetaSearch(promoCaptureRules, weights = EvaluationClassic)
|
||||
search.bestMove(ctx, maxDepth = 1) should be(Some(promoCapture))
|
||||
|
||||
test("quiet promotion is treated as tactical in quiescence"):
|
||||
// Pawn pushes to an empty back-rank square (no capture). Must still be searched in
|
||||
// quiescence so a bot does not skip queening at the search horizon.
|
||||
val quietPromo = Move(Square(File.E, Rank.R7), Square(File.E, Rank.R8), MoveType.Promotion(PromotionPiece.Queen))
|
||||
val board = Board(
|
||||
Map(
|
||||
Square(File.E, Rank.R7) -> Piece.WhitePawn,
|
||||
),
|
||||
)
|
||||
val ctx = GameContext.initial.withBoard(board).withTurn(Color.White)
|
||||
val quietPromoRules = new RuleSet:
|
||||
def candidateMoves(context: GameContext)(square: Square): List[Move] = Nil
|
||||
def legalMoves(context: GameContext)(square: Square): List[Move] = Nil
|
||||
def allLegalMoves(context: GameContext): List[Move] = List(quietPromo)
|
||||
def isCheck(context: GameContext): Boolean = false
|
||||
def isCheckmate(context: GameContext): Boolean = false
|
||||
def isStalemate(context: GameContext): Boolean = false
|
||||
def isInsufficientMaterial(context: GameContext): Boolean = false
|
||||
def isFiftyMoveRule(context: GameContext): Boolean = false
|
||||
def isThreefoldRepetition(context: GameContext): Boolean = false
|
||||
def applyMove(context: GameContext)(move: Move): GameContext = context
|
||||
val search = AlphaBetaSearch(quietPromoRules, weights = EvaluationClassic)
|
||||
search.bestMove(ctx, maxDepth = 1) should be(Some(quietPromo))
|
||||
|
||||
test("draw when isInsufficientMaterial with legal moves present"):
|
||||
val legalMove = Move(Square(File.E, Rank.R2), Square(File.E, Rank.R4), MoveType.Normal())
|
||||
val drawRules = new RuleSet:
|
||||
|
||||
Reference in New Issue
Block a user