10 namespace eval tactics {
12 set infoEngineLabel ""
13 set solved "problem solved"
14 set failed "problem failed"
19 set cancelScoreReset 0
21 set labelSolution ". . . . . . "
30 proc configBases {win} {
31 $win.d.search configure -state disabled
32 $win.fbutton.ok configure -state disabled -command {}
33 $win.fbutton.reset configure -state disabled -command {}
34 $win.fbutton.cancel configure -text [
tr stop] -command {progressBarCancel}
35 $win.fbutton.help configure -state disabled
36 $win.s.bases delete [$win.s.bases children {}]
40 set fileList [lsort -dictionary [ glob -nocomplain -directory $::scidBasesDir *.si4]]
43 catch {
set progressIncr [
expr {602.0 / [llength $fileList]}]}
45 foreach fname $fileList {
46 set fname [
file rootname [
file nativename $fname]]
47 set baseId [
sc_base slot $fname]
50 if { [
catch {
sc_base open $fname} baseId] } {
51 if {$::errorCode == $::ERROR::UserCancel} { break}
63 sc_filter search $baseId $filter header -filter RESET -flag S -flag| T
64 set nTactics [
sc_filter count $baseId $filter]
65 sc_filter search $baseId $filter header -filter AND -site "\"$::tactics::solved\""
66 set solvedCount [
sc_filter count $baseId $filter]
69 foreach {tagname tagvalue} [
sc_base extra $baseId] {
70 if {$tagname eq "description"} {
75 set line [list [
file tail $fname] $desc $solvedCount $nTactics]
79 if {![
info exists nTactBases]} {
83 set pos [
incr nTactBases]
85 $win.s.bases insert {} $pos -id $fname -values $line
86 $win.s.bases see $fname
88 $win.s.bases item $fname -tag empty
90 if {$valid == ""} {
set valid $fname}
93 set progress [
expr {$progress + $progressIncr +1}]
94 $win.pbar coords bar 0 0 [
expr {int($progress)}] 12
97 if {$wasOpened == 0} {
102 if {$::errorCode == $::ERROR::UserCancel} { break}
109 $win.pbar coords bar 0 0 602 12
110 $win.fbutton.help configure -state normal
111 grid $win.fbutton.reset
112 set eager [$win.s.bases selection]
115 $win.s.bases selection set {}
117 bind $win.s.bases <<TreeviewSelect>> "::tactics::configValidBase $win"
118 $win.s.bases selection set [list $valid]
119 $win.s.bases see $valid
120 $win.d.search configure -state normal
123 proc configValidDir {win} {
124 bind $win.s.bases <<TreeviewSelect>> {}
125 $win.fbutton.reset configure -state disabled -command {}
126 $win.fbutton.ok configure -state disabled -command {}
127 $win.d.search configure -text $::tr(Search)
128 $win.pbar coords bar 0 0 0 0
129 $win.fbutton.cancel configure -text [
tr Cancel] -command "focus .; destroy $win"
130 if {[
file isdirectory $::scidBasesDir]} {
131 $win.d.basedir configure -foreground black
132 $win.d.search configure -state normal \
133 -command "::tactics::configBases $win"
134 after idle "after 1 ::tactics::configBases $win"
136 $win.d.basedir configure -foreground red
137 $win.d.search configure -state disabled -command {}
141 proc configValidBase {win} {
142 set fname [$win.s.bases selection]
143 $win.fbutton.cancel configure -text [
tr Cancel] -command "focus .; destroy $win"
144 if {$fname != "" && [$win.s.bases item {*}$fname -tags] != "empty"} {
145 $win.fbutton.ok configure -state normal \
146 -command "set e \[$win.e.engines selection\]; destroy $win; ::tactics::createWin $fname \$e"
147 $win.fbutton.reset configure -state normal \
148 -command "::tactics::resetScores $fname; ::tactics::configValidDir $win"
150 $win.fbutton.ok configure -state disabled -command {}
151 $win.fbutton.reset configure -state disabled -command {}
158 if {[
winfo exists $w]} {
162 set w ".configTactics"
163 if {[
winfo exists $w]} {
168 wm title $w $::tr(ConfigureTactics)
173 $w.dummy create rectangle 0 0 0 0
176 grid [ttk::frame $w.sep1 -height 10] -sticky news
177 ttk::label $w.engine -font font_Bold -text "[
tr Engine]:"
178 grid $w.engine -sticky we
179 grid [ttk::frame $w.e] -sticky nwes
180 ttk::treeview $w.e.engines -columns {0 1} -selectmode browse -show {} -height 4
181 $w.e.engines column 0 -width 200
182 $w.e.engines column 1 -width 400
186 foreach e $::engines(list) {
187 if {[
lindex $e 7] != 0} {
188 $w.e.engines insert {} end -id $i -values [
lrange $e 0 1]
189 if {$uci == 0} { $w.e.engines selection set [list $i]}
196 set msg "This feature require at least one UCI engine"
197 tk_messageBox -type ok -icon error -title "$::tr(ConfigureTactics)" -message $msg
202 grid [ttk::frame $w.t] -sticky news
203 grid columnconfigure $w.t 2 -weight 1
204 ttk::label $w.t.analabel -text "[
tr SecondsPerMove]: "
205 ttk::scale $w.t.analysisTime -orient horizontal -from 1 -to 60 -length 120 -variable ::tactics::analysisTime \
206 -command { ::utils::validate::roundScale ::tactics::analysisTime 1 }
207 ttk::label $w.t.value -textvar ::tactics::analysisTime
208 grid $w.t.analabel $w.t.value $w.t.analysisTime -sticky nwes
212 grid [ttk::frame $w.sep2 -height 20] -sticky nwes
213 grid [ttk::frame $w.d] -sticky news
214 ttk::label $w.d.lbl -font font_Bold -text "[
tr ChooseTrainingBase]:"
215 grid $w.d.lbl -sticky w -columnspan 3
216 grid columnconfigure $w.d 1 -weight 1
217 ttk::button $w.d.selectDir -image tb_open -command "setTacticsBasesDir; ::tactics::configValidDir $w"
218 ttk::label $w.d.basedir -textvariable scidBasesDir
219 ttk::button $w.d.search -text [
tr Search]
220 grid $w.d.selectDir $w.d.basedir $w.d.search -sticky w
224 grid [ttk::frame $w.s] -sticky news
225 ttk::treeview $w.s.bases -columns {0 1 2 3} -show headings -selectmode browse -height 8
226 $w.s.bases tag configure empty -foreground #a5a2ac
227 $w.s.bases heading 0 -text [
tr DatabaseName]
228 $w.s.bases heading 1 -text [
tr Description]
229 $w.s.bases heading 2 -text Solved
230 $w.s.bases heading 3 -text [
tr Total]
231 $w.s.bases column 0 -width 140
232 $w.s.bases column 1 -width 300
233 $w.s.bases column 2 -width 80 -anchor c
234 $w.s.bases column 3 -width 80 -anchor c
237 canvas $w.pbar -width 600 -height 10 -bg white -relief solid -border 1
238 $w.pbar create rectangle 0 0 0 0 -fill blue -outline blue -tags bar
239 grid $w.pbar -pady "0 5"
243 grid [ttk::frame $w.fbutton] -sticky news
244 grid columnconfigure $w.fbutton 1 -weight 1
245 ttk::button $w.fbutton.ok -text [
tr Continue]
246 ttk::button $w.fbutton.cancel
247 ttk::button $w.fbutton.reset -text [
tr ResetScores]
248 ttk::button $w.fbutton.help -text [
tr Help] \
249 -command "destroy $w; helpWindow TacticsTrainer"
250 grid $w.fbutton.ok -row 0 -column 2 -padx 10
251 grid $w.fbutton.cancel -row 0 -column 3
252 grid $w.fbutton.reset -row 0 -column 1 -sticky w -padx 10
253 grid $w.fbutton.help -row 0 -column 0 -sticky w
256 set x [
expr ([
winfo screenwidth $w] - 600) / 2]
257 set y [
expr ([
winfo screenheight $w] - 600) / 2]
258 wm geometry $w +$x+$y
266 proc createWin { base engineIdx} {
267 global ::tactics::analysisEngine
270 set analysisEngine(analyzeMode) 0
274 if {$err != 0} {
return}
277 if {$err != 0} {
return}
281 if {[
winfo exists $w]} {
focus $w
return}
286 wm minsize $w 170 170
287 ttk::frame $w.f1 -relief groove
288 ttk::label $w.f1.labelInfo -textvariable ::tactics::infoEngineLabel -background linen
289 ttk::checkbutton $w.f1.cbWinWonGame -text $::tr(WinWonGame) -variable ::tactics::winWonGame
290 pack $w.f1.labelInfo $w.f1.cbWinWonGame -expand yes -fill both -side top
297 ttk::frame $w.f2 -relief groove
298 ttk::checkbutton $w.f2.cbSolution -text $::tr(ShowSolution) -variable ::tactics::showSolution -command ::tactics::toggleSolution
299 ttk::label $w.f2.lSolution -textvariable ::tactics::labelSolution -wraplength 120
300 pack $w.f2.cbSolution $w.f2.lSolution -expand yes -fill both -side top
302 ttk::frame $w.fbuttons -relief groove -borderwidth 1
303 pack $w.f1 $w.fclock $w.f2 $w.fbuttons -expand yes -fill both
307 ttk::button $w.fbuttons.next -text $::tr(Next) -command {
308 ::tactics::stopAnalyze
309 ::tactics::loadNextGame }
310 ttk::button $w.fbuttons.close -textvar ::tr(Abort) -command "destroy $w"
311 pack $w.fbuttons.next $w.fbuttons.close -expand yes -fill both -padx 20 -pady 2
312 bind $w <Destroy> "if {\[string equal $w %W\]} {::tactics::endTraining}"
313 bind $w <F1> { helpWindow TacticsTrainer }
317 set ::playMode "::tactics::callback"
320 proc callback {cmd} {
322 stop {
destroy .tacticsWin}
327 proc endTraining {} {
328 after cancel ::tactics::mainLoop
329 after cancel ::tactics::loadNextGame
334 sc_filter reset $::tactics::baseId dbfilter full
345 proc toggleSolution {} {
346 global ::tactics::showSolution ::tactics::labelSolution ::tactics::analysisEngine
348 set labelSolution "$analysisEngine(score) : [
::trans $analysisEngine(moves)]"
350 set labelSolution ". . . . . . "
356 proc resetScores {fname} {
357 global ::tactics::cancelScoreReset
360 set baseId [
sc_base slot $fname]
362 if { [
catch {
sc_base open $fname} baseId] } {
374 sc_filter search $baseId $filter header -filter RESET -site "\"$::tactics::solved\""
377 set numGames [
sc_filter count $baseId $filter]
378 set cancelScoreReset 0
379 progressWindow "Scid" $::tr(ResettingScore) $::tr(Cancel) "set ::tactics::cancelScoreReset 1"
380 for {
set g 0} {$g < $numGames && $cancelScoreReset == 0} {
incr g 100} {
383 foreach {idx line deleted} [
sc_base gameslist $baseId $g 100 $filter N+] {
384 foreach {n ply} [
split $idx "_"] {
393 if { ! $wasOpened } {
396 if {$curr_game == 0} {
410 proc loadNextGame {} {
415 while {![
sc_pos isAt end]} {
417 set cmt [
sc_pos getComment]
418 if {[regexp {^\*\*\*\*D?[0-9]} $cmt]} {
423 if {$nextTactic == 0} {
426 tk_messageBox -title "Scid" -icon info -type ok -message $::tr(AllExercisesDone)
430 if {[
sc_pos fen] == "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"} {
431 after idle ::tactics::loadNextGame
442 set ::tactics::prevFen [
sc_pos fen]
449 proc isPlayerTurn {} {
461 tk_messageBox -title "Scid" -icon info -type ok -message $::tr(MateFound)
462 sc_game tags set -site $::tactics::solved
470 proc abnormalContinuation {} {
478 set ::tactics::prevFen [
sc_pos fen]
486 global ::tactics::prevScore ::tactics::prevLine ::tactics::analysisEngine ::tactics::nextEngineMove
488 after cancel ::tactics::mainLoop
490 if {[
sc_pos fen] != $::tactics::prevFen && [
sc_pos isAt start]} {
497 after 1000 ::tactics::mainLoop
501 set ::tactics::prevFen [
sc_pos fen]
504 set move_done [
sc_game info previousMove]
508 if {$analysisEngine(analyzeMode)} {
vwait ::tactics::analysisEngine(analyzeMode)}
510 if {![
winfo exists .tacticsWin]} {
return}
512 if {[
sc_pos fen] != $::tactics::prevFen && [
sc_pos isAt start]} {
518 set prevScore $analysisEngine(score)
519 set prevLine $analysisEngine(moves)
523 if {$analysisEngine(analyzeMode)} {
vwait ::tactics::analysisEngine(analyzeMode)}
524 if {[
sc_pos fen] != $::tactics::prevFen && [
sc_pos isAt start]} {
532 tk_messageBox -title "Scid" -icon info -type ok -message "$::tr(BestSolutionNotFound)\n$res"
534 set analysisEngine(score) $prevScore
535 set analysisEngine(moves) $prevLine
536 sc_game tags set -site $::tactics::failed
540 set ::tactics::prevFen [
sc_pos fen]
542 catch {
sc_move addSan $nextEngineMove}
543 set ::tactics::prevFen [
sc_pos fen]
545 if { $::tactics::matePending } {
549 sc_game tags set -site $::tactics::solved
554 after 1000 ::tactics::mainLoop
562 proc foundBestLine {} {
563 global ::tactics::analysisEngine ::tactics::prevScore ::tactics::prevLine ::tactics::nextEngineMove ::tactics::matePending
564 set score $analysisEngine(score)
565 set line $analysisEngine(moves)
567 set s [ regsub -all "\[\.\]{3} " $line ""]
568 set s [ regsub -all "\[0-9\]+\[\.\] " $s ""]
569 set nextEngineMove [
lindex [
split $s] 0]
570 set ply [
llength [
split $s]]
573 set s [ regsub -all "\[\.\]{3} " $prevLine ""]
574 set s [ regsub -all "\[0-9\]+\[\.\] " $s ""]
575 set prevBestMove [
lindex [
split $s] 1]
576 if { [
sc_game info previousMoveNT] == $prevBestMove} {
581 if { [
string index $prevLine end] == "#"} {
584 if {[
string index $line end] != "#"} {
586 if {[
sc_pos side] == "white" && $score < -300 || [
sc_pos side] == "black" && $score > 300} {
589 if {! $::tactics::winWonGame } {
590 return $::tr(MateNotFound)
593 if {[
sc_pos side] == "white" && $score < -300 || [
sc_pos side] == "black" && $score > 300} {
596 return $::tr(MateNotFound)
601 set s [ regsub -all "\[\.\]{3} " $prevLine ""]
602 set s [ regsub -all "\[0-9\]+\[\.\] " $s ""]
603 set prevPly [
llength [
split $s]]
604 if { $ply > [
expr $prevPly - 1] && ! $::tactics::winWonGame } {
605 return $::tr(ShorterMateExists)
613 if {$::tactics::winWonGame} {
615 if {[
sc_pos side] == "white" && $prevScore < 0 && $score >= $threshold || \
616 [
sc_pos side] == "black" && $prevScore >= 0 && $score < [
expr 0 - $threshold] } {
617 return "$::tr(ScorePlayed) $score\n$::tr(Expected) $prevScore"
622 if {[
expr abs($prevScore)] > 3.0 } {
set threshold 1.0}
623 if {[
expr abs($prevScore)] > 5.0 } {
set threshold 1.5}
625 if {[
sc_pos side] == "white" && $score < [
expr $prevScore + $threshold] || \
626 [
sc_pos side] == "black" && $score > [
expr $prevScore - $threshold] } {
629 return "$::tr(ScorePlayed) $score\n$::tr(Expected) $prevScore"
636 proc loadBase { name } {
637 global ::tactics::baseId ::tactics::filter
638 set baseId [
sc_base slot $name]
642 progressWindow "Scid" "$::tr(OpeningTheDatabase): [
file tail "$name"]..."
643 set err [
catch {
sc_base open "$name"} baseId]
645 if {$err && $::errorCode != $::ERROR::NameDataLoss } {
653 sc_filter search $baseId $filter header -filter RESET -flag S -flag| T -site! "\"$::tactics::solved\""
663 proc resetValues {} {
664 set ::tactics::prevScore 0
665 set ::tactics::prevLine ""
666 set ::tactics::nextEngineMove ""
667 set ::tactics::matePending 0
668 set ::tactics::showSolution 0
669 set ::tactics::labelSolution ""
670 set ::tactics::prevFen ""
675 proc setInfoEngine { s { color linen } } {
676 set ::tactics::infoEngineLabel $s
677 .tacticsWin.f1.labelInfo configure -background $color
683 proc sendToEngine {text} {
691 proc startAnalyze { } {
692 global ::tactics::analysisEngine ::tactics::analysisTime
696 if {$analysisEngine(analyzeMode)} {
700 set analysisEngine(analyzeMode) 1
701 after cancel ::tactics::stopAnalyze
704 after [
expr 1000 * $analysisTime] ::tactics::stopAnalyze
710 proc stopAnalyze { } {
711 global ::tactics::analysisEngine ::tactics::analysisTime
713 if {!$analysisEngine(analyzeMode)} {
return}
715 set pv [
lindex $::analysis(multiPV$::tactics::engineSlot) 0]
716 set analysisEngine(score) [
lindex $pv 1]
717 set analysisEngine(moves) [
lindex $pv 2]
719 set analysisEngine(analyzeMode) 0
721 if {[
winfo exists .tacticsWin]} {