From fe21981a7b12fe8087b49b16090e3bbd0f07772d Mon Sep 17 00:00:00 2001 From: luccabb Date: Mon, 19 Jan 2026 20:28:50 -0800 Subject: [PATCH] [8/9] Add futility pruning Implements futility pruning to skip quiet moves that can't improve alpha: **Futility Pruning:** - At low depths (1-2), compute static evaluation - If eval + margin < alpha, quiet moves can't help - Skip quiet moves (no capture, check, or promotion) - Never prune the first move (might be the only good one) **Margin Calculation:** - Depth 1: 100 centipawns margin - Depth 2: 200 centipawns margin - Larger margin at deeper depths allows for more potential improvement **Conditions for pruning:** - Depth <= 2 - Not in check (check positions are critical) - Static eval + margin < alpha - Move is quiet (not capture/check/promotion) - Not the first move in the list This is a forward pruning technique that can miss some moves, but the marginsare conservative enough to rarely affect results while significantly reducing nodes searched. Co-Authored-By: Claude Opus 4.5 --- moonfish/engines/alpha_beta.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/moonfish/engines/alpha_beta.py b/moonfish/engines/alpha_beta.py index 8926b53..ece8cf4 100644 --- a/moonfish/engines/alpha_beta.py +++ b/moonfish/engines/alpha_beta.py @@ -302,11 +302,32 @@ def negamax( in_check = board.is_check() + # Futility pruning: if static eval is far below alpha, quiet moves won't help + # Only compute if we might use it (low depth, not in check) + futility_margin = 0 + can_futility_prune = False + if depth <= 2 and not in_check: + static_eval = self.eval_board(board) + # Margin increases with depth: depth 1 = 100cp, depth 2 = 200cp + futility_margin = 100 * depth + can_futility_prune = static_eval + futility_margin < alpha + for move_index, move in enumerate(moves): is_capture = board.is_capture(move) gives_check = board.gives_check(move) is_promotion = move.promotion is not None + # Futility pruning: skip quiet moves that can't raise alpha + # Conditions: low depth, not PV move, quiet move, not in check + if ( + can_futility_prune + and move_index > 0 # Don't prune first move + and not is_capture + and not gives_check + and not is_promotion + ): + continue + # make the move board.push(move)