/
tactics.go
164 lines (135 loc) · 4.24 KB
/
tactics.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
package main
import (
"bufio"
"fmt"
"os"
"strings"
"time"
)
type TacticsOptions struct {
thinkingtimeMs uint
epdRegex string
tacticsDebug string
tacticsHashVariation string
tacticsDepth uint
}
func RunTacticsFile(epdFile string, variation string, options TacticsOptions) (bool, error) {
successPositions := 0
totalPositions := 0
lines, err := ParseAndFilterEpdFile(epdFile, options.epdRegex)
if err != nil {
return false, err
}
if variation != "" && len(lines) > 1 {
return false, fmt.Errorf("Can only specify variation if regex filters to 1 positions, got %d", len(lines))
}
var totalStats SearchStats
for _, line := range lines {
prettyMove, result, err := RunTacticsFen(line.fen, variation, options)
if err != nil {
return false, err
}
totalStats.add(result.stats)
var res string
totalPositions++
var moveToCheck string
var wantMatch bool
var desiredSummary string
if line.bestMove != "" {
moveToCheck = line.bestMove
wantMatch = true
desiredSummary = "expected"
} else if line.avoidMove != "" {
moveToCheck = line.avoidMove
wantMatch = false
desiredSummary = "avoid"
}
var success bool
if moveToCheck != "" {
var moveMatches bool
if strings.Contains(moveToCheck, prettyMove) ||
strings.Contains(moveToCheck, SquareToAlgebraicString(result.move.From())+SquareToAlgebraicString(result.move.To())) {
moveMatches = true
}
success = moveMatches == wantMatch
} else {
// for now assume no move specified means checkmate is the desired result
moveToCheck = "Mate"
success = result.flags == CHECKMATE_FLAG
}
if prettyMove != "" && success {
res = "\033[1;32mOK\033[0m"
successPositions++
} else {
res = "\033[1;31mFAIL\033[0m"
}
fmt.Printf("[%s - %s] %s=%s move=%s result=%s\n",
line.name, res, desiredSummary, moveToCheck, prettyMove, result.String())
}
fmt.Printf("Complete. %d/%d positions correct (%.2f%%)\n", successPositions, totalPositions,
100.0*float64(successPositions)/float64(totalPositions))
fmt.Printf("Final stats %s", totalStats.String())
if totalPositions == successPositions {
return true, nil
}
return false, nil
}
func RunTacticsFen(fen string, variation string, options TacticsOptions) (string, SearchResult, error) {
boardState, err := CreateBoardStateFromFENStringWithVariation(fen, variation)
if err != nil {
return "", SearchResult{}, err
}
ch := make(chan SearchResult)
thinkingChan := make(chan ThinkingOutput)
output := bufio.NewWriter(os.Stderr)
output.Write([]byte(boardState.String()))
output.WriteRune('\n')
output.Flush()
go func() {
for thinkingOutput := range thinkingChan {
if thinkingOutput.ply > 0 {
sendThinkingOutput(output, thinkingOutput)
}
}
}()
config := ExternalSearchConfig{}
config.isDebug = options.tacticsDebug != ""
config.debugMoves = options.tacticsDebug
config.searchToDepth = options.tacticsDepth
thinkingTimeMs := options.thinkingtimeMs
if options.tacticsDepth != 0 {
thinkingTimeMs = INFINITY
}
// Question: Why are stats needed both in the thinkAndChooseMove and
// returned in the SearchResult?
stats := SearchStats{}
go thinkAndChooseMove(&boardState, thinkingTimeMs, &stats, config, ch, thinkingChan)
result := <-ch
output.Flush()
if result.move == 0 {
// no result was given in thinking time :(
return "", result, nil
}
if options.tacticsHashVariation != "" {
// "Wiggle room" to allow search to abort
time.Sleep(time.Duration(200) * time.Millisecond)
fmt.Println("---- Transposition Table information ----")
moveList, err := VariationToMoveList(options.tacticsHashVariation, &boardState)
if err != nil {
return "", SearchResult{}, err
}
hasEntry, e := ProbeTranspositionTable(&boardState)
fmt.Printf("%s - %s\n", boardState.String(), e.String())
for i := 0; i < len(moveList) && hasEntry; i++ {
boardState.ApplyMove(moveList[i])
hasEntry, e = ProbeTranspositionTable(&boardState)
fmt.Printf("%s - %s\n", boardState.String(), e.String())
}
for i := len(moveList) - 1; i >= 0; i-- {
boardState.UnapplyMove(moveList[i])
}
}
// .... can't use variation from transposition table while boardState is still being
// searched, bad things
return MoveToPrettyString(result.move, &boardState), result, nil
}