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
22 changes: 20 additions & 2 deletions moonfish/engines/alpha_beta.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@
NEG_INF = float("-inf")
NULL_MOVE = Move.null()

# History table type: 64x64 array (from_square, to_square) -> score
HISTORY_TYPE = list[list[int]]


def new_history_table() -> HISTORY_TYPE:
"""Create a fresh 64x64 history table initialized to zeros."""
return [[0] * 64 for _ in range(64)]


class AlphaBeta:
"""
Expand Down Expand Up @@ -176,6 +184,7 @@ def negamax(
cache: DictProxy | CACHE_KEY,
alpha: float = NEG_INF,
beta: float = INF,
history: HISTORY_TYPE | None = None,
) -> tuple[float | int, Move | None]:
"""
This functions receives a board, depth and a player; and it returns
Expand Down Expand Up @@ -250,6 +259,7 @@ def negamax(
cache,
-beta,
-beta + 1,
history,
)[0]
board.pop()
if board_score >= beta:
Expand All @@ -260,7 +270,7 @@ def negamax(

# initializing best_score
best_score = NEG_INF
moves = organize_moves(board)
moves = organize_moves(board, history)

for move in moves:
# make the move
Expand All @@ -273,6 +283,7 @@ def negamax(
cache,
-beta,
-alpha,
history,
)[0]
if board_score > self.config.checkmate_threshold:
board_score -= 1
Expand All @@ -284,6 +295,9 @@ def negamax(

# beta-cutoff
if board_score >= beta:
# Update history heuristic for quiet moves that cause beta cutoff
if history is not None and not board.is_capture(move):
history[move.from_square][move.to_square] += depth * depth
cache[cache_key] = (board_score, move)
return board_score, move

Expand Down Expand Up @@ -313,9 +327,13 @@ def search_move(self, board: Board) -> Move:
self.nodes = 0
# create shared cache
cache: CACHE_KEY = {}
# History table: tracks quiet moves that cause beta cutoffs
# Indexed by (from_square, to_square), higher = more likely to be good
history = new_history_table()

best_move = self.negamax(
board, self.config.negamax_depth, self.config.null_move, cache
board, self.config.negamax_depth, self.config.null_move, cache,
history=history,
)[1]
assert best_move is not None, "Best move from root should not be None"
return best_move
15 changes: 13 additions & 2 deletions moonfish/move_ordering.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,17 @@
from moonfish.psqt import evaluate_capture, evaluate_piece, get_phase


def organize_moves(board: Board):
def organize_moves(board: Board, history: list[list[int]] | None = 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).
Quiet moves are sorted by history heuristic when available.

Arguments:
- board: chess board state
- history: optional 64x64 history table for quiet move ordering

Returns:
- legal_moves: list of all the possible moves for the current player.
Expand All @@ -28,7 +30,16 @@ def organize_moves(board: Board):
non_captures.append(move)

random.shuffle(captures)
random.shuffle(non_captures)

# Sort quiet moves by history heuristic if available
if history is not None:
non_captures.sort(
key=lambda m: history[m.from_square][m.to_square],
reverse=True,
)
else:
random.shuffle(non_captures)

return captures + non_captures


Expand Down
Loading