1###
2### uci.tcl: part of Scid.
3### Copyright (C) 2007  Pascal Georges
4###
5######################################################################
6### add UCI engine support
7
8namespace 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} {
239                    updateAnalysisText $n
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} {
327            updateAnalysisText $n
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
341            uciConfigWin
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 }
404        win::createDialog $w
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
600        addHorizontalRule $w
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 } {
630            writeOptions
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
643        ::enginelist::write
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###
866