Skip to content

Commit

Permalink
search+rosaline: move iterative deepening into NegamaxSearcher and keep
Browse files Browse the repository at this point in the history
track of principal variation
  • Loading branch information
e0ff committed Dec 1, 2023
1 parent a9dc8cd commit e01d17b
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 72 deletions.
12 changes: 5 additions & 7 deletions cmd/rosaline/interfaces/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,17 +83,15 @@ func (i cliInterface) Loop() {
}
}

results := i.searcher.Search(&position, depth)
fmt.Println("best move:", results.BestMove)
fmt.Println("score:", results.Score)
fmt.Println("elapsed:", results.Time.Seconds())
bestMove := i.searcher.Search(&position, depth, false)
fmt.Println("best move:", bestMove)
} else if cmd == "evaluate" {
score := i.evaluator.Evaluate(&position)
fmt.Println("score:", score)
} else if cmd == "play" {
results := i.searcher.Search(&position, DefaultDepth)
position.MakeMove(results.BestMove)
fmt.Println("played:", results.BestMove)
bestMove := i.searcher.Search(&position, DefaultDepth, false)
position.MakeMove(bestMove)
fmt.Println("played:", bestMove)
} else if cmd == "fen" {
fmt.Println(position.Fen())
} else if cmd == "setfen" {
Expand Down
22 changes: 1 addition & 21 deletions cmd/rosaline/interfaces/uci.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package interfaces
import (
"bufio"
"fmt"
"math"
"os"
"rosaline/internal/chess"
"rosaline/internal/evaluation"
Expand Down Expand Up @@ -53,28 +52,9 @@ loop:
break
case "go":
go func() {
var bestMove chess.Move
var bestScore int = math.MinInt

for depth := 1; depth <= 4; depth++ {
results := i.searcher.Search(&position, depth)

if i.searcher.Stopped() {
break
}

if results.Score > bestScore {
bestMove = results.BestMove
bestScore = results.Score
}

fmt.Printf("info depth %d score cp %d nodes %d nps %f time %d tbhits %d\n", depth, results.Score, results.Nodes, results.NPS, results.Time.Milliseconds(), results.Hits)
}

bestMove := i.searcher.Search(&position, DefaultDepth, true)
position.MakeMove(bestMove)
fmt.Println("bestmove", bestMove)

i.searcher.ClearPreviousSearch()
}()
break
case "stop":
Expand Down
109 changes: 66 additions & 43 deletions internal/search/negamax.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ package search

import (
"cmp"
"fmt"
"math"
"rosaline/internal/chess"
"rosaline/internal/evaluation"
"slices"
"strings"
"time"
)

Expand All @@ -16,18 +18,11 @@ const (
maxNumberKillerMoves = 128

nullMovePruningReduction = 2
)

type SearchResults struct {
BestMove chess.Move
Score int
Depth int
Nodes int
Time time.Duration
NPS float64
Hits int
Misses int
}
window = 500

MaxDepth = 16
)

type NegamaxSearcher struct {
evaluator evaluation.Evaluator
Expand All @@ -38,6 +33,9 @@ type NegamaxSearcher struct {

ttable TranspositionTable

pvtable [MaxDepth][MaxDepth]chess.Move
pvlength [MaxDepth]int

stop bool

nodes int
Expand All @@ -54,55 +52,59 @@ func NewNegamaxSearcher(evaluator evaluation.Evaluator) NegamaxSearcher {
}
}

func (s *NegamaxSearcher) Search(position *chess.Position, depth int) SearchResults {
s.nodes = 0
s.stop = false

s.ttable.ResetCounters()

start := time.Now()
func (s *NegamaxSearcher) Search(position *chess.Position, depth int, print bool) chess.Move {
s.ClearPreviousSearch()

bestMove := chess.Move{}
bestMove := chess.NullMove
bestScore := math.MinInt + 1

moves := position.GenerateMoves(chess.LegalMoveGeneration)
for d := 1; d <= depth; d++ {
start := time.Now()

slices.SortFunc(moves, func(m1, m2 chess.Move) int {
return cmp.Compare(s.scoreMove(position, m1), s.scoreMove(position, m2))
})
score := s.doSearch(position, initialAlpha, initialBeta, d, 0, 0)

for _, move := range moves {
position.MakeMove(move)
score := -s.doSearch(position, initialAlpha, initialBeta, depth, 0, 0)
position.Undo()
elapsed := time.Since(start)

if score > bestScore {
bestScore = score
bestMove = move
bestMove = s.pvtable[0][0]
}

if print {
nps := float64(s.nodes) / float64(elapsed.Milliseconds())
fmt.Printf("info depth %d score cp %d nodes %d nps %f pv %s time %d tbhits %d\n", d, bestScore, s.nodes, nps, s.getPV(), elapsed.Milliseconds(), s.ttable.Hits())
}

if s.stop {
break
}
}

elapsed := time.Since(start)
nps := float64(s.nodes) / float64(elapsed.Milliseconds())

return SearchResults{
BestMove: bestMove,
Score: bestScore,
Depth: depth,
Nodes: s.nodes,
Time: elapsed,
NPS: nps,
Hits: s.ttable.hits,
Misses: s.ttable.misses,
return bestMove
}

func (s NegamaxSearcher) getPV() string {
var builder strings.Builder

length := s.pvlength[0]
for i := 0; i < length; i++ {
builder.WriteString(s.pvtable[0][i].String())

if i != length {
builder.WriteString(" ")
}
}

return builder.String()
}

func (s NegamaxSearcher) scoreMove(position *chess.Position, move chess.Move) int {
func (s NegamaxSearcher) scoreMove(position *chess.Position, move chess.Move, ply int) int {
turn := position.Turn()

if s.pvtable[0][ply] == move {
return 2000
}

if slices.Contains(s.killerMoves[turn], move) {
return 1000
}
Expand All @@ -111,6 +113,8 @@ func (s NegamaxSearcher) scoreMove(position *chess.Position, move chess.Move) in
}

func (s *NegamaxSearcher) doSearch(position *chess.Position, alpha int, beta int, depth int, ply int, extensions int) int {
s.pvlength[ply] = ply

if s.stop {
return 0
}
Expand All @@ -124,8 +128,8 @@ func (s *NegamaxSearcher) doSearch(position *chess.Position, alpha int, beta int
}

pvNode := beta-alpha != 1

inCheck := position.IsKingInCheck(position.Turn())

if inCheck && extensions < 2 { // limit the number of depth increases we will do to 2
depth++
extensions++
Expand Down Expand Up @@ -155,6 +159,8 @@ func (s *NegamaxSearcher) doSearch(position *chess.Position, alpha int, beta int
if entry.Depth >= depth {
switch entry.Type {
case ExactNode:
s.pvlength[ply] = ply + 1
s.pvtable[ply][ply] = entry.Move
return entry.Score
case UpperNode:
if entry.Score < alpha {
Expand Down Expand Up @@ -189,7 +195,7 @@ func (s *NegamaxSearcher) doSearch(position *chess.Position, alpha int, beta int
}

slices.SortFunc(moves, func(m1, m2 chess.Move) int {
return cmp.Compare(s.scoreMove(position, m1), s.scoreMove(position, m2))
return cmp.Compare(s.scoreMove(position, m1, ply), s.scoreMove(position, m2, ply))
})

bestMove := chess.NullMove
Expand Down Expand Up @@ -238,6 +244,15 @@ func (s *NegamaxSearcher) doSearch(position *chess.Position, alpha int, beta int
if score > alpha {
alpha = score
nodeType = ExactNode

s.pvtable[ply][ply] = move

for i := ply + 1; i < s.pvlength[ply+1]; i++ {
pvMove := s.pvtable[ply+1][i]
s.pvtable[ply][i] = pvMove
}

s.pvlength[ply] = s.pvlength[ply+1]
}
}

Expand Down Expand Up @@ -286,8 +301,16 @@ func (s NegamaxSearcher) Stopped() bool {
}

func (s *NegamaxSearcher) ClearPreviousSearch() {
s.nodes = 0
s.stop = false

s.ttable.ResetCounters()

clear(s.killerMoves)
s.killerMoveIndex = 0

s.pvtable = [MaxDepth][MaxDepth]chess.Move{}
s.pvlength = [MaxDepth]int{}
}

// Reset clears any information about searched positions.
Expand Down
2 changes: 1 addition & 1 deletion internal/search/negamax_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ func BenchmarkNegamax(b *testing.B) {
searcher := NewNegamaxSearcher(evaluator)

for i := 0; i < b.N; i++ {
searcher.Search(&position, 4)
searcher.Search(&position, 4, false)
}
}

0 comments on commit e01d17b

Please sign in to comment.