-
Notifications
You must be signed in to change notification settings - Fork 4
Add MVV-LVA capture ordering, check extensions, and delta pruning #50
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
luccabb
wants to merge
1
commit into
master
Choose a base branch
from
improve/search-enhancements
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
|
|
@@ -2,15 +2,24 @@ | |||
|
|
||||
| from chess import BLACK, Board, Move | ||||
|
|
||||
| from moonfish.psqt import evaluate_capture, evaluate_piece, get_phase | ||||
| from moonfish.psqt import ( | ||||
| MG_PIECE_VALUES, | ||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unused import
Suggested change
|
||||
| evaluate_capture, | ||||
| evaluate_piece, | ||||
| get_phase, | ||||
| ) | ||||
|
|
||||
| # Simple integer piece values for fast MVV-LVA ordering in main search | ||||
| # Index by piece type: 0=None, 1=PAWN, 2=KNIGHT, 3=BISHOP, 4=ROOK, 5=QUEEN, 6=KING | ||||
| _MVV_LVA_VALUES = (0, 100, 300, 300, 500, 900, 10000) | ||||
|
|
||||
|
|
||||
| def organize_moves(board: Board): | ||||
| """ | ||||
| This function receives a board and it returns a list of all the | ||||
| possible moves for the current player, sorted by importance. | ||||
| It sends capturing moves at the starting positions in | ||||
| the array (to try to increase pruning and do so earlier). | ||||
| Captures are sorted by MVV-LVA (Most Valuable Victim - Least Valuable Attacker). | ||||
| Promotions are prioritized. Non-captures are shuffled. | ||||
|
|
||||
| Arguments: | ||||
| - board: chess board state | ||||
|
|
@@ -24,14 +33,37 @@ def organize_moves(board: Board): | |||
| for move in board.legal_moves: | ||||
| if board.is_capture(move): | ||||
| captures.append(move) | ||||
| elif move.promotion is not None: | ||||
| # Promotions without capture — prioritize them | ||||
| captures.append(move) | ||||
| else: | ||||
| non_captures.append(move) | ||||
|
|
||||
| random.shuffle(captures) | ||||
| # Sort captures by MVV-LVA: highest victim value first, then lowest attacker | ||||
| captures.sort(key=lambda m: _mvv_lva_score(board, m), reverse=True) | ||||
| random.shuffle(non_captures) | ||||
| return captures + non_captures | ||||
|
|
||||
|
|
||||
| def _mvv_lva_score(board: Board, move: Move) -> int: | ||||
| """Fast integer MVV-LVA score for move ordering.""" | ||||
| if move.promotion is not None: | ||||
| # Promotions get high score; queen promotion highest | ||||
| return _MVV_LVA_VALUES[move.promotion] + 10000 | ||||
|
|
||||
| # Victim value - attacker value (we want high victim, low attacker) | ||||
| attacker = board.piece_type_at(move.from_square) | ||||
| victim = board.piece_type_at(move.to_square) | ||||
|
|
||||
| if victim is None: | ||||
| # En passant | ||||
| return _MVV_LVA_VALUES[1] - _MVV_LVA_VALUES[1] # pawn captures pawn | ||||
|
|
||||
| attacker_val = _MVV_LVA_VALUES[attacker] if attacker else 0 | ||||
| victim_val = _MVV_LVA_VALUES[victim] if victim else 0 | ||||
| return victim_val * 10 - attacker_val | ||||
|
|
||||
|
|
||||
| def is_tactical_move(board: Board, move: Move) -> bool: | ||||
| """ | ||||
| Check if a move is tactical (should be searched in quiescence). | ||||
|
|
||||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unbounded check extensions risk stack overflow
When a move gives check, the extension keeps depth at the same level (
depth - 1 + 1 = depth). In positions with perpetual or long check sequences, this causes the search to never reduce depth, potentially recursing until Python's stack limit is hit.There is no maximum-depth guard in
negamax, and the repetition detection (is_repetition) only exists in quiescence search — not here. While the transposition table might catch some repeated positions, its cache key includes(alpha, beta)which vary across different branches, so there's no guarantee of termination.A common safeguard is to limit total extensions (e.g., track cumulative ply from root and cap extensions once a maximum ply is reached):
Alternatively, adding
board.is_repetition(2)as a draw-return early innegamax(similar to what quiescence already does) would prevent infinite check cycles.