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