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