Hi Everyone,
Uwe Klimmek has been messing with the tournament feature,
and has made some interesting changes. He sums them up:
> - Improved layout
> - Saving and loading tournaments
> - A slipping system (Rutschsystem, carousel) for a better round system.
> Perhaps you know that from Arena.
> - Automated adaptation of engine selection fields.
> - Selection of the games to be played. This way you can also play a
> tournament in parallel by starting 2x scidvspc.
> - Saving the used time
> - Interrupt and resume a tournament after a game.
>
I also note he has added a handy status bar, made losing on time a lot
harder, and removed my ugly "12 engine" limit.
I'm not sure how much of the changes i will adopt (A couple of them at
least, and maybe all) but users can try out his version by replacing
comp.tcl in scid.gui with the attached version, or by recompiling with it.
In the tournament, if you select "Save Move Time", you'll also have to make
this 1 line change in tcl/tools/graphs.tcl (though this has been fixed in
subversion).
if {$type == "Score Combo" && [llength $scoreValues] < 5} {
> # Scale to emt yaxis , as no Scores
> # ... or we *could* use a "total time" scale if we recalculate
> yticks, hlines
> set scale [expr $maxEmt / $maxLines]
> } else {
> set scale [expr $max / $maxLines]
> }
> + *if { $maxLines <= 0 } { set scale 0 }*
> set scaledWhiteValues {}
> set scaledBlackValues {}
>
Uwe has also been making some ttk (Themed Tk) changes to mainline Scid, and
also updated Scid's Spelling (with Player data and ratings) , Rating and
Photo files.
A new (derived) spelling.ssp is now in our subversion, or you can download
his files from https://sourceforge.net/projects/scid/files/Player%20Data/
Cheers, Steve
### Computer Tournament
###
### comp.tcl: part of Scid.
### Copyright (C) 2010- Steven Atkinson
### Copyright (C) 2018 Uwe Klimmek
# Credit to Fulvio for a few lines of UCI code that enabled me
# to make this run nicely (without constantly reseting analysis),
# and gave me impetus for a decent control structure using
# semaphores/vwait instead of the often abused dig-deeper procedural flow
# sometimes evident in tcl programs.
##################################
set comp(showscores) 1
set comp(showtimes) 1
set comp(showemt) 1
set comp(base) 4
set comp(playing) 0
set comp(endaftergame) 0
set comp(current) 1
set comp(lastgame) 2
set comp(games) {}
set comp(count) 2 ; # number of computer players
set comp(start) 0 ; # "Start at position" radiobutton
set comp(delta) 10000; # 10 seconds is the time
set comp(permoveleeway) 1.75 ;# 175% is the max allowed
set comp(name) "Scid Engine Tournament"
set comp(carousel) 1
set comp(site) "SCID"
set comp(seconds) 180
set comp(timecontrol) pergame
set comp(minutes) 1
set comp(incr) 0
set comp(timeout) 0 ;# disabled by default
set comp(rounds) 2
set comp(showclock) 1
set comp(debug) 0 ; # print info to console
set comp(animate) 0
set comp(firstonly) 0
set comp(ponder) 0
set comp(usebook) 0
set comp(book) {}
set comp(players) {}
set comp(playernames) {}
set comp(statustext) ""
proc storeTimeComment { color } {
set sec [::gameclock::getSec $color]
set h [format "%d" [expr abs($sec) / 60 / 60] ]
set m [format "%02d" [expr (abs($sec) / 60) % 60] ]
set s [format "%02d" [expr abs($sec) % 60] ]
set time "$h:$m:$s"
#Replace %clk if present, otherwise prepend it
if {[regsub {\[%clk\s*.*?\]} [sc_pos getComment] "\[%clk $time\]" comment]} {
sc_pos setComment "$comment"
} else {
sc_pos setComment "\[%clk $time\]$comment"
}
}
proc storeEmtComment { h m s } {
set time "[format "%d" $h]:[format "%02d" $m]:[format "%02d" $s]"
#Replace %emt if present, otherwise prepend it
if {[regsub {\[%emt\s*.*?\]} [sc_pos getComment] "\[%emt $time\]" comment]} {
sc_pos setComment "$comment"
} else {
sc_pos setComment "\[%emt $time\]$comment"
}
}
### Non-transient options are set in start.tcl
proc calcGames {} {
global comp
if { $comp(firstonly)} {
set comp(lastgame) [expr {($comp(count)-1) * $comp(rounds)}]
} else {
set comp(lastgame) [expr {$comp(count) * ($comp(count)-1) * $comp(rounds) / 2}]
}
}
proc compInit { } {
global analysis comp engines
set w .comp
if {[winfo exists $w]} {
raiseWin $w
return
}
toplevel $w
wm state $w withdrawn
wm title $w "Configure Tournament"
setWinLocation $w
grid [ttk::labelframe $w.engines -text Engines] -row 0 -column 0 -rowspan 3 -sticky nswe -padx "0 10"
grid [ttk::labelframe $w.tournament -text Tournament] -row 0 -column 1 -sticky nswe
grid [ttk::labelframe $w.time -text {Time Control}] -row 1 -column 1 -sticky nswe -pady 5
grid [ttk::labelframe $w.config -text Setup] -row 2 -column 1 -sticky nswe
grid [ttk::frame $w.buttons] -row 4 -column 0 -columnspan 2 -sticky we -pady "5 0"
grid [ttk::frame $w.games] -row 3 -column 0 -sticky nswe
### Engines
grid [ttk::frame $w.engines.top] -sticky nw -pady "0 5"
ttk::button $w.engines.top.engAdd -text " + " -width 0 -command { if { $comp(count) < [llength $engines(list)] } {
incr comp(count)
drawCombos
calcGames
set comp(current) 1
.comp.engines.top.engSub configure -state enable
if { $comp(count) == [llength $engines(list)] } { .comp.engines.top.engAdd configure -state disabled }
}
}
ttk::button $w.engines.top.engSub -text " - " -width 0 -command { if { $comp(count) > 2 } {
incr comp(count) -1
drawCombos
calcGames
set comp(current) 1
.comp.engines.top.engAdd configure -state enable
if { $comp(count) == 2 } { .comp.engines.top.engSub configure -state disabled }
}
}
ttk::button $w.engines.top.eng2 -text " 2 " -width 0 -command { set comp(count) 2
drawCombos
calcGames
set comp(current) 1
.comp.engines.top.engSub configure -state disabled
.comp.engines.top.engAdd configure -state enabled
}
ttk::button $w.engines.top.engAll -text "All" -width 0 -command { set comp(count) [llength $engines(list)]
drawCombos
calcGames
set comp(current) 1
.comp.engines.top.engSub configure -state enable
.comp.engines.top.engAdd configure -state disabled
}
pack $w.engines.top.eng2 $w.engines.top.engSub $w.engines.top.engAdd $w.engines.top.engAll -side left -padx 5
set comp(endaftergame) 0
set comp(countcombos) $comp(count)
drawCombos
### Config widgets
set row 0
ttk::label $w.tournament.eventlabel -text "Event:"
ttk::entry $w.tournament.evententry -width 26 -textvariable comp(name)
grid $w.tournament.eventlabel -row $row -column 0 -sticky w -pady 2
grid $w.tournament.evententry -row $row -column 1 -sticky we -pady 2 -columnspan 3
incr row
ttk::label $w.tournament.sitelabel -text "Site:"
ttk::entry $w.tournament.siteentry -width 26 -textvariable comp(site)
grid $w.tournament.sitelabel -row $row -column 0 -sticky w -pady 2
grid $w.tournament.siteentry -row $row -column 1 -sticky we -pady 2 -columnspan 3
incr row
ttk::frame $w.tournament.firstonlyvalue
ttk::label $w.tournament.firstonlyvalue.0 -text "Type:"
ttk::radiobutton $w.tournament.firstonlyvalue.1 -text {Round-Robin} -variable comp(firstonly) -value 0 -command "calcGames"
ttk::radiobutton $w.tournament.firstonlyvalue.2 -text {Gauntlet} -variable comp(firstonly) -value 1 -command "calcGames"
pack $w.tournament.firstonlyvalue.0 $w.tournament.firstonlyvalue.1 $w.tournament.firstonlyvalue.2 -side left -padx "0 5"
grid $w.tournament.firstonlyvalue -row $row -column 0 -pady 2 -sticky w -columnspan 4
incr row
ttk::label $w.tournament.roundslabel -text "Rounds:"
ttk::label $w.tournament.aktlabel -text "Next game:"
ttk::label $w.tournament.roundsakt -textvar comp(current)
ttk::spinbox $w.tournament.roundsvalue -textvariable comp(rounds) -from 1 -to 10 -width 3 -command "calcGames"
grid $w.tournament.roundslabel -row $row -column 0 -sticky w -pady 2
grid $w.tournament.roundsvalue -row $row -column 1 -pady 2 -sticky w
grid $w.tournament.aktlabel -row $row -column 2 -sticky w -pady 2
grid $w.tournament.roundsakt -row $row -column 3 -sticky w -pady 2
incr row
ttk::frame $w.time.control
ttk::label $w.time.control.0 -text {per}
ttk::radiobutton $w.time.control.1 -variable comp(timecontrol) -value pergame -text Game -command checkTimeControl
ttk::radiobutton $w.time.control.2 -variable comp(timecontrol) -value permove -text Move -command checkTimeControl
pack $w.time.control.0 $w.time.control.1 $w.time.control.2 -side left -padx "0 5"
grid $w.time.control -row $row -column 0 -columnspan 3 -sticky w -pady 2
incr row
ttk::frame $w.time.timegame
ttk::label $w.time.timegame.label -text "Time per\ngame"
ttk::spinbox $w.time.timegame.base -textvariable comp(base) -from 0 -to 7200 -incr 5 -width 4
ttk::label $w.time.timegame.label2 -text "Basic\nSec."
ttk::spinbox $w.time.timegame.incr -textvariable comp(incr) -from 0 -to 60 -width 4
ttk::label $w.time.timegame.label3 -text "Increment\nSec."
pack $w.time.timegame.label -side left -padx "0 5"
pack $w.time.timegame.base -side left
pack $w.time.timegame.label2 -side left -padx "0 5"
pack $w.time.timegame.label3 $w.time.timegame.incr -side right
grid $w.time.timegame -row $row -column 0 -columnspan 2 -sticky ew -pady 2
incr row
ttk::frame $w.time.timesecs
ttk::label $w.time.timesecs.label -text "Time per\nmove"
set tmp $comp(seconds)
ttk::spinbox $w.time.timesecs.value -textvariable comp(seconds) -from 1 -to 3600 -width 4
set comp(seconds) $tmp
ttk::label $w.time.timesecs.label2 -text "Sek."
pack $w.time.timesecs.label -side left -padx "0 5"
pack $w.time.timesecs.value $w.time.timesecs.label2 -side left
grid $w.time.timesecs -row $row -column 0 -columnspan 2 -sticky we -pady 2
incr row
ttk::frame $w.time.show
ttk::label $w.time.show.l -text "Store time: "
ttk::radiobutton $w.time.show.time0 -text "no" -variable comp(showtimes) -value 0
ttk::radiobutton $w.time.show.time1 -text "Clock" -variable comp(showtimes) -value 1
ttk::radiobutton $w.time.show.time2 -text "Move" -variable comp(showtimes) -value 2
pack $w.time.show.l -side left
pack $w.time.show.time0 $w.time.show.time1 $w.time.show.time2 -side left -padx "5 0"
grid $w.time.show -row $row -column 0 -sticky w -columnspan 3
incr row
ttk::checkbutton $w.config.showclockvalue -text {Show Clocks} -variable comp(showclock)
grid $w.config.showclockvalue -row $row -column 0 -sticky w
checkTimeControl
ttk::checkbutton $w.config.carousel -text "Carousel" -variable comp(carousel)
grid $w.config.carousel -row $row -column 1 -sticky w
ttk::checkbutton $w.config.pondervalue -text {Permanent Thinking} -variable comp(ponder)
grid $w.config.pondervalue -row $row -column 2 -sticky w
incr row
ttk::checkbutton $w.config.animatevalue -text {Animate Moves} -variable comp(animate)
grid $w.config.animatevalue -row $row -column 0 -sticky w
ttk::checkbutton $w.config.scorevalue -text {Engine Scores as Comments} -variable comp(showscores)
grid $w.config.scorevalue -row $row -column 1 -columnspan 2 -sticky w
### Opening Book
incr row
ttk::frame $w.config.book
ttk::checkbutton $w.config.book.value -variable comp(usebook) -textvar ::tr(UseBook)
set bookPath $::scidBooksDir
set bookList [ lsort -dictionary [ glob -nocomplain -directory $bookPath *.bin ] ]
set tmp {}
ttk::combobox $w.config.book.combo -width 12
if { [llength $bookList] == 0 } {
$w.config.book.value configure -state disabled
set comp(usebook) 0
} else {
set i 0
set idx 0
foreach file $bookList {
lappend tmp [file tail $file]
if { $comp(book) == [file tail $file]} {
set idx $i
}
incr i
}
$w.config.book.combo configure -values $tmp
$w.config.book.combo current $idx
}
grid $w.config.book -row $row -column 0 -columnspan 3 -sticky w
pack $w.config.book.combo $w.config.book.value -side right -padx "0 5"
label $w.games.aktstart -text "Games "
label $w.games.aktend -text " - "
ttk::spinbox $w.games.roundakt -textvariable comp(current) -from 1 -to 999 -width 3
ttk::spinbox $w.games.roundend -textvariable comp(lastgame) -from 1 -to 999 -width 3
pack $w.games.aktstart $w.games.roundakt $w.games.aktend $w.games.roundend -side left
### OK, Cancel Buttons
ttk::button $w.buttons.cancel -text $::tr(Cancel) -command compClose
set starttext "Start"
ttk::button $w.buttons.ok -text $starttext -command "startComp; compOk"
ttk::button $w.buttons.save -text Save -command "startComp; compSave"
ttk::button $w.buttons.load -text Load -command "loadComp"
focus $w.buttons.ok
packbuttons right $w.buttons.cancel $w.buttons.ok $w.buttons.save $w.buttons.load -anchor e
bind $w <Configure> "recordWinSize $w"
wm protocol $w WM_DELETE_WINDOW compClose
wm resizable $w 0 0
bind $w <Escape> compClose
bind $w <F1> {helpWindow Tourney}
update
wm state $w normal
}
proc checkTimeControl {} {
set w .comp
if {$::comp(timecontrol) == "permove" } {
foreach i [winfo children $w.time.timesecs] {
$i configure -state normal
}
foreach i "[winfo children $w.time.timegame] $w.config.showclockvalue" {
$i configure -state disabled
}
} else {
foreach i [winfo children $w.time.timesecs] {
$i configure -state disabled
}
foreach i "[winfo children $w.time.timegame] $w.config.showclockvalue" {
$i configure -state normal
}
}
update
}
proc loadComp {} {
global comp engines scidConfigDir
set filename ""
set types { {{Scid Tournament Files} {.sto}} }
set filename [tk_getOpenFile -initialdir $scidConfigDir -initialfile $comp(name) -filetypes $types -defaultextension ".sto"]
if {$filename != ""} {
source $filename
}
set comp(players) {}
for {set i 0} {$i < $comp(count)} {incr i} {
set j 0
set found 0
foreach e $engines(list) {
if { [lindex $comp(playernames) $i] == [lindex $e 0] } {
lappend comp(players) $j
set found 1
break
}
incr j
}
if { ! $found } {
tk_messageBox -type ok -title {Scid: Fehler} -message "Engine [lindex $comp(playernames) $i] nicht gefunden."
return
}
}
.comp.config.book.combo set $comp(book)
compInit
createGames $comp(carousel)
destroy .comp.engines.list
drawCombos
}
proc createGames { carousel } {
global comp
### Place games in cue
set comp(games) {}
set gamesPerRound [expr int($comp(count)/2)]
set even [expr ($comp(count)+1) % 2]
set rounds [expr $comp(count) -1 ]
set last [expr $comp(count) -1 ]
set plist {}
set hlist {}
if { !$even } {
set last [expr $comp(count) -2 ]
set rounds $comp(count)
}
if { $carousel && ! $comp(firstonly) } {
for {set k 1} {$k <= $comp(rounds)} {incr k} {
for {set i 0} {$i < $comp(count)} {incr i} {
lappend plist $i
}
## Carousel or Rutschsystem
for {set l 1} {$l <= $rounds} {incr l} {
for {set i 0; set j $last } {$i < $gamesPerRound} {incr i; incr j -1 } {
if { $even && [expr ($l-1) % 2] } {
lappend comp(games) [list [lindex $comp(players) [lindex $plist $j]] [lindex $comp(players) [lindex $plist $i]] \
[lindex $comp(playernames) [lindex $plist $j]] [lindex $comp(playernames) [lindex $plist $i]] "$k.$l"]
lappend hlist [list [lindex $comp(players) [lindex $plist $i]] [lindex $comp(players) [lindex $plist $j]] \
[lindex $comp(playernames) [lindex $plist $i]] [lindex $comp(playernames) [lindex $plist $j]] "[expr $k+1].$l"]
} else {
lappend comp(games) [list [lindex $comp(players) [lindex $plist $i]] [lindex $comp(players) [lindex $plist $j]] \
[lindex $comp(playernames) [lindex $plist $i]] [lindex $comp(playernames) [lindex $plist $j]] "$k.$l"]
lappend hlist [list [lindex $comp(players) [lindex $plist $j]] [lindex $comp(players) [lindex $plist $i]] \
[lindex $comp(playernames) [lindex $plist $j]] [lindex $comp(playernames) [lindex $plist $i]] "[expr $k+1].$l"]
}
}
set help [lindex $plist end];
set xlist [linsert $plist $even $help]
set plist [lreplace $xlist end end]
}
incr k
if { $k <= $comp(rounds) } {
append comp(games) " $hlist"
set hlist {}
}
}
} else {
for {set k 1} {$k <= $comp(rounds)} {incr k} {
for {set i 0} {$i < $comp(count)} {incr i} {
for {set j [expr $i + 1]} { $j < $comp(count) } {incr j} {
lappend comp(games) [list [lindex $comp(players) $i] [lindex $comp(players) $j] [lindex $comp(playernames) $i] [lindex $comp(playernames) $j] $k]
lappend hlist [list [lindex $comp(players) $j] [lindex $comp(players) $i] [lindex $comp(playernames) $j] [lindex $comp(playernames) $i] [expr $k+1]]
}
if {$comp(firstonly)} {break}
}
incr k
if { $k <= $comp(rounds) } {
append comp(games) " $hlist"
set hlist {}
}
}
}
}
proc startComp { } {
global analysis comp engines
set comp(startfen) [sc_pos fen]
# make sure decimals have a leading 0
catch {
set comp(incr) [expr $comp(incr)]
set comp(base) [expr $comp(base)]
set comp(seconds) [expr $comp(seconds)]
}
if {$comp(count) != $comp(countcombos)} {
drawCombos
return
}
set comp(players) {} ;# to remember which engines are selected between widget restarts
set comp(playernames) {}
set names {}
set comp(book) [.comp.config.book.combo get]
if {$comp(timecontrol) == "permove"} {
set comp(time) [expr {int($comp(seconds) * 1000)}]
puts "Move delay is $comp(time) milliseconds"
}
for {set i 0} {$i < $comp(count)} {incr i} {
set j [.comp.engines.list.$i.combo current]
lappend comp(players) $j
lappend comp(playernames) [lindex [lindex $engines(list) $j] 0]
}
if {$comp(firstonly)} {
set comp(firstN) [lindex $comp(players) 0]
}
### Check players are unique
if {[llength [lsort -unique $comp(players)]] != $comp(count)} {
tk_messageBox -type ok -parent .comp -title {Scid: error} \
-message {Duplicate engines not supported}
return
}
createGames $comp(carousel)
}
proc toggleEndAfterGame {} {
global comp
if { $comp(endaftergame) } {
set comp(endaftergame) 0
.comp.buttons.save state !pressed
} else {
set comp(endaftergame) 1
.comp.buttons.save state pressed
}
}
proc compOk {} {
global analysis comp engines
if {[winfo exists .analysisWin1]} "destroy .analysisWin1"
if {[winfo exists .analysisWin2]} "destroy .analysisWin2"
## wait short for cleanup the engine analysis windows
set ::wfwd 0
after 800 set ::wfwd 1
vwait ::wfwd
set w .comp
if {[sc_base isReadOnly]} {
set answer [tk_messageBox -title Tournanment -icon question -type okcancel \
-message {Database is read only. Continue ?} -parent $w]
if {$answer != "ok"} {return}
}
if {![sc_pos isAt end] && $comp(start) > 0} {
set answer [tk_messageBox -title Tournanment -icon question -type okcancel \
-message {Current game is not at end of game. Continue ?} -parent $w]
if {$answer != "ok"} {return}
}
### Reconfigure init widget for pausing
for {set i 0} {$i < $comp(count)} {incr i} {
$w.engines.list.$i.combo configure -state disabled ; # disable widgets too
$w.engines.list.$i.configure configure -state disabled
}
foreach j {.comp.config .comp.engines .comp.engines.top .comp.time.control .comp.time.timesecs .comp.time.timegame .comp.config.book .comp.tournament} {
foreach i [winfo children $j] {
catch {$i configure -state disabled}
}
}
$w.buttons.ok configure -text Pause -command compPause -state normal
$w.buttons.save configure -text "Stop after\nthis game" -command "toggleEndAfterGame" -state normal
pack forget $w.buttons.load
$w.buttons.cancel configure -text {Cancel} -command compAbort -state normal
wm title $w "$::scidName Tournament"
focus $w.buttons.ok
### Clocks
set row 4
if {$comp(showclock) && $comp(timecontrol) == "pergame"} {
ttk::frame $w.clocks
grid $w.clocks -column 0 -row $row -rowspan 2 -sticky nswe
::gameclock::new $w.clocks 1 120 1 horizontal digital
::gameclock::new $w.clocks 2 120 1 horizontal digital
::gameclock::setColor 1 white
::gameclock::setColor 2 black
pack forget $w.clocks.clock1 $w.clocks.clock2
pack $w.clocks.clock1 $w.clocks.clock2 -side left -fill x -padx "0 10"
}
### Extra decision buttons
ttk::frame $w.say
grid $w.say -column 1 -row 3 -sticky e
ttk::button $w.say.white -text "$::tr(White) wins" -command {compGameEnd 1}
ttk::button $w.say.draw -text "$::tr(Draw)" -command {compGameEnd =}
ttk::button $w.say.black -text "$::tr(Black) wins" -command {compGameEnd 0}
pack $w.say.white $w.say.draw $w.say.black -side left -padx 5 -pady "5 0"
# This is no longer reliable because of comp(firstonly) option
# set num_games [expr {$comp(count) * ($comp(count)-1) * $comp(rounds) / 2}]
set num_games [llength $comp(games)]
puts "$num_games GAMES total: $comp(games)"
ttk::progressbar $w.progress -mode determinate \
-maximum $num_games -variable comp(current)
grid $w.progress -column 0 -row 6 -columnspan 2 -sticky we
ttk::label $w.status -textvariable comp(statustext)
grid $w.status -column 0 -row 7 -columnspan 2 -sticky we
### Play games
set comp(statustext) ""
set thisgame [lindex $comp(games) [expr $comp(current) - 1]]
while {$thisgame != {} } {
set n [lindex $thisgame 0]
set m [lindex $thisgame 1]
set name1 [lindex $thisgame 2]
set name2 [lindex $thisgame 3]
set k [lindex $thisgame 4]
if {$n != {} && $m != {}} {
append comp(statustext) "\nGame $comp(current): $name1 - $name2"
compNM $n $m $k
set res [sc_game tags get Result]
switch -- $res {
0 { set res "Black wins" }
1 { set res "White wins" }
= { set res "Draw" }
* { set res "Canceled" }
}
set res1 [sc_pos getComment]
set wbt [string first "wtime" $res1]
if { $wbt >= 0 } { set res1 [string range $res1 0 [expr $wbt-1]] }
set comp(statustext) "Result $name1 - $name2: $res $res1"
incr comp(current)
## wait short for cleanup the engine analysis windows
set ::wfwd 0
after 800 set ::wfwd 1
vwait ::wfwd
}
if { $comp(endaftergame) } { toggleEndAfterGame; compSave; break }
set thisgame [lindex $comp(games) [expr $comp(current) - 1]]
if { $thisgame != {} && $comp(current) > $comp(lastgame)} { compSave; break }
}
### Comp over
if { $comp(current) > $comp(lastgame)} {
append comp(statustext) "\nComp finished"
set comp(current) $comp(lastgame)
pack forget $w.buttons.save
} else {
append comp(statustext) "\nComp paused"
$w.buttons.save configure -text [tr Continue] -state normal -command {
compDestroy
update
compInit
startComp
compOk
}
}
if {[winfo exists .comp]} {
# voodoo that you do
wm geometry .comp [wm geometry .comp]
# Hmm - if we leave this window open , and run F2 (say) the engines can sometimes stop working
# So better make sure this window gets closed
.comp.buttons.ok configure -text [tr Restart] -command {
compDestroy
update
compInit
set comp(current) 1
}
.comp.buttons.cancel configure -text [tr Close] -command {
if { $comp(current) >= $comp(lastgame) } { set comp(current) 1 }
compDestroy
}
foreach i [winfo children $w.say] {
catch {$i configure -state disabled}
}
raiseWin .comp
}
}
proc compNM {n m k} {
global analysis comp ::uci::uciInfo
set comp(result) {}
if {$comp(timecontrol) == "pergame"} {
set comp(wtime) [expr int($comp(base)*1000)]
set comp(btime) [expr int($comp(base)*1000)]
set total [expr int($comp(base))]
set mins [expr $total/60]
set secs [expr $total%60]
if {$secs == 0} {
set timecontrol $mins
} else {
if {$secs < 10} {
set secs "0$secs"
}
set timecontrol $mins:$secs
}
if {$comp(showclock) && $comp(timecontrol) == "pergame"} {
::gameclock::setSec 1 [ expr -int($comp(base)) ]
::gameclock::setSec 2 [ expr -int($comp(base)) ]
}
}
sc_game new
if {($comp(start) == 1 && $comp(current) == 1) || $comp(start) == 2} {
sc_game startBoard $comp(startfen)
set comp(startpos) "fen $comp(startfen)"
} else {
set comp(startpos) startpos
}
set comp(playing) 1
set comp(paused) 0
set comp(white) $n
set comp(fen) {}
## add 50ms to avoid lose on time
set incr [expr int($comp(incr) * 1000) + 50]
set comp(inbook) $comp(usebook)
if {[winfo exists .analysisWin$n]} "destroy .analysisWin$n"
if {[winfo exists .analysisWin$m]} "destroy .analysisWin$m"
### makeAnalysisWin creates a toplevel widget to run an engine but we don't really need a toplevel %<
# The problem is that only UCI has a procedure for running an engine without a toplevel (uci::startSilentEngine).
# There is currently no equivalent for xboard engines
makeAnalysisWin $n
if {![winfo exists .analysisWin$n]} {
set comp(games) {}
return
}
## ToDo
## remove button bar of analysis window to avoid misuse
pack forget .analysisWin$n.b1
toggleMovesDisplay $n
makeAnalysisWin $m
if {![winfo exists .analysisWin$m]} {
set comp(games) {}
return
}
pack forget .analysisWin$m.b1
toggleMovesDisplay $m
# Stop all engines
# (Is this necessary ? We are sending a "stop" straight away. &&&)
if {$analysis(analyzeMode$n)} { toggleEngineAnalysis $n }
if {$analysis(analyzeMode$m)} { toggleEngineAnalysis $m }
sc_game tags set -white $analysis(name$n)
sc_game tags set -black $analysis(name$m)
sc_game tags set -event $comp(name)
sc_game tags set -site $comp(site)
set lextra [list "Time \"[clock format [clock seconds] -format %T]\"\n"]
if {$comp(timecontrol) == "permove"} {
lappend lextra "Movetime \"$comp(seconds)\""
sc_game tags set -date [::utils::date::today] -round $k -extra $lextra
} else {
lappend lextra "TimeControl \"$timecontrol/$comp(incr)\""
sc_game tags set -date [::utils::date::today] -round $k -extra $lextra
}
if {$comp(firstonly)} {
# Show games from first players view
if {( $n == $comp(firstN) && [::board::isFlipped .main.board] ) || \
( $m == $comp(firstN) && ![::board::isFlipped .main.board] ) } {
::board::flip .main.board
}
}
update idletasks
updateBoard -pgn
updateTitle
update
### Thanks to HGM and Talkchess for help with UCI/Xboard protocols
### Initialisation
foreach current_engine "$n $m" {
### don't display engine output
set analysis(movesDisplay$current_engine) 2
toggleMovesDisplay $current_engine
if { $analysis(uci$current_engine)} {
### UCI initialisation
# fulvio issues isready every move ??
set analysis(waitForReadyOk$current_engine) 1
sendToEngine $current_engine ucinewgame
if {$comp(ponder)} {
sendToEngine $current_engine "setoption name Ponder value true"
} else {
sendToEngine $current_engine "setoption name Ponder value false"
}
sendToEngine $current_engine "isready"
vwait analysis(waitForReadyOk$current_engine)
# if {!$comp(playing)} {break}
# sendToEngine $current_engine {debug off}
} else {
### xboard initialisation
sendToEngine $current_engine xboard
if {!$analysis(seen$current_engine)} {
vwait analysis(seen$current_engine)
}
sendToEngine $current_engine "protover 2"
if {$comp(ponder)} {
sendToEngine $current_engine "hard"
} else {
sendToEngine $current_engine "easy"
}
sendToEngine $current_engine "bk off"
# done later
# sendToEngine $current_engine "st $comp(seconds)"
### Hacks
if {0} {
# Sjeng or Chen run too fast unless "hard" is issued
if {[regexp -nocase arasan $analysis(name$current_engine)]} {
sendToEngine $current_engine hard
}
if {[regexp -nocase sjeng $analysis(name$current_engine)]} {
sendToEngine $current_engine hard
}
if {[regexp -nocase xchen $analysis(name$current_engine)] || \
[regexp -nocase chenard $analysis(name$current_engine)] } {
sendToEngine $current_engine hard
}
}
### Setup initial position
if {$comp(startpos) == "startpos"} {
if {$current_engine == $m} {
# "new" command means "play black"
sendToEngine $current_engine "new"
}
} else {
## Don't test for setboard as Phalanx doest report this working feature
# if {$analysis(has_setboard$current_engine)}
sendToEngine $current_engine "setboard $comp(startfen)"
# Without force, some engines try to play both side !?
sendToEngine $current_engine "force"
}
### Send initial time control
if {$comp(timecontrol) == "permove"} {
sendToEngine $current_engine "st $comp(seconds)"
} else {
set secs [expr $comp(wtime)/1000]
set seconds [expr $secs%60]
if {$seconds < 10} {
set seconds 0$seconds
}
set mins [expr $secs/60]:$seconds
##Winboard supports sec only
set wincr [expr int($comp(incr) / 1)]
sendToEngine $current_engine "level 0 $mins $wincr"
### set search depth.
# "sd" is meant to be orthogonal to the "time" command, but the first engine i tested (zct) never came back
# While spike needs it set to something reasonable, otherwise it never plays a move
# but spike is uci too... hmmm
# sendToEngine $current_engine "sd 12"
}
}
}
if {[sc_pos side] == "white"} {
set current_engine $n
set other_engine $m
} else {
set current_engine $m
set other_engine $n
}
# Automatically set a timeout value &&&
if {$comp(timecontrol) == "permove"} {
# Automatically time-out comp in $movetime + 10 secs
after [expr {$comp(time) + $comp(delta)}] compTimeout
} else {
after [expr {$comp(wtime) + $comp(delta)}] compTimeout
}
### Main control loop
# Thanks to Fulvio for inspiration to rewrite this properly :>
while {$comp(playing)} {
# hmm... promo pieces are shown in uppercase, but this crashes some engines
# todo: make command "sc_game move uci"
set movehistory1 [sc_game moves coord]
set movehistory [string tolower $movehistory1]
if {$comp(showclock) && $comp(timecontrol) == "pergame"} {
if {$current_engine == $n} {
::gameclock::start 1
} else {
::gameclock::start 2
}
}
set comp(move) $current_engine
set comp(nextmove) $other_engine
set lastmove [lindex $movehistory end]
set comp(bookmove) {}
set comp(lasttime) [clock clicks -milli]
if {$analysis(uci$current_engine)} {
### UCI main loop
if {$comp(inbook) && $comp(book) != ""} {
catch {
# KomodoVariety.bin has bugs
set comp(bookmove) [::book::getMove $comp(book) [sc_pos fen] $::sergame::bookSlot]
}
if {$comp(bookmove) == ""} {
set comp(inbook) 0
}
}
if {$comp(bookmove) == ""} {
### position
set hit 0
if {$movehistory == {}} {
sendToEngine $current_engine "position $comp(startpos)"
} elseif {!$comp(ponder)} {
sendToEngine $current_engine "position $comp(startpos) moves $movehistory"
} else {
if {$uciInfo(ponder$current_engine) == $lastmove && $lastmove != {}} {
sendToEngine $current_engine "ponderhit"
set hit 1
} else {
if {[llength $movehistory] > 1 && $uciInfo(ponder$current_engine) != {}} {
sendToEngine $current_engine "stop"
set uciInfo(bestmove$current_engine) stop
}
sendToEngine $current_engine "position $comp(startpos) moves $movehistory"
}
}
### go
set comp(lasttime) [clock clicks -milli]
if {$comp(timecontrol) == "permove"} {
if {!$hit} {
sendToEngine $current_engine "go movetime $comp(time)"
}
} else {
if {!$hit} {
sendToEngine $current_engine "go wtime $comp(wtime) btime $comp(btime) winc $incr binc $incr"
}
}
# set analysis(fen$current_engine) [sc_pos fen]
set analysis(maxmovenumber$current_engine) 0
set analysis(waitForBestMove$current_engine) 1
vwait analysis(waitForBestMove$current_engine)
}
if {!$comp(playing)} {break}
} else {
### Xboard main loop
# Setup times
if {$comp(timecontrol) != "permove"} {
# Should we only send time, otim if has "time" as a feature &&&
if {$comp(white) == $current_engine} {
sendToEngine $current_engine "time [expr $comp(wtime)/10]"
sendToEngine $current_engine "otim [expr $comp(btime)/10]"
} else {
sendToEngine $current_engine "time [expr $comp(btime)/10]"
sendToEngine $current_engine "otim [expr $comp(wtime)/10]"
}
} else {
### permove time control doesn't need reissuing ?
# sendToEngine $current_engine "st $comp(seconds)"
}
# Setup move
set comp(lasttime) [clock clicks -milli]
set nummoves [llength $comp(fen)]
if {$nummoves == 0 } {
sendToEngine $current_engine "go"
} elseif {$nummoves == 1 && $comp(startpos) != "startpos"} {
sendToEngine $current_engine "setboard [sc_pos fen]"
sendToEngine $current_engine "go"
} else {
### Send the previous move to engine
# (protocol 2 can also use "usermove MOVE")
if {$lastmove != {}} {
sendToEngine $current_engine $lastmove
} ; # else "go" ?
}
vwait analysis(waitForBestMove$current_engine)
if {!$comp(playing)} {break}
}
set expired [expr [clock clicks -milli] - $comp(lasttime)]
if {$analysis(uci$other_engine) && $comp(ponder) && ($uciInfo(ponder$other_engine) != "")} {
### UCI other engine
# position
if {$movehistory != {}} {
### todo - verfiy this works all scenarios (ponder with non-standard start)
sendToEngine $other_engine "position $comp(startpos) moves $movehistory $uciInfo(ponder$other_engine)"
}
# go
if {$comp(timecontrol) == "permove"} {
sendToEngine $other_engine "go ponder movetime $comp(time)"
} else {
sendToEngine $other_engine "go ponder wtime $comp(wtime) btime $comp(btime) winc $incr binc $incr"
}
}
# Updating the board (via makeAnalysisMove) takes order of
# 1/40th second (23000microseconds) on my core2quad
if {[makeCompMove $current_engine]} {
### Store evaluation (and time)
if {$comp(showscores)} {
if {1} {
set comment $analysis(score$current_engine)
if {$comment != 0} {
if {$comment > 0} {
set comment +$comment
}
sc_pos setComment $comment
}
} else {
sc_pos setComment "\[%ms $expired\]\[%eval $analysis(score$current_engine)\]"
}
}
### Move success
after cancel compTimeout
if {$comp(showclock) && $comp(timecontrol) == "pergame"} {
if {$current_engine == $n} {
::gameclock::stop 1
} else {
::gameclock::stop 2
}
}
# Note - No time slice enforcement for permove time control
while {$comp(paused)} {
vwait comp(paused)
}
### Check if game is over
## Check for unsufficent material
set ps [lrange [split [sc_pos fen]] 0 0]
set pieces [string map { / "" 1 "" 2 "" 3 "" 4 "" 5 "" 6 "" 7 "" 8 "" } $ps ]
set anzPieces [string length $pieces]
if { $anzPieces < 5 } {
set lp [split $pieces ""]
set wbn [llength [lsearch -all -inline -regexp $lp "\[bn\]"]]
set bbn [llength [lsearch -all -inline -regexp $lp "\[BN\]"]]
if { $anzPieces < 3 || \
($anzPieces == 3 && ( $wbn == 1 || $bbn == 1)) ||
($anzPieces == 4 && $wbn == 1 && $bbn == 1) } {
sc_game tags set -result =
sc_pos setComment {Scid: Insufficient Material}
break
}
}
if {[sc_pos moves] == {}} {
if {![sc_pos isCheck]} {
### stalemate
sc_game tags set -result =
sc_pos setComment [tr stalemate]
} else {
### checkmate
if {[sc_pos side] == {black}} {
sc_game tags set -result 1
} else {
sc_game tags set -result 0
}
}
break
} else {
set elt [lrange [split [sc_pos fen]] 0 2]
# append the position only if different from the last element
if { $elt != [ lindex $comp(fen) end ] } {
lappend comp(fen) $elt
} else {
##ToDo
# sc_pos setComment "Double Fen: $::analysis(name$current_engine): $elt"
}
set fen [sc_pos fen]
if {[lindex $fen 4] > 99} {
sc_pos setComment "50 move rule"
sc_game tags set -result =
### draw
break
} elseif {[llength [lsearch -all $comp(fen) $elt]] >= 3 } {
# Could we use "sc_pos analyse" for 3 fold detection ?
sc_pos setComment "3 fold repetition"
sc_game tags set -result =
### draw
break
}
}
if {$comp(showtimes) == 2} {
set min 0
set sec [expr int($expired / 1000) ]
if { $sec > 59 } { set min [expr int($sec / 60) ]; set [expr $sec % 60 ] }
storeEmtComment 0 $min $sec
}
set ::analysis(moves$current_engine) ""
set ::analysis(moves$other_engine) ""
### Swap players
if {$current_engine != $n} {
if {$comp(timecontrol) == "pergame"} {
set comp(btime) [expr $comp(btime) - $expired]
if {$comp(btime) < 0} {
sc_game tags set -result 1
sc_pos setComment {Black loses on time}
break
}
# add time increment
if {$comp(bookmove) == ""} {
incr comp(btime) $incr
}
if {$comp(showclock) && $comp(timecontrol) == "pergame"} {
::gameclock::setSec 2 [ expr -int($comp(btime)/1000) ]
if {$comp(showtimes) == 1} { storeTimeComment 2 }
}
# In case white hangs, automatically time-out comp in $wtime + 2 secs
after [expr {$comp(wtime) + $comp(delta)}] compTimeout
} else {
if {$expired > $comp(permoveleeway)*$comp(time)} {
sc_game tags set -result 1
sc_pos setComment "Blacks move takes $expired secs"
break
}
# Automatically time-out comp in $movetime + 2 secs
after [expr {$comp(time) + $comp(delta)}] compTimeout
}
# Now its whites turn
set current_engine $n
set other_engine $m
} else {
if {$comp(timecontrol) == "pergame"} {
set comp(wtime) [expr $comp(wtime) - $expired]
if {$comp(wtime) < 0} {
sc_game tags set -result 0
sc_pos setComment {White loses on time}
break
}
# add time increment
if {$comp(bookmove) == ""} {
incr comp(wtime) $incr
}
if {$comp(showclock) && $comp(timecontrol) == "pergame"} {
::gameclock::setSec 1 [ expr -int($comp(wtime)/1000) ]
if {$comp(showtimes) == 1} { storeTimeComment 1 }
}
# In case black hangs, automatically time-out comp in $wtime + 2 secs
after [expr {$comp(btime) + $comp(delta)}] compTimeout
} else {
if {$expired > $comp(permoveleeway)*$comp(time)} {
sc_game tags set -result 0
sc_pos setComment "Whites move takes $expired secs"
break
}
# Automatically time-out comp in $movetime + 2 secs
after [expr {$comp(time) + $comp(delta)}] compTimeout
}
# Now its blacks turn
set current_engine $m
set other_engine $n
}
} else {
### Move failed... don't swap players
### Unlikely, but could happen
while {$comp(paused)} {
after cancel compTimeout
##ToDo
sc_pos setComment "Move failed:"
vwait comp(paused)
# todo - handle wtime/btime
if {$comp(timecontrol) == "pergame"} {
after [expr {$comp(time) + $comp(delta)}] compTimeout
}
}
}
} ; # end main control loop
### This game is over
after cancel compTimeout
if {$comp(showclock) && $comp(timecontrol) == "pergame"} {
::gameclock::stop 1
::gameclock::stop 2
}
### Save game
# Perhaps game has been adjudicated by user ?
if {$comp(result) != {}} {
sc_game tags set -result $comp(result)
}
puts "Game $n - $m is over. Result [sc_game tags get Result]"
if {$comp(timecontrol) == "pergame"} {
set comment [sc_pos getComment]
if {$comment == {}} {
sc_pos setComment "wtime $comp(wtime), btime $comp(btime)"
} else {
sc_pos setComment "$comment. wtime $comp(wtime), btime $comp(btime)"
}
}
if {![sc_base isReadOnly]} {
sc_game save [sc_game number]
::windows::gamelist::Refresh
::crosstab::Refresh
}
# ::pgn::Refresh 1
updateBoard -pgn
catch {destroy .analysisWin$n}
catch {destroy .analysisWin$m}
set comp(playing) 0
}
proc makeCompMove {current_engine} {
if {$::comp(bookmove) != {}} {
sc_move addSan $::comp(bookmove)
if {$::comp(animate)} {
updateBoard -pgn -animate
} else {
updateBoard -pgn
}
return 1
} else {
::tools::graphs::score::Refresh2
return [makeAnalysisMove $current_engine]
}
}
proc compPause {} {
global analysis comp engines
set w .comp
after cancel compTimeout
$w.buttons.ok configure -text Resume -command compResume
set comp(paused) 1
}
proc compResume {} {
global analysis comp engines
set w .comp
$w.buttons.ok configure -text Pause -command compPause
set comp(paused) 0
}
proc drawCombos {} {
global analysis comp engines
# Check number of engines is sane
if {![string is integer -strict $comp(count)] || $comp(count) < 2} {
set comp(count) 2
update
}
if {$comp(count) > [llength $engines(list)]} {
set comp(count) [llength $engines(list)]
update
}
set w .comp
set l $w.engines.list
if {[winfo exists $l]} {
# Remember current players
# ... before destroying widget (which is easiest)
set comp(players) {}
for {set i 0} {$i < $comp(count)} {incr i} {
catch {
lappend comp(players) [$l.$i.combo current]
}
}
destroy $l
}
grid [ttk::frame $l] -sticky n
set values {}
foreach e $engines(list) {
lappend values [lindex $e 0]
}
set col 0
set row 0
set maxr 11
set startpad 0
for {set i 0} {$i < $comp(count)} {incr i} {
ttk::frame $l.$i
# Only pack so many
grid $l.$i -column $col -row $row
ttk::combobox $l.$i.combo -width 12 -state readonly -values $values
ttk::button $l.$i.configure -image uci -text "uci" -command "::uci::uciConfigN \[ $l.$i.combo current \] .comp"
button $l.$i.log -image tb_annotate -command "
engineShowLog \[ $l.$i.combo current \]
"
pack $l.$i.log $l.$i.combo -side left -padx "$startpad 0"
pack $l.$i.configure -side left -padx "5 0"
incr row
if { $row > $maxr } { set row 0; incr col; set startpad 10}
if {[info exists comp(players)]} {
# Set the combo boxes to the previous players if we can
set prev_player [lindex $comp(players) $i]
if {[catch {$l.$i.combo current $prev_player}]} {
$l.$i.combo current $i
}
} else {
$l.$i.combo current $i
}
}
set comp(countcombos) $comp(count)
update
return 1
}
proc compTimeout {} {
global analysis comp
puts "Timed out"
set expired [expr [clock clicks -milli] - $comp(lasttime)]
if {[sc_pos side] == "white"} {
if {$comp(timecontrol) == "pergame"} {
set comp(wtime) [expr $comp(wtime) - $expired]
set comment {Timed out}
} else {
set comment "White movetime [expr $expired / 1000.0] secs"
}
set result 0
} else {
if {$comp(timecontrol) == "pergame"} {
set comp(btime) [expr $comp(btime) - $expired]
set comment {Timed out}
} else {
set comment "Black movetime [expr $expired / 1000.0] secs"
}
set result 1
}
compGameEnd $result $comment
}
proc compGameEnd {result {comment {Manual adjudication}}} {
global analysis comp
if {$comp(paused)} {
compResume
}
set comp(playing) 0
set comp(result) $result
sc_pos setComment $comment
set analysis(waitForReadyOk$comp(move)) 1
set analysis(waitForBestMove$comp(move)) 1
}
proc compAbort {} {
# Close all games, called when game is active
global analysis comp
if {$comp(paused)} {
compResume
}
set comp(playing) 0
set comp(games) {}
catch {
set analysis(waitForReadyOk$comp(move)) 1
set analysis(waitForBestMove$comp(move)) 1
}
}
proc compClose {} {
global analysis comp
if {[.comp.buttons.cancel cget -text] == {End Comp}} {
# comp is running. Double check before exitting
set msg {A Computer Tournament is running.}
set answer [tk_dialog .unsaved "Scid: Confirm Quit" $msg question {} " [tr FileExit] " [tr Cancel]]
if {$answer != 0} {
return
}
}
compDestroy
}
proc compDestroy {} {
global comp
### there's some ttk bug when destroying widget, but havent found it yet
# ttk::combobox seems to need destroying
# for {set i 0} {$i < $comp(countcombos)} {incr i} {
# must unbind .comp Destroy
# destroy .comp.engines.list.$i
# }
set comp(games) {}
set comp(playing) 0
update idletasks
destroy .comp
}
proc compSave {} {
global comp
global scidConfigDir
set comp(fen) {}
set filename ""
set types { {{Scid Tournament Files} {.sto}} }
set filename [tk_getSaveFile -initialdir $scidConfigDir -initialfile $comp(name) -filetypes $types -defaultextension ".sto"]
if {$filename != ""} {
if { [catch {open $filename w} stofile]} {
tk_messageBox -title "Scid: Save Tournament" -type ok -icon warning -message "Unable to write options file:\n$filename"
} else {
puts $stofile "# Scid comptournament file"
puts $stofile ""
foreach i [array names comp] {
puts $stofile "set comp($i) [list $comp($i)]"
}
close $stofile
}
}
}
###
### End of file: comp.tcl
###
### from scidvspc analysis.tcl
_______________________________________________
Scidvspc-users mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/scidvspc-users