-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathuci.go
272 lines (259 loc) · 8.29 KB
/
uci.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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
package main
import (
"bufio"
"fmt"
"github.com/CameronHonis/chess"
"os"
"strconv"
"strings"
)
type Uci struct {
pos *Position
tt *TranspTable
}
func NewUci(tt *TranspTable) *Uci {
return &Uci{
pos: InitPos(),
tt: tt,
}
}
func (uci *Uci) Start() {
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
line := scanner.Text()
uci.handleInput(line)
}
if err := scanner.Err(); err != nil {
_, _ = fmt.Fprintln(os.Stderr, "reading stdin: ", err)
}
}
func (uci *Uci) handleInput(s string) {
toks := strings.Split(s, " ")
cmd := toks[0]
if cmd == "uci" {
fmt.Println("uciok")
} else if cmd == "position" {
pos, err := handlePositionCmd(toks)
if err != nil {
fmt.Println(err)
} else if pos != nil {
fmt.Println("set position to", pos.FEN())
uci.pos = pos
}
} else if cmd == "go" {
constraints, err := handleGoCmd(toks, uci.pos)
if err != nil {
fmt.Println(err)
} else if constraints != nil {
fmt.Println("starting search")
go NewSearch(uci.pos, constraints, uci.tt).Start()
}
} else if cmd == "isready" {
fmt.Println("readyok")
} else {
fmt.Println("unknown command:", cmd)
}
}
func handlePositionCmd(toks []string) (*Position, error) {
if len(toks) < 2 || toks[1] == "--help" || toks[1] == "help" {
printPositionCmdHelp()
return nil, nil
}
tokIdx := 1
var pos *Position
if toks[tokIdx] == "fen" {
if len(toks) < 8 {
return nil, fmt.Errorf("incorrect number of FEN segments, got %d, expected 6", 8-len(toks))
}
fen := strings.Join(toks[2:8], " ")
var err error
pos, err = FromFEN(fen)
if err != nil {
return nil, fmt.Errorf("could not parse FEN %s: %s", fen, err)
}
tokIdx = 8
} else if toks[tokIdx] == "startpos" {
pos = InitPos()
tokIdx = 2
}
var isMoveToks = false
for ; tokIdx < len(toks); tokIdx++ {
if toks[tokIdx] == "moves" {
isMoveToks = true
continue
}
if isMoveToks {
board, boardErr := chess.BoardFromFEN(pos.FEN())
if boardErr != nil {
return nil, fmt.Errorf("could not convert from pos to board: %s", boardErr)
}
move, moveErr := chess.MoveFromAlgebraic(toks[tokIdx], board)
if moveErr != nil {
return nil, fmt.Errorf("could not parse move: %s", moveErr)
}
board = chess.GetBoardFromMove(board, move)
var posErr error
pos, posErr = FromFEN(board.ToFEN())
if posErr != nil {
return nil, fmt.Errorf("could not convert from board to pos: %s", posErr)
}
}
}
return pos, nil
}
func printPositionCmdHelp() {
fmt.Println("UCI position: set up the position on the internal board")
fmt.Println("Usage:")
fmt.Println(" position [fen | startpos] [moves] ...")
fmt.Println(Bold("position fen") + " {FEN} ...")
fmt.Println(" set up the position from the given fen")
fmt.Println("")
fmt.Println(Bold("position startpos") + " ...")
fmt.Println(" set up the position from the start pos")
fmt.Println("")
fmt.Println("position ... " + Bold("moves") + " {move1} {move2} ...")
fmt.Println(" an optional argument that describes the moves that follow the given board position.")
fmt.Println(" moves must be formatted in UCI-compliant 'Long-Algebraic' format, for more")
fmt.Println(" information on this format visit: ")
fmt.Println(" https://www.chessprogramming.org/Algebraic_Chess_Notation#LAN")
}
func handleGoCmd(toks []string, pos *Position) (*SearchConstraints, error) {
if len(toks) == 2 && (toks[1] == "--help" || toks[1] == "help") {
printGoCmdHelp()
}
opts := &SearchConstraints{}
var tokIdx = 1
for tokIdx < len(toks) {
currTok := toks[tokIdx]
if currTok == "searchmoves" {
var moveIdx = 0
for ; moveIdx+tokIdx+1 < len(toks); moveIdx++ {
moveStr := toks[moveIdx+tokIdx+1]
board, boardErr := chess.BoardFromFEN(pos.FEN())
if boardErr != nil {
return nil, fmt.Errorf("could not convert from pos to board: %s", boardErr)
}
legacyMove, legacyMoveErr := chess.MoveFromLongAlgebraic(moveStr, board)
if legacyMoveErr != nil {
return nil, fmt.Errorf("could not parse move %s: %s", moveStr, legacyMoveErr)
}
if opts.moves == nil {
opts.moves = make([]Move, 0)
}
move, moveErr := LegalMoveFromLegacyMove(legacyMove, pos)
if moveErr != nil {
return nil, fmt.Errorf("could not find legal move match from legacy move: %s", moveErr)
}
opts.moves = append(opts.moves, move)
}
tokIdx += moveIdx + 1
} else if currTok == "wtime" {
if tokIdx+1 >= len(toks) {
return nil, fmt.Errorf("missing argument for wtime")
}
wtimeStr := toks[tokIdx+1]
wtime, parseErr := strconv.Atoi(wtimeStr)
if parseErr != nil {
return nil, fmt.Errorf("could not parse %s as wtime: %s", wtimeStr, parseErr)
}
opts.whiteMs = wtime
tokIdx += 2
} else if currTok == "btime" {
if tokIdx+1 >= len(toks) {
return nil, fmt.Errorf("missing argument for btime")
}
btimeStr := toks[tokIdx+1]
btime, parseErr := strconv.Atoi(btimeStr)
if parseErr != nil {
return nil, fmt.Errorf("could not parse %s as btime: %s", btimeStr, parseErr)
}
opts.blackMs = btime
tokIdx += 2
} else if currTok == "winc" {
if tokIdx+1 >= len(toks) {
return nil, fmt.Errorf("missing argument for winc")
}
wincStr := toks[tokIdx+1]
winc, parseErr := strconv.Atoi(wincStr)
if parseErr != nil {
return nil, fmt.Errorf("could not parse %s as winc: %s", wincStr, parseErr)
}
opts.whiteIncrMs = winc
tokIdx += 2
} else if currTok == "binc" {
if tokIdx+1 >= len(toks) {
return nil, fmt.Errorf("missing argument for binc")
}
bincStr := toks[tokIdx+1]
binc, parseErr := strconv.Atoi(bincStr)
if parseErr != nil {
return nil, fmt.Errorf("could not parse %s as winc: %s", bincStr, parseErr)
}
opts.blackIncrMs = binc
tokIdx += 2
} else if currTok == "depth" {
if tokIdx+1 >= len(toks) {
return nil, fmt.Errorf("missing argument for depth")
}
depthStr := toks[tokIdx+1]
depth, parseErr := strconv.Atoi(depthStr)
if parseErr != nil {
return nil, fmt.Errorf("could not parse %s as depth: %s", depthStr, parseErr)
}
opts.maxDepth = uint8(depth)
tokIdx += 2
} else if currTok == "nodes" {
if tokIdx+1 >= len(toks) {
return nil, fmt.Errorf("missing argument for nodes")
}
nodesStr := toks[tokIdx+1]
nodes, parseErr := strconv.Atoi(nodesStr)
if parseErr != nil {
return nil, fmt.Errorf("could not parse %s as nodes: %s", nodesStr, parseErr)
}
opts.maxNodes = nodes
tokIdx += 2
} else if currTok == "movetime" {
if tokIdx+1 >= len(toks) {
return nil, fmt.Errorf("missing argument for movetime")
}
movetimeStr := toks[tokIdx+1]
movetime, parseErr := strconv.Atoi(movetimeStr)
if parseErr != nil {
return nil, fmt.Errorf("could not parse %s as movetime: %s", movetimeStr, parseErr)
}
opts.maxMs = movetime
tokIdx += 2
} else {
return nil, fmt.Errorf("unknown argument: %s", currTok)
}
}
return opts, nil
}
func printGoCmdHelp() {
fmt.Println("UCI go: start search on the current internal position")
fmt.Println("Usage:")
fmt.Println(" go [arguments] ...")
fmt.Println("")
fmt.Println("The arguments are:")
fmt.Println(Tabbed(1, Bold("searchmoves")) + " {move1} ...")
fmt.Println(Tabbed(2, "restrict search to these moves only"))
fmt.Println(Tabbed(2, "moves denoted in UCI-compliant 'Long-Algebraic' Notation"))
fmt.Println(Tabbed(2, "for more information on this format, refer to:"))
fmt.Println(Tabbed(2, "https://www.chessprogramming.org/Algebraic_Chess_Notation#LAN"))
fmt.Println(Tabbed(2, "E.g. `go searchmoves e2e4 d2d4"))
fmt.Println(Tabbed(1, Bold("wtime")+" {mSec}"))
fmt.Println(Tabbed(2, "informs the engine that white has x msec left on the clock"))
fmt.Println(Tabbed(1, Bold("btime")+" {mSec}"))
fmt.Println(Tabbed(2, "informs the engine that black has x msec left on the clock"))
fmt.Println(Tabbed(1, Bold("winc")+" {mSec}"))
fmt.Println(Tabbed(2, "informs the engine that white receives x msec after each move"))
fmt.Println(Tabbed(1, Bold("binc")+" {mSec}"))
fmt.Println(Tabbed(2, "informs the engine that black receives x msec after each move"))
fmt.Println(Tabbed(1, Bold("depth")+" {depth}"))
fmt.Println(Tabbed(2, "limits the search to x plies max"))
fmt.Println(Tabbed(1, Bold("nodes")+" {nodes}"))
fmt.Println(Tabbed(2, "limits the search only x nodes"))
fmt.Println(Tabbed(1, Bold("movetime")+" {mSec}"))
fmt.Println(Tabbed(2, "requires the search to last exactly x msec"))
}