Scid  4.6.5
tactics.tcl
Go to the documentation of this file.
1 ### tactics.tcl: part of Scid.
2 ### Copyright (C) 2007 Pascal Georges
3 ### Copyright (C) 2015 Fulvio Benini
4 ###
5 ######################################################################
6 ### Solve tactics (mate in n moves for example)
7 # use Site token in pgn notation to store progress
8 #
9 
10 namespace eval tactics {
11 
12  set infoEngineLabel ""
13  set solved "problem solved"
14  set failed "problem failed"
15  set prevScore 0
16  set prevLine ""
17  set nextEngineMove ""
18  set matePending 0
19  set cancelScoreReset 0
20  set showSolution 0
21  set labelSolution ". . . . . . "
22  set prevFen ""
23  set engineSlot 5
24  # Don't try to find the exact best move but to win a won game (that is a mate in 5 is ok even if there was a pending mate in 2)
25  set winWonGame 0
26 
27  ################################################################################
28  # Tacticts training
29  ################################################################################
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 {}]
37 
38  set prevBase [sc_base current]
39  set valid {}
40  set fileList [lsort -dictionary [ glob -nocomplain -directory $::scidBasesDir *.si4]]
41  set progress 0.0
42  set progressIncr 1.0
43  catch { set progressIncr [expr {602.0 / [llength $fileList]}]}
44  busyCursor .
45  foreach fname $fileList {
46  set fname [file rootname [file nativename $fname]]
47  set baseId [sc_base slot $fname]
48  if {$baseId == 0} {
49  progressBarSet $win.dummy 100 10
50  if { [catch { sc_base open $fname} baseId] } {
51  if {$::errorCode == $::ERROR::UserCancel} { break}
53  continue
54  }
55  set wasOpened 0
56  } else {
57  set wasOpened 1
58  }
59 
60  set filter [sc_filter new $baseId]
61  progressBarSet $win.dummy 100 10
62  set err [catch {
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]
67 
68  set line [list [file tail $fname] [sc_base extra $baseId description] $solvedCount $nTactics]
69 
70  set pos "end"
71  if {[sc_base extra $baseId type] == 15} {
72  if {![info exists nTactBases]} {
73  set nTactBases -1
74  set valid $fname
75  }
76  set pos [incr nTactBases]
77  }
78  $win.s.bases insert {} $pos -id $fname -values $line
79  $win.s.bases see $fname
80  if {$nTactics == 0} {
81  $win.s.bases item $fname -tag empty
82  } else {
83  if {$valid == ""} { set valid $fname}
84  }
85 
86  set progress [expr {$progress + $progressIncr +1}]
87  $win.pbar coords bar 0 0 [expr {int($progress)}] 12
88  }]
89  sc_filter release $baseId $filter
90  if {$wasOpened == 0} {
91  sc_base close $baseId
92  }
93 
94  if {$err} {
95  if {$::errorCode == $::ERROR::UserCancel} { break}
97  continue
98  }
99  }
100  unbusyCursor .
101  sc_base switch $prevBase
102  $win.pbar coords bar 0 0 602 12
103  $win.fbutton.help configure -state normal
104  grid $win.fbutton.reset
105  set eager [$win.s.bases selection]
106  if {$eager != ""} {
107  set valid $eager
108  $win.s.bases selection set {}
109  }
110  bind $win.s.bases <<TreeviewSelect>> "::tactics::configValidBase $win"
111  $win.s.bases selection set [list $valid]
112  $win.s.bases see $valid
113  $win.d.search configure -state normal
114  }
115 
116  proc configValidDir {win} {
117  bind $win.s.bases <<TreeviewSelect>> {}
118  $win.fbutton.reset configure -state disabled -command {}
119  $win.fbutton.ok configure -state disabled -command {}
120  $win.d.search configure -text $::tr(Search)
121  $win.pbar coords bar 0 0 0 0
122  $win.fbutton.cancel configure -text [tr Cancel] -command "focus .; destroy $win"
123  if {[file isdirectory $::scidBasesDir]} {
124  $win.d.basedir configure -foreground black
125  $win.d.search configure -state normal \
126  -command "::tactics::configBases $win"
127  after idle "after 1 ::tactics::configBases $win"
128  } else {
129  $win.d.basedir configure -foreground red
130  $win.d.search configure -state disabled -command {}
131  }
132  }
133 
134  proc configValidBase {win} {
135  set fname [$win.s.bases selection]
136  $win.fbutton.cancel configure -text [tr Cancel] -command "focus .; destroy $win"
137  if {$fname != "" && [$win.s.bases item {*}$fname -tags] != "empty"} {
138  $win.fbutton.ok configure -state normal \
139  -command "set e \[$win.e.engines selection\]; destroy $win; ::tactics::createWin $fname \$e"
140  $win.fbutton.reset configure -state normal \
141  -command "::tactics::resetScores $fname; ::tactics::configValidDir $win"
142  } else {
143  $win.fbutton.ok configure -state disabled -command {}
144  $win.fbutton.reset configure -state disabled -command {}
145  }
146  }
147 
148  proc config {} {
149  # check if tactics window is already opened. If so, abort serial.
150  set w .tacticsWin
151  if {[winfo exists $w]} {
152  destroy $w
153  }
154 
155  set w ".configTactics"
156  if {[winfo exists $w]} {
157  focus $w
158  return
159  }
160  toplevel $w
161  wm title $w $::tr(ConfigureTactics)
162  wm resizable $w 0 0
163 
164  #dummy progressbar
165  canvas $w.dummy
166  $w.dummy create rectangle 0 0 0 0
167 
168  #Engine selection
169  grid [frame $w.sep1 -height 10] -sticky news -padx 10
170  label $w.engine -font font_Bold -text "[tr Engine]:"
171  grid $w.engine -sticky w -padx 10
172  grid [ttk::frame $w.e] -sticky news -padx 10
173  ttk::treeview $w.e.engines -columns {0 1} -selectmode browse -show {} -height 4
174  $w.e.engines column 0 -width 200
175  $w.e.engines column 1 -width 400
176 
177  set i 0
178  set uci 0
179  foreach e $::engines(list) {
180  if {[lindex $e 7] != 0} {
181  $w.e.engines insert {} end -id $i -values [lrange $e 0 1]
182  if {$uci == 0} { $w.e.engines selection set [list $i]}
183  incr uci
184  }
185  incr i
186  }
187  if {$uci == 0} {
188  destroy $w
189  set msg "This feature require at least one UCI engine"
190  tk_messageBox -type ok -icon error -title "$::tr(ConfigureTactics)" -message $msg
191  return
192  }
193  autoscrollframe -bars both $w.e "" $w.e.engines
194 
195  grid [ttk::frame $w.t] -sticky news -padx 10
196  grid columnconfigure $w.t 2 -weight 1
197  label $w.t.analabel -text "[tr SecondsPerMove]: "
198  scale $w.t.analysisTime -orient horizontal -from 1 -to 60 -length 120 -variable ::tactics::analysisTime \
199  -showvalue 0 -command { ::utils::validate::roundScale ::tactics::analysisTime 1 }
200  label $w.t.value -textvar ::tactics::analysisTime
201  grid $w.t.analabel $w.t.value $w.t.analysisTime -sticky news
202 
203 
204  #BaseDir selection
205  grid [frame $w.sep2 -height 20] -sticky news -padx 10 -pady 10
206  grid [frame $w.d] -sticky news -padx 10 -pady 10
207  label $w.d.lbl -font font_Bold -text "[tr ChooseTrainingBase]:"
208  grid $w.d.lbl -sticky w -columnspan 3
209  grid columnconfigure $w.d 1 -weight 1
210  button $w.d.selectDir -image tb_open -command "setTacticsBasesDir; ::tactics::configValidDir $w"
211  label $w.d.basedir -textvariable scidBasesDir
212  button $w.d.search -text [tr Search]
213  grid $w.d.selectDir $w.d.basedir $w.d.search -sticky w -padx 10
214 
215 
216  #Base selection
217  grid [ttk::frame $w.s] -sticky news -padx 10
218  ttk::treeview $w.s.bases -columns {0 1 2 3} -show headings -selectmode browse -height 8
219  $w.s.bases tag configure empty -foreground #a5a2ac
220  $w.s.bases heading 0 -text [tr DatabaseName]
221  $w.s.bases heading 1 -text [tr Description]
222  $w.s.bases heading 2 -text Solved
223  $w.s.bases heading 3 -text [tr Total]
224  $w.s.bases column 0 -width 140
225  $w.s.bases column 1 -width 300
226  $w.s.bases column 2 -width 80 -anchor c
227  $w.s.bases column 3 -width 80 -anchor c
228  autoscrollframe -bars both $w.s "" $w.s.bases
229 
230  canvas $w.pbar -width 600 -height 10 -bg white -relief solid -border 1
231  $w.pbar create rectangle 0 0 0 0 -fill blue -outline blue -tags bar
232  grid $w.pbar
233 
234 
235  #Buttons
236  grid [ttk::frame $w.fbutton] -sticky news
237  grid columnconfigure $w.fbutton 1 -weight 1
238  ttk::button $w.fbutton.ok -text [tr Continue]
239  ttk::button $w.fbutton.cancel
240  ttk::button $w.fbutton.reset -text [tr ResetScores]
241  ttk::button $w.fbutton.help -text [tr Help] \
242  -command "destroy $w; helpWindow TacticsTrainer"
243  grid $w.fbutton.ok -row 0 -column 2
244  grid $w.fbutton.cancel -row 0 -column 3
245  grid $w.fbutton.reset -row 0 -column 1 -sticky w
246  grid $w.fbutton.help -row 0 -column 0 -sticky w
247 
248  # Set up geometry for middle of screen:
249  set x [expr ([winfo screenwidth $w] - 600) / 2]
250  set y [expr ([winfo screenheight $w] - 600) / 2]
251  wm geometry $w +$x+$y
252  grab $w
253  configValidDir $w
254  focus $w
255  }
256  ################################################################################
257  #
258  ################################################################################
259  proc createWin { base engineIdx} {
260  global ::tactics::analysisEngine
261 
262  ::uci::resetUciInfo $::tactics::engineSlot
263  set analysisEngine(analyzeMode) 0
264  progressWindow "Scid" [tr StartEngine]
265  set err [::uci::startEngine $engineIdx $::tactics::engineSlot]
267  if {$err != 0} { return}
268 
269  set err [::tactics::loadBase $base]
270  if {$err != 0} { return}
271 
272 
273  set w .tacticsWin
274  if {[winfo exists $w]} { focus $w ; return}
275 
276  createToplevel $w .pgnWin
277  setTitle $w $::tr(Tactics)
278  setWinLocation $w
279  # because sometimes the 2 buttons at the bottom are hidden
280  wm minsize $w 170 170
281  ttk::frame $w.f1 -relief groove ;# -borderwidth 1
282  ttk::label $w.f1.labelInfo -textvariable ::tactics::infoEngineLabel -background linen
283  ttk::checkbutton $w.f1.cbWinWonGame -text $::tr(WinWonGame) -variable ::tactics::winWonGame
284  pack $w.f1.labelInfo $w.f1.cbWinWonGame -expand yes -fill both -side top
285 
286  ttk::frame $w.fclock
287  ::gameclock::new $w.fclock 1 80 0
290 
291  ttk::frame $w.f2 -relief groove
292  ttk::checkbutton $w.f2.cbSolution -text $::tr(ShowSolution) -variable ::tactics::showSolution -command ::tactics::toggleSolution
293  ttk::label $w.f2.lSolution -textvariable ::tactics::labelSolution -wraplength 120
294  pack $w.f2.cbSolution $w.f2.lSolution -expand yes -fill both -side top
295 
296  ttk::frame $w.fbuttons -relief groove -borderwidth 1
297  pack $w.f1 $w.fclock $w.f2 $w.fbuttons -expand yes -fill both
298 
299  setInfoEngine $::tr(LoadingBase)
300 
301  ttk::button $w.fbuttons.next -text $::tr(Next) -command {
302  ::tactics::stopAnalyze
303  ::tactics::loadNextGame }
304  ttk::button $w.fbuttons.close -textvar ::tr(Abort) -command "destroy $w"
305  pack $w.fbuttons.next $w.fbuttons.close -expand yes -fill both -padx 20 -pady 2
306  bind $w <Destroy> "if {\[string equal $w %W\]} {::tactics::endTraining}"
307  bind $w <Configure> "recordWinSize $w"
308  bind $w <F1> { helpWindow TacticsTrainer }
310 
311  setInfoEngine "---"
312  set ::playMode "::tactics::callback"
314  }
315  proc callback {cmd} {
316  switch $cmd {
317  stop { destroy .tacticsWin}
318  }
319  return 0
320  }
321 
322  proc endTraining {} {
323  after cancel ::tactics::mainLoop
324  after cancel ::tactics::loadNextGame
326 
327  #TODO:
328  #sc_filter release $::tactics::baseId $::tactics::filter
329  sc_filter reset $::tactics::baseId dbfilter full
330  catch { ::uci::closeUCIengine $::tactics::engineSlot}
331 
332  unset ::playMode
333  ::board::flipAuto .main.board
336  }
337  ################################################################################
338  #
339  ################################################################################
340  proc toggleSolution {} {
341  global ::tactics::showSolution ::tactics::labelSolution ::tactics::analysisEngine
342  if {$showSolution} {
343  set labelSolution "$analysisEngine(score) : [::trans $analysisEngine(moves)]"
344  } else {
345  set labelSolution ". . . . . . "
346  }
347  }
348  ################################################################################
349  #
350  ################################################################################
351  proc resetScores {fname} {
352  global ::tactics::cancelScoreReset
353 
354  set prevBase [sc_base current]
355  set baseId [sc_base slot $fname]
356  if {$baseId == 0} {
357  if { [catch { sc_base open $fname} baseId] } {
359  continue
360  }
361  set wasOpened 0
362  } else {
363  sc_base switch $baseId
364  set curr_game [sc_game number]
365  sc_game push
366  set wasOpened 1
367  }
368  set filter [sc_filter new $baseId]
369  sc_filter search $baseId $filter header -filter RESET -site "\"$::tactics::solved\""
370 
371  #reset site tag for each game
372  set numGames [sc_filter count $baseId $filter]
373  set cancelScoreReset 0
374  progressWindow "Scid" $::tr(ResettingScore) $::tr(Cancel) "set ::tactics::cancelScoreReset 1"
375  for {set g 0} {$g < $numGames && $cancelScoreReset == 0} {incr g 100} {
376  updateProgressWindow $g $numGames
377 
378  foreach {idx line deleted} [sc_base gameslist $baseId $g 100 $filter N+] {
379  foreach {n ply} [split $idx "_"] {
380  sc_game load $n
381  sc_game tags set -site ""
382  sc_game save [sc_game number]
383  }
384  }
385  }
387  sc_filter release $baseId $filter
388  if { ! $wasOpened } {
389  sc_base close $baseId
390  } else {
391  if {$curr_game == 0} {
392  sc_game new
393  } else {
394  sc_game load $curr_game
395  }
396  sc_game pop
399  }
400  sc_base switch $prevBase
401  }
402  ################################################################################
403  #
404  ################################################################################
405  proc loadNextGame {} {
407  setInfoEngine $::tr(LoadingGame)
408 
409  set nextTactic 0
410  while {![sc_pos isAt end]} {
411  sc_move forward
412  set cmt [sc_pos getComment]
413  if {[regexp {^\*\*\*\*D?[0-9]} $cmt]} {
414  set nextTactic 1
415  break
416  }
417  }
418  if {$nextTactic == 0} {
419  set g [sc_filter next]
420  if {$g == 0} {
421  tk_messageBox -title "Scid" -icon info -type ok -message $::tr(AllExercisesDone)
422  return
423  }
424  sc_game load $g
425  if {[sc_pos fen] == "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"} {
426  after idle ::tactics::loadNextGame
427  return
428  }
429  }
431  ::board::flipAuto .main.board [expr {[sc_pos side] == "black"}]
432  focus .main
433 
436 
437  set ::tactics::prevFen [sc_pos fen]
440  }
441  ################################################################################
442  #
443  ################################################################################
444  proc isPlayerTurn {} {
445  if { [sc_pos side] == "white" && ![::board::isFlipped .main.board] || [sc_pos side] == "black" && [::board::isFlipped .main.board] } {
446  return 1
447  }
448  return 0
449  }
450  ################################################################################
451  #
452  ################################################################################
453  proc exSolved {} {
456  tk_messageBox -title "Scid" -icon info -type ok -message $::tr(MateFound)
457  sc_game tags set -site $::tactics::solved
458  sc_game save [sc_game number]
460  }
461  ################################################################################
462  # Handle the case where position was changed not during normal play but certainly with
463  # move back / forward / rewind commands
464  ################################################################################
465  proc abnormalContinuation {} {
470  if { [sc_pos side] == "white" && [::board::isFlipped .main.board] || [sc_pos side] == "black" && ![::board::isFlipped .main.board] } {
471  ::board::flip .main.board
472  }
473  set ::tactics::prevFen [sc_pos fen]
476  }
477  ################################################################################
478  # waits for the user to play and check the move played
479  ################################################################################
480  proc mainLoop {} {
481  global ::tactics::prevScore ::tactics::prevLine ::tactics::analysisEngine ::tactics::nextEngineMove
482 
483  after cancel ::tactics::mainLoop
484 
485  if {[sc_pos fen] != $::tactics::prevFen && [sc_pos isAt start]} {
487  return
488  }
489 
490  # is this player's turn (which always plays from bottom of the board) ?
491  if { [::tactics::isPlayerTurn] } {
492  after 1000 ::tactics::mainLoop
493  return
494  }
495 
496  set ::tactics::prevFen [sc_pos fen]
497 
498  # check if player's move is a direct mate : no need to wait for engine analysis in this case
499  set move_done [sc_game info previousMove]
500  if { [string index $move_done end] == "#"} { ::tactics::exSolved; return}
501 
502  # if the engine is still analyzing, wait the end of it
503  if {$analysisEngine(analyzeMode)} { vwait ::tactics::analysisEngine(analyzeMode)}
504 
505  if {![winfo exists .tacticsWin]} { return}
506 
507  if {[sc_pos fen] != $::tactics::prevFen && [sc_pos isAt start]} {
509  return
510  }
511 
512  # the player moved and analysis is over : check if his move was as good as expected
513  set prevScore $analysisEngine(score)
514  set prevLine $analysisEngine(moves)
516 
517  # now wait for the end of analyzis
518  if {$analysisEngine(analyzeMode)} { vwait ::tactics::analysisEngine(analyzeMode)}
519  if {[sc_pos fen] != $::tactics::prevFen && [sc_pos isAt start]} {
521  return
522  }
523 
524  # compare results
525  set res [::tactics::foundBestLine]
526  if { $res != ""} {
527  tk_messageBox -title "Scid" -icon info -type ok -message "$::tr(BestSolutionNotFound)\n$res"
528  # take back last move so restore engine status
529  set analysisEngine(score) $prevScore
530  set analysisEngine(moves) $prevLine
531  sc_game tags set -site $::tactics::failed
532  sc_game save [sc_game number]
533  sc_move back
534  updateBoard -pgn
535  set ::tactics::prevFen [sc_pos fen]
536  } else {
537  catch { sc_move addSan $nextEngineMove}
538  set ::tactics::prevFen [sc_pos fen]
539  updateBoard -pgn
540  if { $::tactics::matePending } {
541  # continue until end of game
542  } else {
543  setInfoEngine $::tr(GoodMove)
544  sc_game tags set -site $::tactics::solved
545  sc_game save [sc_game number]
546  }
547  }
548 
549  after 1000 ::tactics::mainLoop
550  }
551  ################################################################################
552  # Returns "" if the user played the best line, otherwise an explanation about the missed move :
553  # - guessed the same next move as engine
554  # - mate found in the minimal number of moves
555  # - combination's score is close enough (within 0.5 point)
556  ################################################################################
557  proc foundBestLine {} {
558  global ::tactics::analysisEngine ::tactics::prevScore ::tactics::prevLine ::tactics::nextEngineMove ::tactics::matePending
559  set score $analysisEngine(score)
560  set line $analysisEngine(moves)
561 
562  set s [ regsub -all "\[\.\]{3} " $line ""]
563  set s [ regsub -all "\[0-9\]+\[\.\] " $s ""]
564  set nextEngineMove [ lindex [ split $s] 0]
565  set ply [ llength [split $s]]
566 
567  # check if the player played the same move predicted by engine
568  set s [ regsub -all "\[\.\]{3} " $prevLine ""]
569  set s [ regsub -all "\[0-9\]+\[\.\] " $s ""]
570  set prevBestMove [ lindex [ split $s] 1]
571  if { [sc_game info previousMoveNT] == $prevBestMove} {
572  return ""
573  }
574 
575  # Case of mate
576  if { [string index $prevLine end] == "#"} {
577  set matePending 1
578  # Engine may find a mate then put a score != 300 but rather 10
579  if {[string index $line end] != "#"} {
580  # Engine line does not end with a # but the score is a mate (we can't count plies here)
581  if {[sc_pos side] == "white" && $score < -300 || [sc_pos side] == "black" && $score > 300} {
582  return ""
583  }
584  if {! $::tactics::winWonGame } {
585  return $::tr(MateNotFound)
586  } else {
587  # win won game but still have to find a mate
588  if {[sc_pos side] == "white" && $score < -300 || [sc_pos side] == "black" && $score > 300} {
589  return ""
590  } else {
591  return $::tr(MateNotFound)
592  }
593  }
594  }
595  # Engine found a mate, search in how many plies
596  set s [ regsub -all "\[\.\]{3} " $prevLine ""]
597  set s [ regsub -all "\[0-9\]+\[\.\] " $s ""]
598  set prevPly [ llength [ split $s]]
599  if { $ply > [ expr $prevPly - 1] && ! $::tactics::winWonGame } {
600  return $::tr(ShorterMateExists)
601  } else {
602  return ""
603  }
604  } else {
605  # no mate case
606  set matePending 0
607  set threshold 0.5
608  if {$::tactics::winWonGame} {
609  # Only alert when the advantage clearly changes side
610  if {[sc_pos side] == "white" && $prevScore < 0 && $score >= $threshold || \
611  [sc_pos side] == "black" && $prevScore >= 0 && $score < [expr 0 - $threshold] } {
612  return "$::tr(ScorePlayed) $score\n$::tr(Expected) $prevScore"
613  } else {
614  return ""
615  }
616  }
617  if {[ expr abs($prevScore)] > 3.0 } { set threshold 1.0}
618  if {[ expr abs($prevScore)] > 5.0 } { set threshold 1.5}
619  # the player moved : score is from opponent side
620  if {[sc_pos side] == "white" && $score < [ expr $prevScore + $threshold] || \
621  [sc_pos side] == "black" && $score > [ expr $prevScore - $threshold] } {
622  return ""
623  } else {
624  return "$::tr(ScorePlayed) $score\n$::tr(Expected) $prevScore"
625  }
626  }
627  }
628  ################################################################################
629  # Loads a base bundled with Scid (in ./bases directory)
630  ################################################################################
631  proc loadBase { name } {
632  global ::tactics::baseId ::tactics::filter
633  set baseId [sc_base slot $name]
634  if {$baseId != 0} {
635  ::file::SwitchToBase $baseId 0
636  } else {
637  progressWindow "Scid" "$::tr(OpeningTheDatabase): [file tail "$name"]..."
638  set err [catch {sc_base open "$name"} baseId]
640  if {$err && $::errorCode != $::ERROR::NameDataLoss } {
641  ERROR::MessageBox "$fName\n"
642  return $err
643  }
644  }
645  #TODO:
646  #set filter [sc_filter new $baseId]
647  set filter dbfilter
648  sc_filter search $baseId $filter header -filter RESET -flag S -flag| T -site! "\"$::tactics::solved\""
649 
652  return 0
653  }
654  ################################################################################
655  ## resetValues
656  # Resets global data.
657  ################################################################################
658  proc resetValues {} {
659  set ::tactics::prevScore 0
660  set ::tactics::prevLine ""
661  set ::tactics::nextEngineMove ""
662  set ::tactics::matePending 0
663  set ::tactics::showSolution 0
664  set ::tactics::labelSolution ""
665  set ::tactics::prevFen ""
666  }
667  ################################################################################
668  #
669  ################################################################################
670  proc setInfoEngine { s { color linen } } {
671  set ::tactics::infoEngineLabel $s
672  .tacticsWin.f1.labelInfo configure -background $color
673  }
674  # ======================================================================
675  # sendToEngine:
676  # Send a command to a running analysis engine.
677  # ======================================================================
678  proc sendToEngine {text} {
679  ::uci::sendToEngine $::tactics::engineSlot $text
680  }
681 
682  # ======================================================================
683  # startAnalyzeMode:
684  # Put the engine in analyze mode
685  # ======================================================================
686  proc startAnalyze { } {
687  global ::tactics::analysisEngine ::tactics::analysisTime
688  setInfoEngine "$::tr(Thinking) ..." PaleVioletRed
689 
690  # Check that the engine has not already had analyze mode started:
691  if {$analysisEngine(analyzeMode)} {
693  }
694 
695  set analysisEngine(analyzeMode) 1
696  after cancel ::tactics::stopAnalyze
697  ::tactics::sendToEngine "position fen [sc_pos fen]"
698  ::tactics::sendToEngine "go infinite"
699  after [expr 1000 * $analysisTime] ::tactics::stopAnalyze
700  }
701  # ======================================================================
702  # stopAnalyzeMode:
703  # Stop the engine analyze mode
704  # ======================================================================
705  proc stopAnalyze { } {
706  global ::tactics::analysisEngine ::tactics::analysisTime
707  # Check that the engine has already had analyze mode started:
708  if {!$analysisEngine(analyzeMode)} { return}
709 
710  set pv [lindex $::analysis(multiPV$::tactics::engineSlot) 0]
711  set analysisEngine(score) [lindex $pv 1]
712  set analysisEngine(moves) [lindex $pv 2]
713 
714  set analysisEngine(analyzeMode) 0
716  if {[winfo exists .tacticsWin]} {
717  setInfoEngine $::tr(AnalyzeDone) PaleGreen3
718  }
719  }
720 
721 }
722 
723 ###
724 ### End of file: tactics.tcl
725 ###