Scid  4.7.0
analysis.tcl
Go to the documentation of this file.
1 
2 ###
3 ### analysis.tcl: part of Scid.
4 ### Copyright (C) 1999-2003 Shane Hudson.
5 ### Copyright (C) 2007 Pascal Georges
6 
7 ######################################################################
8 ### Analysis window: uses a chess engine to analyze the board.
9 
10 # analysis(logMax):
11 # The maximum number of log message lines to be saved in a log file.
12 set analysis(logMax) 5000
13 
14 # analysis(log_stdout):
15 # Set this to 1 if you want Scid-Engine communication log messages
16 # to be echoed to stdout.
17 #
18 set analysis(log_stdout) 0
19 
20 set useAnalysisBook 1
21 set analysisBookSlot 1
22 set useAnalysisBookName ""
23 set wentOutOfBook 0
24 # State variable: 1 <=> engine is making an initial
25 # assessment of the current position, before progressing
26 # into the game
27 set initialAnalysis 0
28 
29 # State variable: 1 <=> We will not add a variation to
30 # this move, since this cannot be different from the
31 # (engine variation to the) main line
32 set atStartOfLine 0
33 
34 set isBatch 0
35 set batchEnd 1
36 set isBatchOpening 0
37 set isBatchOpeningMoves 12
38 set stack ""
39 
40 set markTacticalExercises 0
41 
42 set isAnnotateVar 0
43 set isShortAnnotation 0
44 set addScoreToShortAnnotations 0
45 set addAnnotatorTag 0
46 
47 ################################################################################
48 #
49 ################################################################################
50 # resetEngine:
51 # Resets all engine-specific data.
52 #
53 proc resetEngine {n} {
54  global analysis
55  set analysis(pipe$n) "" ;# Communication pipe file channel
56  set analysis(seen$n) 0 ;# Seen any output from engine yet?
57  set analysis(seenEval$n) 0 ;# Seen evaluation line yet?
58  set analysis(score$n) 0 ;# Current score in centipawns
59  set analysis(prevscore$n) 0 ;# Immediately previous score in centipawns
60  set analysis(scoremate$n) 0 ;# Current mating distance (0 if infinite)
61  set analysis(prevscoremate$n) 0 ;# Previous mating distance
62  set analysis(prevmoves$n) "" ;# Immediately previous best line out from engine
63  set analysis(nodes$n) 0 ;# Number of (kilo)nodes searched
64  set analysis(depth$n) 0 ;# Depth in ply
65  set analysis(prev_depth$n) 0 ;# Previous depth
66  set analysis(time$n) 0 ;# Time in centisec (or sec; see below)
67  set analysis(moves$n) "" ;# PV (best line) output from engine
68  set analysis(seldepth$n) 0
69  set analysis(currmove$n) "" ;# current move output from engine
70  set analysis(currmovenumber$n) 0 ;# current move number output from engine
71  set analysis(hashfull$n) 0
72  set analysis(nps$n) 0
73  set analysis(tbhits$n) 0
74  set analysis(sbhits$n) 0
75  set analysis(cpuload$n) 0
76  set analysis(movelist$n) {} ;# Moves to reach current position
77  set analysis(nonStdStart$n) 0 ;# Game has non-standard start
78  set analysis(has_analyze$n) 0 ;# Engine has analyze command
79  set analysis(has_setboard$n) 0 ;# Engine has setboard command
80  set analysis(send_sigint$n) 0 ;# Engine wants INT signal
81  set analysis(wants_usermove$n) 0 ;# Engine wants "usermove" before moves
82  set analysis(isCrafty$n) 0 ;# Engine appears to be Crafty
83  set analysis(wholeSeconds$n) 0 ;# Engine times in seconds not centisec
84  set analysis(analyzeMode$n) 0 ;# Scid has started analyze mode
85  set analysis(invertScore$n) 1 ;# Score is for side to move, not white
86  set analysis(automove$n) 0
87  set analysis(automoveThinking$n) 0
88  set analysis(automoveTime$n) 4000
89  set analysis(lastClicks$n) 0
90  set analysis(after$n) ""
91  set analysis(log$n) "" ;# Log file channel
92  set analysis(logCount$n) 0 ;# Number of lines sent to log file
93  set analysis(wbEngineDetected$n) 0 ;# Is this a special Winboard engine?
94  set analysis(multiPV$n) {} ;# multiPV list sorted : depth score moves
95  set analysis(multiPVraw$n) {} ;# same thing but with raw UCI moves
96  set analysis(uci$n) 0 ;# UCI engine
97  # UCI engine options in format ( name min max ). This is not engine config but its capabilities
98  set analysis(uciOptions$n) {}
99  # the number of lines in multiPV. If =1 then act the traditional way
100  set analysis(multiPVCount$n) 1 ;# number of N-best lines
101  set analysis(uciok$n) 0 ;# uciok sent by engine in response to uci command
102  set analysis(name$n) "" ;# engine name
103  set analysis(processInput$n) 0 ;# the time of the last processed event
104  set analysis(waitForBestMove$n) 0
105  set analysis(waitForReadyOk$n) 0
106  set analysis(onUciOk$n) ""
107  set analysis(movesDisplay$n) 1 ;# if false, hide engine lines, only display scores
108  set analysis(lastHistory$n) {} ;# last best line
109  set analysis(maxmovenumber$n) 0 ;# the number of moves in this position
110  set analysis(lockEngine$n) 0 ;# the engine is locked to current position
111  set analysis(fen$n) {} ;# the position that engine is analyzing
112 }
113 
114 resetEngine 1
115 resetEngine 2
116 
117 set annotateMode 0
118 set annotateModeButtonValue 0 ; # feedback of annotate mode
119 
120 set annotateMoves all
121 set annotateBlunders blundersonly
122 set scoreAllMoves 0
123 
124 ################################################################################
125 # calculateNodes:
126 # Divides string-represented node count by 1000
127 ################################################################################
128 proc calculateNodes {{n}} {
129  set len [string length $n]
130  if { $len < 4 } {
131  return 0
132  } else {
133  set shortn [string range $n 0 [expr {$len - 4}]]
134  scan $shortn "%d" nd
135  return $nd
136  }
137 }
138 
139 
140 # resetAnalysis:
141 # Resets the analysis statistics: score, depth, etc.
142 #
143 proc resetAnalysis {{n 1}} {
144  global analysis
145  set analysis(score$n) 0
146  set analysis(scoremate$n) 0
147  set analysis(nodes$n) 0
148  set analysis(prev_depth$n) 0
149  set analysis(depth$n) 0
150  set analysis(time$n) 0
151  set analysis(moves$n) ""
152  set analysis(multiPV$n) {}
153  set analysis(multiPVraw$n) {}
154  set analysis(lastHistory$n) {}
155  set analysis(maxmovenumber$n) 0
156 }
157 
158 namespace eval enginelist {}
159 
160 set engines(list) {}
161 
162 # engine:
163 # Adds an engine to the engine list.
164 # Calls to this function will be found in the user engines.lis
165 # file, which is sourced below.
166 #
167 proc engine {arglist} {
168  global engines
169  array set newEngine {}
170  foreach {attr value} $arglist {
171  set newEngine($attr) $value
172  }
173  # Check that required attributes exist:
174  if {! [info exists newEngine(Name)]} { return 0}
175  if {! [info exists newEngine(Cmd)]} { return 0}
176  if {! [info exists newEngine(Dir)]} { return 0}
177  # Fill in optional attributes:
178  if {! [info exists newEngine(Args)]} { set newEngine(Args) ""}
179  if {! [info exists newEngine(Elo)]} { set newEngine(Elo) 0}
180  if {! [info exists newEngine(Time)]} { set newEngine(Time) 0}
181  if {! [info exists newEngine(URL)]} { set newEngine(URL) ""}
182  # puts this option here for compatibility with previous file format (?!)
183  if {! [info exists newEngine(UCI)]} { set newEngine(UCI) 0}
184  if {! [info exists newEngine(UCIoptions)]} { set newEngine(UCIoptions) ""}
185 
186  lappend engines(list) [list $newEngine(Name) $newEngine(Cmd) \
187  $newEngine(Args) $newEngine(Dir) \
188  $newEngine(Elo) $newEngine(Time) \
189  $newEngine(URL) $newEngine(UCI) $newEngine(UCIoptions)]
190  return 1
191 }
192 
193 # ::enginelist::read
194 # Reads the user Engine list file.
195 #
196 proc ::enginelist::read {} {
197  catch {source [scidConfigFile engines]}
198 }
199 
200 # ::enginelist::write:
201 # Writes the user Engine list file.
202 #
203 proc ::enginelist::write {} {
204  global engines ::uci::newOptions
205 
206  set enginesFile [scidConfigFile engines]
207  set enginesBackupFile [scidConfigFile engines.bak]
208  # Try to rename old file to backup file and open new file:
209  catch {file rename -force $enginesFile $enginesBackupFile}
210  if {[catch {open $enginesFile w} f]} {
211  catch {file rename $enginesBackupFile $enginesFile}
212  return 0
213  }
214 
215  puts $f "\# Analysis engines list file for Scid $::scidVersion with UCI support"
216  puts $f ""
217  foreach e $engines(list) {
218  set name [lindex $e 0]
219  set cmd [lindex $e 1]
220  set args [lindex $e 2]
221  set dir [lindex $e 3]
222  set elo [lindex $e 4]
223  set time [lindex $e 5]
224  set url [lindex $e 6]
225  set uci [lindex $e 7]
226  set opt [lindex $e 8]
227  puts $f "engine {"
228  puts $f " Name [list $name]"
229  puts $f " Cmd [list $cmd]"
230  puts $f " Args [list $args]"
231  puts $f " Dir [list $dir]"
232  puts $f " Elo [list $elo]"
233  puts $f " Time [list $time]"
234  puts $f " URL [list $url]"
235  puts $f " UCI [list $uci]"
236  puts $f " UCIoptions [list $opt]"
237  puts $f "}"
238  puts $f ""
239  }
240  close $f
241  return 1
242 }
243 
244 # Read the user Engine List file now:
245 #
246 catch { ::enginelist::read}
247 if {[llength $engines(list)] == 0} {
248  # No engines, so set up a default engine list:
249  set scidlet "scidlet"
250  set phalanx "phalanx-scid"
251  set togaII "togaII"
252  if { $::windowsOS } {
253  set scidlet "scidlet.exe"
254  set phalanx "phalanx-scid.exe"
255  set togaII "TogaII.exe"
256  }
257  set scidEngPaths [list $::scidExeDir [file join $::scidExeDir "engines"] [file join $::scidShareDir "engines"] \
258  [ file join $::scidUserDir "engines"] [ file join /usr local share scid engines] \
259  [ file join /usr local bin] [ file join /usr bin] [ file join /usr local games] [ file join /usr games] \
260  [file join $::scidExeDir "engines" "phalanx-scid"] [file join $::scidExeDir "engines" "togaII1.2.1a" "src"]]
261 
262  # The next four lists should have the same length!
263  set scidEngCmds [list $phalanx $togaII $scidlet]
264  set scidEngNames [list "Phalanx-Scid" "Toga II" "Scidlet"]
265  array set parentDirs "
266  $phalanx { phalanx-scid Phalanx-XXII }
267  $togaII { togaII1.2.1a toga togaII [ file join togaII1.2.1a src] }
268  $scidlet { . }
269  "
270 
271  set isUCI [list 0 1 0]
272 
273  # Let's search the engines:
274  foreach cmd $scidEngCmds name $scidEngNames uci $isUCI {
275  set leave 0
276  foreach path $scidEngPaths {
277  set c [ file join $path $cmd]
278  if { [file executable $c] && ! [ file isdirectory $c] } {
279  engine [list \
280  Name $name \
281  Cmd $c \
282  Dir . \
283  UCI $uci \
284  UCIoptions {}]
285  set leave 1
286  } else {
287  foreach parent $parentDirs($cmd) {
288  set c [ file join $path $parent $cmd]
289  if { [file executable $c] && ! [ file isdirectory $c] } {
290  engine [list \
291  Name $name \
292  Cmd $c \
293  Dir . \
294  UCI $uci \
295  UCIoptions {}]
296  set leave 1
297  break
298  }
299  }
300  }
301  if { $leave } { break}
302  }
303  }
304 }
305 
306 # ::enginelist::date
307 # Given a time in seconds since 1970, returns a
308 # formatted date string.
309 proc ::enginelist::date {time} {
310  return [clock format $time -format "%a %b %d %Y %H:%M"]
311 }
312 
313 # ::enginelist::sort
314 # Sort the engine list.
315 # If the engine-open dialog is open, its list is updated.
316 # The type defaults to the current engines(sort) value.
317 #
318 proc ::enginelist::sort {{type ""}} {
319  global engines
320 
321  if {$type == ""} {
322  set type $engines(sort)
323  } else {
324  set engines(sort) $type
325  }
326  switch $type {
327  Name {
328  set engines(list) [lsort -dictionary -index 0 $engines(list)]
329  }
330  Elo {
331  set engines(list) [lsort -integer -decreasing -index 4 $engines(list)]
332  }
333  Time {
334  set engines(list) [lsort -integer -decreasing -index 5 $engines(list)]
335  }
336  }
337 
338  # If the Engine-open dialog is open, update it:
339  #
340  set w .enginelist
341  if {! [winfo exists $w]} { return}
342  set f $w.list.list
343  $f delete 0 end
344  set count 0
345  foreach engine $engines(list) {
346  incr count
347  set name [lindex $engine 0]
348  set elo [lindex $engine 4]
349  set time [lindex $engine 5]
350  set uci [lindex $engine 7]
351  set date [::enginelist::date $time]
352  set text [format "%2u. %-21s " $count $name]
353  set eloText " "
354  if {$elo > 0} { set eloText [format "%4u" $elo]}
355  append text $eloText
356  set timeText " "
357  if {$time > 0} { set timeText " $date"}
358  append text $timeText
359  $f insert end $text
360  }
361  $f selection set 0
362 
363  # Show the sorted column heading in red text:
364  $w.title configure -state normal
365  foreach i {Name Elo Time} {
366  $w.title tag configure $i -foreground {}
367  }
368  $w.title tag configure $engines(sort) -foreground red
369  $w.title configure -state disabled
370 }
371 ################################################################################
372 # ::enginelist::choose
373 # Select an engine from the Engine List.
374 # Returns an integer index into the engines(list) list variable.
375 # If no engine is selected, returns the empty string.
376 ################################################################################
377 proc ::enginelist::choose {} {
378  global engines
379  set w .enginelist
380  if {[winfo exists $w]} {
381  raise .enginelist
382  return}
384  ::setTitle $w "Scid: [tr ToolsAnalysis]"
385  ttk::label $w.flabel -text $::tr(EngineList:) -font font_Bold -anchor center
386  pack $w.flabel -side top -fill x
387  pack [ttk::frame $w.buttons] -side bottom -fill x
388 
389  # Set up title frame for sorting the list:
390  text $w.title -width 55 -height 1 -font font_Fixed -relief flat \
391  -cursor top_left_arrow
392  $w.title insert end " "
393  $w.title insert end $::tr(EngineName) Name
394  for {set i [string length $::tr(EngineName)]} {$i < 21} { incr i} {
395  $w.title insert end " "
396  }
397  $w.title insert end " "
398  $w.title insert end $::tr(EngineElo) Elo
399  for {set i [string length $::tr(EngineElo)]} {$i < 4} { incr i} {
400  $w.title insert end " "
401  }
402  $w.title insert end " "
403  $w.title insert end $::tr(EngineTime) Time
404  foreach i {Name Elo Time} {
405  $w.title tag bind $i <Any-Enter> \
406  "$w.title tag configure $i -background yellow"
407  $w.title tag bind $i <Any-Leave> \
408  "$w.title tag configure $i -background {}"
409  $w.title tag bind $i <1> [list ::enginelist::sort $i]
410  }
411  $w.title configure -state disabled
412  pack $w.title -side top -fill x
413 
414  # The list of choices:
415  set f $w.list
416  pack [ttk::frame $f] -side top -expand yes -fill both
417  listbox $f.list -height 10 -width 55 -selectmode browse \
418  -background white -setgrid 1 \
419  -yscrollcommand "$f.ybar set" -font font_Fixed -exportselection 0
420  bind $f.list <Double-ButtonRelease-1> "$w.buttons.ok invoke; break"
421  ttk::scrollbar $f.ybar -command "$f.list yview"
422  pack $f.ybar -side right -fill y
423  pack $f.list -side top -fill both -expand yes
424  $f.list selection set 0
425 
426  set f $w.buttons
427  dialogbutton $f.add -text $::tr(EngineNew...) -command {::enginelist::edit -1}
428  dialogbutton $f.edit -text $::tr(EngineEdit...) -command {
429  ::enginelist::edit [lindex [.enginelist.list.list curselection] 0]
430  }
431  dialogbutton $f.delete -text $::tr(Delete...) -command {
432  ::enginelist::delete [lindex [.enginelist.list.list curselection] 0]
433  }
434  ttk::label $f.sep -text " "
435  dialogbutton $f.ok -text "OK" -command {
436  set engines(selection) [lindex [.enginelist.list.list curselection] 0]
437  destroy .enginelist
438  }
439  dialogbutton $f.cancel -text $::tr(Cancel) -command {
440  set engines(selection) ""
441  destroy .enginelist
442  }
443  packbuttons right $f.cancel $f.ok
444  pack $f.add $f.edit $f.delete -side left -padx 1
445 
448  focus $w.list.list
449  wm protocol $w WM_DELETE_WINDOW "destroy $w"
450  bind $w <F1> { helpWindow Analysis List }
451  bind $w <Escape> "destroy $w"
452  bind $w.list.list <Return> "$w.buttons.ok invoke; break"
453  set engines(selection) ""
454  catch {grab $w}
455  tkwait window $w
456  return $engines(selection)
457 }
458 
459 # ::enginelist::setTime
460 # Sets the last-opened time of the engine specified by its
461 # index in the engines(list) list variable.
462 # The time should be in standard format (seconds since 1970)
463 # and defaults to the current time.
464 #
465 proc ::enginelist::setTime {index {time -1}} {
466  global engines
467  set e [lindex $engines(list) $index]
468  if {$time < 0} { set time [clock seconds]}
469  set e [lreplace $e 5 5 $time]
470  set engines(list) [lreplace $engines(list) $index $index $e]
471 }
472 
473 trace variable engines(newElo) w [list ::utils::validate::Integer [sc_info limit elo] 0]
474 
475 # ::enginelist::delete
476 # Removes an engine from the list.
477 #
478 proc ::enginelist::delete {index} {
479  global engines
480  if {$index == "" || $index < 0} { return}
481  set e [lindex $engines(list) $index]
482  set msg "Name: [lindex $e 0]\n"
483  append msg "Command: [lindex $e 1]\n\n"
484  append msg "Do you really want to remove this engine from the list?"
485  set answer [tk_messageBox -title Scid -icon question -type yesno \
486  -message $msg]
487  if {$answer == "yes"} {
488  set engines(list) [lreplace $engines(list) $index $index]
491  }
492 }
493 
494 # ::enginelist::edit
495 # Opens a dialog for editing an existing engine list entry (if
496 # index >= 0), or adding a new entry (if index is -1).
497 #
498 proc ::enginelist::edit {index} {
499  global engines
500  if {$index == ""} { return}
501 
502  if {$index >= 0 || $index >= [llength $engines(list)]} {
503  set e [lindex $engines(list) $index]
504  } else {
505  set e [list "" "" "" . 0 0 "" 1]
506  }
507 
508  set engines(newIndex) $index
509  set engines(newName) [lindex $e 0]
510  set engines(newCmd) [lindex $e 1]
511  set engines(newArgs) [lindex $e 2]
512  set engines(newDir) [lindex $e 3]
513  set engines(newElo) [lindex $e 4]
514  set engines(newTime) [lindex $e 5]
515  set engines(newURL) [lindex $e 6]
516  set engines(newUCI) [lindex $e 7]
517  set engines(newUCIoptions) [lindex $e 8]
518 
519  set engines(newDate) $::tr(None)
520  if {$engines(newTime) > 0 } {
521  set engines(newDate) [::enginelist::date $engines(newTime)]
522  }
523 
524  set w .engineEdit
526  ::setTitle $w Scid
527 
528  set f [ttk::frame $w.f]
529  pack $f -side top -fill x -expand yes
530  set row 0
531  foreach i {Name Cmd Args Dir URL} {
532  ttk::label $f.l$i -text $i
533  if {[info exists ::tr(Engine$i)]} {
534  $f.l$i configure -text $::tr(Engine$i)
535  }
536  ttk::entry $f.e$i -textvariable engines(new$i) -width 40
537  bindFocusColors $f.e$i
538  grid $f.l$i -row $row -column 0 -sticky w
539  grid $f.e$i -row $row -column 1 -sticky we
540 
541  # Browse button for choosing an executable file:
542  if {$i == "Cmd"} {
543  ttk::button $f.b$i -text "..." -command {
544  if {$::windowsOS} {
545  set ftype {
546  {"Applications" {".bat" ".exe"} }
547  {"All files" {"*"} }
548  }
549  set fName [tk_getOpenFile -initialdir $engines(newDir) \
550  -title "Scid: [tr ToolsAnalysis]" -filetypes $ftype]
551  } else {
552  set fName [tk_getOpenFile -initialdir $engines(newDir) \
553  -title "Scid: [tr ToolsAnalysis]"]
554  }
555  if {$fName != ""} {
556  set engines(newCmd) $fName
557  # Set the directory from the executable path if possible:
558  set engines(newDir) [file dirname $fName]
559  if {$engines(newDir) == ""} [ set engines(newDir) .]
560  }
561  }
562  grid $f.b$i -row $row -column 2 -sticky we
563  }
564 
565  if {$i == "Dir"} {
566  ttk::button $f.current -text " . " -command {
567  set engines(newDir) .
568  }
569  ttk::button $f.user -text "~/.scid" -command {
570  set engines(newDir) $scidUserDir
571  }
572  if {$::windowsOS} {
573  $f.user configure -text "scid.exe dir"
574  }
575  grid $f.current -row $row -column 2 -sticky we
576  grid $f.user -row $row -column 3 -sticky we
577  }
578 
579  if {$i == "URL"} {
580  ttk::button $f.bURL -text [tr FileOpen] -command {
581  if {$engines(newURL) != ""} { openURL $engines(newURL) }
582  }
583  grid $f.bURL -row $row -column 2 -sticky we
584  }
585 
586  incr row
587  }
588 
589  grid columnconfigure $f 1 -weight 1
590 
591  ttk::checkbutton $f.cbUci -text UCI -variable engines(newUCI) -style Bold.TCheckbutton
592  ttk::button $f.bConfigUCI -text $::tr(ConfigureUCIengine) -command {
593  ::uci::uciConfig 2 [ toAbsPath $engines(newCmd) ] $engines(newArgs) \
594  [ toAbsPath $engines(newDir) ] $engines(newUCIoptions)
595  }
596  # Mark required fields:
597  $f.lName configure -font font_Bold
598  $f.lCmd configure -font font_Bold
599  $f.lDir configure -font font_Bold
600  # $f.cbUci configure -font font_Bold
601 
602  ttk::label $f.lElo -text $::tr(EngineElo)
603  ttk::entry $f.eElo -textvariable engines(newElo) -justify right -width 5
604  bindFocusColors $f.eElo
605  grid $f.lElo -row $row -column 0 -sticky w
606  grid $f.eElo -row $row -column 1 -sticky w
607  incr row
608  grid $f.cbUci -row $row -column 0 -sticky w
609  grid $f.bConfigUCI -row $row -column 1 -sticky w
610  incr row
611 
612  ttk::label $f.lTime -text $::tr(EngineTime)
613  ttk::label $f.eTime -textvariable engines(newDate) -anchor w -width 1
614  grid $f.lTime -row $row -column 0 -sticky w
615  grid $f.eTime -row $row -column 1 -sticky we
616  ttk::button $f.clearTime -text $::tr(Clear) -command {
617  set engines(newTime) 0
618  set engines(newDate) $::tr(None)
619  }
620  ttk::button $f.nowTime -text $::tr(Update) -command {
621  set engines(newTime) [clock seconds]
622  set engines(newDate) [::enginelist::date $engines(newTime)]
623  }
624  grid $f.clearTime -row $row -column 2 -sticky we
625  grid $f.nowTime -row $row -column 3 -sticky we
626 
628  set f [ttk::frame $w.buttons]
629  ttk::button $f.ok -text OK -command {
630  if {[string trim $engines(newName)] == "" ||
631  [string trim $engines(newCmd)] == "" ||
632  [string trim $engines(newDir)] == ""} {
633  tk_messageBox -title Scid -icon info \
634  -message "The Name, Command and Directory fields must not be empty."
635  } else {
636  set newEntry [list $engines(newName) $engines(newCmd) \
637  $engines(newArgs) $engines(newDir) \
638  $engines(newElo) $engines(newTime) \
639  $engines(newURL) $engines(newUCI) $::uci::newOptions ]
640  if {$engines(newIndex) < 0} {
641  lappend engines(list) $newEntry
642  } else {
643  set engines(list) [lreplace $engines(list) \
644  $engines(newIndex) $engines(newIndex) $newEntry]
645  }
646  destroy .engineEdit
647  ::enginelist::sort
648  ::enginelist::write
649  raise .enginelist
650  focus .enginelist
651  }
652  }
653  ttk::button $f.cancel -text $::tr(Cancel) -command "destroy $w; raise .enginelist; focus .enginelist"
654  pack $f -side bottom -fill x
655  pack $f.cancel $f.ok -side right -padx 2 -pady 2
656  ttk::label $f.required -font font_Small -text $::tr(EngineRequired)
657  pack $f.required -side left
658 
659  bind $w <Return> "$f.ok invoke"
660  bind $w <Escape> "destroy $w; raise .enginelist; focus .enginelist"
661  bind $w <F1> { helpWindow Analysis List }
662  focus $w.f.eName
663  wm resizable $w 1 0
664  catch {grab $w}
665 }
666 # ################################################################################
667 #
668 ################################################################################
669 proc autoplay {} {
670  global autoplayDelay autoplayMode annotateMode analysis
671 
672  # Was autoplay stopped by the user since the last time the timer ran out?
673  # If so, silently exit this handler
674  #
675  if { $autoplayMode == 0 } {
676  return
677  }
678 
679  # Add annotation if needed
680  #
681  if { $annotateMode } {
683  }
684 
685  if { $::initialAnalysis } {
686  # Stop analysis if it is running
687  # We do not want initial super-accuracy
688  #
690  set annotateMode 1
691  # First do the book analysis (if this is configured)
692  # The latter condition is handled by the operation itself
693  set ::wentOutOfBook 0
695  # Start the engine
697 
698  # Autoplay comes in two flavours:
699  # + It can run through a game, with or without annotation
700  # + It can be annotating just opening sections of games
701  # See if such streak ends here and now
702  #
703  } elseif { [sc_pos isAt end] || ($annotateMode && $::isBatchOpening && ([sc_pos moveNumber] > $::isBatchOpeningMoves)) } {
704 
705  # Stop the engine
706  #
708 
709  # Are we running a batch analysis?
710  #
711  if { $annotateMode && $::isBatch } {
712  # First replace the game we just finished
713  #
714  set gameNo [sc_game number]
715  if { $gameNo != 0 } {
716  sc_game save $gameNo
717  }
718 
719  # See if we must advance to the next game
720  #
721  if { $gameNo < $::batchEnd } {
722  incr gameNo
723  sc_game load $gameNo
725  updateBoard -pgn
726  # First do book analysis
727  #
728  set ::wentOutOfBook 0
730  # Start with initial assessment of the position
731  #
732  set ::initialAnalysis 1
733  # Start the engine
734  #
736  } else {
737  # End of batch, stop
738  #
740  return
741  }
742  } else {
743  # Not in a batch, just stop
744  #
746  return
747  }
748  } elseif { $annotateMode && $::isAnnotateVar } {
749  # A construction to prune empty variations here and now
750  # It makes no sense to discover only after some engine
751  # time that we entered a dead end.
752  #
753  set emptyVar 1
754  while { $emptyVar } {
755  set emptyVar 0
756  # Are we at the end of a variation?
757  # If so, pop back into the parent
758  #
759  if { [sc_pos isAt vend] } {
760  sc_var exit
761  set lastVar [::popAnalysisData]
762  } else {
763  set lastVar [sc_var count]
764  }
765  # Is there a subvariation here?
766  # If so, enter it after pushing where we are
767  #
768  if { $lastVar > 0 } {
769  incr lastVar -1
770  sc_var enter $lastVar
771  ::pushAnalysisData $lastVar
772  # Check if this line is empty
773  # If so, we will pop back immediately in the next run
774  #
775  if { [sc_pos isAt vstart] && [sc_pos isAt vend] } {
776  set emptyVar 1
777  } else {
778  # We are in a new line!
779  # Tell the annotator (he might be interested)
780  #
781  updateBoard -pgn
782  set ::atStartOfLine 1
783  }
784  } else {
785  # Just move ahead following the current line
786  #
788  }
789  }
790  } else {
791  # Just move ahead following the main line
792  #
794  }
795 
796  # Respawn
797  #
798  after $autoplayDelay autoplay
799 }
800 
801 proc startAutoplay { } {
802  set ::autoplayMode 1
803  after 100 autoplay
804 }
805 
806 proc setAnnotateModeButtonValue { value } {
807  if { ! $value } {
808  .analysisWin1.b1.annotate configure -image tb_annotate
809  .analysisWin1.b1.annotate state !pressed
810  } else {
811  .analysisWin1.b1.annotate configure -image tb_annotate_on
812  .analysisWin1.b1.annotate state pressed
813  }
814  set ::annotateModeButtonValue $value
815 }
816 
817 proc cancelAutoplay {} {
818  set ::autoplayMode 0
819  set ::annotateMode 0
821  after cancel autoplay
823 }
824 
825 
826 proc configAnnotation {n} {
827  global autoplayDelay tempdelay blunderThreshold annotateModeButtonValue
828 
829  set w .configAnnotation
830  # Do not do anything if the window exists
831  #
832  if { [winfo exists $w] } {
833  raise $w
834  focus $w
835  return
836  }
837 
838  if { ! $annotateModeButtonValue } {
840  } else {
842  }
843  # If the annotation button is pressed while annotation is
844  # running, stop the annotation
845  #
846  if { ! $annotateModeButtonValue } {
848  return
849  }
850 
851  trace variable blunderThreshold w {::utils::validate::Regexp {^[0-9]*\.?[0-9]*$}}
852 
853  set tempdelay [expr {int($autoplayDelay / 1000.0)}]
855  ::setTitle $w "Scid: $::tr(Annotate)"
856  wm resizable $w 0 0
857  set f [ttk::frame $w.f]
858  pack $f -expand 1
859 
860  ttk::labelframe $f.analyse -text $::tr(GameReview)
861  ttk::label $f.analyse.label -text $::tr(AnnotateTime)
862  ttk::spinbox $f.analyse.spDelay -background white -width 4 -textvariable tempdelay -from 1 -to 999 -increment 1 \
863  -validate key -validatecommand { return [string is digit %S] }
864  ttk::radiobutton $f.analyse.allmoves -text $::tr(AnnotateAllMoves) -variable annotateBlunders -value allmoves
865  ttk::radiobutton $f.analyse.blundersonly -text $::tr(AnnotateBlundersOnly) -variable annotateBlunders -value blundersonly
866  ttk::frame $f.analyse.blunderbox
867  ttk::label $f.analyse.blunderbox.label -text $::tr(BlundersThreshold:)
868  ttk::spinbox $f.analyse.blunderbox.spBlunder -background white -width 4 -textvariable blunderThreshold \
869  -from 0.1 -to 3.0 -increment 0.1
870  ttk::checkbutton $f.analyse.cbBook -text $::tr(UseBook) -variable ::useAnalysisBook
871  # choose a book for analysis
872  # load book names
873  set bookPath $::scidBooksDir
874  set bookList [ lsort -dictionary [ glob -nocomplain -directory $bookPath *.bin]]
875 
876  # No book found
877  if { [llength $bookList] == 0 } {
878  set ::useAnalysisBook 0
879  $f.analyse.cbBook configure -state disabled
880  }
881 
882  set tmp {}
883  set idx 0
884  set i 0
885  foreach file $bookList {
886  lappend tmp [ file tail $file]
887  if {$::book::lastBook == [ file tail $file] } {
888  set idx $i
889  }
890  incr i
891  }
892  ttk::combobox $f.analyse.comboBooks -width 12 -values $tmp
893  catch { $f.analyse.comboBooks current $idx}
894  pack $f.analyse.comboBooks -side bottom -anchor w -padx 20
895  pack $f.analyse.cbBook -side bottom -anchor w
896  pack $f.analyse.blunderbox.label -side left -padx { 20 0 }
897  pack $f.analyse.blunderbox.spBlunder -side left -anchor w
898  pack $f.analyse.blunderbox -side bottom -anchor w
899  pack $f.analyse.blundersonly -side bottom -anchor w
900  pack $f.analyse.allmoves -side bottom -anchor w
901  pack $f.analyse.label -side left -anchor w
902  pack $f.analyse.spDelay -side right -anchor e
903  bind $w <Escape> { .configAnnotation.f.buttons.cancel invoke }
904  bind $w <Return> { .configAnnotation.f.buttons.ok invoke }
905 
906  ttk::labelframe $f.av -text $::tr(AnnotateWhich)
907  ttk::radiobutton $f.av.all -text $::tr(AnnotateAll) -variable annotateMoves -value all
908  ttk::radiobutton $f.av.white -text $::tr(AnnotateWhite) -variable annotateMoves -value white
909  ttk::radiobutton $f.av.black -text $::tr(AnnotateBlack) -variable annotateMoves -value black
910  pack $f.av.all $f.av.white $f.av.black -side top -fill x -anchor w
911 
912  ttk::labelframe $f.comment -text $::tr(Comments)
913  ttk::checkbutton $f.comment.cbAnnotateVar -text $::tr(AnnotateVariations) -variable ::isAnnotateVar
914  ttk::checkbutton $f.comment.cbShortAnnotation -text $::tr(ShortAnnotations) -variable ::isShortAnnotation
915  ttk::checkbutton $f.comment.cbAddScore -text $::tr(AddScoreToShortAnnotations) -variable ::addScoreToShortAnnotations
916  ttk::checkbutton $f.comment.cbAddAnnotatorTag -text $::tr(addAnnotatorTag) -variable ::addAnnotatorTag
917  # Checkmark to enable all-move-scoring
918  ttk::checkbutton $f.comment.scoreAll -text $::tr(ScoreAllMoves) -variable scoreAllMoves
919  ttk::checkbutton $f.comment.cbMarkTactics -text $::tr(MarkTacticalExercises) -variable ::markTacticalExercises
920  if {! $::analysis(uci1)} {
921  set ::markTacticalExercises 0
922  $f.comment.cbMarkTactics configure -state disabled
923  }
924 
925  pack $f.comment.scoreAll $f.comment.cbAnnotateVar $f.comment.cbShortAnnotation $f.comment.cbAddScore \
926  $f.comment.cbAddAnnotatorTag $f.comment.cbMarkTactics -fill x -anchor w
927  # batch annotation of consecutive games, and optional opening errors finder
928  ttk::frame $f.batch
929  ttk::frame $f.buttons
930  grid $f.analyse -row 0 -column 0 -pady { 0 10 } -sticky nswe -padx { 0 10 }
931  grid $f.comment -row 0 -column 1 -pady { 0 10 } -sticky nswe -padx { 10 0 }
932  grid $f.av -row 1 -column 0 -pady { 10 0 } -sticky nswe -padx { 0 10 }
933  grid $f.batch -row 1 -column 1 -pady { 10 0 } -sticky nswe -padx { 10 0 }
934  grid $f.buttons -row 2 -column 1 -sticky we
935 
936  set to [sc_base numGames $::curr_db]
937  if {$to <1} { set to 1}
938  ttk::checkbutton $f.batch.cbBatch -text $::tr(AnnotateSeveralGames) -variable ::isBatch
939  ttk::spinbox $f.batch.spBatchEnd -background white -width 8 -textvariable ::batchEnd \
940  -from 1 -to $to -increment 1 -validate all -validatecommand { regexp {^[0-9]+$} %P }
941  ttk::checkbutton $f.batch.cbBatchOpening -text $::tr(FindOpeningErrors) -variable ::isBatchOpening
942  ttk::spinbox $f.batch.spBatchOpening -background white -width 2 -textvariable ::isBatchOpeningMoves \
943  -from 10 -to 20 -increment 1 -validate all -validatecommand { regexp {^[0-9]+$} %P }
944  ttk::label $f.batch.lBatchOpening -text $::tr(moves)
945  pack $f.batch.cbBatch -side top -anchor w -pady { 0 0 }
946  pack $f.batch.spBatchEnd -side top -padx 20 -anchor w
947  pack $f.batch.cbBatchOpening -side top -anchor w
948  pack $f.batch.spBatchOpening -side left -anchor w -padx { 20 4 }
949  pack $f.batch.lBatchOpening -side left
950  set ::batchEnd $to
951 
952  ttk::button $f.buttons.cancel -text $::tr(Cancel) -command {
953  destroy .configAnnotation
954  setAnnotateModeButtonValue 0
955  .analysisWin1.b1.annotate configure -image tb_annotate
956  .analysisWin1.b1.annotate state !pressed
957  }
958  ttk::button $f.buttons.ok -text "OK" -command {
959  set ::useAnalysisBookName [.configAnnotation.f.analyse.comboBooks get]
960  set ::book::lastBook $::useAnalysisBookName
961 
962  # tactical positions is selected, must be in multipv mode
963  if {$::markTacticalExercises} {
964  if { $::analysis(multiPVCount1) < 2} {
965  # TODO: Why not put it at the (apparent) minimum of 2?
966  #
967  set ::analysis(multiPVCount1) 4
968  changePVSize 1
969  }
970  }
971 
972  if {$tempdelay < 0.1} { set tempdelay 0.1 }
973  set autoplayDelay [expr {int($tempdelay * 1000)}]
974  destroy .configAnnotation
975  cancelAutoplay
976  setAnnotateModeButtonValue 1
977  .analysisWin1.b1.annotate configure -image tb_annotate_on
978  # Tell the analysis mode that we want an initial assessment of the
979  # position. So: no comments yet, please!
980  set ::initialAnalysis 1
981  # And start the time slicer
982  startAutoplay
983  }
984  pack $f.buttons.cancel $f.buttons.ok -side right -padx 5 -pady 5
985  focus $f.analyse.spDelay
986  bind $w <Destroy> { focus . }
987 }
988 ################################################################################
989 # Part of annotation process : will check the moves if they are in te book, and add a comment
990 # when going out of it
991 ################################################################################
992 proc bookAnnotation { {n 1} } {
993  global analysis
994 
995  if {$::annotateMode && $::useAnalysisBook} {
996 
997  set prevbookmoves ""
998  set bn [ file join $::scidBooksDir $::useAnalysisBookName]
999  sc_book load $bn $::analysisBookSlot
1000 
1001  set bookmoves [sc_book moves $::analysisBookSlot]
1002  while {[string length $bookmoves] != 0 && ![sc_pos isAt vend]} {
1003  # we are in book, so move immediately forward
1005  set prevbookmoves $bookmoves
1006  set bookmoves [sc_book moves $::analysisBookSlot]
1007  }
1008  sc_book close $::analysisBookSlot
1009  set ::wentOutOfBook 1
1010 
1011  set verboseMoveOutOfBook " $::tr(MoveOutOfBook)"
1012  set verboseLastBookMove " $::tr(LastBookMove)"
1013 
1014  set theCatch 0
1015  if { [ string match -nocase "*[sc_game info previousMoveNT]*" $prevbookmoves] != 1 } {
1016  if {$prevbookmoves != ""} {
1017  sc_pos setComment "[sc_pos getComment]$verboseMoveOutOfBook [::trans $prevbookmoves]"
1018  } else {
1019  sc_pos setComment "[sc_pos getComment]$verboseMoveOutOfBook"
1020  }
1021  # last move was out of book: it needs to be analyzed, so take back
1022  #
1023  set theCatch [catch {sc_move back 1}]
1024  } else {
1025  sc_pos setComment "[sc_pos getComment]$verboseLastBookMove"
1026  }
1027 
1028  if { ! $theCatch } {
1030  updateBoard -pgn
1031  }
1032  set analysis(prevscore$n) $analysis(score$n)
1033  set analysis(prevmoves$n) $analysis(moves$n)
1034  set analysis(prevscoremate$n) $analysis(scoremate$n)
1035  set analysis(prevdepth$n) $analysis(depth$n)
1036  }
1037 }
1038 ################################################################################
1039 # Will add **** to any position considered as a tactical shot
1040 # returns 1 if an exercise was marked, 0 if for some reason it was not (obvious move for example)
1041 ################################################################################
1042 proc markExercise { prevscore score nag} {
1043 
1044  sc_pos addNag $nag
1045 
1046  if {!$::markTacticalExercises} { return 0}
1047 
1048  # check at which depth the tactical shot is found
1049  # this assumes analysis by an UCI engine
1050  if {! $::analysis(uci1)} { return 0}
1051 
1052  set deltamove [expr {$score - $prevscore}]
1053  # filter tactics so only those with high gains are kept
1054  if { [expr abs($deltamove)] < $::informant("+/-") } { return 0}
1055  # dismiss games where the result is already clear (high score,and we continue in the same way)
1056  if { [expr $prevscore * $score] >= 0} {
1057  if { [expr abs($prevscore)] > $::informant("++-") } { return 0}
1058  if { [expr abs($prevscore)] > $::informant("+-") && [expr abs($score)] < [expr 2 * abs($prevscore)]} { return 0}
1059  }
1060 
1061  # The best move is much better than others.
1062  if { [llength $::analysis(multiPV1)] < 2 } {
1063  puts "error, not enough PV"
1064  return 0
1065  }
1066  set sc2 [lindex [ lindex $::analysis(multiPV1) 1] 1]
1067  if { [expr abs( $score - $sc2 )] < 1.5 } { return 0}
1068 
1069  # There is no other winning moves (the best move may not win, of course, but
1070  # I reject exercises when there are e.g. moves leading to +9, +7 and +5 scores)
1071  if { [expr $score * $sc2] > 0.0 && [expr abs($score)] > $::informant("+-") && [expr abs($sc2)] > $::informant("+-") } {
1072  return 0
1073  }
1074 
1075  # The best move does not lose position.
1076  if {[sc_pos side] == "white" && $score < [expr 0.0 - $::informant("+/-")] } { return 0}
1077  if {[sc_pos side] == "black" && $score > $::informant("+/-") } { return 0}
1078 
1079  # Move is not obvious: check that it is not the first move guessed at low depths
1080  set pv [ lindex [ lindex $::analysis(multiPV1) 0] 2]
1081  set bm0 [lindex $pv 0]
1082  foreach depth {1 2 3} {
1083  set res [ sc_pos analyze -time 1000 -hashkb 32 -pawnkb 1 -searchdepth $depth]
1084  set bm$depth [lindex $res 1]
1085  }
1086  if { $bm0 == $bm1 && $bm0 == $bm2 && $bm0 == $bm3 } {
1087  puts "obvious move"
1088  return 0
1089  }
1090 
1091  # find what time is needed to get the solution (use internal analyze function)
1092  set timer {1 2 5 10 50 100 200 1000}
1093  set movelist {}
1094  for {set t 0} {$t < [llength $timer]} { incr t} {
1095  set res [sc_pos analyze -time [lindex $timer $t] -hashkb 1 -pawnkb 1 -mindepth 0]
1096  set move_analyze [lindex $res 1]
1097  lappend movelist $move_analyze
1098  }
1099 
1100  # find at what timing the right move was reliably found
1101  # only the move is checked, not if the score is close to the expected one
1102  for {set t [expr [llength $timer] -1]} {$t >= 0} { incr t -1} {
1103  if { [lindex $movelist $t] != $bm0 } {
1104  break
1105  }
1106  }
1107 
1108  set difficulty [expr $t +2]
1109 
1110  # If the base opened is read only, like a PGN file, avoids an exception
1111  catch { sc_base gameflag [sc_base current] [sc_game number] set T}
1112  sc_pos setComment "****D${difficulty} [format %.1f $prevscore]->[format %.1f $score] [sc_pos getComment]"
1113  updateBoard
1114 
1115  return 1
1116 }
1117 ################################################################################
1118 #
1119 ################################################################################
1120 proc addAnnotation { {n 1} } {
1121  global analysis annotateMoves annotateBlunders annotateMode blunderThreshold scoreAllMoves autoplayDelay
1122 
1123  # Check if we only need to register an initial
1124  # assessment of the position
1125  # If so, we do not generate any annotation yet
1126  #
1127  if { $::initialAnalysis } {
1128  set ::initialAnalysis 0
1129 
1130  if { $::isBatchOpening && ([sc_pos moveNumber] < $::isBatchOpeningMoves ) } {
1131  appendAnnotator "opBlunder [sc_pos moveNumber] ([sc_pos side])"
1132  }
1133  if { $::addAnnotatorTag } {
1134  appendAnnotator "$analysis(name1) ([expr {$autoplayDelay / 1000}] sec)"
1135  }
1136 
1137  set analysis(prevscore$n) $analysis(score$n)
1138  set analysis(prevmoves$n) $analysis(moves$n)
1139  set analysis(prevscoremate$n) $analysis(scoremate$n)
1140  set analysis(prevdepth$n) $analysis(depth$n)
1141 
1142  return
1143  }
1144 
1145  # Check if we are at the start of a subline
1146  # If so, we will not include the engine line as a variation.
1147  # Rationale: this line cannot be different from the line for the
1148  # main move, that we will include anyway.
1149  #
1150  set skipEngineLine $::atStartOfLine
1151  set ::atStartOfLine 0
1152 
1153  # First look in the book selected
1154  # TODO: Is this dead code by now?
1155  # TODO: Seek for an opportunity to do book analysis on a move by
1156  # move basis, thus allowing variations to be included
1157  #
1158  if { ! $::wentOutOfBook && $::useAnalysisBook } {
1160  return
1161  }
1162 
1163  # Let's try to assess the situation:
1164  # We are here, now that the engine has analyzed the position reached by
1165  # our last move. Currently it is the opponent to move:
1166  #
1167  set tomove [sc_pos side]
1168 
1169  # And this is his best line:
1170  #
1171  set moves $analysis(moves$n)
1172  # For non-uci lines, trim space characters in <moveno>.[ *][...]<move>
1173  set moves [regsub -all {\. *} $moves {.}]
1174 
1175  # The best line we could have followed, and the game move we just played instead, are here:
1176  #
1177  set prevmoves $analysis(prevmoves$n)
1178  # For non-uci lines, trim space characters in <moveno>.[ *][...]<move>
1179  set prevmoves [regsub -all {\. *} $prevmoves {.}]
1180 
1181  set gamemove [sc_game info previousMoveNT]
1182 
1183  # Bail out if we have a mate
1184  #
1185  if { [expr { [string index $gamemove end] == "#" }] } {
1186  set analysis(prevscore$n) $analysis(score$n)
1187  set analysis(prevmoves$n) $analysis(moves$n)
1188  set analysis(prevscoremate$n) $analysis(scoremate$n)
1189  set analysis(prevdepth$n) $analysis(depth$n)
1190  return
1191  }
1192 
1193  # We will add a closing line at the end of variation or game
1194  #
1195  set addClosingLine 0
1196  if { [sc_pos isAt vend] } {
1197  set addClosingLine 1
1198  }
1199 
1200  # We do not want to insert a best-line variation into the game
1201  # if we did play along that line. Even not when annotating all moves.
1202  # It simply makes no sense to do so (unless we are debugging the engine!)
1203  # Sooner or later the game will deviate anyway; a variation at that point will
1204  # do nicely and is probably more accurate as well.
1205  #
1206  set bestMovePlayed 0
1207  set bestMoveIsMate 0
1208  if { $prevmoves != "" } {
1209  # Following lines of code have only one goal:
1210  # Transform an engine move (e.g. "g1f3") into the short notation that we use
1211  # for moves in our games ("Nf3"), such that they can be (string) compared.
1212  # We create a scratch copy of the game, add the engine move and then ask
1213  # the game about the most recent move that was played.
1214  # This might not be the most subtle solution...
1215  sc_game push copyfast
1216  set bestmove [lindex $prevmoves 0]
1217  sc_move back 1
1218  sc_move_add $bestmove $n
1219  set bestmove [sc_game info previousMoveNT]
1220  sc_game pop
1221 
1222  if { $bestmove == $gamemove } {
1223  set bestMovePlayed 1
1224  }
1225 
1226  # Did we miss a mate in one?
1227  #
1228  set bestMoveIsMate [expr { [string index $bestmove end] == "#" }]
1229  }
1230 
1231 
1232  # As said, another reason not to include the engine line
1233  #
1234  set skipEngineLine [expr {$skipEngineLine + $bestMovePlayed}]
1235 
1236  # As to the engine evaluations
1237  # This is score the opponent will have if he plays his best move next
1238  #
1239  set score $analysis(score$n)
1240 
1241  # This is the score we could have had if we had played our best move
1242  #
1243  set prevscore $analysis(prevscore$n)
1244 
1245  # Let's help the engine a bit...
1246  # It makes no sense to criticise the players for moving insights at
1247  # engine end. So we upgrade the old score to the new score if the lines
1248  # start with the same move.
1249  #
1250  if { $bestMovePlayed } {
1251  set prevscore $score
1252  }
1253 
1254  # Note that the engine's judgement is in absolute terms, a negative score
1255  # being favorable to black, a positive score favorable to white
1256  # Looking primarily for blunders, we are interested in the score decay,
1257  # which, for white, is (previous-current)
1258  #
1259  set deltamove [expr {$prevscore - $score}]
1260  # and whether the game was already lost for us
1261  #
1262  set gameIsLost [expr {$prevscore < (0.0 - $::informant("++-"))}]
1263 
1264  # Invert this logic for black
1265  #
1266  if { $tomove == "white" } {
1267  set deltamove [expr {0.0 - $deltamove}]
1268  set gameIsLost [expr {$prevscore > $::informant("++-")}]
1269  }
1270 
1271  # Note btw that if the score decay is - unexpectedly - negative, we played
1272  # a better move than the engine's best line!
1273 
1274  # Set an "isBlunder" filter.
1275  # Let's mark moves with a decay greater than the threshold.
1276  #
1277  set isBlunder 0
1278  if { $deltamove > $blunderThreshold } {
1279  set isBlunder 2
1280  } elseif { $deltamove > 0 } {
1281  set isBlunder 1
1282  }
1283 
1284  set absdeltamove [expr { abs($deltamove) }]
1285 
1286  set exerciseMarked 0
1287 
1288  # to parse scores if the engine's name contains - or + chars (see sc_game_scores)
1289  #
1290  set engine_name [string map {"-" " " "+" " "} $analysis(name$n)]
1291 
1292  # Prepare score strings for the opponent
1293  #
1294  if { $analysis(scoremate$n) != 0 } {
1295  set text [format "%d:M%d" $analysis(depth$n) $analysis(scoremate$n)]
1296  } else {
1297  set text [format "%d:%+.2f" $analysis(depth$n) $score]
1298  }
1299  # And for the my (missed?) chance
1300  #
1301  if { $analysis(prevscoremate$n) != 0 } {
1302  set prevtext [format "%d:M%d" $analysis(prevdepth$n) $analysis(prevscoremate$n)]
1303  } else {
1304  set prevtext [format "%d:%+.2f" $analysis(prevdepth$n) $prevscore]
1305  }
1306 
1307  # Must we annotate our own moves? If no, we bail out unless
1308  # - we must add a closing line
1309  #
1310  if { ( $annotateMoves == "white" && $tomove == "white" ||
1311  $annotateMoves == "black" && $tomove == "black" ) && ! $addClosingLine } {
1312  set analysis(prevscore$n) $analysis(score$n)
1313  set analysis(prevmoves$n) $analysis(moves$n)
1314  set analysis(prevscoremate$n) $analysis(scoremate$n)
1315  set analysis(prevdepth$n) $analysis(depth$n)
1316 
1317  # Add score for this position anyway if this is configured
1318  #
1319  if { $scoreAllMoves } {
1320  sc_pos setComment "[sc_pos getComment] $text"
1321  }
1322 
1323  updateBoard -pgn
1324  }
1325 
1326 
1327  # See if we have the threshold filter activated.
1328  # If so, take only bad moves and missed mates until the position is lost anyway
1329  #
1330  # Or that we must annotate all moves
1331  #
1332  if { ( $annotateBlunders == "blundersonly"
1333  && ($isBlunder > 1 || ($isBlunder > 0 && [expr abs($score)] >= 327.0))
1334  && ! $gameIsLost)
1335  || ($annotateBlunders == "allmoves") } {
1336  if { $isBlunder > 0 } {
1337  # Add move score nag, and possibly an exercise
1338  #
1339  if { $absdeltamove > $::informant("??") } {
1340  set exerciseMarked [ markExercise $prevscore $score "??"]
1341  } elseif { $absdeltamove > $::informant("?") } {
1342  set exerciseMarked [ markExercise $prevscore $score "?"]
1343  } elseif { $absdeltamove > $::informant("?!") } {
1344  sc_pos addNag "?!"
1345  }
1346  } elseif { $absdeltamove > $::informant("!?") } {
1347  sc_pos addNag "!?"
1348  }
1349 
1350  # Add score comment and engine name if needed
1351  #
1352  if { ! $::isShortAnnotation } {
1353  sc_pos setComment "[sc_pos getComment] $engine_name: $text"
1354  } elseif { $::addScoreToShortAnnotations || $scoreAllMoves } {
1355  sc_pos setComment "[sc_pos getComment] $text"
1356  }
1357 
1358  # Add position score nag
1359  #
1360  sc_pos addNag [scoreToNag $score]
1361 
1362  # Add the variation
1363  #
1364  if { $skipEngineLine == 0 } {
1365  sc_move back
1366  if { $annotateBlunders == "blundersonly" } {
1367  # Add a diagram tag, but avoid doubles
1368  #
1369  if { [string first "D" "[sc_pos getNags]"] == -1 } {
1370  sc_pos addNag "D"
1371  }
1372  }
1373  if { $prevmoves != ""} {
1374  sc_var create
1375  # Add the starting move
1376  sc_move_add [lrange $prevmoves 0 0] $n
1377  # Add its score
1378  if { ! $bestMoveIsMate } {
1379  if { ! $::isShortAnnotation || $::addScoreToShortAnnotations } {
1380  sc_pos setComment "$prevtext"
1381  }
1382  }
1383  # Add remaining moves
1384  sc_move_add [lrange $prevmoves 1 end] $n
1385  # Add position NAG, unless the line ends in mate
1386  if { $analysis(prevscoremate$n) == 0 } {
1387  sc_pos addNag [scoreToNag $prevscore]
1388  }
1389  sc_var exit
1390  }
1391  sc_move forward
1392  }
1393  } else {
1394  if { $isBlunder == 0 && $absdeltamove > $::informant("!?") } {
1395  sc_pos addNag "!?"
1396  }
1397  if { $scoreAllMoves } {
1398  # Add a score mark anyway
1399  #
1400  sc_pos setComment "[sc_pos getComment] $text"
1401  }
1402  }
1403 
1404  if { $addClosingLine } {
1405  sc_move back
1406  sc_var create
1407  sc_move addSan $gamemove
1408  if { $analysis(scoremate$n) == 0 } {
1409  if { ! $::isShortAnnotation || $::addScoreToShortAnnotations } {
1410  sc_pos setComment "$text"
1411  }
1412  }
1413  sc_move_add $moves 1
1414  if { $analysis(scoremate$n) == 0 } {
1415  sc_pos addNag [scoreToNag $score]
1416  }
1417  sc_var exit
1418  # Now up to the end of the game
1420  }
1421 
1422  set analysis(prevscore$n) $analysis(score$n)
1423  set analysis(prevmoves$n) $analysis(moves$n)
1424  set analysis(prevscoremate$n) $analysis(scoremate$n)
1425  set analysis(prevdepth$n) $analysis(depth$n)
1426 
1427  updateBoard -pgn
1428 }
1429 
1430 # Informant index strings
1431 array set ana_informantList { 0 "+=" 1 "+/-" 2 "+-" 3 "++-" }
1432 # Nags. Note the slight inconsistency for the "crushing" symbol (see game.cpp)
1433 array set ana_nagList { 0 "=" 1 "+=" 2 "+/-" 3 "+-" 4 "+--" 5 "=" 6 "=+" 7 "-/+" 8 "-+" 9 "--+" }
1434 ################################################################################
1435 #
1436 ################################################################################
1437 proc scoreToNag {score} {
1438  global ana_informantList ana_nagList
1439  # Find the score in the informant map
1440  set tmp [expr { abs( $score ) }]
1441  for { set i 0} { $i < 4 } { incr i} {
1442  if { $tmp < $::informant("$ana_informantList($i)") } {
1443  break
1444  }
1445  }
1446  # Jump into negative counterpart
1447  if { $score < 0.0 } {
1448  set i [expr {$i + 5}]
1449  }
1450  return $ana_nagList($i)
1451 }
1452 ################################################################################
1453 # will append arg to current game Annotator tag
1454 ################################################################################
1455 proc appendAnnotator { s } {
1456  # Get the current collection of extra tags
1457  set extra [sc_game tags get "Extra"]
1458 
1459  set annot 0
1460  set other ""
1461  set nExtra {}
1462  # Walk through the extra tags, just copying the crap we do not need
1463  # If we meet the existing annotator tag, add our name to the list
1464  foreach line $extra {
1465  if { $annot == 1 } {
1466  lappend nExtra "Annotator \"$line, $s\"\n"
1467  set annot 2
1468  } elseif { $other != "" } {
1469  lappend nExtra "$other \"$line\"\n"
1470  set other ""
1471  } elseif {[string match "Annotator" $line]} {
1472  set annot 1
1473  } else {
1474  set other $line
1475  }
1476  }
1477 
1478  # First annotator: Create a tag
1479  if { $annot == 0 } {
1480  lappend nExtra "Annotator \"$s\"\n"
1481  }
1482  # Put the extra tags back to the game
1483  sc_game tags set -extra $nExtra
1484 }
1485 ################################################################################
1486 #
1487 ################################################################################
1488 proc pushAnalysisData { { lastVar } { n 1 } } {
1489  global analysis
1490  lappend ::stack [list $analysis(prevscore$n) $analysis(prevscoremate$n) $analysis(prevdepth$n) \
1491  $analysis(score$n) $analysis(scoremate$n) $analysis(depth$n) \
1492  $analysis(prevmoves$n) $analysis(moves$n) $lastVar]
1493 }
1494 ################################################################################
1495 #
1496 ################################################################################
1497 proc popAnalysisData { { n 1 } } {
1498  global analysis
1499  # the start of analysis is in the middle of a variation
1500  if {[llength $::stack] == 0} {
1501  set analysis(prevscore$n) 0
1502  set analysis(prevscoremate$n) 0
1503  set analysis(prevdepth$n) 0
1504  set analysis(score$n) 0
1505  set analysis(scoremate$n) 0
1506  set analysis(depth$n) 0
1507  set analysis(prevmoves$n) ""
1508  set analysis(moves$n) ""
1509  set lastVar 0
1510  return
1511  }
1512  set tmp [lindex $::stack end]
1513  set analysis(prevscore$n) [lindex $tmp 0]
1514  set analysis(prevscoremate$n) [lindex $tmp 1]
1515  set analysis(prevdepth$n) [lindex $tmp 2]
1516  set analysis(score$n) [lindex $tmp 3]
1517  set analysis(scoremate$n) [lindex $tmp 4]
1518  set analysis(depth$n) [lindex $tmp 5]
1519  set analysis(prevmoves$n) [lindex $tmp 6]
1520  set analysis(moves$n) [lindex $tmp 7]
1521  set lastVar [lindex $tmp 8]
1522  set ::stack [lreplace $::stack end end]
1523  return $lastVar
1524 }
1525 
1526 ################################################################################
1527 #
1528 ################################################################################
1529 proc addAnalysisVariation {{n 1}} {
1530  global analysis
1531 
1532  if {! [winfo exists .analysisWin$n]} { return}
1533 
1534  # Cannot add a variation to an empty variation:
1535  if {[sc_pos isAt vstart] && [sc_pos isAt vend]} { return}
1536 
1537  # if we are at the end of the game, we cannot add variation
1538  # so we add the analysis one move before and append the last game move at the beginning of the analysis
1539  set addAtEnd [sc_pos isAt vend]
1540 
1541  set moves $analysis(moves$n)
1542  if {$analysis(uci$n)} {
1543  set tmp_moves [ lindex [ lindex $analysis(multiPV$n) 0] 2]
1544  set text [format "\[%s\] %d:%s" $analysis(name$n) $analysis(depth$n) [scoreToMate $analysis(score$n) $tmp_moves $n]]
1545  } else {
1546  set text [format "\[%s\] %d:%+.2f" $analysis(name$n) $analysis(depth$n) $analysis(score$n)]
1547  }
1548 
1549  if {$addAtEnd} {
1550  # get the last move of the game
1551  set lastMove [sc_game info previousMoveUCI]
1552  #back one move
1553  sc_move back
1554  }
1555 
1556  # Add the variation:
1557  sc_var create
1558  # Add the comment at the start of the variation:
1559  sc_pos setComment "[sc_pos getComment] $text"
1560  if {$addAtEnd} {
1561  # Add the last move of the game at the beginning of the analysis
1562  sc_move_add $lastMove $n
1563  }
1564  # Add as many moves as possible from the engine analysis:
1565  sc_move_add $moves $n
1566  sc_var exit
1567 
1568  if {$addAtEnd} {
1569  #forward to the last move
1570  sc_move forward
1571  }
1572 
1574 }
1575 ################################################################################
1576 #
1577 ################################################################################
1578 proc addAllVariations {{n 1}} {
1579  global analysis
1580 
1581  if {! [winfo exists .analysisWin$n]} { return}
1582 
1583  # Cannot add a variation to an empty variation:
1584  if {[sc_pos isAt vstart] && [sc_pos isAt vend]} { return}
1585 
1586  # if we are at the end of the game, we cannot add variation
1587  # so we add the analysis one move before and append the last game move at the beginning of the analysis
1588  set addAtEnd [sc_pos isAt vend]
1589 
1590  foreach i $analysis(multiPVraw$n) j $analysis(multiPV$n) {
1591  set moves [lindex $i 2]
1592 
1593  set tmp_moves [ lindex $j 2]
1594  set text [format "\[%s\] %d:%s" $analysis(name$n) [lindex $i 0] [scoreToMate [lindex $i 1] $tmp_moves $n]]
1595 
1596  if {$addAtEnd} {
1597  # get the last move of the game
1598  set lastMove [sc_game info previousMoveUCI]
1599  sc_move back
1600  }
1601 
1602  # Add the variation:
1603  sc_var create
1604  # Add the comment at the start of the variation:
1605  sc_pos setComment "[sc_pos getComment] $text"
1606  if {$addAtEnd} {
1607  # Add the last move of the game at the beginning of the analysis
1608  sc_move_add $lastMove $n
1609  }
1610  # Add as many moves as possible from the engine analysis:
1611  sc_move_add $moves $n
1612  sc_var exit
1613 
1614  if {$addAtEnd} {
1615  #forward to the last move
1616  sc_move forward
1617  }
1618 
1619  }
1620 
1622 }
1623 ################################################################################
1624 #
1625 ################################################################################
1626 proc makeAnalysisMove {{n 1} {comment ""}} {
1627  regexp {[^[:alpha:]]*(.*?)( .*|$)} $::analysis(moves$n) -> move
1628  if {![info exists move]} { return 0}
1629 
1630  if { $::analysis(uci$n) } {
1631  ::addMoveUCI $move
1632  } else {
1633  ::addSanMove $move
1634  }
1635 
1636  if {$comment != ""} {
1637  set tmp [sc_pos getComment]
1638  if {$tmp != ""} { lappend tmp " - "}
1639  sc_pos setComment "$tmp$comment"
1640  }
1641 
1642  return 1
1643 }
1644 ################################################################################
1645 #
1646 ################################################################################
1647 
1648 # destroyAnalysisWin:
1649 # Closes an engine, because its analysis window is being destroyed.
1650 #
1651 proc destroyAnalysisWin {{n 1}} {
1652 
1653  global windowsOS analysis annotateMode
1654 
1655  if {$::finishGameMode} { toggleFinishGame}
1656 
1657  if { $n == 1 && $annotateMode } {
1659  }
1660 
1661  # Cancel scheduled commands
1662  if {$analysis(after$n) != ""} {
1663  after cancel $analysis(after$n)
1664  }
1665 
1666  # Check the pipe is not already closed:
1667  if {$analysis(pipe$n) == ""} {
1668  set ::analysisWin$n 0
1669  return
1670  }
1671 
1672  # Send interrupt signal if the engine wants it:
1673  if {(!$windowsOS) && $analysis(send_sigint$n)} {
1674  catch {exec -- kill -s INT [pid $analysis(pipe$n)]}
1675  }
1676 
1677  # Some engines in analyze mode may not react as expected to "quit"
1678  # so ensure the engine exits analyze mode first:
1679  if {$analysis(uci$n)} {
1680  sendToEngine $n "stop"
1681  sendToEngine $n "quit"
1682  } else {
1683  sendToEngine $n "exit"
1684  sendToEngine $n "quit"
1685  }
1686  catch { flush $analysis(pipe$n)}
1687 
1688  # Uncomment the following line to turn on blocking mode before
1689  # closing the engine (but probably not a good idea!)
1690  # fconfigure $analysis(pipe$n) -blocking 1
1691 
1692  # Close the engine, ignoring any errors since nothing can really
1693  # be done about them anyway -- maybe should alert the user with
1694  # a message box?
1695  catch {close $analysis(pipe$n)}
1696 
1697  if {$analysis(log$n) != ""} {
1698  catch {close $analysis(log$n)}
1699  set analysis(log$n) ""
1700  }
1701  resetEngine $n
1702  set ::analysisWin$n 0
1703 }
1704 
1705 # sendToEngine:
1706 # Send a command to a running analysis engine.
1707 #
1708 proc sendToEngine {n text} {
1709  # puts " -------- Scid>> $text"
1710  logEngine $n "Scid : $text"
1711  catch {puts $::analysis(pipe$n) $text}
1712 }
1713 
1714 # sendMoveToEngine:
1715 # Sends a move to a running analysis engine, using sendToEngine.
1716 # If the engine has indicated (with "usermove=1" on a "feature" line)
1717 # that it wants it, send with "usermove " before the move.
1718 #
1719 proc sendMoveToEngine {n move} {
1720  # Convert "e7e8Q" into "e7e8q" since that is the XBoard/WinBoard
1721  # standard for sending moves in coordinate notation:
1722  set move [string tolower $move]
1723  if {$::analysis(uci$n)} {
1724  # should be position fen [sc_pos fen] moves ?
1725  sendToEngine $n "position fen [sc_pos fen] moves $move"
1726  } else {
1727  if {$::analysis(wants_usermove$n)} {
1728  sendToEngine $n "usermove $move"
1729  } else {
1730  sendToEngine $n $move
1731  }
1732  }
1733 }
1734 
1735 # logEngine:
1736 # Log Scid-Engine communication.
1737 #
1738 proc logEngine {n text} {
1739  global analysis
1740 
1741  # Print the log message to stdout if applicable:
1742  if {$::analysis(log_stdout)} {
1743  puts stdout $text
1744  }
1745 
1746  if { [ info exists ::analysis(log$n)] && $::analysis(log$n) != ""} {
1747  puts $::analysis(log$n) $text
1748  catch { flush $::analysis(log$n)}
1749 
1750  # Close the log file if the limit is reached:
1751  incr analysis(logCount$n)
1752  if {$analysis(logCount$n) >= $analysis(logMax)} {
1753  puts $::analysis(log$n) \
1754  "NOTE : Log file size limit reached; closing log file."
1755  catch {close $analysis(log$n)}
1756  set analysis(log$n) ""
1757  }
1758  }
1759 }
1760 
1761 # logEngineNote:
1762 # Add a note to the engine communication log file.
1763 #
1764 proc logEngineNote {n text} {
1765  logEngine $n "NOTE : $text"
1766 }
1767 
1768 ################################################################################
1769 #
1770 # makeAnalysisWin:
1771 # Produces the engine list dialog box for choosing an engine,
1772 # then opens an analysis window and starts the engine.
1773 ################################################################################
1774 proc makeAnalysisWin { {n 1} {index -1} {autostart 1}} {
1775  global analysisWin$n font_Analysis analysisCommand analysis annotateModeButtonValue
1776 
1777  set w ".analysisWin$n"
1778  if {[winfo exists $w]} {
1779  focus .
1780  destroy $w
1781  return
1782  }
1783 
1784  resetEngine $n
1785 
1786  if { $index < 0 } {
1787  # engine selection dialog
1788  set index [::enginelist::choose]
1789  if { $index == "" || $index < 0 } { return}
1790  catch {
1791  ::enginelist::setTime $index
1792  }
1793  } else {
1794  # F2, F3
1795  set index [expr {$n - 1}]
1796  }
1797 
1798  set n_engines [llength $::engines(list)]
1799  if { $index >= $n_engines} {
1800  if { $n_engines > 0 } {
1801  tk_messageBox -message "Invalid Engine Number: [expr $index +1]"
1802  makeAnalysisWin $n -1
1803  }
1804  return
1805  }
1806 
1807  # Set the button in non-annotation state
1808  #
1809  if { $n == 1 } {
1810  set annotateModeButtonValue 0
1811  }
1812 
1813  set engineData [lindex $::engines(list) $index]
1814  set analysisName [lindex $engineData 0]
1815  set analysisCommand [ toAbsPath [lindex $engineData 1]]
1816  set analysisArgs [lindex $engineData 2]
1817  set analysisDir [ toAbsPath [lindex $engineData 3]]
1818  set analysis(uci$n) [ lindex $engineData 7]
1819 
1820  # If the analysis directory is not current dir, cd to it:
1821  set oldpwd ""
1822  if {$analysisDir != "."} {
1823  set oldpwd [pwd]
1824  catch {cd $analysisDir}
1825  }
1826 
1827  # Try to execute the analysis program:
1828  set open_err [catch {set analysis(pipe$n) [open "| [list $analysisCommand] $analysisArgs" "r+"]}]
1829 
1830  # Return to original dir if necessary:
1831  if {$oldpwd != ""} { catch {cd $oldpwd}}
1832 
1833  if {$open_err} {
1834  tk_messageBox -title "Scid: error starting analysis" \
1835  -icon warning -type ok \
1836  -message "Unable to start the program:\n$analysisCommand"
1837  resetEngine $n
1838  return
1839  }
1840 
1841  # Open log file if applicable:
1842  set analysis(log$n) ""
1843  if {$analysis(logMax) > 0} {
1844  if {! [catch {open [file join $::scidLogDir "engine$n.log"] w} log]} {
1845  set analysis(log$n) $log
1846  logEngine $n "Scid-Engine communication log file"
1847  logEngine $n "Engine: $analysisName"
1848  logEngine $n "Command: $analysisCommand"
1849  logEngine $n "Date: [clock format [clock seconds]]"
1850  logEngine $n ""
1851  logEngine $n "This file was automatically generated by Scid."
1852  logEngine $n "It is rewritten every time an engine is started in Scid."
1853  logEngine $n ""
1854  }
1855  }
1856 
1857  set analysis(name$n) $analysisName
1858 
1859  # Configure pipe for line buffering and non-blocking mode:
1860  fconfigure $analysis(pipe$n) -buffering line -blocking 0
1861 
1862  #
1863  # Set up the analysis window:
1864  #
1865  ::createToplevel $w
1866  set analysisWin$n 1
1867  if {$n == 1} {
1868  ::setTitle $w "Analysis: $analysisName"
1869  } else {
1870  ::setTitle $w "Analysis $n: $analysisName"
1871  }
1872  bind $w <F1> { helpWindow Analysis }
1873 
1874  ::board::new $w.bd 25
1875  $w.bd configure -relief solid -borderwidth 1
1876  set analysis(showBoard$n) 0
1877  set analysis(showEngineInfo$n) 0
1878 
1879  ttk::frame $w.b1
1880  pack $w.b1 -side bottom -fill x
1881  ttk::button $w.b1.automove -image tb_training -command "toggleAutomove $n"
1882  ::utils::tooltip::Set $w.b1.automove $::tr(Training)
1883 
1884  ttk::button $w.b1.lockengine -image tb_lockengine -command "toggleLockEngine $n"
1885  ::utils::tooltip::Set $w.b1.lockengine $::tr(LockEngine)
1886  .analysisWin$n.b1.lockengine configure -state disabled
1887 
1888  ttk::button $w.b1.line -image tb_addvar -command "addAnalysisVariation $n"
1889  ::utils::tooltip::Set $w.b1.line $::tr(AddVariation)
1890 
1891  ttk::button $w.b1.alllines -image tb_addallvars -command "addAllVariations $n"
1892  ::utils::tooltip::Set $w.b1.alllines $::tr(AddAllVariations)
1893 
1894  ttk::button $w.b1.move -image tb_addmove -command "makeAnalysisMove $n"
1895  ::utils::tooltip::Set $w.b1.move $::tr(AddMove)
1896 
1897  ttk::spinbox $w.b1.multipv -from 1 -to 8 -increment 1 -textvariable analysis(multiPVCount$n) -state disabled -width 2 \
1898  -command "changePVSize $n"
1899  ::utils::tooltip::Set $w.b1.multipv $::tr(Lines)
1900 
1901  # add a button to start/stop engine analysis
1902  ttk::button $w.b1.bStartStop -image tb_eng_on -command "toggleEngineAnalysis $n"
1903  ::utils::tooltip::Set $w.b1.bStartStop "$::tr(StartEngine) (F[expr 3 + $n])"
1904 
1905  if {$n == 1} {
1906  set ::finishGameMode 0
1907  ttk::button $w.b1.bFinishGame -image tb_finish_off -command "toggleFinishGame $n"
1908  ::utils::tooltip::Set $w.b1.bFinishGame $::tr(FinishGame)
1909  }
1910  ttk::button $w.b1.showboard -image tb_coords -command "toggleAnalysisBoard $n"
1911  ::utils::tooltip::Set $w.b1.showboard $::tr(ShowAnalysisBoard)
1912 
1913  ttk::button $w.b1.showinfo -image tb_engineinfo -command "toggleEngineInfo $n"
1914  ::utils::tooltip::Set $w.b1.showinfo $::tr(ShowInfo)
1915 
1916  if {$n == 1} {
1917  ttk::button $w.b1.annotate -image tb_annotate -command "configAnnotation $n"
1918  ::utils::tooltip::Set $w.b1.annotate $::tr(Annotate...)
1919  }
1920  ttk::button $w.b1.priority -image tb_cpu_hi -command "setAnalysisPriority $w $n"
1921  ::utils::tooltip::Set $w.b1.priority $::tr(LowPriority)
1922 
1923  ttk::button $w.b1.update -image tb_update -command "if {$analysis(uci$n)} {sendToEngine $n .}" ;# UCI does not support . command
1924  ::utils::tooltip::Set $w.b1.update $::tr(Update)
1925 
1926  ttk::button $w.b1.help -image tb_help -command { helpWindow Analysis }
1927  ::utils::tooltip::Set $w.b1.help $::tr(Help)
1928 
1929  pack $w.b1.bStartStop $w.b1.lockengine $w.b1.move $w.b1.line -side left
1930  if {$analysis(uci$n)} {
1931  pack $w.b1.alllines -side left
1932  }
1933  if {$n ==1} {
1934  pack $w.b1.multipv $w.b1.annotate $w.b1.automove $w.b1.bFinishGame -side left
1935  } else {
1936  pack $w.b1.multipv $w.b1.automove -side left
1937  }
1938  pack $w.b1.help $w.b1.priority $w.b1.showboard -side right
1939  if {! $analysis(uci$n)} {
1940  pack $w.b1.update -side right
1941  } else {
1942  pack $w.b1.showinfo -side right
1943  }
1944  if {$analysis(uci$n)} {
1945  text $w.text -width 60 -height 1 -fg black -bg white -font font_Bold -wrap word -setgrid 1 ;# -spacing3 2
1946  } else {
1947  text $w.text -width 60 -height 4 -fg black -bg white -font font_Fixed -wrap word -setgrid 1
1948  }
1949  ttk::frame $w.hist
1950  text $w.hist.text -width 60 -height 8 -fg black -bg white -font font_Fixed \
1951  -wrap word -setgrid 1 -yscrollcommand "$w.hist.ybar set"
1952  $w.hist.text tag configure indent -lmargin2 [font measure font_Fixed "xxxxxxxxxxxx"]
1953  ttk::scrollbar $w.hist.ybar -command "$w.hist.text yview" -takefocus 0
1954  pack $w.text -side top -fill both
1955  pack $w.hist -side top -expand 1 -fill both
1956  pack $w.hist.ybar -side right -fill y
1957  pack $w.hist.text -side left -expand 1 -fill both
1958 
1959  bind $w.hist.text <ButtonPress-$::MB3> "toggleMovesDisplay $n"
1960  $w.text tag configure blue -foreground blue
1961  $w.text tag configure bold -font font_Bold
1962  $w.text tag configure small -font font_Small
1963  $w.hist.text tag configure blue -foreground blue -lmargin2 [font measure font_Fixed "xxxxxxxxxxxx"]
1964  $w.hist.text tag configure gray -foreground gray
1965  if {$autostart != 0} {
1966  $w.text insert end "Please wait a few seconds for engine initialisation (with some engines, you will not see any analysis \
1967  until the board changes. So if you see this message, try changing the board \
1968  by moving backward or forward or making a new move.)" small
1969  }
1970  $w.text configure -state disabled
1971  bind $w <Destroy> "if {\[string equal $w %W\]} { destroyAnalysisWin $n }"
1972  bind $w <Escape> "focus .; destroy $w"
1973  bind $w <Key-a> "$w.b1.bStartStop invoke"
1974  wm minsize $w 25 0
1976 
1977  set analysis(onUciOk$n) "onUciOk $n $w.b1.multipv $autostart [list [ lindex $engineData 8]]"
1978  if {$analysis(uci$n)} {
1979  fileevent $analysis(pipe$n) readable "::uci::processAnalysisInput $n"
1980  } else {
1981  fileevent $analysis(pipe$n) readable "processAnalysisInput $n"
1982  }
1983  after 1000 "checkAnalysisStarted $n"
1984 
1985  # We hope the engine is correctly started at that point, so we can send the first analyze command
1986  # this problem only happens with winboard engine, as we don't know when they are ready
1987  if { !$analysis(uci$n) && $autostart != 0 } {
1989  }
1990 
1991  catch {
1994  }
1995 
1996 }
1997 
1998 proc onUciOk {{n} {multiPv_spin} {autostart} {uci_options}} {
1999  foreach opt $::analysis(uciOptions$n) {
2000  if { [lindex $opt 0] == "MultiPV" } {
2001  set min [lindex $opt 1]
2002  set max [lindex $opt 2]
2003  $multiPv_spin configure -from $min -to $max -state normal
2004  break
2005  }
2006  }
2007  ::uci::sendUCIoptions $n $uci_options
2008 
2009  if {$autostart} { startEngineAnalysis $n}
2010 }
2011 
2012 
2013 
2014 ################################################################################
2015 #
2016 ################################################################################
2017 proc toggleMovesDisplay { {n 1} } {
2018  set ::analysis(movesDisplay$n) [expr 1 - $::analysis(movesDisplay$n)]
2019  set h .analysisWin$n.hist.text
2020  $h configure -state normal
2021  $h delete 1.0 end
2022  $h configure -state disabled
2024 }
2025 
2026 ################################################################################
2027 # will truncate PV list if necessary and tell the engine to send N best lines
2028 ################################################################################
2029 proc changePVSize { n } {
2030  global analysis
2031  if { $analysis(multiPVCount$n) < [llength $analysis(multiPV$n)] } {
2032  set analysis(multiPV$n) {}
2033  set analysis(multiPVraw$n) {}
2034  }
2035  set h .analysisWin$n.hist.text
2036  if {[winfo exists $h] && $analysis(multiPVCount$n) == 1} {
2037  $h configure -state normal
2038  $h delete 0.0 end
2039  $h configure -state disabled
2040  set analysis(lastHistory$n) {}
2041  }
2042  if { ! $analysis(uci$n) } { return}
2043 
2044  # if the UCI engine was analysing, stop and restart
2045  if {$analysis(analyzeMode$n)} {
2046  stopAnalyzeMode $n
2047  set analysis(waitForReadyOk$n) 1
2048  sendToEngine $n "isready"
2049  set dont_stuck [ after 60000 "set ::analysis(waitForReadyOk$n) 0"]
2050  vwait analysis(waitForReadyOk$n)
2051  after cancel $dont_stuck
2052  sendToEngine $n "setoption name MultiPV value $analysis(multiPVCount$n)"
2053  startAnalyzeMode $n
2054  } else {
2055  sendToEngine $n "setoption name MultiPV value $analysis(multiPVCount$n)"
2056  }
2057 }
2058 ################################################################################
2059 # setAnalysisPriority
2060 # Sets the priority class (in Windows) or nice level (in Unix)
2061 # of a running analysis engine.
2062 ################################################################################
2063 proc setAnalysisPriority {w n} {
2064  global analysis
2065 
2066  # Get the process ID of the analysis engine:
2067  if {$analysis(pipe$n) == ""} { return}
2068  set pidlist [pid $analysis(pipe$n)]
2069  if {[llength $pidlist] < 1} { return}
2070  set pid [lindex $pidlist 0]
2071 
2072  # Set the priority class (idle or normal):
2073  set priority "normal"
2074  if {[lindex [$w.b1.priority configure -image] end] eq "tb_cpu_hi"} { set priority "idle"}
2075  catch {sc_info priority $pid $priority}
2076 
2077  # Re-read the priority class for confirmation:
2078  if {[catch {sc_info priority $pid} newpriority]} { return}
2079  if {$newpriority > 0} {
2080  $w.b1.priority configure -image tb_cpu
2081  $w.b1.priority state pressed
2082  } else {
2083  $w.b1.priority configure -image tb_cpu_hi
2084  $w.b1.priority state !pressed
2085  }
2086  }
2087 ################################################################################
2088 # checkAnalysisStarted
2089 # Called a short time after an analysis engine was started
2090 # to send it commands if Scid has not seen any output from
2091 # it yet.
2092 ################################################################################
2093 proc checkAnalysisStarted {n} {
2094  global analysis
2095  if {$analysis(seen$n)} { return}
2096  # Some Winboard engines do not issue any output when
2097  # they start up, so the fileevent above is never triggered.
2098  # Most, but not all, of these engines will respond in some
2099  # way once they have received input of some type. This
2100  # proc will issue the same initialization commands as
2101  # those in processAnalysisInput below, but without the need
2102  # for a triggering fileevent to occur.
2103 
2104  logEngineNote $n {Quiet engine (still no output); sending it initial commands.}
2105 
2106  if {$analysis(uci$n)} {
2107  # in order to get options
2108  sendToEngine $n "uci"
2109  # egine should respond uciok
2110  sendToEngine $n "isready"
2111  set analysis(seen$n) 1
2112  } else {
2113  sendToEngine $n "xboard"
2114  sendToEngine $n "protover 2"
2115  sendToEngine $n "ponder off"
2116  sendToEngine $n "post"
2117  # Prevent some engines from making an immediate "book"
2118  # reply move as black when position is sent later:
2119  sendToEngine $n "force"
2120  }
2121 }
2122 ################################################################################
2123 # with wb engines, we don't know when the startup phase is over and when the
2124 # engine is ready : so wait for the end of initial output and take some margin
2125 # to issue an analyze command
2126 ################################################################################
2127 proc initialAnalysisStart {n} {
2128  global analysis
2129 
2130  update
2131 
2132  if { $analysis(processInput$n) == 0 } {
2133  after 500 initialAnalysisStart $n
2134  return
2135  }
2136  set cl [clock clicks -milliseconds]
2137  if {[expr $cl - $analysis(processInput$n)] < 1000} {
2138  after 200 initialAnalysisStart $n
2139  return
2140  }
2141  after 200 startEngineAnalysis $n 1
2142 }
2143 ################################################################################
2144 # processAnalysisInput (only for win/xboard engines)
2145 # Called from a fileevent whenever there is a line of input
2146 # from an analysis engine waiting to be processed.
2147 ################################################################################
2148 proc processAnalysisInput {{n 1}} {
2149  global analysis
2150 
2151  # Get one line from the engine:
2152  set line [gets $analysis(pipe$n)]
2153 
2154  # this is only useful at startup but costs less than 10 microseconds
2155  set analysis(processInput$n) [clock clicks -milliseconds]
2156 
2157  logEngine $n "Engine: $line"
2158 
2159  if { ! [ checkEngineIsAlive $n] } { return}
2160 
2161  if {! $analysis(seen$n)} {
2162  set analysis(seen$n) 1
2163  # First line of output from the program, so send initial commands:
2164  logEngineNote $n {First line from engine seen; sending it initial commands now.}
2165  sendToEngine $n "xboard"
2166  sendToEngine $n "protover 2"
2167  sendToEngine $n "ponder off"
2168  sendToEngine $n "post"
2169  }
2170 
2171  # Check for "feature" commands so we can determine if the engine
2172  # has the setboard and analyze commands:
2173  #
2174  if {! [string compare [string range $line 0 6] "feature"]} {
2175  if {[string match "*analyze=1*" $line]} { set analysis(has_analyze$n) 1}
2176  if {[string match "*setboard=1*" $line]} { set analysis(has_setboard$n) 1}
2177  if {[string match "*usermove=1*" $line]} { set analysis(wants_usermove$n) 1}
2178  if {[string match "*sigint=1*" $line]} { set analysis(send_sigint$n) 1}
2179  if {[string match "*myname=*" $line] } {
2180  if { !$analysis(wbEngineDetected$n) } { detectWBEngine $n $line}
2181  if { [regexp "myname=\"(\[^\"\]*)\"" $line dummy name]} {
2182  if {$n == 1} {
2183  catch {::setTitle .analysisWin$n "Analysis: $name"}
2184  } else {
2185  catch {::setTitle .analysisWin$n "Analysis $n: $name"}
2186  }
2187  }
2188  }
2189  return
2190  }
2191 
2192  # Check for a line starting with "Crafty", so Scid can work well
2193  # with older Crafty versions that do not recognize "protover":
2194  #
2195  if {! [string compare [string range $line 0 5] "Crafty"]} {
2196  logEngineNote $n {Seen "Crafty"; assuming analyze and setboard commands.}
2197  set major 0
2198  if {[scan $line "Crafty v%d.%d" major minor] == 2 && $major >= 18} {
2199  logEngineNote $n {Crafty version is >= 18.0; assuming scores are from White perspective.}
2200  set analysis(invertScore$n) 0
2201  }
2202  # Turn off crafty logging, to reduce number of junk files:
2203  sendToEngine $n "log off"
2204  # Set a fairly low noise value so Crafty is responsive to board changes,
2205  # but not so low that we get lots of short-ply search data:
2206  sendToEngine $n "noise 1000"
2207  set analysis(isCrafty$n) 1
2208  set analysis(has_setboard$n) 1
2209  set analysis(has_analyze$n) 1
2210  return
2211  }
2212 
2213  # Scan the line from the engine for the analysis data:
2214  #
2215  set res [scan $line "%d%c %d %d %s %\[^\n\]\n" \
2216  temp_depth dummy temp_score \
2217  temp_time temp_nodes temp_moves]
2218  if {$res == 6} {
2219  if {$analysis(invertScore$n) && (![string compare [sc_pos side] "black"])} {
2220  set temp_score [expr { 0.0 - $temp_score }]
2221  }
2222  set analysis(depth$n) $temp_depth
2223  set analysis(score$n) $temp_score
2224  # Convert score to pawns from centipawns:
2225  set analysis(score$n) [expr {double($analysis(score$n)) / 100.0}]
2226  set analysis(moves$n) [formatAnalysisMoves $temp_moves]
2227  set analysis(time$n) $temp_time
2228  set analysis(nodes$n) [calculateNodes $temp_nodes]
2229 
2230  # Convert time to seconds from centiseconds:
2231  if {! $analysis(wholeSeconds$n)} {
2232  set analysis(time$n) [expr {double($analysis(time$n)) / 100.0}]
2233  }
2234 
2236 
2237  if {! $analysis(seenEval$n)} {
2238  # This is the first evaluation line seen, so send the current
2239  # position details to the engine:
2240  set analysis(seenEval$n) 1
2241  }
2242 
2243  return
2244  }
2245 
2246  # Check for a "stat01:" line, the reply to the "." command:
2247  #
2248  if {! [string compare [string range $line 0 6] "stat01:"]} {
2249  if {[scan $line "%s %d %s %d" \
2250  dummy temp_time temp_nodes temp_depth] == 4} {
2251  set analysis(depth$n) $temp_depth
2252  set analysis(time$n) $temp_time
2253  set analysis(nodes$n) [calculateNodes $temp_nodes]
2254  # Convert time to seconds from centiseconds:
2255  if {! $analysis(wholeSeconds$n)} {
2256  set analysis(time$n) [expr {double($analysis(time$n)) / 100.0}]
2257  }
2259  }
2260  return
2261  }
2262 
2263  # Check for other engine-specific lines:
2264  # The following checks are intended to make Scid work with
2265  # various WinBoard engines that are not properly configured
2266  # by the "feature" line checking code above.
2267  #
2268  # Many thanks to Allen Lake for testing Scid with many
2269  # WinBoard engines and providing this code and the detection
2270  # code in wbdetect.tcl
2271  if { !$analysis(wbEngineDetected$n) } {
2272  detectWBEngine $n $line
2273  }
2274 
2275 }
2276 ################################################################################
2277 # returns 0 if engine died abruptly or 1 otherwise
2278 ################################################################################
2279 proc checkEngineIsAlive { {n 1} } {
2280  global analysis
2281 
2282  if {$analysis(pipe$n) == ""} { return 0}
2283 
2284  if {[eof $analysis(pipe$n)]} {
2285  fileevent $analysis(pipe$n) readable {}
2286  set exit_status 0
2287  if {[catch {close $analysis(pipe$n)} standard_error] != 0} {
2288  global errorCode
2289  if {"CHILDSTATUS" == [lindex $errorCode 0]} {
2290  set exit_status [lindex $errorCode 2]
2291  }
2292  }
2293  set analysis(pipe$n) ""
2294  if { $exit_status != 0 } {
2295  logEngineNote $n {Engine terminated with exit code $exit_status: "\"$standard_error\""}
2296  tk_messageBox -type ok -icon info -parent . -title "Scid" \
2297  -message "The analysis engine terminated with exit code $exit_status: \"$standard_error\""
2298  } else {
2299  logEngineNote $n {Engine terminated without exit code: "\"$standard_error\""}
2300  tk_messageBox -type ok -icon info -parent . -title "Scid" \
2301  -message "The analysis engine terminated without exit code: \"$standard_error\""
2302  }
2303  catch {destroy .analysisWin$n}
2304  return 0
2305  }
2306  return 1
2307 }
2308 ################################################################################
2309 # formatAnalysisMoves:
2310 # Given the text at the end of a line of analysis data from an engine,
2311 # this proc tries to strip out some extra stuff engines add to make
2312 # the text more compatible for adding as a variation.
2313 ################################################################################
2314 proc formatAnalysisMoves {text} {
2315  # Yace puts ".", "t", "t-" or "t+" at the start of its moves text,
2316  # unless directed not to in its .ini file. Get rid of it:
2317  if {[strIsPrefix ". " $text]} { set text [string range $text 2 end]}
2318  if {[strIsPrefix "t " $text]} { set text [string range $text 2 end]}
2319  if {[strIsPrefix "t- " $text]} { set text [string range $text 3 end]}
2320  if {[strIsPrefix "t+ " $text]} { set text [string range $text 3 end]}
2321 
2322  # Trim any initial or final whitespace:
2323  set text [string trim $text]
2324 
2325  # Yace often adds "H" after a move, e.g. "Bc4H". Remove them:
2326  regsub -all "H " $text " " text
2327 
2328  # Crafty adds "<HT>" for a hash table comment. Change it to "{HT}":
2329  regsub "<HT>" $text "{HT}" text
2330 
2331  return $text
2332 }
2333 
2334 set finishGameMode 0
2335 
2336 ################################################################################
2337 # will ask engine(s) to play the game till the end
2338 ################################################################################
2339 proc toggleFinishGame { { n 1 } } {
2340  global analysis
2341  set b ".analysisWin$n.b1.bFinishGame"
2342  if { $::annotateModeButtonValue || $::autoplayMode } { return}
2343  if { ! $analysis(uci$n) } {
2344  if { !$analysis(analyzeMode$n) || ! [sc_pos isAt vend] } { return}
2345 
2346  if {!$::finishGameMode} {
2347  set ::finishGameMode 1
2348  $b configure -image tb_finish_on
2349  after $::autoplayDelay autoplayFinishGame
2350  } else {
2351  set ::finishGameMode 0
2352  $b configure -image tb_finish_off
2353  after cancel autoplayFinishGame
2354  }
2355  return
2356  }
2357 
2358  # UCI engines
2359  # Default values
2360  if {! [info exists ::finishGameEng1] } { set ::finishGameEng1 1}
2361  if {! [info exists ::finishGameEng2] } { set ::finishGameEng2 1}
2362  if {! [info exists ::finishGameCmd1] } { set ::finishGameCmd1 "movetime"}
2363  if {! [info exists ::finishGameCmdVal1] } { set ::finishGameCmdVal1 5}
2364  if {! [info exists ::finishGameCmd2] } { set ::finishGameCmd2 "movetime"}
2365  if {! [info exists ::finishGameCmdVal2] } { set ::finishGameCmdVal2 5}
2366  if {! [info exists ::finishGameAnnotate] } { set ::finishGameAnnotate 1}
2367  if {! [info exists ::finishGameAnnotateShort] } { set ::finishGameAnnotateShort 1}
2368  # On exit save values in options.dat
2369  options.save ::finishGameEng1
2370  options.save ::finishGameEng2
2371  options.save ::finishGameCmd1
2372  options.save ::finishGameCmdVal1
2373  options.save ::finishGameCmd2
2374  options.save ::finishGameCmdVal2
2375  options.save ::finishGameAnnotate
2376  options.save ::finishGameAnnotateShort
2377 
2378  if {$::finishGameMode} {
2379  set ::finishGameMode 0
2380  sendToEngine 1 "stop"
2381  set analysis(waitForReadyOk1) 0
2382  set analysis(waitForBestMove1) 0
2383  sendToEngine 2 "stop"
2384  set analysis(waitForReadyOk2) 0
2385  set analysis(waitForBestMove2) 0
2386  $b configure -image tb_finish_off
2387  grab release .analysisWin$n
2388  .analysisWin$n.b1.bStartStop configure -state normal
2389  .analysisWin$n.b1.move configure -state normal
2390  .analysisWin$n.b1.line configure -state normal
2391  .analysisWin$n.b1.alllines configure -state normal
2392  .analysisWin$n.b1.annotate configure -state normal
2393  .analysisWin$n.b1.automove configure -state normal
2394  return
2395  }
2396 
2397  set w .configFinishGame
2399  wm resizable $w 0 0
2400  ::setTitle $w "Scid: $::tr(FinishGame)"
2401 
2402  ttk::labelframe $w.wh_f -text "$::tr(White)" -padding 5
2403  grid $w.wh_f -column 0 -row 0 -columnspan 2 -sticky we -pady 8
2404  ttk::label $w.wh_f.p -image wk$::board::_size(.main.board)
2405  grid $w.wh_f.p -column 0 -row 0 -rowspan 3
2406  ttk::radiobutton $w.wh_f.e1 -text $analysis(name1) -variable ::finishGameEng1 -value 1
2407  if {[winfo exists .analysisWin2] && $analysis(uci2) } {
2408  ttk::radiobutton $w.wh_f.e2 -text $analysis(name2) -variable ::finishGameEng1 -value 2
2409  } else {
2410  set ::finishGameEng1 1
2411  ttk::radiobutton $w.wh_f.e2 -text $::tr(StartEngine) -variable ::finishGameEng1 -value 2 -state disabled
2412  }
2413  grid $w.wh_f.e1 -column 1 -row 0 -columnspan 3 -sticky w
2414  grid $w.wh_f.e2 -column 1 -row 1 -columnspan 3 -sticky w
2415  ttk::spinbox $w.wh_f.cv -width 4 -textvariable ::finishGameCmdVal1 -from 1 -to 999
2416  ttk::radiobutton $w.wh_f.c1 -text $::tr(seconds) -variable ::finishGameCmd1 -value "movetime"
2417  ttk::radiobutton $w.wh_f.c2 -text $::tr(FixedDepth) -variable ::finishGameCmd1 -value "depth"
2418  grid $w.wh_f.cv -column 1 -row 2 -sticky w
2419  grid $w.wh_f.c1 -column 2 -row 2 -sticky w
2420  grid $w.wh_f.c2 -column 3 -row 2 -sticky w
2421  grid columnconfigure $w.wh_f 2 -weight 1
2422 
2423  ttk::labelframe $w.bk_f -text "$::tr(Black)" -padding 5
2424  grid $w.bk_f -column 0 -row 1 -columnspan 2 -sticky we -pady 8
2425  ttk::label $w.bk_f.p -image bk$::board::_size(.main.board)
2426  grid $w.bk_f.p -column 0 -row 0 -rowspan 3
2427  ttk::radiobutton $w.bk_f.e1 -text $analysis(name1) -variable ::finishGameEng2 -value 1
2428  if {[winfo exists .analysisWin2] && $analysis(uci2) } {
2429  ttk::radiobutton $w.bk_f.e2 -text $analysis(name2) -variable ::finishGameEng2 -value 2
2430  } else {
2431  set ::finishGameEng2 1
2432  ttk::radiobutton $w.bk_f.e2 -text $::tr(StartEngine) -variable ::finishGameEng2 -value 2 -state disabled
2433  }
2434  grid $w.bk_f.e1 -column 1 -row 0 -columnspan 3 -sticky w
2435  grid $w.bk_f.e2 -column 1 -row 1 -columnspan 3 -sticky w
2436  ttk::spinbox $w.bk_f.cv -width 4 -textvariable ::finishGameCmdVal2 -from 1 -to 999
2437  ttk::radiobutton $w.bk_f.c1 -text $::tr(seconds) -variable ::finishGameCmd2 -value "movetime"
2438  ttk::radiobutton $w.bk_f.c2 -text $::tr(FixedDepth) -variable ::finishGameCmd2 -value "depth"
2439  grid $w.bk_f.cv -column 1 -row 2 -sticky w
2440  grid $w.bk_f.c1 -column 2 -row 2 -sticky w
2441  grid $w.bk_f.c2 -column 3 -row 2 -sticky w
2442  grid columnconfigure $w.bk_f 2 -weight 1
2443 
2444  ttk::checkbutton $w.annotate -text $::tr(Annotate) -variable ::finishGameAnnotate
2445  grid $w.annotate -column 0 -row 2 -sticky w -padx 5 -pady 8
2446  ttk::checkbutton $w.annotateShort -text $::tr(ShortAnnotations) -variable ::finishGameAnnotateShort
2447  grid $w.annotateShort -column 1 -row 2 -sticky w -padx 5 -pady 8
2448 
2449  ttk::frame $w.fbuttons
2450  ttk::button $w.fbuttons.cancel -text $::tr(Cancel) -command { destroy .configFinishGame }
2451  ttk::button $w.fbuttons.ok -text "OK" -command {
2452  if {$::finishGameEng1 == $::finishGameEng2} {
2453  set ::finishGameMode 1
2454  } else {
2455  set ::finishGameMode 2
2456  }
2457  set tmp [sc_pos getComment]
2458  sc_pos setComment "$tmp $::tr(FinishGame) $::tr(White): $analysis(name$::finishGameEng1) $::tr(Black): $analysis(name$::finishGameEng2)"
2459  destroy .configFinishGame
2460  }
2461  packbuttons right $w.fbuttons.cancel $w.fbuttons.ok
2462  grid $w.fbuttons -row 3 -column 1 -columnspan 2 -sticky we
2463  focus $w.fbuttons.ok
2464  bind $w <Escape> { .configFinishGame.cancel invoke }
2465  bind $w <Return> { .configFinishGame.ok invoke }
2466  bind $w <Destroy> { focus .analysisWin1 }
2467  ::tk::PlaceWindow $w widget .analysisWin1
2468  grab $w
2469  bind $w <ButtonPress> {
2470  set w .configFinishGame
2471  if {%x < 0 || %x > [winfo width $w] || %y < 0 || %y > [winfo height $w] } { ::tk::PlaceWindow $w pointer }
2472  }
2473  tkwait window $w
2474  if {!$::finishGameMode} { return}
2475 
2476  set gocmd(1) "go $::finishGameCmd1 $::finishGameCmdVal1"
2477  set gocmd(2) "go $::finishGameCmd2 $::finishGameCmdVal2"
2478  if {$::finishGameCmd1 == "movetime" } { append gocmd(1) "000"}
2479  if {$::finishGameCmd2 == "movetime" } { append gocmd(2) "000"}
2480  if {[sc_pos side] == "white"} {
2481  set current_cmd 1
2482  set current_engine $::finishGameEng1
2483  } else {
2484  set current_cmd 2
2485  set current_engine $::finishGameEng2
2486  }
2487 
2490  $b configure -image tb_finish_on
2491  .analysisWin$n.b1.bStartStop configure -state disabled
2492  .analysisWin$n.b1.move configure -state disabled
2493  .analysisWin$n.b1.line configure -state disabled
2494  .analysisWin$n.b1.alllines configure -state disabled
2495  .analysisWin$n.b1.annotate configure -state disabled
2496  .analysisWin$n.b1.automove configure -state disabled
2497  grab .analysisWin$n
2498 
2499  while { [string index [sc_game info previousMove] end] != "#"} {
2500  set analysis(waitForReadyOk$current_engine) 1
2501  sendToEngine $current_engine "isready"
2502  vwait analysis(waitForReadyOk$current_engine)
2503  if {!$::finishGameMode} { break}
2504  sendToEngine $current_engine "position fen [sc_pos fen]"
2505  sendToEngine $current_engine $gocmd($current_cmd)
2506  set analysis(fen$current_engine) [sc_pos fen]
2507  set analysis(maxmovenumber$current_engine) 0
2508  set analysis(waitForBestMove$current_engine) 1
2509  vwait analysis(waitForBestMove$current_engine)
2510  if {!$::finishGameMode} { break}
2511 
2512  if { ! [sc_pos isAt vend] } { sc_var create}
2513  if {$::finishGameAnnotate} {
2514  set moves [ lindex [ lindex $analysis(multiPV$current_engine) 0] 2]
2515  if {$::finishGameAnnotateShort} {
2516  set text [format "%d:%+.2f" \
2517  $analysis(depth$current_engine) \
2518  $analysis(score$current_engine)]
2519  makeAnalysisMove $current_engine $text
2520  } else {
2521  set text [format "%d:%+.2f" \
2522  $analysis(depth$current_engine) \
2523  $analysis(score$current_engine)]
2524  makeAnalysisMove $current_engine $text
2525  sc_var create
2526  set moves $analysis(moves$current_engine)
2527  sc_move_add $moves $current_engine
2528  sc_var exit
2529  sc_move forward
2530  }
2531  storeEmtComment 0 0 [expr {int($analysis(time$current_engine))}]
2532  } else {
2533  makeAnalysisMove $current_engine
2534  }
2535 
2536  incr current_cmd
2537  if {$current_cmd > 2} { set current_cmd 1}
2538  if {$::finishGameMode == 2} {
2539  incr current_engine
2540  if {$current_engine > 2 } { set current_engine 1}
2541  }
2542  }
2543  if {$::finishGameMode} { toggleFinishGame}
2544 }
2545 ################################################################################
2546 #
2547 ################################################################################
2548 proc autoplayFinishGame { {n 1} } {
2549  if {!$::finishGameMode || ![winfo exists .analysisWin$n]} {return}
2550  .analysisWin$n.b1.move invoke
2551  if { [string index [sc_game info previousMove] end] == "#"} {
2552  toggleFinishGame $n
2553  return
2554  }
2555  after $::autoplayDelay autoplayFinishGame
2556 }
2557 
2558 ################################################################################
2559 #
2560 ################################################################################
2561 proc startEngineAnalysis { {n 1} {force 0} } {
2562  global analysis
2563 
2564  if { !$analysis(analyzeMode$n) } {
2565  set b ".analysisWin$n.b1.bStartStop"
2566 
2567  startAnalyzeMode $n $force
2568  $b configure -image tb_eng_off
2569  ::utils::tooltip::Set $b "$::tr(StopEngine)(a)"
2570  # enable lock button
2571  .analysisWin$n.b1.lockengine configure -state normal
2572  }
2573 }
2574 
2575 ################################################################################
2576 #
2577 ################################################################################
2578 proc stopEngineAnalysis { {n 1} } {
2579  global analysis
2580 
2581  if { $analysis(analyzeMode$n) } {
2582  set b ".analysisWin$n.b1.bStartStop"
2583 
2584  stopAnalyzeMode $n
2585  $b configure -image tb_eng_on
2586  ::utils::tooltip::Set $b "$::tr(StartEngine)"
2587  # reset lock mode and disable lock button
2588  set analysis(lockEngine$n) 1
2589  toggleLockEngine $n
2590  .analysisWin$n.b1.lockengine configure -state disabled
2591  }
2592 }
2593 
2594 ################################################################################
2595 #
2596 ################################################################################
2597 proc toggleEngineAnalysis { { n 1 } { force 0 } } {
2598  global analysis
2599 
2600  if { $n == 1} {
2601  if { ($::annotateMode || $::finishGameMode) && ! $force } {
2602  return
2603  }
2604  }
2605 
2606  if {$analysis(analyzeMode$n)} {
2608  } else {
2609  startEngineAnalysis $n $force
2610  }
2611 }
2612 ################################################################################
2613 # startAnalyzeMode:
2614 # Put the engine in analyze mode.
2615 ################################################################################
2616 proc startAnalyzeMode {{n 1} {force 0}} {
2617  global analysis
2618 
2619  # Check that the engine has not already had analyze mode started:
2620  if {$analysis(analyzeMode$n) && ! $force } { return}
2621  set analysis(analyzeMode$n) 1
2622  if { $analysis(uci$n) } {
2623  updateAnalysis $n
2624  } else {
2625  if {$analysis(has_setboard$n)} {
2626  sendToEngine $n "setboard [sc_pos fen]"
2627  }
2628  if { $analysis(has_analyze$n) } {
2629  sendToEngine $n "analyze"
2630  } else {
2631  updateAnalysis $n ;# in order to handle special cases (engines without setboard and analyse commands)
2632  }
2633  }
2634 }
2635 ################################################################################
2636 # stopAnalyzeMode
2637 ################################################################################
2638 proc stopAnalyzeMode { {n 1} } {
2639  global analysis
2640  if {! $analysis(analyzeMode$n)} { return}
2641  set analysis(analyzeMode$n) 0
2642  if { $analysis(uci$n) } {
2643  if {$analysis(after$n) != ""} {
2644  after cancel $analysis(after$n)
2645  set analysis(after$n) ""
2646  }
2647  sendToEngine $n "stop"
2648  set analysis(waitForReadyOk$n) 1
2649  sendToEngine $n "isready"
2650  } else {
2651  sendToEngine $n "exit"
2652  }
2653  set analysis(fen$n) {}
2654 }
2655 ################################################################################
2656 # toggleLockEngine
2657 # Toggle whether engine is locked to current position.
2658 ################################################################################
2659 proc toggleLockEngine {n} {
2660  global analysis
2661  if { $analysis(lockEngine$n) } {
2662  set analysis(lockEngine$n) 0
2663  } else {
2664  set analysis(lockEngine$n) 1
2665  }
2666  if { $analysis(lockEngine$n) } {
2667  set state disabled
2668  set analysis(lockN$n) [sc_pos moveNumber]
2669  set analysis(lockSide$n) [sc_pos side]
2670  .analysisWin$n.b1.lockengine state pressed
2671  } else {
2672  set state normal
2673  .analysisWin$n.b1.lockengine state !pressed
2674  }
2675  set w ".analysisWin$n"
2676  $w.b1.move configure -state $state
2677  $w.b1.line configure -state $state
2678  if {$analysis(uci$n)} {
2679  $w.b1.multipv configure -state $state
2680  }
2681  $w.b1.alllines configure -state $state
2682  $w.b1.automove configure -state $state
2683  if { $n == 1 } {
2684  $w.b1.annotate configure -state $state
2685  $w.b1.bFinishGame configure -state $state
2686  }
2687  updateAnalysis $n
2688 }
2689 ################################################################################
2690 # updateAnalysisText
2691 # Update the text in an analysis window.
2692 ################################################################################
2693 proc updateAnalysisText {{n 1}} {
2694  global analysis
2695 
2696  set nps 0
2697  if {$analysis(currmovenumber$n) > $analysis(maxmovenumber$n) } {
2698  set analysis(maxmovenumber$n) $analysis(currmovenumber$n)
2699  }
2700  if {$analysis(time$n) > 0.0} {
2701  set nps [expr {round($analysis(nodes$n) / $analysis(time$n))}]
2702  }
2703  set score $analysis(score$n)
2704 
2705  set t .analysisWin$n.text
2706  set h .analysisWin$n.hist.text
2707 
2708  $t configure -state normal
2709  $t delete 0.0 end
2710 
2711  if { $analysis(uci$n) } {
2712  if { [expr abs($score)] >= 327.0 } {
2713  if { [catch { set tmp [format "M%d " $analysis(scoremate$n)]}] } {
2714  set tmp [format "%+.1f " $score]
2715  }
2716  } else {
2717  set tmp [format "%+.1f " $score]
2718  }
2719  $t insert end $tmp
2720 
2721  $t insert end "[tr Depth]: "
2722  if {$analysis(showEngineInfo$n) && $analysis(seldepth$n) != 0} {
2723  $t insert end [ format "%2u/%u " $analysis(depth$n) $analysis(seldepth$n)] small
2724  } else {
2725  $t insert end [ format "%2u " $analysis(depth$n)] small
2726  }
2727  $t insert end "[tr Nodes]: "
2728  $t insert end [ format "%6uK (%u kn/s) " $analysis(nodes$n) $nps] small
2729  $t insert end "[tr Time]: "
2730  $t insert end [ format "%6.2f s" $analysis(time$n)] small
2731  if {$analysis(showEngineInfo$n)} {
2732  $t insert end "\n" small
2733  $t insert end "[tr Current]: "
2734  $t insert end [ format "%s (%s/%s) " [::trans $analysis(currmove$n)] $analysis(currmovenumber$n) $analysis(maxmovenumber$n)] small
2735  $t insert end "TB Hits: "
2736  $t insert end [ format "%u " $analysis(tbhits$n)] small
2737  $t insert end "Nps: "
2738  $t insert end [ format "%u n/s " $analysis(nps$n)] small
2739  $t insert end "Hash Full: "
2740  set hashfull [expr {round($analysis(hashfull$n) / 10)}]
2741  $t insert end [ format "%u%% " $hashfull] small
2742  $t insert end "CPU Load: "
2743  set cpuload [expr {round($analysis(cpuload$n) / 10)}]
2744  $t insert end [ format "%u%% " $cpuload] small
2745 
2746  #$t insert end [ format "\nCurrent: %s (%s) - Hashfull: %u - nps: %u - TBhits: %u - CPUload: %u" $analysis(currmove$n) $analysis(currmovenumber$n) $analysis(hashfull$n) $analysis(nps$n) $analysis(tbhits$n) $analysis(cpuload$n) ]
2747  }
2748  } else {
2749  set newStr [format "Depth: %6u Nodes: %6uK (%u kn/s)\n" $analysis(depth$n) $analysis(nodes$n) $nps]
2750  append newStr [format "Score: %+8.2f Time: %9.2f seconds\n" $score $analysis(time$n)]
2751  $t insert 1.0 $newStr small
2752  }
2753 
2754 
2755  if {$analysis(automove$n)} {
2756  if {$analysis(automoveThinking$n)} {
2757  set moves " Thinking..... "
2758  } else {
2759  set moves " Your move..... "
2760  }
2761 
2762  if { ! $analysis(uci$n) } {
2763  $t insert end $moves blue
2764  }
2765  $t configure -state disabled
2766  updateAnalysisBoard $n ""
2767  return
2768  }
2769 
2770  if {! $::analysis(movesDisplay$n)} {
2771  $h configure -state normal
2772  $h delete 0.0 end
2773  $h insert end " $::tr(ClickHereToSeeMoves)\n" blue
2774  updateAnalysisBoard $n ""
2775  $h configure -state disabled
2776  return
2777  }
2778 
2779  if { $analysis(uci$n) } {
2780  set moves [ lindex [ lindex $analysis(multiPV$n) 0] 2]
2781  } else {
2782  set moves $analysis(moves$n)
2783  }
2784 
2785  $h configure -state normal
2786  set cleared 0
2787  if { $analysis(depth$n) < $analysis(prev_depth$n) || $analysis(prev_depth$n) == 0 } {
2788  $h delete 1.0 end
2789  set cleared 1
2790  }
2791 
2792  ################################################################################
2793  if { $analysis(uci$n) } {
2794  if {$cleared} { set analysis(multiPV$n) {} ; set analysis(multiPVraw$n) {}}
2795  if {$analysis(multiPVCount$n) == 1} {
2796  set newhst [format "%2d %s %s" $analysis(depth$n) [scoreToMate $score $moves $n] [addMoveNumbers $n [::trans $moves]]]
2797  if {$newhst != $analysis(lastHistory$n) && $moves != ""} {
2798  $h insert end [format "%s (%.2f)\n" $newhst $analysis(time$n)] indent
2799  $h see end-1c
2800  set analysis(lastHistory$n) $newhst
2801  }
2802  } else {
2803  $h delete 1.0 end
2804  # First line
2805  set pv [lindex $analysis(multiPV$n) 0]
2806  if { $pv != "" } {
2807  catch { set newStr [format "%2d %s " [lindex $pv 0] [scoreToMate $score [lindex $pv 2] $n]]}
2808 
2809  $h insert end "1 " gray
2810  append newStr "[addMoveNumbers $n [::trans [lindex $pv 2]]] [format (%.2f)\n [lindex $pv 4]]"
2811  $h insert end $newStr blue
2812 
2813  set lineNumber 1
2814  foreach pv $analysis(multiPV$n) {
2815  if {$lineNumber == 1} { incr lineNumber ; continue}
2816  $h insert end "$lineNumber " gray
2817  set score [scoreToMate [lindex $pv 1] [lindex $pv 2] $n]
2818  $h insert end [format "%2d %s %s (%.2f)\n" [lindex $pv 0] $score [addMoveNumbers $n [::trans [lindex $pv 2]]] [lindex $pv 4]] indent
2819  incr lineNumber
2820  }
2821  }
2822  }
2823  ################################################################################
2824  } else {
2825  # original Scid analysis display
2826  $h insert end [format "%2d %+5.2f %s (%.2f)\n" $analysis(depth$n) $score [::trans $moves] $analysis(time$n)] indent
2827  $h see end-1c
2828  }
2829 
2830  $h configure -state disabled
2831  set analysis(prev_depth$n) $analysis(depth$n)
2832  if { ! $analysis(uci$n) } {
2833  $t insert end [::trans $moves] blue
2834  }
2835  # $t tag add score 2.0 2.13
2836  $t configure -state disabled
2837 
2838  updateAnalysisBoard $n $analysis(moves$n)
2839 }
2840 ################################################################################
2841 # args = score, pv
2842 # returns M X if mate detected (# or ++) or original score
2843 ################################################################################
2844 proc scoreToMate { score pv n } {
2845 
2846  if {$::analysis(lockEngine$n)} {
2847  return [format "%+5.2f" $score]
2848  }
2849 
2850  if { [string index $pv end] == "#" || [string index $pv end] == "+" && [string index $pv end-1] == "+"} {
2851  set plies [llength $pv]
2852 
2853  set mate [expr $plies / 2 + 1]
2854 
2855  set sign ""
2856  if {[expr $plies % 2] == 0 && [sc_pos side] == "white" || [expr $plies % 2] == 1 && [sc_pos side] == "black"} {
2857  set sign "-"
2858  }
2859  if {[sc_pos side] == "white" } {
2860  if { $sign == "" } {
2861  set mate [expr $plies / 2 + 1]
2862  } else {
2863  set mate [expr $plies / 2]
2864  }
2865  } else {
2866  if { $sign == "" } {
2867  set mate [expr $plies / 2]
2868  } else {
2869  set mate [expr $plies / 2 + 1]
2870  }
2871  }
2872 
2873  set ret "M$sign$mate"
2874  } else {
2875  set ret [format "%+5.2f" $score]
2876  }
2877 
2878  return $ret
2879 }
2880 ################################################################################
2881 # returns the pv with move numbers added
2882 # ::pgn::moveNumberSpaces controls space between number and move
2883 ################################################################################
2884 proc addMoveNumbers { e pv } {
2885  global analysis
2886 
2887  if { $analysis(lockEngine$e) } {
2888  set n $analysis(lockN$e)
2889  set turn $analysis(lockSide$e)
2890  } else {
2891  set n [sc_pos moveNumber]
2892  set turn [sc_pos side]
2893  }
2894 
2895  if {$::pgn::moveNumberSpaces} {
2896  set spc { }
2897  } else {
2898  set spc {}
2899  }
2900 
2901  set ret ""
2902  set start 0
2903  if {$turn == "black"} {
2904  set ret "$n.$spc... [lindex $pv 0] "
2905  incr start
2906  incr n
2907  }
2908  for {set i $start} {$i < [llength $pv]} {incr i} {
2909  set m [lindex $pv $i]
2910  if { [expr $i % 2] == 0 && $start == 0 || [expr $i % 2] == 1 && $start == 1 } {
2911  append ret "$n.$spc$m "
2912  } else {
2913  append ret "$m "
2914  incr n
2915  }
2916  }
2917  return $ret
2918 }
2919 ################################################################################
2920 # toggleAnalysisBoard
2921 # Toggle whether the small analysis board is shown.
2922 ################################################################################
2923 proc toggleAnalysisBoard {n} {
2924  global analysis
2925  if { $analysis(showBoard$n) } {
2926  set analysis(showBoard$n) 0
2927  pack forget .analysisWin$n.bd
2928  setWinSize .analysisWin$n
2929  .analysisWin$n.b1.showboard state !pressed
2930  } else {
2931  bind .analysisWin$n <Configure> ""
2932  set analysis(showBoard$n) 1
2933  pack .analysisWin$n.bd -side right -before .analysisWin$n.b1 -padx 4 -pady 4 -anchor n
2934  update
2935  .analysisWin$n.hist.text configure -setgrid 0
2936  .analysisWin$n.text configure -setgrid 0
2937  set x [winfo reqwidth .analysisWin$n]
2938  set y [winfo reqheight .analysisWin$n]
2939  wm geometry .analysisWin$n ${x}x${y}
2940  .analysisWin$n.hist.text configure -setgrid 1
2941  .analysisWin$n.text configure -setgrid 1
2942  .analysisWin$n.b1.showboard state pressed
2943  }
2944 }
2945 ################################################################################
2946 # toggleEngineInfo
2947 # Toggle whether engine info are shown.
2948 ################################################################################
2949 proc toggleEngineInfo {n} {
2950  global analysis
2951  if { $analysis(showEngineInfo$n) } {
2952  set analysis(showEngineInfo$n) 0
2953  .analysisWin$n.text configure -height 1
2954  .analysisWin$n.b1.showinfo state !pressed
2955  } else {
2956  set analysis(showEngineInfo$n) 1
2957  .analysisWin$n.text configure -height 2
2958  .analysisWin$n.b1.showinfo state pressed
2959  }
2961 }
2962 ################################################################################
2963 #
2964 ################################################################################
2965 # updateAnalysisBoard
2966 # Update the small analysis board in the analysis window,
2967 # showing the position after making the specified moves
2968 # from the current main board position.
2969 #
2970 proc updateAnalysisBoard {n moves} {
2971  global analysis
2972  # PG : this should not be commented
2973  if {! $analysis(showBoard$n)} { return}
2974 
2975  set bd .analysisWin$n.bd
2976  # Push a temporary copy of the current game:
2977  sc_game push copyfast
2978 
2979  # Make the engine moves and update the board:
2980  sc_move_add $moves $n
2981  ::board::update $bd [sc_pos board]
2982 
2983  # Pop the temporary game:
2984  sc_game pop
2985 }
2986 
2987 ################################################################################
2988 # sendFENtoEngineUCI
2989 # Wait for the engine to be ready then send position and go infinite
2990 # engine_n: number of the engine that will receive the commands
2991 # delay: delay the commands - INTERNAL - DON'T USE OUTSIDE sendFENtoEngineUCI
2992 ################################################################################
2993 proc sendFENtoEngineUCI {engine_n {delay 0}} {
2994  global analysis
2995  set analysis(after$engine_n) ""
2996 
2997  if {$analysis(waitForReadyOk$engine_n) } {
2998  #If too slow something is wrong: give up
2999  if {$delay > 250} { return}
3000 
3001  # Engine is not ready: process events, idle tasks and then call me back
3002  incr delay
3003  set cmd "set ::analysis(after$engine_n) "
3004  append cmd { [ } " after $delay sendFENtoEngineUCI $engine_n $delay " { ] }
3005  set analysis(after$engine_n) [eval [list after idle $cmd]]
3006  } else {
3007  sendToEngine $engine_n "position fen $analysis(fen$engine_n)"
3008  sendToEngine $engine_n "go infinite"
3009  }
3010 }
3011 
3012 ################################################################################
3013 # updateAnalysis
3014 # Update an analysis window by sending the current board
3015 # to the engine.
3016 ################################################################################
3017 proc updateAnalysis {{n 1}} {
3018  global analysis
3019  if {$analysis(pipe$n) == ""} { return}
3020  # Just return if no output has been seen from the analysis program yet:
3021  if {! $analysis(seen$n)} { return}
3022  # No need to update if no analysis is running
3023  if { ! $analysis(analyzeMode$n) } { return}
3024  # No need to send current board if engine is locked
3025  if { $analysis(lockEngine$n) } { return}
3026 
3027  if { $analysis(uci$n) } {
3028  if {$analysis(after$n) == "" } {
3029  if { $analysis(fen$n) != "" } { sendToEngine $n "stop"}
3030  set analysis(waitForReadyOk$n) 1
3031  sendToEngine $n "isready"
3032  set analysis(after$n) [after idle "sendFENtoEngineUCI $n"]
3033  }
3034  set analysis(fen$n) [sc_pos fen]
3035  set analysis(maxmovenumber$n) 0
3036  set analysis(movelist$n) [sc_game moves coord list]
3037  set analysis(nonStdStart$n) [sc_game startBoard]
3038  } else {
3039  #TODO: remove 0.3s delay even for other engines
3040 
3041  global analysisWin windowsOS
3042 
3043  # If too close to the previous update, and no other future update is
3044  # pending, reschedule this update to occur in another 0.3 seconds:
3045  #
3046  if {[catch {set clicks [clock clicks -milliseconds]}]} {
3047  set clicks [clock clicks]
3048  }
3049  set diff [expr {$clicks - $analysis(lastClicks$n)}]
3050  if {$diff < 300 && $diff >= 0} {
3051  if {$analysis(after$n) == ""} {
3052  set analysis(after$n) [after 300 updateAnalysis $n]
3053  }
3054  return
3055  }
3056  set analysis(lastClicks$n) $clicks
3057  set analysis(after$n) ""
3058  after cancel updateAnalysis $n
3059 
3060  set old_movelist $analysis(movelist$n)
3061  set movelist [sc_game moves coord list]
3062  set analysis(movelist$n) $movelist
3063  set nonStdStart [sc_game startBoard]
3064  set old_nonStdStart $analysis(nonStdStart$n)
3065  set analysis(nonStdStart$n) $nonStdStart
3066 
3067  # This section is for engines that support "analyze":
3068  if {$analysis(has_analyze$n)} {
3069  sendToEngine $n "exit" ;# Get out of analyze mode, to send moves.
3070 
3071  # On Crafty, "force" command has different meaning when not in
3072  # XBoard mode, and some users have noticed Crafty not being in
3073  # that mode at this point -- although I cannot reproduce this.
3074  # So just re-send "xboard" to Crafty to make sure:
3075  if {$analysis(isCrafty$n)} { sendToEngine $n "xboard"}
3076 
3077  sendToEngine $n "force" ;# Stop engine replying to moves.
3078  # Check if the setboard command must be used -- that is, if the
3079  # previous or current position arose from a non-standard start.
3080 
3081  #if {$analysis(has_setboard$n) && ($old_nonStdStart || $nonStdStart)}
3082  # We skip all code below if the engine has setboard capability : this is provides less error prone behavior
3083  if {$analysis(has_setboard$n)} {
3084  sendToEngine $n "setboard [sc_pos fen]"
3085  # Most engines with setboard do not recognize the crafty "mn"
3086  # command (it is not in the XBoard/WinBoard protocol), so only send it to crafty:
3087  if {$analysis(isCrafty$n)} { sendToEngine $n "mn [sc_pos moveNumber]"}
3088  sendToEngine $n "analyze"
3089  return
3090  }
3091 
3092  # If we need a non-standard start and the engine does not have
3093  # setboard, the user is out of luck:
3094  if {$nonStdStart} {
3095  set analysis(moves$n) " Sorry, this game has a non-standard start position."
3097  return
3098  }
3099 
3100  # Here, the engine has the analyze command (and no setboard) but this game does
3101  # not have a non-standard start position.
3102 
3103  set oldlen [llength $old_movelist]
3104  set newlen [llength $movelist]
3105 
3106  # Check for optimization to minimize the commands to be sent:
3107  # Scid sends "undo" to backup wherever possible, and avoid "new" as
3108  # on many engines this would clear hash tables, causing poor
3109  # hash table performance.
3110 
3111  # Send just the new move if possible (if the new move list is exactly
3112  # the same as the previous move list, with one extra move):
3113  if {($newlen == $oldlen + 1) && ($old_movelist == [lrange $movelist 0 [expr {$oldlen - 1}]])} {
3114  sendMoveToEngine $n [lindex $movelist $oldlen]
3115 
3116  } elseif {($newlen + 1 == $oldlen) && ($movelist == [lrange $old_movelist 0 [expr {$newlen - 1}]])} {
3117  # Here the new move list is the same as the old list but with one
3118  # less move, just send one "undo":
3119  sendToEngine $n "undo"
3120 
3121  } elseif {$newlen == $oldlen && $old_movelist == $movelist} {
3122 
3123  # Here the board has not changed, so send nothing
3124 
3125  } else {
3126 
3127  # Otherwise, undo and re-send all moves:
3128  for {set i 0} {$i < $oldlen} {incr i} {
3129  sendToEngine $n "undo"
3130  }
3131  foreach m $movelist {
3132  sendMoveToEngine $n $m
3133  }
3134 
3135  }
3136 
3137  sendToEngine $n "analyze"
3138 
3139  } else {
3140 
3141  # This section is for engines without the analyze command:
3142  # In this case, Scid just sends "new", "force" and a bunch
3143  # of moves, then sets a very long search time/depth and
3144  # sends "go". This is not ideal but it works OK for engines
3145  # without "analyze" that I have tried.
3146 
3147  # If Unix OS and engine wants it, send an INT signal:
3148  if {(!$windowsOS) && $analysis(send_sigint$n)} {
3149  catch {exec -- kill -s INT [pid $analysis(pipe$n)]}
3150  }
3151  sendToEngine $n "new"
3152  sendToEngine $n "force"
3153  if { $nonStdStart && ! $analysis(has_setboard$n) } {
3154  set analysis(moves$n) " Sorry, this game has a non-standard start position."
3156  return
3157  }
3158  if {$analysis(has_setboard$n)} {
3159  sendToEngine $n "setboard [sc_pos fen]"
3160  } else {
3161  foreach m $movelist {
3162  sendMoveToEngine $n $m
3163  }
3164  }
3165  # Set engine to be white or black:
3166  sendToEngine $n [sc_pos side]
3167  # Set search time and depth to something very large and start search:
3168  sendToEngine $n "st 120000"
3169  sendToEngine $n "sd 50"
3170  sendToEngine $n "post"
3171  sendToEngine $n "go"
3172  }
3173  }
3174 }
3175 ################################################################################
3176 #
3177 ################################################################################
3178 
3179 set temptime 0
3180 trace variable temptime w {::utils::validate::Regexp {^[0-9]*\.?[0-9]*$}}
3181 
3182 proc setAutomoveTime {{n 1}} {
3183  global analysis temptime dialogResult
3184  set ::tempn $n
3185  set temptime [expr {$analysis(automoveTime$n) / 1000.0}]
3186  set w .apdialog
3188  #wm transient $w .analysisWin
3189  ::setTitle $w "Scid: Engine thinking time"
3190  wm resizable $w 0 0
3191  ttk::frame $w.f
3192  pack $w.f -expand 1
3193  ttk::label $w.f.label -text "Set the engine thinking time per move in seconds:"
3194  pack $w.f.label -side top -pady 5 -padx 5
3195  ttk::entry $w.f.entry -background white -width 10 -textvariable temptime
3196  pack $w.f.entry -side top -pady 5
3197  bind $w.f.entry <Escape> { .apdialog.buttons.cancel invoke }
3198  bind $w.f.entry <Return> { .apdialog.buttons.ok invoke }
3199 
3201 
3202  set dialogResult ""
3203  set b [ttk::frame $w.buttons]
3204  pack $b -side top -fill x
3205  ttk::button $b.cancel -text $::tr(Cancel) -command {
3206  focus .
3207  catch {grab release .apdialog}
3208  destroy .apdialog
3209  focus .
3210  set dialogResult Cancel
3211  }
3212  ttk::button $b.ok -text "OK" -command {
3213  catch {grab release .apdialog}
3214  if {$temptime < 0.1} { set temptime 0.1 }
3215  set analysis(automoveTime$tempn) [expr {int($temptime * 1000)} ]
3216  focus .
3217  catch {grab release .apdialog}
3218  destroy .apdialog
3219  focus .
3220  set dialogResult OK
3221  }
3222  pack $b.cancel $b.ok -side right -padx 5 -pady 5
3223  focus $w.f.entry
3224  update
3225  catch {grab .apdialog}
3226  tkwait window .apdialog
3227  if {$dialogResult != "OK"} {
3228  return 0
3229  }
3230  return 1
3231 }
3232 
3233 proc toggleAutomove {{n 1}} {
3234  global analysis
3235  .analysisWin1.b1.automove state !pressed
3236  if { $analysis(automove$n) } {
3237  set analysis(automove$n) 0
3238  cancelAutomove $n
3239  } else {
3240  set analysis(automove$n) 0
3241  if {! [setAutomoveTime $n]} {
3242  return
3243  }
3244  set analysis(automove$n) 1
3245  .analysisWin1.b1.automove state pressed
3246  automove $n
3247  }
3248 }
3249 
3250 proc cancelAutomove {{n 1}} {
3251  global analysis
3252  set analysis(automove$n) 0
3253  after cancel "automove $n"
3254  after cancel "automove_go $n"
3255 }
3256 
3257 proc automove {{n 1}} {
3258  global analysis autoplayDelay
3259  if {! $analysis(automove$n)} { return}
3260  after cancel "automove $n"
3261  set analysis(automoveThinking$n) 1
3262  after $analysis(automoveTime$n) "automove_go $n"
3263 }
3264 
3265 proc automove_go {{n 1}} {
3266  global analysis
3267  if {$analysis(automove$n)} {
3268  if {[makeAnalysisMove $n]} {
3269  set analysis(autoMoveThinking$n) 0
3270  updateBoard -pgn
3271  after cancel "automove $n"
3273  } else {
3274  after 1000 "automove $n"
3275  }
3276  }
3277 }
3278 ################################################################################
3279 # If UCI engine, add move through a dedicated function in uci namespace
3280 # returns the error caught by catch
3281 ################################################################################
3282 proc sc_move_add { moves n } {
3283  if { $::analysis(uci$n) } {
3284  return [::uci::sc_move_add $moves]
3285  } else {
3286  return [ catch { sc_move addSan $moves}]
3287  }
3288 }
3289 ################################################################################
3290 # append scid directory if path starts with .
3291 ################################################################################
3292 proc toAbsPath { path } {
3293  set new $path
3294  if {[string index $new 0] == "." } {
3295  set scidInstallDir [file dirname [info nameofexecutable]]
3296  set new [ string replace $new 0 0 $scidInstallDir]
3297  }
3298  return $new
3299 }
3300 ################################################################################
3301 #
3302 ################################################################################
3303 
3304 ###
3305 ### End of file: analysis.tcl
3306 ###
3307 ###