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