Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 33 additions & 2 deletions moonfish/engines/alpha_beta.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,8 @@ def negamax(
cache: DictProxy | CACHE_TYPE,
alpha: float = NEG_INF,
beta: float = INF,
ply: int = 0,
killers: list | None = None,
) -> tuple[float | int, Move | None]:
"""
This functions receives a board, depth and a player; and it returns
Expand Down Expand Up @@ -283,6 +285,8 @@ def negamax(
cache,
-beta,
-beta + 1,
ply + 1,
killers,
)[0]
board.pop()
if board_score >= beta:
Expand All @@ -292,9 +296,12 @@ def negamax(

best_move = None
best_score = NEG_INF
moves = organize_moves(board)
ply_killers = killers[ply] if killers and ply < len(killers) else None
moves = organize_moves(board, ply_killers)

for move in moves:
is_capture = board.is_capture(move)

# make the move
board.push(move)

Expand All @@ -305,6 +312,8 @@ def negamax(
cache,
-beta,
-alpha,
ply + 1,
killers,
)[0]
if board_score > self.config.checkmate_threshold:
board_score -= 1
Expand All @@ -321,6 +330,18 @@ def negamax(

# beta-cutoff: opponent won't allow this position
if best_score >= beta:
# Update killer moves for quiet moves that cause beta cutoff
# Add to killers if not already there (keep 2 killers per ply)
if (
killers
and not is_capture
and ply < len(killers)
and move not in killers[ply]
):
killers[ply].insert(0, move)
if len(killers[ply]) > 2:
killers[ply].pop()

# LOWER_BOUND: true score is at least best_score
cache[cache_key] = (best_score, best_move, Bound.LOWER_BOUND, depth)
return best_score, best_move
Expand Down Expand Up @@ -348,8 +369,18 @@ def search_move(self, board: Board) -> Move:
# create shared cache
cache: CACHE_TYPE = {}

# Killer moves table: 2 killers per ply
# Max ply is roughly target_depth + quiescence_depth + some buffer
max_ply = self.config.negamax_depth + self.config.quiescence_search_depth + 10
killers: list = [[] for _ in range(max_ply)]

best_move = self.negamax(
board, self.config.negamax_depth, self.config.null_move, cache
board,
self.config.negamax_depth,
self.config.null_move,
cache,
ply=0,
killers=killers,
)[1]
assert best_move is not None, "Best move from root should not be None"
return best_move
28 changes: 24 additions & 4 deletions moonfish/move_ordering.py
Original file line number Diff line number Diff line change
@@ -1,34 +1,54 @@
import random
from typing import List, Optional

from chess import BLACK, Board, Move

from moonfish.psqt import evaluate_capture, evaluate_piece, get_phase


def organize_moves(board: Board):
def organize_moves(board: Board, killers: Optional[List[Move]] = None):
"""
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).

Order: captures (sorted by MVV-LVA) -> killer moves -> other quiet moves

Arguments:
- board: chess board state
- killers: optional list of killer moves for this ply

Returns:
- legal_moves: list of all the possible moves for the current player.
"""
non_captures = []
captures = []
phase = get_phase(board)

for move in board.legal_moves:
if board.is_capture(move):
captures.append(move)
else:
non_captures.append(move)

random.shuffle(captures)
# Sort captures by MVV-LVA (best captures first)
captures.sort(
key=lambda move: mvv_lva(board, move, phase), reverse=(board.turn != BLACK)
)

# Shuffle non-captures for variety, then we'll extract killers
random.shuffle(non_captures)

# Extract killer moves from non-captures and put them first
if killers:
killer_moves = []
remaining_quiet = []
for move in non_captures:
if move in killers:
killer_moves.append(move)
else:
remaining_quiet.append(move)
non_captures = killer_moves + remaining_quiet

return captures + non_captures


Expand Down