Scid  4.7.0
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros
tacgame.tcl
Go to the documentation of this file.
1 ###
2 ### tacgame.tcl: part of Scid.
3 ### Copyright (C) 2006 Pascal Georges
4 ###
5 
6 namespace eval tacgame {
7  ######################################################################
8  ### Tacgame window: uses a chess engine (Phalanx) in easy mode and
9  ### another engine (for example Toga) to track blunders
10 
11  set resignCount 0
12 
13  # if true, follow a specific opening
14  set openingMovesList {}
15  set openingMovesHash {}
16  set openingMoves ""
17  set outOfOpening 0
18 
19  # list of fen positions played to detect 3 fold repetition
20  set lFen {}
21 
22  set index1 0
23  set index2 0
24 
25  set lastblundervalue 0.0
26  set prev_lastblundervalue 0.0
27  set blundermissed false
28  set blunderwarning false
29  set blunderwarningvalue 0.0
30  set blundermissedvalue 0.0
31 
32  set blunderWarningLabel $::tr(Noblunder)
33  set scoreLabel 0.0
34 
35  set blunderpending 0
36  set prev_blunderpending 0
37  set currentPosHash 0
38  set lscore {}
39 
40  set analysisCoach(automove1) 0
41 
42  # ======================================================================
43  # resetValues
44  # Resets all blunders data.
45  # ======================================================================
46  proc resetValues {} {
47  # see tcl/start.tcl
48  set ::tacgame::blundermissed false
49  set ::tacgame::lastblundervalue 0.0
50  set ::tacgame::prev_lastblundervalue 0.0
51  set ::tacgame::prev_blunderpending 0
52  set ::tacgame::currentPosHash [sc_pos hash]
53  set ::tacgame::lscore {}
54  set ::tacgame::resignCount 0
55  }
56 
57  # ======================================================================
58  # resetEngine:
59  # Resets all engine-specific data.
60  # ======================================================================
61  proc resetEngine {n} {
62 
63  global ::tacgame::analysisCoach
64  set analysisCoach(pipe$n) "" ;# Communication pipe file channel
65  set analysisCoach(seen$n) 0 ;# Seen any output from engine yet?
66  set analysisCoach(seenEval$n) 0 ;# Seen evaluation line yet?
67  set analysisCoach(score$n) 0 ;# Current score in centipawns
68  set analysisCoach(moves$n) "" ;# PV (best line) output from engine
69  set analysisCoach(movelist$n) {} ;# Moves to reach current position
70  set analysisCoach(nonStdStart$n) 0 ;# Game has non-standard start
71  set analysisCoach(has_analyze$n) 0 ;# Engine has analyze command
72  set analysisCoach(has_setboard$n) 0 ;# Engine has setboard command
73  set analysisCoach(send_sigint$n) 0 ;# Engine wants INT signal
74  set analysisCoach(wants_usermove$n) 0 ;# Engine wants "usermove" before moves
75  set analysisCoach(wholeSeconds$n) 0 ;# Engine times in seconds not centisec
76  set analysisCoach(analyzeMode$n) 0 ;# Scid has started analyze mode
77  set analysisCoach(invertScore$n) 1 ;# Score is for side to move, not white
78  set analysisCoach(automove$n) 0
79  set analysisCoach(automoveThinking$n) 0
80  set analysisCoach(automoveTime$n) 2000
81  set analysisCoach(lastClicks$n) 0
82  set analysisCoach(after$n) ""
83  set analysisCoach(log$n) "" ;# Log file channel
84  set analysisCoach(logCount$n) 0 ;# Number of lines sent to log file
85  set analysisCoach(wbEngineDetected$n) 0 ;# Is this a special Winboard engine?
86  }
87 
88  # ======================================================================
89  # ::tacgame::config
90  # Configure coach games :
91  # - Phalanx engine (because it has an 'easy' option)
92  # - Coach engine (Toga is the best)
93  # - level of difficulty
94  # ======================================================================
95  proc config {} {
96 
97  global ::tacgame::configWin ::tacgame::analysisCoachCommand ::tacgame::analysisCoach \
98  engineCoach1 engineCoach2 ::tacgame::level ::tacgame::levelFixed \
99  ::tacgame::isLimitedAnalysisTime ::tacgame::analysisTime ::tacgame::index1 ::tacgame::index2 ::tacgame::chosenOpening
100 
101  # check if game window is already opened. If yes abort previous game
102  if {[winfo exists .coachWin]} {
103  focus .
104  destroy .coachWin
107  }
108 
109  # find Phalanx and a UCI engine
110  set i 0
111  set index1 -1
112  set index2 -1
113  foreach e $::engines(list) {
114  if { $index1 != -1 && $index2 != -1 } { break}
115  set name [lindex $e 0]
116  if { [ string match -nocase "*phalanx*" $name] } {
117  set engineCoach1 $name
118  set index1 $i
119  }
120 
121  if {[lindex $e 7] != 0} {
122  set engineCoach2 $name
123  set index2 $i
124  }
125  incr i
126  }
127 
128  # could not find engines
129  if { $index1 == -1 || $index2 == -1 } {
130  tk_messageBox -title "Scid" -icon warning -type ok -message $::tr(PhalanxOrTogaMissing)
131  return
132  }
133 
134  set w ".configWin"
135  if {[winfo exists $w]} {
136  focus $w
137  # wm attributes $w -topmost
138  return
139  }
140 
142  wm title $w "$::tr(configurecoachgame)"
143 
144  bind $w <F1> { helpWindow TacticalGame }
145  setWinLocation $w
146 
147  ttk::labelframe $w.flevel -text [string toupper $::tr(difficulty) 0 0]
148  ttk::frame $w.flevel.diff_fixed
149  ttk::frame $w.flevel.diff_random
150  ttk::labelframe $w.fopening -text $::tr(Opening)
151  ttk::labelframe $w.flimit -text $::tr(Time)
152  ttk::frame $w.fbuttons
153 
154  pack $w.flevel -side top -fill x
155  pack $w.flevel.diff_fixed -side top -anchor w
156  pack $w.flevel.diff_random -side top -anchor w
157  pack $w.fopening -side top -fill both -expand 1 -pady 10
158  pack $w.flimit $w.fbuttons -side top -fill x
159 
160  ttk::radiobutton $w.flevel.diff_random.cb -text $::tr(RandomLevel) -variable ::tacgame::randomLevel -value 1 -width 15
161  ttk::scale $w.flevel.diff_random.lMin -orient horizontal -from 1200 -to 2200 -length 100 -variable ::tacgame::levelMin \
162  -command { ::utils::validate::roundScale ::tacgame::levelMin 50 }
163  ttk::label $w.flevel.diff_random.labelMin -textvariable ::tacgame::levelMin
164  ttk::scale $w.flevel.diff_random.lMax -orient horizontal -from 1200 -to 2200 -length 100 -variable ::tacgame::levelMax \
165  -command { ::utils::validate::roundScale ::tacgame::levelMax 50 }
166  ttk::label $w.flevel.diff_random.labelMax -textvariable ::tacgame::levelMax
167  ttk::radiobutton $w.flevel.diff_fixed.cb -text $::tr(FixedLevel) -variable ::tacgame::randomLevel -value 0 -width 15
168  ttk::label $w.flevel.diff_fixed.labelFixed -textvariable ::tacgame::levelFixed
169  ttk::scale $w.flevel.diff_fixed.scale -orient horizontal -from 1200 -to 2200 -length 200 \
170  -variable ::tacgame::levelFixed -command { ::utils::validate::roundScale ::tacgame::levelFixed 50 }
171 
172  grid $w.flevel.diff_fixed.cb -row 0 -column 0 -rowspan 2
173  grid $w.flevel.diff_fixed.labelFixed -row 0 -column 1
174  grid $w.flevel.diff_fixed.scale -row 1 -column 1 -padx "10 0" -sticky e
175  grid $w.flevel.diff_random.cb -row 0 -column 0 -rowspan 2
176  grid $w.flevel.diff_random.labelMin -row 0 -column 1
177  grid $w.flevel.diff_random.lMin -row 1 -column 1 -padx "10 0" -sticky e
178  grid $w.flevel.diff_random.labelMax -row 0 -column 2
179  grid $w.flevel.diff_random.lMax -row 1 -column 2 -sticky e
180 
181  # start new game
182  ttk::radiobutton $w.fopening.cbNew -text $::tr(StartNewGame) -variable ::tacgame::openingType -value new
183 
184  # start from current position
185  ttk::radiobutton $w.fopening.cbPosition -text $::tr(StartFromCurrentPosition) -variable ::tacgame::openingType -value current
186 
187  # or choose a specific opening
188  ttk::radiobutton $w.fopening.cbSpecific -text $::tr(SpecificOpening) -variable ::tacgame::openingType -value specific
189 
190  pack $w.fopening.cbNew -anchor w
191  pack $w.fopening.cbPosition -anchor w
192  pack $w.fopening.cbSpecific -anchor w
193 
194  ttk::frame $w.fopening.fOpeningList
195  listbox $w.fopening.fOpeningList.lbOpening -yscrollcommand "$w.fopening.fOpeningList.ybar set" \
196  -height 5 -width 40 -list ::tacgame::openingList
197  $w.fopening.fOpeningList.lbOpening selection set $::tacgame::chosenOpening
198  $w.fopening.fOpeningList.lbOpening see $::tacgame::chosenOpening
199 
200  ttk::scrollbar $w.fopening.fOpeningList.ybar -command "$w.fopening.fOpeningList.lbOpening yview"
201  pack $w.fopening.fOpeningList.lbOpening -side left -fill both -expand 1
202  pack $w.fopening.fOpeningList.ybar -side right -fill y
203  pack $w.fopening.fOpeningList -expand yes -fill both -side top -expand 1
204 
205  # in order to limit CPU usage, limit the time for analysis (this prevents noise on laptops)
206  ttk::checkbutton $w.flimit.blimit -text $::tr(limitanalysis) -variable ::tacgame::isLimitedAnalysisTime
207  ttk::label $w.flimit.labelsecval -textvariable ::tacgame::analysisTime
208  ttk::label $w.flimit.labelsec -text $::tr(seconds)
209  ttk::scale $w.flimit.analysisTime -orient horizontal -from 5 -to 60 -length 200 -variable ::tacgame::analysisTime \
210  -command { ::utils::validate::roundScale ::tacgame::analysisTime 1 }
211  grid $w.flimit.blimit -column 0 -row 0 -columnspan 2 -sticky we
212  grid $w.flimit.labelsecval -column 0 -row 1 -sticky e
213  grid $w.flimit.labelsec -column 1 -row 1 -sticky w -padx 5
214  grid $w.flimit.analysisTime -column 0 -row 2 -columnspan 2 -sticky we
215  # pack $w.flimit.blimit $w.flimit.labelsec $w.flimit.analysisTime $w.flimit.labelsecval -side left -expand yes -pady 5
216 
217  ttk::button $w.fbuttons.close -text $::tr(Play) -command {
218  focus .
219  set ::tacgame::chosenOpening [.configWin.fopening.fOpeningList.lbOpening curselection]
220  destroy .configWin
221  ::tacgame::play
222  }
223  ttk::button $w.fbuttons.cancel -textvar ::tr(Cancel) -command "focus .; destroy $w"
224 
225  packdlgbuttons $w.fbuttons.cancel $w.fbuttons.close
226 
227  bind $w <Escape> { .configWin.fbuttons.cancel invoke }
228  bind $w <Return> { .configWin.fbuttons.close invoke }
229  bind $w <F1> { helpWindow TacticalGame }
230  bind $w <Destroy> ""
231  bind $w <Configure> "recordWinSize $w"
232  wm minsize $w 45 0
233  }
234  # ======================================================================
235  #
236  # ::tacgame::play
237  #
238  # ======================================================================
239  proc play { } {
240  global ::tacgame::analysisCoach ::tacgame::threshold ::tacgame::showblunder ::tacgame::showblundervalue \
241  ::tacgame::blunderfound ::tacgame::showmovevalue ::tacgame::level ::tacgame::levelFixed engineCoach1 \
242  engineCoach2 ::tacgame::index1 ::tacgame::index2 ::tacgame::chosenOpening \
243  ::tacgame::openingType ::tacgame::openingList ::tacgame::openingMovesList \
244  ::tacgame::openingMovesHash ::tacgame::openingMoves ::tacgame::outOfOpening
245 
246  if {$::tacgame::openingType ne "current"} {
247  if {[::game::Clear] eq "cancel"} { return}
248  }
249 
250  resetEngine 1
251  resetEngine 2
252  catch { unset ::uci::uciInfo(score2)}
253 
254  set ::tacgame::lFen {}
255 
256  set w .coachWin
257  if {[winfo exists $w]} {
258  focus .
259  destroy $w
260  return
261  }
262 
263  if {$::tacgame::randomLevel} {
264  if {$::tacgame::levelMax < $::tacgame::levelMin} {
265  set tmp $::tacgame::levelMax
266  set ::tacgame::levelMax $::tacgame::levelMin
267  set ::tacgame::levelMin $tmp
268  }
269  set level [expr int(rand()*($::tacgame::levelMax - $::tacgame::levelMin)) + $::tacgame::levelMin]
270  } else {
271  set level $::tacgame::levelFixed
272  }
273 
274  # if will follow a specific opening line
275  if {$openingType == "specific"} {
276  set fields [split [lindex $openingList $chosenOpening] ":"]
277  set openingName [lindex $fields 0]
278  set openingMoves [string trim [lindex $fields 1]]
279  set openingMovesList ""
280  set openingMovesHash ""
281  set outOfOpening 0
282  foreach m [split $openingMoves] {
283  # in case of multiple adjacent spaces in opening line
284  if {$m =={}} {
285  continue
286  }
287  set n [string trim $m]
288  lappend openingMovesList [string trim [regsub {^[1-9]+\.} $m ""]]
289  }
290 
291  lappend openingMovesHash [sc_pos hash]
292  foreach m $openingMovesList {
293  if {[catch {sc_move addSan $m}]} {
294  }
295  lappend openingMovesHash [sc_pos hash]
296  }
297  }
298 
299  # create a new game if a DB is opened
300  if {$::tacgame::openingType != "current"} {
301  sc_game tags set -event "Tactical game"
302  if { [::board::isFlipped .main.board] } {
303  sc_game tags set -white "Phalanx - $level ELO"
304  } else {
305  sc_game tags set -black "Phalanx - $level ELO"
306  }
307  sc_game tags set -date [::utils::date::today]
308  }
309 
311 
312  createToplevel $w
314  setTitle $w "$::tr(coachgame) (Elo $level)"
315 
316  ttk::frame $w.fdisplay
317  ttk::frame $w.fthreshold
318  ttk::frame $w.finformations
319  ttk::labelframe $w.fclockw -text "$::tr(Time) $::tr(Player)"
320  ttk::labelframe $w.fclockb -text "$::tr(Time) $::tr(Engine)"
321  ttk::frame $w.fbuttons
322  pack $w.fdisplay -side top -fill both -pady 5 -padx 10
323  pack [ttk::separator $w.line1 -orient horizontal] -side top -fill x -padx 10 -pady 5
324  pack $w.fthreshold -side top -fill both -pady 5 -padx 10
325  pack [ttk::separator $w.line2 -orient horizontal] -side top -fill x -padx 10 -pady 5
326  pack $w.finformations $w.fclockb $w.fclockw -side top -fill both -pady 5 -padx 10
327  pack $w.fbuttons -side top -pady "10 15"
328 
329  ttk::checkbutton $w.fdisplay.b1 -text $::tr(showblunderexists) -variable ::tacgame::showblunder
330  ttk::checkbutton $w.fdisplay.b2 -text $::tr(showblundervalue) -variable ::tacgame::showblundervalue
331  ttk::checkbutton $w.fdisplay.b5 -text $::tr(showscore) -variable ::tacgame::showevaluation
332  pack $w.fdisplay.b1 $w.fdisplay.b2 $w.fdisplay.b5 -anchor w
333 
334  ttk::label $w.fthreshold.l -text $::tr(moveblunderthreshold) -wraplength 300
335 
336  ttk::scale $w.fthreshold.t -orient horizontal -from 0.0 -to 10.0 -length 200 \
337  -variable ::tacgame::threshold -command { ::utils::validate::floatScale ::tacgame::threshold 0.1 }
338  ttk::label $w.fthreshold.labelt -textvariable ::tacgame::threshold
339  pack $w.fthreshold.l $w.fthreshold.labelt $w.fthreshold.t -side top -anchor w
340 
341  ttk::label $w.finformations.l1 -textvariable ::tacgame::blunderWarningLabel -background linen
342  ttk::label $w.finformations.l3 -textvariable ::tacgame::scoreLabel -foreground WhiteSmoke -background SlateGray
343  pack $w.finformations.l1 $w.finformations.l3 -padx 5 -pady 5 -side top -fill x
344 
345  ::gameclock::new $w.fclockb 2 80
346  ::gameclock::new $w.fclockw 1 80
349 
350  ttk::button $w.fbuttons.close -textvar ::tr(Abort) -command "destroy .coachWin"
351  pack $w.fbuttons.close -expand yes -fill both -padx 20 -pady 2
352 
353  ::tacgame::launchengine $index1 1
354  ::uci::startEngine $index2 2
355  set ::uci::uciInfo(multipv2) 1
356  changePVSize 2
357 
360 
361  bind $w <F1> { helpWindow TacticalGame }
362  bind $w <Destroy> "if {\[string equal $w %W\]} {::tacgame::abortGame}"
363  bind $w <Escape> "destroy .coachWin"
364  wm minsize $w 45 0
366 
367  set ::playMode "::tacgame::callback"
369  }
370 
371  proc callback {cmd} {
372  switch $cmd {
373  stop { destroy .coachWin}
374  }
375  return 0
376  }
377 
378  proc toggleClocks {} {
379  if {[::gameclock::stop 1]} {
382  } elseif {[::gameclock::stop 2]} {
385  }
386  ::notify::PosChanged -pgn -animate
387  }
388 
389 
390  ################################################################################
391  #
392  ################################################################################
393  proc abortGame { { destroyWin 1 } } {
394  catch { unset ::playMode}
395  after cancel ::tacgame::phalanxGo
402  }
403  # ======================================================================
404  # ::tacgame::launchengine
405  # - launches both engines
406  # - updates values for :
407  # blundermissed (boolean), blunderwarning (boolean)
408  # blunderwarningvalue (real), blundermissedvalue (real)
409  # totalblundersmissed (real), totalblunders (real)
410  # ======================================================================
411 
412  proc launchengine {index n} {
413  global ::tacgame::analysisCoach ::tacgame::level
414 
416 
417  set engineData [lindex $::engines(list) $index]
418  set analysisName [lindex $engineData 0]
419  set analysisCommand [ ::toAbsPath [ lindex $engineData 1]]
420  set analysisArgs [lindex $engineData 2]
421  set analysisDir [ ::toAbsPath [lindex $engineData 3]]
422 
423  # turn phalanx book, ponder and learning off, easy on
424  if {$n == 1} {
425  # convert Elo = 1200 to level 100 up to Elo=2200 to level 0
426  set easylevel [expr int(100-(100*($level-1200)/(2200-1200)))]
427  append analysisArgs " -b+ -p- -l- -e $easylevel "
428  }
429 
430  # If the analysis directory is not current dir, cd to it:
431  set oldpwd ""
432  if {$analysisDir != "."} {
433  set oldpwd [pwd]
434  catch {cd $analysisDir}
435  }
436 
437  # Try to execute the analysis program:
438  if {[catch {set analysisCoach(pipe$n) [open "| [list $analysisCommand] $analysisArgs" "r+"]} result]} {
439  if {$oldpwd != ""} { catch {cd $oldpwd}}
440  tk_messageBox -title "Scid: error starting analysis" \
441  -icon warning -type ok \
442  -message "Unable to start the program:\n$analysisCommand"
444  return
445  }
446 
447  # Return to original dir if necessary:
448  if {$oldpwd != ""} { catch {cd $oldpwd}}
449 
450  # Configure pipe for line buffering and non-blocking mode:
451  fconfigure $analysisCoach(pipe$n) -buffering line -blocking 0
452 
453  if {$n == 1} {
454  fileevent $analysisCoach(pipe$n) readable "::tacgame::processInput"
455  after 1000 "::tacgame::checkAnalysisStarted $n"
456  }
457 
458  }
459 
460  # ======================================================================
461  # ::tacgame::closeEngine
462  # Close an engine.
463  # ======================================================================
464  proc closeEngine {n} {
465  global windowsOS ::tacgame::analysisCoach
466 
467  # Check the pipe is not already closed:
468  if { $n == 1 } {
469  if {$analysisCoach(pipe$n) == "" } {
470  return
471  }
472  }
473  if { $n == 2 } {
475  return
476  }
477 
478  # Send interrupt signal if the engine wants it:
479  if {(!$windowsOS) && $analysisCoach(send_sigint$n)} {
480  catch {exec -- kill -s INT [pid $analysisCoach(pipe$n)]}
481  }
482 
483  # Some engines in analyze mode may not react as expected to "quit"
484  # so ensure the engine exits analyze mode first:
485  sendToEngine $n "exit"
486  sendToEngine $n "quit"
487  catch { flush $analysisCoach(pipe$n)}
488 
489  # Uncomment the following line to turn on blocking mode before
490  # closing the engine (but probably not a good idea!)
491  # fconfigure $analysisCoach(pipe$n) -blocking 1
492 
493  # Close the engine, ignoring any errors since nothing can really
494  # be done about them anyway -- maybe should alert the user with
495  # a message box?
496  catch {close $analysisCoach(pipe$n)}
497 
498  set analysisCoach(pipe$n) ""
499  }
500  # ======================================================================
501  # sendToEngine:
502  # Send a command to a running analysis engine.
503  # ======================================================================
504  proc sendToEngine {n text} {
505  catch {puts $::tacgame::analysisCoach(pipe$n) $text}
506  }
507 
508  # ======================================================================
509  # checkAnalysisStarted
510  # Called a short time after an analysis engine was started
511  # to send it commands if Scid has not seen any output from
512  # it yet.
513  # ======================================================================
514  proc checkAnalysisStarted {n} {
515  global ::tacgame::analysisCoach
516  if {$analysisCoach(seen$n)} { return}
517 
518  # Some Winboard engines do not issue any output when
519  # they start up, so the fileevent above is never triggered.
520  # Most, but not all, of these engines will respond in some
521  # way once they have received input of some type. This
522  # proc will issue the same initialization commands as
523  # those in processAnalysisInput below, but without the need
524  # for a triggering fileevent to occur.
525 
526  set analysisCoach(seen$n) 1
527  ::tacgame::sendToEngine $n "xboard"
528  ::tacgame::sendToEngine $n "protover 2"
529  ::tacgame::sendToEngine $n "post"
530  ::tacgame::sendToEngine $n "ponder off"
531 
532  # Prevent some engines from making an immediate "book"
533  # reply move as black when position is sent later:
534  ::tacgame::sendToEngine $n "force"
535  }
536 
537  # ======================================================================
538  #
539  # processInput from the engine blundering (Phalanx)
540  #
541  # ======================================================================
542  proc processInput {} {
543  global ::tacgame::analysisCoach ::tacgame::analysis
544 
545  # Get one line from the engine:
546  set line [gets $analysisCoach(pipe1)]
547 
548  # Check that the engine did not terminate unexpectedly:
549  if {[eof $analysisCoach(pipe1)]} {
550  fileevent $analysisCoach(pipe1) readable {}
551  catch {close $analysisCoach(pipe1)}
552  set analysisCoach(pipe1) ""
553  tk_messageBox -type ok -icon info -parent .main -title "Scid" \
554  -message "The analysis engine 1 terminated without warning; it probably crashed or had an internal error."
555  }
556 
557  if {! $analysisCoach(seen1)} {
558  # First line of output from the program, so send initial commands:
559  set analysisCoach(seen1) 1
560  ::tacgame::sendToEngine 1 "xboard"
561  ::tacgame::sendToEngine 1 "post"
562  }
563 
565 
566  }
567 
568  # ======================================================================
569  # startAnalyzeMode:
570  # Put the engine in analyze mode
571  # ======================================================================
572  proc startAnalyze { } {
573  global ::tacgame::analysisCoach ::tacgame::isLimitedAnalysisTime ::tacgame::analysisTime
574  set n 2
575  set ::analysis(waitForReadyOk$n) 1
576  ::uci::sendToEngine $n "isready"
577  vwait ::analysis(waitForReadyOk$n)
578  ::uci::sendToEngine $n "position fen [sc_pos fen]"
579  ::uci::sendToEngine $n "go infinite"
580 
581  if { $isLimitedAnalysisTime == 1 } {
582  after [expr 1000 * $analysisTime] ::tacgame::stopAnalyze
583  }
584 
585  }
586  # ======================================================================
587  # stopAnalyzeMode:
588  # Stop the engine analyze mode
589  # ======================================================================
590  proc stopAnalyze { } {
591  global ::tacgame::analysisCoach ::tacgame::isLimitedAnalysisTime ::tacgame::analysisTime
592 
593  after cancel ::tacgame::stopAnalyze
594  ::uci::sendToEngine 2 "stop"
595  }
596  ################################################################################
597  # returns true if last move is a mate and stops clocks
598  ################################################################################
599  proc endOfGame {} {
600  if { [string index [sc_game info previousMove] end] == "#"} {
601  return 1
602  }
603  return 0
604  }
605  # ======================================================================
606  # phalanxGo
607  # it is phalanx's turn to play
608  # ======================================================================
609  proc phalanxGo {} {
610  global ::tacgame::analysisCoach ::tacgame::openingType ::tacgame::openingMovesList \
611  ::tacgame::openingMovesHash ::tacgame::openingMoves ::tacgame::outOfOpening
612 
613  after cancel ::tacgame::phalanxGo
614 
615  if { [::tacgame::endOfGame] } {
617  return
618  }
619 
620  # check if Phalanx is already thinking
621  if { $analysisCoach(automoveThinking1) == 1 } {
622  after 1000 ::tacgame::phalanxGo
623  return
624  }
625 
627 
628  if { [sc_pos side] != [::tacgame::getPhalanxColor] } {
629  after 1000 ::tacgame::phalanxGo
630  return
631  }
632 
634  repetition
635 
636  # make a move corresponding to a specific opening, (it is Phalanx's turn)
637  if {$openingType == "specific" && !$outOfOpening} {
638  set index 0
639  # Warn if the user went out of the opening line chosen
640  if { !$outOfOpening } {
641  set ply [ expr [sc_pos moveNumber] * 2 - 1]
642  if { [sc_pos side] == "white" } {
643  set ply [expr $ply - 1]
644  }
645 
646  if { [lsearch $openingMovesHash [sc_pos hash]] == -1 && [llength $openingMovesList] >= $ply} {
647  set answer [tk_messageBox -icon question -parent .main -title $::tr(OutOfOpening) -type yesno \
648  -message "$::tr(NotFollowedLine) $openingMoves\n $::tr(DoYouWantContinue)"]
649  if {$answer == no} {
650  sc_move back 1
652  after 1000 ::tacgame::phalanxGo
653  return
654  } else {
655  set outOfOpening 1
656  }
657  }
658  }
659 
660  set hpos [sc_pos hash]
661  # Find a corresponding position in the opening line
662  set length [llength $openingMovesHash]
663  for {set i 0} { $i < [expr $length-1] } { incr i} {
664  set h [lindex $openingMovesHash $i]
665  if {$h == $hpos} {
666  set index [lsearch $openingMovesHash $h]
667  set move [lindex $openingMovesList $index]
668  # play the move
669  set action "replace"
670  if {![sc_pos isAt vend]} { set action [confirmReplaceMove]}
671  if {$action == "replace"} {
672  if {[catch {sc_move addSan $move}]} {}
673  } elseif {$action == "var"} {
674  sc_var create
675  if {[catch {sc_move addSan $move}]} {}
676  } elseif {$action == "mainline"} {
677  sc_var create
678  if {[catch {sc_move addSan $move}]} {}
679  sc_var promote
680  sc_move forward 1
681  }
682 
684 
686  repetition
687  after 1000 ::tacgame::phalanxGo
688  return
689  }
690  }
691 
692  }
693 
694  # Pascal Georges : original Phalanx does not have 'setboard'
695  set analysisCoach(automoveThinking1) 1
696  sendToEngine 1 "setboard [sc_pos fen]"
697  sendToEngine 1 "go"
698  after 1000 ::tacgame::phalanxGo
699  }
700  ################################################################################
701  # add current position for 3fold repetition detection and returns 1 if
702  # the position is a repetition
703  ################################################################################
704  proc repetition {} {
705  set elt [lrange [split [sc_pos fen]] 0 2]
706  # append the position only if different from the last element
707  if { $elt != [ lindex $::tacgame::lFen end] } {
708  lappend ::tacgame::lFen $elt
709  }
710  if { [llength [lsearch -all $::tacgame::lFen $elt]] >=3 } {
711  tk_messageBox -type ok -message $::tr(Draw) -parent .main -icon info
712  return 1
713  }
714  return 0
715  }
716  # ======================================================================
717  # makePhalanxMove:
718  #
719  # ======================================================================
720  proc makePhalanxMove { input } {
721  global ::tacgame::lscore ::tacgame::analysisCoach ::tacgame::currentPosHash ::tacgame::resignCount
722 
723  # The input move is of the form "my move is MOVE"
724  if {[scan $input "my move is %s" move] != 1} { return 0}
725 
727 
728  # Phalanx will move : update the score list to detect any blunder
729  if {[info exists ::tacgame::sc1]} {
730  lappend lscore $::tacgame::sc1
731  }
732 
733  # if the resign value has been reached more than 3 times in a raw, resign
734  if { ( [getPhalanxColor] == "black" && [lindex $lscore end] > $::informant("++-") ) || \
735  ( [getPhalanxColor] == "white" && [lindex $lscore end] < [expr 0.0 - $::informant("++-")] ) } {
736  incr resignCount
737  } else {
738  set resignCount 0
739  }
740 
741  # check the sequence of moves
742  # in case of any event (board setup, move back/forward), reset score list
743  if { ![sc_pos isAt start] && ![sc_pos isAt vstart]} {
744  sc_move back 1
745  if { [sc_pos hash] != $currentPosHash} {
746  set lscore {}
748  }
749  sc_move forward 1
750  } else {
751  if { [sc_pos hash] != $currentPosHash} {
752  set lscore {}
754  }
755  }
756 
757  # play the move
758  set action "replace"
759  if {![sc_pos isAt vend]} { set action [confirmReplaceMove]}
760  if {$action == "replace"} {
761  if {[catch {sc_move addSan $move}]} {
762  # No move from Phalanx : remove the score (last element)
763  set lscore [lreplace $lscore end end]
764  return 0
765  }
766  } elseif {$action == "var"} {
767  sc_var create
768  if {[catch {sc_move addSan $move}]} {
769  # No move from Phalanx : remove the score (last element)
770  set lscore [lreplace $lscore end end]
771  return 0
772  }
773  } elseif {$action == "mainline"} {
774  sc_var create
775  if {[catch {sc_move addSan $move}]} {
776  # No move from Phalanx : remove the score (last element)
777  set lscore [lreplace $lscore end end]
778  return 0
779  }
780  sc_var promote
781  sc_move forward 1
782  }
783  if { $::tacgame::showevaluation == 1 && [info exists ::uci::uciInfo(score2)] } {
784  storeEvalComment $::uci::uciInfo(score2)
785  }
786 
787  set analysisCoach(automoveThinking1) 0
788  set currentPosHash [sc_pos hash]
789 
792 
794  repetition
795 
796  if { $resignCount > 3 } {
797  tk_messageBox -type ok -message $::tr(Iresign) -parent .main -icon info
798  set resignCount 0
799  }
800 
801  return 1
802  }
803 
804  # ======================================================================
805  # updateScore
806  # ======================================================================
807  proc updateScore { } {
808  if { ! $::tacgame::showevaluation } { return}
809  if {![info exists ::uci::uciInfo(score2)]} {
810  set ::tacgame::scoreLabel ""
811  return
812  } else {
813  set ::tacgame::scoreLabel "Score: $::uci::uciInfo(score2)"
814  }
815  }
816 
817  # ======================================================================
818  # updateAnalysisText
819  # Update the text in an analysis window.
820  # Human blunders are not checked, only Phalanx'one
821  # ======================================================================
822  proc updateAnalysisText { } {
823  global ::tacgame::analysisCoach ::tacgame::showblunder ::tacgame::blunderWarningLabel \
824  ::tacgame::showblunder ::tacgame::showblundervalue ::tacgame::showblunderfound ::tacgame::showmovevalue \
825  ::tacgame::showevaluation ::tacgame::lscore ::tacgame::threshold \
826  ::tacgame::lastblundervalue ::tacgame::prev_lastblundervalue ::tacgame::scoreLabel \
827  ::tacgame::blunderpending ::tacgame::prev_blunderpending ::tacgame::sc1
828 
829  # only update when it is human turn
830  if { [getPhalanxColor] == [sc_pos side] } { return}
831 
832  catch {
833  set sc1 $::uci::uciInfo(score2)
834  set sc2 [lindex $lscore end]
835  }
836 
837  # There are less than 2 scores in the list
838  if {[llength $lscore] < 2} {
839  set blunderWarningLabel $::tr(Noinfo)
840  set scoreLabel ""
841  if {[llength $lscore] == 1 && $showevaluation } {
842  set scoreLabel "Score : [lindex $lscore end]"
843  }
844  return
845  }
846 
847  # set sc1 [lindex $lscore end]
848  # set sc2 [lindex $lscore end-1]
849 
850  if { $analysisCoach(automoveThinking1) } {
851  set blunderWarningLabel $::tr(Noinfo)
852  }
853 
854  # Check if a blunder was made by Phalanx at last move.
855  # The check is done during player's turn
856  if { $showblunder && [::tacgame::getPhalanxColor] != [sc_pos side] } {
857  if {[llength $lscore] >=2} {
858  if { ($sc1 - $sc2 > $threshold && [::tacgame::getPhalanxColor] == "black") || \
859  ($sc1 - $sc2 < [expr 0.0 - $threshold] && [::tacgame::getPhalanxColor] == "white") } {
860  set lastblundervalue [expr $sc1-$sc2]
861  # append a ?!, ? or ?? to the move if there is none yet and if the game was not dead yet
862  # (that is if the score was -6, if it goes down to -10, this is a normal evolution
863  if { [expr abs($sc2)] < $::informant("++-") } {
864  sc_pos clearNags
865  set b [expr abs($lastblundervalue)]
866  if { $b >= $::informant("?!") && $b < $::informant("?") } {
867  sc_pos addNag "?!"
868  } elseif { $b >= $::informant("?") && $b < $::informant("??") } {
869  sc_pos addNag "?"
870  } elseif { $b >= $::informant("??") } {
871  sc_pos addNag "??"
872  }
873  }
874 
875  .coachWin.finformations.l1 configure -background LightCoral
876  if { $showblundervalue } {
877  set tmp $::tr(blunder)
878  append tmp [format " %+8.2f" [expr abs($sc1-$sc2)]]
879  set blunderWarningLabel $tmp
880  set blunderpending 1
881  } else {
882  set blunderWarningLabel "$::tr(blunder) !"
883  }
884  } else {
885  sc_pos clearNags
886  .coachWin.finformations.l1 configure -background linen
887  set blunderWarningLabel $::tr(Noblunder)
888  set blunderpending 0
889  }
890  }
891  } else {
892  set blunderWarningLabel "---"
893  }
894 
895  if { !$showblunder || $analysisCoach(automoveThinking1) } {
896  set blunderWarningLabel "---"
897  }
898 
899  # displays current score sent by the "good" engine (Toga)
900  if { $showevaluation } {
901  set scoreLabel "Score : $sc1"
902  } else {
903  set scoreLabel ""
904  }
905  }
906 
907  # ======================================================================
908  # getPhalanxColor
909  # Returns "white" or "black" (Phalanx always plays at top)
910  # ======================================================================
911  proc getPhalanxColor {} {
912  # Phalanx always plays for the upper side
913  if { [::board::isFlipped .main.board] == 0 } {
914  return "black"
915  } else {
916  return "white"
917  }
918  }
919 
920  ################################################################################
921  #
922  ################################################################################
923  set openingList [ list \
924  "$::tr(Reti): 1.Nf3" \
925  "$::tr(English): 1.c4" \
926  "$::tr(d4Nf6Miscellaneous): 1.d4 Nf6" \
927  "$::tr(Trompowsky): 1.d4 Nf6 2.Bg5" \
928  "$::tr(Budapest): 1.d4 Nf6 2.c4 e5" \
929  "$::tr(OldIndian): 1.d4 Nf6 2.c4 d6" \
930  "$::tr(BenkoGambit): 1.d4 Nf6 2.c4 c5 3.d5 b5" \
931  "$::tr(ModernBenoni): 1.d4 Nf6 2.c4 c5 3.d5 e6" \
932  "$::tr(DutchDefence): 1.d4 f5" \
933  "1.e4" \
934  "$::tr(Scandinavian): 1.e4 d5" \
935  "$::tr(AlekhineDefence): 1.e4 Nf6" \
936  "$::tr(Pirc): 1.e4 d6" \
937  "$::tr(CaroKann): 1.e4 c6" \
938  "$::tr(CaroKannAdvance): 1.e4 c6 2.d4 d5 3.e5" \
939  "$::tr(Sicilian): 1.e4 c5" \
940  "$::tr(SicilianAlapin): 1.e4 c5 2.c3" \
941  "$::tr(SicilianClosed): 1.e4 c5 2.Nc3" \
942  "$::tr(Sicilian): 1.e4 c5 2.Nf3 Nc6" \
943  "$::tr(Sicilian): 1.e4 c5 2.Nf3 e6" \
944  "$::tr(SicilianRauzer): 1.e4 c5 2.Nf3 d6 3.d4 cxd4 4.Nxd4 Nf6 5.Nc3 Nc6" \
945  "$::tr(SicilianDragon): 1.e4 c5 2.Nf3 d6 3.d4 cxd4 4.Nxd4 Nf6 5.Nc3 g6 " \
946  "$::tr(SicilianScheveningen): 1.e4 c5 2.Nf3 d6 3.d4 cxd4 4.Nxd4 Nf6 5.Nc3 e6" \
947  "$::tr(SicilianNajdorf): 1.e4 c5 2.Nf3 d6 3.d4 cxd4 4.Nxd4 Nf6 5.Nc3 a6" \
948  "$::tr(OpenGame): 1.e4 e5" \
949  "$::tr(Vienna): 1.e4 e5 2.Nc3" \
950  "$::tr(KingsGambit): 1.e4 e5 2.f4" \
951  "$::tr(RussianGame): 1.e4 e5 2.Nf3 Nf6" \
952  "$::tr(OpenGame): 1.e4 e5 2.Nf3 Nc6" \
953  "$::tr(ItalianTwoKnights): 1.e4 e5 2.Nf3 Nc6 3.Bc4" \
954  "$::tr(Spanish): 1.e4 e5 2.Nf3 Nc6 3.Bb5" \
955  "$::tr(SpanishExchange): 1.e4 e5 2.Nf3 Nc6 3.Bb5 a6 4.Bxc6" \
956  "$::tr(SpanishOpen): 1.e4 e5 2.Nf3 Nc6 3.Bb5 a6 4.Ba4 Nf6 5.O-O Nxe4" \
957  "$::tr(SpanishClosed): 1.e4 e5 2.Nf3 Nc6 3.Bb5 a6 4.Ba4 Nf6 5.O-O Be7" \
958  "$::tr(FrenchDefence): 1.e4 e6" \
959  "$::tr(FrenchAdvance): 1.e4 e6 2.d4 d5 3.e5" \
960  "$::tr(FrenchTarrasch): 1.e4 e6 2.d4 d5 3.Nd2" \
961  "$::tr(FrenchWinawer): 1.e4 e6 2.d4 d5 3.Nc3 Bb4" \
962  "$::tr(FrenchExchange): 1.e4 e6 2.d4 d5 3.exd5 exd5" \
963  "$::tr(QueensPawn): 1.d4 d5" \
964  "$::tr(Slav): 1.d4 d5 2.c4 c6" \
965  "$::tr(QGA): 1.d4 d5 2.c4 dxc4" \
966  "$::tr(QGD): 1.d4 d5 2.c4 e6" \
967  "$::tr(QGDExchange): 1.d4 d5 2.c4 e6 3.cxd5 exd5" \
968  "$::tr(SemiSlav): 1.d4 d5 2.c4 e6 3.Nc3 Nf6 4.Nf3 c6" \
969  "$::tr(QGDwithBg5): 1.d4 d5 2.c4 e6 3.Nc3 Nf6 4.Bg5" \
970  "$::tr(QGDOrthodox): 1.d4 d5 2.c4 e6 3.Nc3 Nf6 4.Bg5 Be7 5.e3 O-O 6.Nf3 Nbd7" \
971  "$::tr(Grunfeld): 1.d4 Nf6 2.c4 g6 3.Nc3 d5" \
972  "$::tr(GrunfeldExchange): 1.d4 Nf6 2.c4 g6 3.Nc3 d5 4.cxd5" \
973  "$::tr(GrunfeldRussian): 1.d4 Nf6 2.c4 g6 3.Nc3 d5 4.Nf3 Bg7 5.Qb3" \
974  "$::tr(Catalan): 1.d4 Nf6 2.c4 e6 3.g3 " \
975  "$::tr(CatalanOpen): 1.d4 Nf6 2.c4 e6 3.g3 d5 4.Bg2 dxc4" \
976  "$::tr(CatalanClosed): 1.d4 Nf6 2.c4 e6 3.g3 d5 4.Bg2 Be7" \
977  "$::tr(QueensIndian): 1.d4 Nf6 2.c4 e6 3.Nf3 b6" \
978  "$::tr(NimzoIndian): 1.d4 Nf6 2.c4 e6 3.Nc3 Bb4" \
979  "$::tr(NimzoIndianClassical): 1.d4 Nf6 2.c4 e6 3.Nc3 Bb4 4.Qc2" \
980  "$::tr(NimzoIndianRubinstein): 1.d4 Nf6 2.c4 e6 3.Nc3 Bb4 4.e3" \
981  "$::tr(KingsIndian): 1.d4 Nf6 2.c4 g6" \
982  "$::tr(KingsIndianSamisch): 1.d4 Nf6 2.c4 g6 4.e4 d6 5.f3" \
983  "$::tr(KingsIndianMainLine): 1.d4 Nf6 2.c4 g6 4.e4 d6 5.Nf3"]
984 }
985 ###
986 ### End of file: tacgame.tcl
987 ###