Scid  4.7.0
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros
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 desc {}
69  foreach {tagname tagvalue} [sc_base extra $baseId] {
70  if {$tagname eq "description"} {
71  set desc $tagvalue
72  break
73  }
74  }
75  set line [list [file tail $fname] $desc $solvedCount $nTactics]
76 
77  set pos "end"
78  if {[getBaseType $baseId] == 15} {
79  if {![info exists nTactBases]} {
80  set nTactBases -1
81  set valid $fname
82  }
83  set pos [incr nTactBases]
84  }
85  $win.s.bases insert {} $pos -id $fname -values $line
86  $win.s.bases see $fname
87  if {$nTactics == 0} {
88  $win.s.bases item $fname -tag empty
89  } else {
90  if {$valid == ""} { set valid $fname}
91  }
92 
93  set progress [expr {$progress + $progressIncr +1}]
94  $win.pbar coords bar 0 0 [expr {int($progress)}] 12
95  }]
96  sc_filter release $baseId $filter
97  if {$wasOpened == 0} {
98  sc_base close $baseId
99  }
100 
101  if {$err} {
102  if {$::errorCode == $::ERROR::UserCancel} { break}
104  continue
105  }
106  }
107  unbusyCursor .
108  sc_base switch $prevBase
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]
113  if {$eager != ""} {
114  set valid $eager
115  $win.s.bases selection set {}
116  }
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
121  }
122 
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"
135  } else {
136  $win.d.basedir configure -foreground red
137  $win.d.search configure -state disabled -command {}
138  }
139  }
140 
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"
149  } else {
150  $win.fbutton.ok configure -state disabled -command {}
151  $win.fbutton.reset configure -state disabled -command {}
152  }
153  }
154 
155  proc config {} {
156  # check if tactics window is already opened. If so, abort serial.
157  set w .tacticsWin
158  if {[winfo exists $w]} {
159  destroy $w
160  }
161 
162  set w ".configTactics"
163  if {[winfo exists $w]} {
164  focus $w
165  return
166  }
168  wm title $w $::tr(ConfigureTactics)
169  wm resizable $w 0 0
170 
171  #dummy progressbar
172  canvas $w.dummy
173  $w.dummy create rectangle 0 0 0 0
174 
175  #Engine selection
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
183 
184  set i 0
185  set uci 0
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]}
190  incr uci
191  }
192  incr i
193  }
194  if {$uci == 0} {
195  destroy $w
196  set msg "This feature require at least one UCI engine"
197  tk_messageBox -type ok -icon error -title "$::tr(ConfigureTactics)" -message $msg
198  return
199  }
200  autoscrollframe -bars both $w.e "" $w.e.engines
201 
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
209 
210 
211  #BaseDir selection
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
221 
222 
223  #Base selection
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
235  autoscrollframe -bars both $w.s "" $w.s.bases
236 
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"
240 
241 
242  #Buttons
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
254 
255  # Set up geometry for middle of screen:
256  set x [expr ([winfo screenwidth $w] - 600) / 2]
257  set y [expr ([winfo screenheight $w] - 600) / 2]
258  wm geometry $w +$x+$y
259  grab $w
260  configValidDir $w
261  focus $w
262  }
263  ################################################################################
264  #
265  ################################################################################
266  proc createWin { base engineIdx} {
267  global ::tactics::analysisEngine
268 
269  ::uci::resetUciInfo $::tactics::engineSlot
270  set analysisEngine(analyzeMode) 0
271  progressWindow "Scid" [tr StartEngine]
272  set err [::uci::startEngine $engineIdx $::tactics::engineSlot]
274  if {$err != 0} { return}
275 
276  set err [::tactics::loadBase $base]
277  if {$err != 0} { return}
278 
279 
280  set w .tacticsWin
281  if {[winfo exists $w]} { focus $w ; return}
282 
283  createToplevel $w .pgnWin
284  setTitle $w $::tr(Tactics)
285  # because sometimes the 2 buttons at the bottom are hidden
286  wm minsize $w 170 170
287  ttk::frame $w.f1 -relief groove ;# -borderwidth 1
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
291 
292  ttk::frame $w.fclock
293  ::gameclock::new $w.fclock 1 80 0
296 
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
301 
302  ttk::frame $w.fbuttons -relief groove -borderwidth 1
303  pack $w.f1 $w.fclock $w.f2 $w.fbuttons -expand yes -fill both
304 
305  setInfoEngine $::tr(LoadingBase)
306 
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 }
315 
316  setInfoEngine "---"
317  set ::playMode "::tactics::callback"
319  }
320  proc callback {cmd} {
321  switch $cmd {
322  stop { destroy .tacticsWin}
323  }
324  return 0
325  }
326 
327  proc endTraining {} {
328  after cancel ::tactics::mainLoop
329  after cancel ::tactics::loadNextGame
331 
332  #TODO:
333  #sc_filter release $::tactics::baseId $::tactics::filter
334  sc_filter reset $::tactics::baseId dbfilter full
335  catch { ::uci::closeUCIengine $::tactics::engineSlot}
336 
337  unset ::playMode
338  ::board::flipAuto .main.board
341  }
342  ################################################################################
343  #
344  ################################################################################
345  proc toggleSolution {} {
346  global ::tactics::showSolution ::tactics::labelSolution ::tactics::analysisEngine
347  if {$showSolution} {
348  set labelSolution "$analysisEngine(score) : [::trans $analysisEngine(moves)]"
349  } else {
350  set labelSolution ". . . . . . "
351  }
352  }
353  ################################################################################
354  #
355  ################################################################################
356  proc resetScores {fname} {
357  global ::tactics::cancelScoreReset
358 
359  set prevBase [sc_base current]
360  set baseId [sc_base slot $fname]
361  if {$baseId == 0} {
362  if { [catch { sc_base open $fname} baseId] } {
364  continue
365  }
366  set wasOpened 0
367  } else {
368  sc_base switch $baseId
369  set curr_game [sc_game number]
370  sc_game push
371  set wasOpened 1
372  }
373  set filter [sc_filter new $baseId]
374  sc_filter search $baseId $filter header -filter RESET -site "\"$::tactics::solved\""
375 
376  #reset site tag for each game
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} {
381  updateProgressWindow $g $numGames
382 
383  foreach {idx line deleted} [sc_base gameslist $baseId $g 100 $filter N+] {
384  foreach {n ply} [split $idx "_"] {
385  sc_game load $n
386  sc_game tags set -site ""
387  sc_game save [sc_game number]
388  }
389  }
390  }
392  sc_filter release $baseId $filter
393  if { ! $wasOpened } {
394  sc_base close $baseId
395  } else {
396  if {$curr_game == 0} {
397  sc_game new
398  } else {
399  sc_game load $curr_game
400  }
401  sc_game pop
404  }
405  sc_base switch $prevBase
406  }
407  ################################################################################
408  #
409  ################################################################################
410  proc loadNextGame {} {
412  setInfoEngine $::tr(LoadingGame)
413 
414  set nextTactic 0
415  while {![sc_pos isAt end]} {
416  sc_move forward
417  set cmt [sc_pos getComment]
418  if {[regexp {^\*\*\*\*D?[0-9]} $cmt]} {
419  set nextTactic 1
420  break
421  }
422  }
423  if {$nextTactic == 0} {
424  set g [sc_filter next]
425  if {$g == 0} {
426  tk_messageBox -title "Scid" -icon info -type ok -message $::tr(AllExercisesDone)
427  return
428  }
429  sc_game load $g
430  if {[sc_pos fen] == "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"} {
431  after idle ::tactics::loadNextGame
432  return
433  }
434  }
436  ::board::flipAuto .main.board [expr {[sc_pos side] == "black"}]
437  focus .main
438 
441 
442  set ::tactics::prevFen [sc_pos fen]
445  }
446  ################################################################################
447  #
448  ################################################################################
449  proc isPlayerTurn {} {
450  if { [sc_pos side] == "white" && ![::board::isFlipped .main.board] || [sc_pos side] == "black" && [::board::isFlipped .main.board] } {
451  return 1
452  }
453  return 0
454  }
455  ################################################################################
456  #
457  ################################################################################
458  proc exSolved {} {
461  tk_messageBox -title "Scid" -icon info -type ok -message $::tr(MateFound)
462  sc_game tags set -site $::tactics::solved
463  sc_game save [sc_game number]
465  }
466  ################################################################################
467  # Handle the case where position was changed not during normal play but certainly with
468  # move back / forward / rewind commands
469  ################################################################################
470  proc abnormalContinuation {} {
475  if { [sc_pos side] == "white" && [::board::isFlipped .main.board] || [sc_pos side] == "black" && ![::board::isFlipped .main.board] } {
476  ::board::flip .main.board
477  }
478  set ::tactics::prevFen [sc_pos fen]
481  }
482  ################################################################################
483  # waits for the user to play and check the move played
484  ################################################################################
485  proc mainLoop {} {
486  global ::tactics::prevScore ::tactics::prevLine ::tactics::analysisEngine ::tactics::nextEngineMove
487 
488  after cancel ::tactics::mainLoop
489 
490  if {[sc_pos fen] != $::tactics::prevFen && [sc_pos isAt start]} {
492  return
493  }
494 
495  # is this player's turn (which always plays from bottom of the board) ?
496  if { [::tactics::isPlayerTurn] } {
497  after 1000 ::tactics::mainLoop
498  return
499  }
500 
501  set ::tactics::prevFen [sc_pos fen]
502 
503  # check if player's move is a direct mate : no need to wait for engine analysis in this case
504  set move_done [sc_game info previousMove]
505  if { [string index $move_done end] == "#"} { ::tactics::exSolved; return}
506 
507  # if the engine is still analyzing, wait the end of it
508  if {$analysisEngine(analyzeMode)} { vwait ::tactics::analysisEngine(analyzeMode)}
509 
510  if {![winfo exists .tacticsWin]} { return}
511 
512  if {[sc_pos fen] != $::tactics::prevFen && [sc_pos isAt start]} {
514  return
515  }
516 
517  # the player moved and analysis is over : check if his move was as good as expected
518  set prevScore $analysisEngine(score)
519  set prevLine $analysisEngine(moves)
521 
522  # now wait for the end of analyzis
523  if {$analysisEngine(analyzeMode)} { vwait ::tactics::analysisEngine(analyzeMode)}
524  if {[sc_pos fen] != $::tactics::prevFen && [sc_pos isAt start]} {
526  return
527  }
528 
529  # compare results
530  set res [::tactics::foundBestLine]
531  if { $res != ""} {
532  tk_messageBox -title "Scid" -icon info -type ok -message "$::tr(BestSolutionNotFound)\n$res"
533  # take back last move so restore engine status
534  set analysisEngine(score) $prevScore
535  set analysisEngine(moves) $prevLine
536  sc_game tags set -site $::tactics::failed
537  sc_game save [sc_game number]
538  sc_move back
539  updateBoard -pgn
540  set ::tactics::prevFen [sc_pos fen]
541  } else {
542  catch { sc_move addSan $nextEngineMove}
543  set ::tactics::prevFen [sc_pos fen]
544  updateBoard -pgn
545  if { $::tactics::matePending } {
546  # continue until end of game
547  } else {
548  setInfoEngine $::tr(GoodMove)
549  sc_game tags set -site $::tactics::solved
550  sc_game save [sc_game number]
551  }
552  }
553 
554  after 1000 ::tactics::mainLoop
555  }
556  ################################################################################
557  # Returns "" if the user played the best line, otherwise an explanation about the missed move :
558  # - guessed the same next move as engine
559  # - mate found in the minimal number of moves
560  # - combination's score is close enough (within 0.5 point)
561  ################################################################################
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)
566 
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]]
571 
572  # check if the player played the same move predicted by engine
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} {
577  return ""
578  }
579 
580  # Case of mate
581  if { [string index $prevLine end] == "#"} {
582  set matePending 1
583  # Engine may find a mate then put a score != 300 but rather 10
584  if {[string index $line end] != "#"} {
585  # Engine line does not end with a # but the score is a mate (we can't count plies here)
586  if {[sc_pos side] == "white" && $score < -300 || [sc_pos side] == "black" && $score > 300} {
587  return ""
588  }
589  if {! $::tactics::winWonGame } {
590  return $::tr(MateNotFound)
591  } else {
592  # win won game but still have to find a mate
593  if {[sc_pos side] == "white" && $score < -300 || [sc_pos side] == "black" && $score > 300} {
594  return ""
595  } else {
596  return $::tr(MateNotFound)
597  }
598  }
599  }
600  # Engine found a mate, search in how many plies
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)
606  } else {
607  return ""
608  }
609  } else {
610  # no mate case
611  set matePending 0
612  set threshold 0.5
613  if {$::tactics::winWonGame} {
614  # Only alert when the advantage clearly changes side
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"
618  } else {
619  return ""
620  }
621  }
622  if {[ expr abs($prevScore)] > 3.0 } { set threshold 1.0}
623  if {[ expr abs($prevScore)] > 5.0 } { set threshold 1.5}
624  # the player moved : score is from opponent side
625  if {[sc_pos side] == "white" && $score < [ expr $prevScore + $threshold] || \
626  [sc_pos side] == "black" && $score > [ expr $prevScore - $threshold] } {
627  return ""
628  } else {
629  return "$::tr(ScorePlayed) $score\n$::tr(Expected) $prevScore"
630  }
631  }
632  }
633  ################################################################################
634  # Loads a base bundled with Scid (in ./bases directory)
635  ################################################################################
636  proc loadBase { name } {
637  global ::tactics::baseId ::tactics::filter
638  set baseId [sc_base slot $name]
639  if {$baseId != 0} {
640  ::file::SwitchToBase $baseId 0
641  } else {
642  progressWindow "Scid" "$::tr(OpeningTheDatabase): [file tail "$name"]..."
643  set err [catch {sc_base open "$name"} baseId]
645  if {$err && $::errorCode != $::ERROR::NameDataLoss } {
646  ERROR::MessageBox "$fName\n"
647  return $err
648  }
649  }
650  #TODO:
651  #set filter [sc_filter new $baseId]
652  set filter dbfilter
653  sc_filter search $baseId $filter header -filter RESET -flag S -flag| T -site! "\"$::tactics::solved\""
654 
657  return 0
658  }
659  ################################################################################
660  ## resetValues
661  # Resets global data.
662  ################################################################################
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 ""
671  }
672  ################################################################################
673  #
674  ################################################################################
675  proc setInfoEngine { s { color linen } } {
676  set ::tactics::infoEngineLabel $s
677  .tacticsWin.f1.labelInfo configure -background $color
678  }
679  # ======================================================================
680  # sendToEngine:
681  # Send a command to a running analysis engine.
682  # ======================================================================
683  proc sendToEngine {text} {
684  ::uci::sendToEngine $::tactics::engineSlot $text
685  }
686 
687  # ======================================================================
688  # startAnalyzeMode:
689  # Put the engine in analyze mode
690  # ======================================================================
691  proc startAnalyze { } {
692  global ::tactics::analysisEngine ::tactics::analysisTime
693  setInfoEngine "$::tr(Thinking) ..." PaleVioletRed
694 
695  # Check that the engine has not already had analyze mode started:
696  if {$analysisEngine(analyzeMode)} {
698  }
699 
700  set analysisEngine(analyzeMode) 1
701  after cancel ::tactics::stopAnalyze
702  ::tactics::sendToEngine "position fen [sc_pos fen]"
703  ::tactics::sendToEngine "go infinite"
704  after [expr 1000 * $analysisTime] ::tactics::stopAnalyze
705  }
706  # ======================================================================
707  # stopAnalyzeMode:
708  # Stop the engine analyze mode
709  # ======================================================================
710  proc stopAnalyze { } {
711  global ::tactics::analysisEngine ::tactics::analysisTime
712  # Check that the engine has already had analyze mode started:
713  if {!$analysisEngine(analyzeMode)} { return}
714 
715  set pv [lindex $::analysis(multiPV$::tactics::engineSlot) 0]
716  set analysisEngine(score) [lindex $pv 1]
717  set analysisEngine(moves) [lindex $pv 2]
718 
719  set analysisEngine(analyzeMode) 0
721  if {[winfo exists .tacticsWin]} {
722  setInfoEngine $::tr(AnalyzeDone) PaleGreen3
723  }
724  }
725 
726 }
727 
728 ###
729 ### End of file: tactics.tcl
730 ###