Scid  4.7.0
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros
uci.tcl
Go to the documentation of this file.
1 ###
2 ### uci.tcl: part of Scid.
3 ### Copyright (C) 2007 Pascal Georges
4 ###
5 ######################################################################
6 ### add UCI engine support
7 
8 namespace eval uci {
9  # will contain the UCI engine options saved
10  variable newOptions {}
11 
12  # set pipe ""
13  set uciOptions {}
14  set optList {}
15  set oldOptions ""
16  array set check ""
17 
18  set autoSaveOptions 0 ; # UCI options are saved as soon as the options dialog is closed
19  set autoSaveOptionsIndex -1
20 
21  # The list of token that comes with info
22  set infoToken { depth seldepth time nodes pv multipv score cp mate lowerbound upperbound \
23  currmove currmovenumber hashfull nps tbhits sbhits cpuload string refutation currline }
24  set optionToken {name type default min max var }
25  set optionImportant { MultiPV Hash OwnBook BookFile UCI_LimitStrength UCI_Elo }
26  set optionToKeep { UCI_LimitStrength UCI_Elo UCI_ShredderbasesPath }
27  array set uciInfo {}
28  ################################################################################
29  #
30  ################################################################################
31  proc resetUciInfo { { n 1 }} {
32  global ::uci::uciInfo
33  set uciInfo(depth$n) 0
34  set uciInfo(seldepth$n) 0
35  set uciInfo(time$n) 0
36  set uciInfo(nodes$n) 0
37  set uciInfo(pv$n) ""
38  set uciInfo(multipv$n) ""
39  # set uciInfo(pvlist$n) {}
40  # set uciInfo(score$n) ""
41  set uciInfo(tmp_score$n) ""
42  set uciInfo(scoremate$n) 0
43  set uciInfo(currmove$n) ""
44  set uciInfo(currmovenumber$n) 0
45  set uciInfo(hashfull$n) 0
46  set uciInfo(nps$n) 0
47  set uciInfo(tbhits$n) 0
48  set uciInfo(sbhits$n) 0
49  set uciInfo(cpuload$n) 0
50  set uciInfo(string$n) ""
51  set uciInfo(refutation$n) ""
52  set uciInfo(currline$n) ""
53  # set uciInfo(bestmove$n) ""
54  }
55  ################################################################################
56  # if analyze = 0 -> engine mode
57  # if analyze = 1 -> analysis mode
58  ################################################################################
59  proc processAnalysisInput { { n 1 } { analyze 1 } } {
60  global analysis ::uci::uciInfo
61 
62  if {$analyze} {
63  set pipe $analysis(pipe$n)
64  if { ! [ ::checkEngineIsAlive $n] } { return}
65  } else {
66  set analysis(fen$n) ""
67  set pipe $uciInfo(pipe$n)
68  if { ! [ ::uci::checkEngineIsAlive $n] } { return}
69  }
70 
71  if {$analyze} {
72  if {! $analysis(seen$n)} {
73  set analysis(seen$n) 1
74  logEngineNote $n {First line from engine seen; sending it initial commands now.}
75  # in order to get options, engine should end reply with "uciok"
76  ::sendToEngine $n "uci"
77  }
78  } else {
79  if {! $uciInfo(seen$n)} {
80  set uciInfo(seen$n) 1
81  logEngineNote $n {First line from engine seen; sending it initial commands now.}
82  ::uci::sendToEngine $n "uci"
83  }
84  }
85 
86  after idle "after 1 ::uci::processInput_ $n $analyze"
87  fileevent $pipe readable {}
88  }
89 
90  proc processInput_ { {n} {analyze} } {
91  global analysis ::uci::uciInfo ::uci::infoToken ::uci::optionToken
92 
93  if {$analyze} {
94  set pipe $analysis(pipe$n)
95  if { ! [ ::checkEngineIsAlive $n] } { return}
96  } else {
97  set analysis(fen$n) ""
98  set pipe $uciInfo(pipe$n)
99  if { ! [ ::uci::checkEngineIsAlive $n] } { return}
100  }
101 
102  # Get one line from the engine:
103  set line [gets $pipe]
104  if {$line == ""} {
105  fileevent $pipe readable "::uci::processAnalysisInput $n $analyze"
106  return
107  }
108 
109  after idle "after 1 ::uci::processInput_ $n $analyze"
110 
111  # puts ">> $line"
112 
113  # To speed up parsing of engine's output. Should be removed if currmove info is used
114  # if {[string first "info currmove" $line ] == 0} { return }
115 
116  logEngine $n "Engine: $line"
117 
118  if {[string match "bestmove*" $line]} {
119  set data [split $line]
120  set uciInfo(bestmove$n) [lindex $data 1]
121  # get ponder move
122  if {[lindex $data 2] == "ponder"} {
123  set uciInfo(ponder$n) [lindex $data 3]
124  } else {
125  set uciInfo(ponder$n) ""
126  }
127  set analysis(waitForBestMove$n) 0
128  return
129  }
130 
131  if {[string match "id *name *" $line]} {
132  set name [ regsub {id[ ]?name[ ]?} $line ""]
133  if {$analyze} {
134  set analysis(name$n) $name
135  } else {
136  set uciInfo(name$n) $name
137  }
138 
139  if {$n == 1} {
140  catch {wm title .analysisWin$n "Scid: Analysis: $name"}
141  } else {
142  catch {wm title .analysisWin$n "Scid: Analysis $n: $name"}
143  }
144  }
145 
146  set toBeFormatted 0
147  # parse an info line
148  if {[string first "info" $line] == 0} {
149  if {$analysis(waitForReadyOk$n)} { return}
150  resetUciInfo $n
151  set data [split $line]
152  set length [llength $data]
153  for {set i 0} {$i < $length } {incr i} {
154  set t [lindex $data $i]
155  if { $t == "info" } { continue}
156  if { $t == "depth" } { incr i ; set uciInfo(depth$n) [ lindex $data $i] ; continue}
157  if { $t == "seldepth" } { incr i ; set uciInfo(seldepth$n) [ lindex $data $i] ; set analysis(seldepth$n) $uciInfo(seldepth$n) ; continue}
158  if { $t == "time" } { incr i ; set uciInfo(time$n) [ lindex $data $i] ; continue}
159  if { $t == "nodes" } { incr i ; set uciInfo(nodes$n) [ lindex $data $i] ; continue}
160  if { $t == "pv" } {
161  incr i
162  set uciInfo(pv$n) [ lindex $data $i]
163  incr i
164  while { [ lsearch -exact $infoToken [ lindex $data $i]] == -1 && $i < $length } {
165  append uciInfo(pv$n) " " [ lindex $data $i]
166  incr i
167  }
168  set toBeFormatted 1
169  incr i -1
170  continue
171  }
172  if { $t == "multipv" } { incr i ; set uciInfo(multipv$n) [ lindex $data $i] ; continue}
173  if { $t == "score" } {
174  incr i
175  set next [ lindex $data $i]
176  # Needed for Prodeo, which is not UCI compliant
177  if { $next != "cp" && $next != "mate" } {
178  return
179  }
180  if { $next == "cp" } {
181  incr i
182  set uciInfo(tmp_score$n) [ lindex $data $i]
183  }
184  if { $next == "mate" } {
185  incr i
186  set next [ lindex $data $i]
187  set uciInfo(scoremate$n) $next
188  if { $next < 0} {
189  set uciInfo(tmp_score$n) [expr {-32767 - 2 * $next}]
190  } else {
191  set uciInfo(tmp_score$n) [expr {32767 - 2 * $next}]
192  }
193  }
194  # convert the score to white's perspective (not engine's one)
195  if { $analysis(fen$n) == "" } {
196  set side [string index [sc_pos side] 0]
197  } else {
198  set side [lindex [split $analysis(fen$n)] 1]
199  }
200  if { $side == "b"} {
201  set uciInfo(tmp_score$n) [ expr 0.0 - $uciInfo(tmp_score$n)]
202  if { $uciInfo(scoremate$n) } {
203  set uciInfo(scoremate$n) [ expr 0 - $uciInfo(scoremate$n)]
204  if { $uciInfo(tmp_score$n) < 0 } {
205  set uciInfo(tmp_score$n) [ expr {$uciInfo(tmp_score$n) - 1.0}]
206  }
207  }
208  } elseif { $uciInfo(scoremate$n) && $uciInfo(tmp_score$n) > 0 } {
209  set uciInfo(tmp_score$n) [ expr {$uciInfo(tmp_score$n) + 1.0}]
210  }
211  set uciInfo(tmp_score$n) [expr {double($uciInfo(tmp_score$n)) / 100.0}]
212 
213  # don't consider lowerbound & upperbound score info
214  continue
215  }
216  if { $t == "currmove" } { incr i ; set uciInfo(currmove$n) [ lindex $data $i] ; set analysis(currmove$n) [formatPv $uciInfo(currmove$n) $analysis(fen$n)] ; continue}
217  if { $t == "currmovenumber" } { incr i ; set uciInfo(currmovenumber$n) [ lindex $data $i] ; set analysis(currmovenumber$n) $uciInfo(currmovenumber$n) ; continue}
218  if { $t == "hashfull" } { incr i ; set uciInfo(hashfull$n) [ lindex $data $i] ; set analysis(hashfull$n) $uciInfo(hashfull$n) ; continue}
219  if { $t == "nps" } { incr i ; set uciInfo(nps$n) [ lindex $data $i] ; set analysis(nps$n) $uciInfo(nps$n) ; continue}
220  if { $t == "tbhits" } { incr i ; set uciInfo(tbhits$n) [ lindex $data $i] ; set analysis(tbhits$n) $uciInfo(tbhits$n) ; continue}
221  if { $t == "sbhits" } { incr i ; set uciInfo(sbhits$n) [ lindex $data $i] ; set analysis(sbhits$n) $uciInfo(sbhits$n) ; continue}
222  if { $t == "cpuload" } { incr i ; set uciInfo(cpuload$n) [ lindex $data $i] ; set analysis(cpuload$n) $uciInfo(cpuload$n) ; continue}
223  if { $t == "string" } {
224  incr i
225  while { $i < $length } {
226  append uciInfo(string$n) [ lindex $data $i] " "
227  incr i
228  }
229  break
230  }
231  # TODO parse following tokens if necessary : refutation currline
232  if { $t == "refutation" } { continue}
233  if { $t == "currline" } { continue}
234  };# end for data loop
235 
236  # return if no interesting info
237  if { $uciInfo(tmp_score$n) == "" || $uciInfo(pv$n) == "" } {
238  if {$analyze} {
240  }
241  return
242  }
243 
244  # handle the case an UCI engine does not send multiPV
245  if { $uciInfo(multipv$n) == "" } { set uciInfo(multipv$n) 1}
246 
247  if { $uciInfo(multipv$n) == 1 } {
248  set uciInfo(score$n) $uciInfo(tmp_score$n)
249  }
250 
251  if { $uciInfo(multipv$n) == 1 && $analyze} {
252  # this is the best line
253  set analysis(prev_depth$n) $analysis(depth$n)
254  set analysis(depth$n) $uciInfo(depth$n)
255  set analysis(score$n) $uciInfo(score$n)
256  set analysis(scoremate$n) $uciInfo(scoremate$n)
257  set analysis(moves$n) $uciInfo(pv$n)
258  set analysis(time$n) [expr {double($uciInfo(time$n)) / 1000.0}]
259  set analysis(nodes$n) [calculateNodes $uciInfo(nodes$n)]
260  }
261 
262  set pvRaw $uciInfo(pv$n)
263 
264  # convert to something more readable
265  if ($toBeFormatted) {
266  set uciInfo(pv$n) [formatPv $uciInfo(pv$n) $analysis(fen$n)]
267  set toBeFormatted 0
268  }
269 
270  set idx [ expr $uciInfo(multipv$n) -1]
271 
272  # was if $analyze etc..
273  if { $idx < $analysis(multiPVCount$n) } {
274  set tmpTime [expr {double($uciInfo(time$n)) / 1000.0}]
275  if {$idx < [llength $analysis(multiPV$n)]} {
276  lset analysis(multiPV$n) $idx "$uciInfo(depth$n) $uciInfo(tmp_score$n) [list $uciInfo(pv$n)] $uciInfo(scoremate$n) $tmpTime"
277  lset analysis(multiPVraw$n) $idx "$uciInfo(depth$n) $uciInfo(tmp_score$n) [list $pvRaw] $uciInfo(scoremate$n) $tmpTime"
278  } else {
279  lappend analysis(multiPV$n) "$uciInfo(depth$n) $uciInfo(tmp_score$n) [list $uciInfo(pv$n)] $uciInfo(scoremate$n) $tmpTime"
280  lappend analysis(multiPVraw$n) "$uciInfo(depth$n) $uciInfo(tmp_score$n) [list $pvRaw] $uciInfo(scoremate$n) $tmpTime"
281  }
282  }
283 
284  } ;# end of info line
285 
286  # the UCI engine answers to <uci> command
287  if { $line == "uciok"} {
288  resetUciInfo $n
289  if {$analyze} {
290  set analysis(uciok$n) 1
291  } else {
292  set uciInfo(uciok$n) 1
293  }
294  if {$analysis(onUciOk$n) != ""} { {*}$analysis(onUciOk$n)}
295  }
296 
297  # the UCI engine answers to <isready> command
298  if { $line == "readyok"} {
299  set analysis(waitForReadyOk$n) 0
300  return
301  }
302 
303  # get options and save only part of data
304  if { [string first "option name" $line] == 0 && $analyze } {
305  set min "" ; set max ""
306  set data [split $line]
307  set length [llength $data]
308  for {set i 0} {$i < $length} {incr i} {
309  set t [lindex $data $i]
310  if {$t == "name"} {
311  incr i
312  set name [ lindex $data $i]
313  incr i
314  while { [ lsearch -exact $optionToken [ lindex $data $i]] == -1 && $i < $length } {
315  append name " " [ lindex $data $i]
316  incr i
317  }
318  incr i -1
319  continue
320  }
321  if {$t == "min"} { incr i ; set min [ lindex $data $i] ; continue}
322  if {$t == "max"} {incr i ; set max [ lindex $data $i] ; continue}
323  }
324  lappend analysis(uciOptions$n) [ list $name $min $max]
325  }
326  if {$analyze} {
328  }
329  }
330  ################################################################################
331  #
332  ################################################################################
333  proc readUCI { n } {
334  global ::uci::uciOptions
335 
336  set line [string trim [gets $::uci::uciInfo(pipe$n)]]
337  # end of options
338  if {$line == "uciok"} {
339  # we got all options, stop engine
340  closeUCIengine $n 1
342  }
343  # get options
344  if { [string first "option name" $line] == 0 } {
345  lappend uciOptions $line
346  }
347  }
348  ################################################################################
349  # build a dialog with UCI options published by the engine
350  # and available in analysis(uciOptions)
351  ################################################################################
352  proc uciConfig { n cmd arg dir options } {
353  global ::uci::uciOptions ::uci::oldOptions
354 
355  if {[info exists ::uci::uciInfo(pipe$n)]} {
356  if {$::uci::uciInfo(pipe$n) != ""} {
357  tk_messageBox -title "Scid" -icon warning -type ok -message "An engine is already running"
358  return
359  }
360  }
361  set oldOptions $options
362 
363  # If the analysis directory is not current dir, cd to it:
364  set oldpwd ""
365  if {$dir != "."} {
366  set oldpwd [pwd]
367  catch {cd $dir}
368  }
369  # Try to execute the analysis program:
370  if {[catch {set pipe [open "| [list $cmd] $arg" "r+"]} result]} {
371  if {$oldpwd != ""} { catch {cd $oldpwd}}
372  tk_messageBox -title "Scid: error starting UCI engine" \
373  -icon warning -type ok -message "Unable to start the program:\n$cmd"
374  return
375  }
376 
377  set ::uci::uciInfo(pipe$n) $pipe
378 
379  # Configure pipe for line buffering and non-blocking mode:
380  fconfigure $pipe -buffering full -blocking 0
381  fileevent $pipe readable "::uci::readUCI $n"
382 
383  # Return to original dir if necessary:
384  if {$oldpwd != ""} { catch {cd $oldpwd}}
385 
386  set uciOptions {}
387 
388  puts $pipe "uci"
389  flush $pipe
390 
391  # give a few seconds for the engine to output its options, then automatically kill it
392  # (to handle xboard engines)
393  after 5000 "::uci::closeUCIengine $n 0"
394  }
395 
396  ################################################################################
397  # builds the dialog for UCI engine configuration
398  ################################################################################
399  proc uciConfigWin {} {
400  global ::uci::uciOptions ::uci::optList ::uci::optionToken ::uci::oldOptions ::uci::optionImportant
401 
402  set w .uciConfigWin
403  if { [winfo exists $w]} { return}
405  wm title $w $::tr(ConfigureUCIengine)
406 
407  autoscrollframe -bars both $w canvas $w.c -highlightthickness 0 -background [ttk::style lookup Button.label -background]
408  bind $w.c <Configure> {
409  set l [winfo reqwidth %W.f]
410  set h [winfo reqheight %W.f]
411  %W configure -scrollregion [list 0 0 $l $h] -width $l -height $h
412  }
413  grid [ttk::frame $w.c.f]
414  $w.c create window 0 0 -window $w.c.f -anchor nw
415  set w $w.c.f
416 
417  proc tokeep {opt} {
418  foreach tokeep $::uci::optionToKeep {
419  if { [lsearch $opt $tokeep] != -1 } {
420  return 1
421  }
422  }
423  return 0
424  }
425 
426  set optList ""
427  array set elt {}
428  foreach opt $uciOptions {
429  set elt(name) "" ; set elt(type) "" ; set elt(default) "" ; set elt(min) "" ; set elt(max) "" ; set elt(var) ""
430  set data [split $opt]
431  # skip options starting with UCI_ and Ponder
432  # some engines like shredder use UCI_* options that should not be ignored
433 
434  if { ![tokeep $opt] && ( [ lsearch -glob $data "UCI_*"] != -1 || [ lsearch $data "Ponder"] != -1 ) } {
435  continue
436  }
437 
438  set length [llength $data]
439  # parse one option
440  for {set i 0} {$i < $length} {incr i} {
441  set t [lindex $data $i]
442  if {$t == "option"} { continue}
443  if {$t == "name"} {
444  incr i
445  set elt(name) [ lindex $data $i]
446  incr i
447  while { [ lsearch -exact $optionToken [ lindex $data $i]] == -1 && $i < $length } {
448  append elt(name) " " [ lindex $data $i]
449  incr i
450  }
451  incr i -1
452  continue
453  }
454  if {$t == "type"} { incr i ; set elt(type) [ lindex $data $i] ; continue}
455  if {$t == "default"} { ;# Glaurung uses a default value that is > one word
456  incr i
457  set elt(default) [ lindex $data $i]
458  incr i
459  while { [ lsearch -exact $optionToken [ lindex $data $i]] == -1 && $i < $length } {
460  append elt(default) " " [ lindex $data $i]
461  incr i
462  }
463  incr i -1
464  continue
465  }
466  if {$t == "min"} { incr i ; set elt(min) [ lindex $data $i] ; continue}
467  if {$t == "max"} { incr i ; set elt(max) [ lindex $data $i] ; continue}
468  if {$t == "var"} {
469  incr i
470  set tmp [ lindex $data $i]
471  incr i
472  while { ([ lsearch -exact $optionToken [ lindex $data $i]] == -1 && $i < $length ) \
473  || [ lindex $data $i] == "var" } {
474  if {[ lindex $data $i] != "var" } {
475  append tmp " " [ lindex $data $i]
476  } else {
477  lappend elt(var) [list $tmp]
478  incr i
479  set tmp [ lindex $data $i]
480  }
481  incr i
482  }
483  lappend elt(var) [list $tmp]
484 
485  incr i -1
486  continue
487  }
488  }
489  lappend optList [array get elt]
490  }
491 
492  # sort list of options so that important ones come first
493  set tmp $optList
494  set optList {}
495  foreach l $tmp {
496  array set elt $l
497  if { [ lsearch $optionImportant $elt(name)] != -1 } {
498  lappend optList $l
499  }
500  }
501  foreach l $tmp {
502  array set elt $l
503  if { [ lsearch $optionImportant $elt(name)] == -1 } {
504  lappend optList $l
505  }
506  }
507 
508  set optnbr 0
509  ttk::frame $w.fopt
510  ttk::frame $w.fbuttons
511 
512  set row 0
513  set col 0
514  set isImportantParam 1
515  foreach l $optList {
516  array set elt $l
517  set name $elt(name)
518  if { [ lsearch $optionImportant $elt(name)] == -1 && $isImportantParam } {
519  set isImportantParam 0
520  incr row
521  set col 0
522  }
523  if {$elt(name) == "MultiPV"} { set name $::tr(MultiPV)}
524  if {$elt(name) == "Hash"} { set name $::tr(Hash)}
525  if {$elt(name) == "OwnBook"} { set name $::tr(OwnBook)}
526  if {$elt(name) == "BookFile"} { set name $::tr(BookFile)}
527  if {$elt(name) == "UCI_LimitStrength"} { set name $::tr(LimitELO)}
528 
529  if { $col > 3 } { set col 0 ; incr row}
530  if {$elt(default) != ""} {
531  set default "\n($elt(default))"
532  } else {
533  set default ""
534  }
535  set value $elt(default)
536  # find the name in oldOptions (the previously saved data)
537  foreach old $oldOptions {
538  if {[lindex $old 0] == $elt(name)} {
539  set value [lindex $old 1]
540  break
541  }
542  }
543  if { $elt(type) == "check"} {
544  ttk::checkbutton $w.fopt.opt$optnbr -text "$name$default" -onvalue true -offvalue false -variable ::uci::check($optnbr)
545  set ::uci::check($optnbr) $value
546  grid $w.fopt.opt$optnbr -row $row -column $col -sticky w
547  }
548  if { $elt(type) == "spin"} {
549  ttk::label $w.fopt.label$optnbr -text "$name$default"
550  if { $elt(name) == "UCI_Elo" } {
551  ttk::spinbox $w.fopt.opt$optnbr -from $elt(min) -to $elt(max) -width 5 -increment 50 -validate all -validatecommand { regexp {^[0-9]+$} %P }
552  } else {
553  ttk::spinbox $w.fopt.opt$optnbr -from $elt(min) -to $elt(max) -width 5 -validate all -validatecommand { regexp {^[0-9]+$} %P }
554  }
555  $w.fopt.opt$optnbr set $value
556  grid $w.fopt.label$optnbr -row $row -column $col -sticky e
557  incr col
558  grid $w.fopt.opt$optnbr -row $row -column $col -sticky w
559  }
560  if { $elt(type) == "combo"} {
561  ttk::label $w.fopt.label$optnbr -text "$name$default"
562  set idx 0
563  set i 0
564  set tmp {}
565  foreach e $elt(var) {
566  lappend tmp [join $e]
567  if {[join $e] == $value} { set idx $i}
568  incr i
569  }
570  ttk::combobox $w.fopt.opt$optnbr -values $tmp
571 
572  $w.fopt.opt$optnbr current $idx
573  grid $w.fopt.label$optnbr -row $row -column $col -sticky e
574  incr col
575  grid $w.fopt.opt$optnbr -row $row -column $col -sticky w
576  }
577  if { $elt(type) == "button"} {
578  ttk::button $w.fopt.opt$optnbr -text "$name$default"
579  grid $w.fopt.opt$optnbr -row $row -column $col -sticky w
580  }
581  if { $elt(type) == "string"} {
582  ttk::label $w.fopt.label$optnbr -text "$name$default"
583  ttk::entry $w.fopt.opt$optnbr
584  $w.fopt.opt$optnbr insert 0 $value
585  grid $w.fopt.label$optnbr -row $row -column $col -sticky e
586  incr col
587  grid $w.fopt.opt$optnbr -row $row -column $col -sticky w
588  }
589  incr col
590  incr optnbr
591  }
592 
593  ttk::button $w.fbuttons.save -text $::tr(Save) -command {
594  ::uci::saveConfig
595  destroy .uciConfigWin
596  }
597  ttk::button $w.fbuttons.cancel -text $::tr(Cancel) -command "destroy .uciConfigWin"
598  pack $w.fbuttons.save $w.fbuttons.cancel -side left -expand yes -fill x -padx 20 -pady 2
599  pack $w.fopt -expand 1 -fill both
601  pack $w.fbuttons -expand 1 -fill both
602  bind $w <Return> "$w.fbuttons.save invoke"
603  bind $w <Escape> "destroy .uciConfigWin"
604  catch {grab .uciConfigWin}
605  }
606  ################################################################################
607  # will generate a list of list {{name}/value} pairs
608  ################################################################################
609  proc saveConfig {} {
610  global ::uci::optList ::uci::newOptions
611  set newOptions {}
612  set w .uciConfigWin.c.f
613  set optnbr 0
614 
615  foreach l $optList {
616  array set elt $l
617  set value ""
618  if { $elt(type) == "check"} {
619  set value $::uci::check($optnbr)
620  }
621  if { $elt(type) == "spin" || $elt(type) == "combo" || $elt(type) == "string" } {
622  set value [$w.fopt.opt$optnbr get]
623  }
624  if { $elt(type) != "button" } {
625  lappend newOptions [ list $elt(name) $value]
626  }
627  incr optnbr
628  }
629  if { $::uci::autoSaveOptions } {
631  set ::uci::autoSaveOptions 0
632  }
633  }
634  ################################################################################
635  # If the config window is called outside the engine dialog, save UCI options
636  # (only the UCI options dialog box is called
637  ################################################################################
638  proc writeOptions {} {
639  set elt [lindex $::engines(list) $::uci::autoSaveOptionsIndex]
640  set elt [ lreplace $elt 8 8 $::uci::newOptions]
641  set ::engines(list) [lreplace $::engines(list) $::uci::autoSaveOptionsIndex $::uci::autoSaveOptionsIndex $elt]
642 
644  }
645  ################################################################################
646  # The engine replied readyok, so it's time to configure it (sends the options to the engine)
647  # It seems necessary to ask first if engine is ready
648  ################################################################################
649  proc sendUCIoptions { n uci_options} {
650  global analysis
651  foreach opt $uci_options {
652  set name [lindex $opt 0]
653  set value [lindex $opt 1]
654  set analysis(waitForReadyOk$n) 1
655  ::sendToEngine $n "isready"
656  vwait analysis(waitForReadyOk$n)
657  ::sendToEngine $n "setoption name $name value $value"
658  if { $name == "MultiPV" } { set analysis(multiPVCount$n) $value}
659  }
660  }
661  ################################################################################
662  # will start an engine for playing (not analysis)
663  ################################################################################
664  proc startEngine {index n} {
665  global ::uci::uciInfo
666  resetUciInfo $n
667  set uciInfo(pipe$n) ""
668  set uciInfo(seen$n) 0
669  set uciInfo(uciok$n) 0
670  ::resetEngine $n
671  set engineData [lindex $::engines(list) $index]
672  set analysisName [lindex $engineData 0]
673  set analysisCommand [ toAbsPath [lindex $engineData 1]]
674  set analysisArgs [lindex $engineData 2]
675  set analysisDir [ toAbsPath [lindex $engineData 3]]
676 
677  # If the analysis directory is not current dir, cd to it:
678  set oldpwd ""
679  if {$analysisDir != "."} {
680  set oldpwd [pwd]
681  catch {cd $analysisDir}
682  }
683 
684  # Try to execute the analysis program:
685  if {[catch {set uciInfo(pipe$n) [open "| [list $analysisCommand] $analysisArgs" "r+"]} result]} {
686  if {$oldpwd != ""} { catch {cd $oldpwd}}
687  tk_messageBox -title "Scid: error starting engine" -icon warning -type ok \
688  -message "Unable to start the program:\n$analysisCommand"
689  return 1
690  }
691 
692  set ::analysis(index$n) $index
693  set ::analysis(pipe$n) $uciInfo(pipe$n)
694 
695  # Return to original dir if necessary:
696  if {$oldpwd != ""} { catch {cd $oldpwd}}
697 
698  fconfigure $uciInfo(pipe$n) -buffering line -blocking 0
699  fileevent $uciInfo(pipe$n) readable "::uci::processAnalysisInput $n 0"
700 
701  # wait a few seconds to be sure the engine had time to start
702  set counter 0
703  while {! $::uci::uciInfo(uciok$n) && $counter < 50 } {
704  incr counter
705  update
706  after 100
707  }
708  return 0
709  }
710  ################################################################################
711  #
712  ################################################################################
713  proc sendToEngine {n text} {
714  logEngine $n "Scid : $text"
715  catch {puts $::uci::uciInfo(pipe$n) $text}
716  }
717  ################################################################################
718  # returns 0 if engine died abruptly or 1 otherwise
719  ################################################################################
720  proc checkEngineIsAlive { n } {
721  global ::uci::uciInfo
722  if { $uciInfo(pipe$n) == "" } { return 0}
723  if {[eof $uciInfo(pipe$n)]} {
724  fileevent $uciInfo(pipe$n) readable {}
725  set exit_status 0
726  if {[catch {close $uciInfo(pipe$n)} standard_error] != 0} {
727  global errorCode
728  if {"CHILDSTATUS" == [lindex $errorCode 0]} {
729  set exit_status [lindex $errorCode 2]
730  }
731  }
732  set uciInfo(pipe$n) ""
733  if { $exit_status != 0 } {
734  logEngineNote $n {Engine terminated with exit code $exit_status: "\"$standard_error\""}
735  tk_messageBox -type ok -icon info -parent . -title "Scid" \
736  -message "The analysis engine terminated with exit code $exit_status: \"$standard_error\""
737  } else {
738  logEngineNote $n {Engine terminated without exit code: "\"$standard_error\""}
739  tk_messageBox -type ok -icon info -parent . -title "Scid" \
740  -message "The analysis engine terminated without exist code: \"$standard_error\""
741  }
742  return 0
743  }
744  return 1
745  }
746  ################################################################################
747  # close the engine
748  # It may be not an UCI one (if the user made an error, trying to configure an xboard engine)
749  ################################################################################
750  proc closeUCIengine { n { uciok 1 } } {
751  global windowsOS ::uci::uciInfo
752 
753  set pipe $uciInfo(pipe$n)
754  # Check the pipe is not already closed:
755  if {$pipe == ""} { return}
756 
757  after cancel "::uci::closeUCIengine $n 0"
758  fileevent $pipe readable {}
759 
760  if {! $uciok } {
761  tk_messageBox -title "Scid: error closing UCI engine" \
762  -icon warning -type ok -message "Not an UCI engine"
763  }
764 
765  # Some engines in analyze mode may not react as expected to "quit"
766  # so ensure the engine exits analyze mode first:
767  catch { puts $pipe "stop" ; puts $pipe "quit"}
768  #in case an xboard engine
769  catch { puts $pipe "exit" ; puts $pipe "quit"}
770 
771  # last resort : try to kill the engine (TODO if Windows : no luck, welcome zombies !)
772  # No longer try to kill the engine as :
773  # - it does not work on Windows
774  # - Rybka MP uses processes instead of threads : killing the main process will leave the children processes running
775  # - engines should normally exit
776  # if { ! $windowsOS } { catch { exec -- kill -s INT [ pid $pipe ] } }
777 
778  catch { flush $pipe}
779  catch { close $pipe}
780  set uciInfo(pipe$n) ""
781  }
782  ################################################################################
783  # UCI moves use long notation
784  # returns 1 if an error occurred when entering a move
785  ################################################################################
786  proc sc_move_add { moves } {
787 
788  foreach m $moves {
789  # get rid of leading piece
790  set c [string index $m 0]
791  if {$c == "K" || $c == "Q" || $c == "R" || $c == "B" || $c == "N"} {
792  set m [string range $m 1 end]
793  }
794  set s1 [string range $m 0 1]
795  set s1 [::board::sq $s1]
796  set s2 [string range $m 2 3]
797  set s2 [::board::sq $s2]
798  if {[string length $m] > 4} {
799  set promo [string range $m 4 end]
800  # inverse transformation : const char PIECE_CHAR [] = "xKQRBNP.xkqrbnpxMm";
801  # it seems capitalisation does not matter (see addMove proc in main.tcl)
802  switch -- $promo {
803  q { set p 2}
804  r { set p 3}
805  b { set p 4}
806  n { set p 5}
807  default {puts "Promo error $promo for moves $moves"}
808  }
809  if { [catch { sc_move add $s1 $s2 $p}] } { return 1}
810  } else {
811  if { [catch { sc_move add $s1 $s2 0}] } { return 1}
812  }
813  }
814  return 0
815  }
816  ################################################################################
817  #make UCI output more readable (b1c3 -> Nc3)
818  ################################################################################
819  proc formatPv { moves { fen "" } } {
820  # Push a temporary copy of the current game:
821  if {$fen != ""} {
822  sc_game push
823  sc_game startBoard $fen
824  } else {
825  sc_game push copyfast
826  }
827  set tmp ""
828  foreach m $moves {
829  if { [sc_move_add $m] == 1 } { break}
830  set prev [sc_game info previousMoveNT]
831  append tmp " $prev"
832  }
833  set tmp [string trim $tmp]
834 
835  # Pop the temporary game:
836  sc_game pop
837 
838  return $tmp
839  }
840  ################################################################################
841  # Format a line starting at current position where <moves> were played (in San notation)
842  ################################################################################
843  proc formatPvAfterMoves { played_moves moves } {
844  sc_game push copyfast
845  sc_move addSan $played_moves
846 
847  set tmp ""
848  foreach m $moves {
849  if { [sc_move_add $m] == 1 } {
850  break
851  }
852  set prev [sc_game info previousMoveNT]
853  append tmp " $prev"
854  }
855  set tmp [string trim $tmp]
856 
857  # Pop the temporary game:
858  sc_game pop
859 
860  return $tmp
861  }
862 }
863 ###
864 ### End of file: uci.tcl
865 ###