Skip to content
Closed
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
11 changes: 11 additions & 0 deletions pkg/board.go
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,17 @@ func (board *Board) IsPieceAtSquareWhite(square byte) bool {
return (board.WhitePawns|board.WhiteKnights|board.WhiteBishops|board.WhiteRooks|board.WhiteQueens|board.WhiteKing)&mask != 0
}

// IsInCheck returns true if the side to move is in check.
func (board *Board) IsInCheck(whiteToMove bool) bool {
var kingSq byte
if whiteToMove {
kingSq = byte(bits.TrailingZeros64(board.WhiteKing))
} else {
kingSq = byte(bits.TrailingZeros64(board.BlackKing))
}
return board.IsSquareAttacked(kingSq, whiteToMove)
}

// SquareToRank returns the rank (0-7) of a square index (0 = rank 0, 7 = rank 7)
func (board *Board) SquareToRank(square byte) byte {
return square / 8
Expand Down
5 changes: 5 additions & 0 deletions pkg/move.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,11 @@ func (board *Board) Move(move Move) MoveState {
board.Castling.BlackQueenSide = false
}
}
/** Defensive: king capture is illegal.
if move.Captured == WhiteKing || move.Captured == BlackKing {
panic(fmt.Sprintf("Illegal move: %s captures the king", move.ToMove()))
}
*/
}

board.WhiteToMove = !board.WhiteToMove
Expand Down
34 changes: 34 additions & 0 deletions pkg/search.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ const (
SearchMaxDepth = 16 // Maximum depth to search
MaxEvaluationScore = 1_000_000 // Maximum score for wining
MaxEvaluationTimeMs = 3_000 // Maximum time for a search at the root level
NullMoveReduction = 2 // Reduction for null move pruning (R)
NullMoveMinDepth = 3 // Minimum depth for null mov
)

type SearchOptions struct {
Expand Down Expand Up @@ -187,6 +189,15 @@ func (board *Board) ParallelRootSearch(depth int, tt *TranspositionTable, moves
}

func (board *Board) AlphaBetaSearch(depth int, maximizing bool, alpha int, beta int, tt *TranspositionTable, stats *SearchResult, ctx *SearchContext, ply int) int {
/*
defer func() {
if r := recover(); r != nil {
stack := debug.Stack()
fmt.Printf("PANIC in AlphaBetaSearch: %v\nStacktrace:\n%s\nContext: depth=%d maximizing=%v alpha=%d beta=%d ply=%d board=%v\n", r, stack, depth, maximizing, alpha, beta, ply, board)
panic(r)
}
}()
*/
// Check for cancellation at every node
select {
case <-ctx.Done:
Expand Down Expand Up @@ -218,6 +229,29 @@ func (board *Board) AlphaBetaSearch(depth int, maximizing bool, alpha int, beta
return board.MateOrStalemateScore(maximizing)
}

// --- Null Move Pruning ---
if depth >= NullMoveMinDepth && ply > 0 && !board.IsInCheck(board.WhiteToMove) && len(board.GenerateLegalMoves()) > 0 {
nullBoard := board.Clone()
nullBoard.WhiteToMove = !nullBoard.WhiteToMove // Switch side to move (null move)
nullEval := 0
newDepth := depth - NullMoveReduction - 1
if newDepth < 1 {
newDepth = 1
}
if maximizing {
nullEval = nullBoard.AlphaBetaSearch(newDepth, false, alpha, beta, tt, stats, ctx, ply+1)
if nullEval >= beta {
return beta // Fail-hard beta cutoff
}
} else {
nullEval = nullBoard.AlphaBetaSearch(newDepth, true, alpha, beta, tt, stats, ctx, ply+1)
if nullEval <= alpha {
return alpha // Fail-hard alpha cutoff
}
}
}

// --- Alpha-Beta Pruning ---
var result int
var bestMove Move
if maximizing {
Expand Down
Loading