Scid  4.6.5
reviewgame.tcl
Go to the documentation of this file.
1 ### reviewgame.tcl: part of Scid.
2 ### Copyright (C) 2009 Pascal Georges
3 ###
4 ######################################################################
5 ### Try to guess the moves of a game
6 #
7 
8 # TODO :
9 # - permettre tourner l'échiquier après le démarrage
10 
11 namespace eval reviewgame {
12 
13  set prevScore 0
14  set prevLine ""
15  set nextEngineMove ""
16  set prevFen ""
17  set engineSlot 6
18  set window ".reviewgame"
19  set timeShort 5
20  set timeExtended 1
21  set margin 0.3
22  set moveOfGameIsBest 0
23  # The score of the move really played
24  set scoreGame 0.0
25  # The score of the user's move
26  set scorePlayed 0.0
27  # Score of the engine
28  set scoreEngine 0.0
29 
30  set sequence 0
31  set pushedgame 0
32 
33  array set analysisEngine {}
34 
35  set progressBarStep 1
36  set progressBarTimer 0
37  set autoProceed 0
38 }
39 
40 ################################################################################
41 #
42 ################################################################################
43 proc ::reviewgame::start {} {
44  if { ! [::reviewgame::launchengine] } {
45  tk_messageBox -type ok -icon warning -title "Scid" -message "This feature require at least one UCI engine"
46  return
47  }
48 
49  set w $::reviewgame::window
51  setTitle $w [::tr "GameReview"]
53  wm minsize $w 200 200
54 
55  ttk::frame $w.fgameinfo
56  set welo [sc_game tags get WhiteElo]
57  set belo [sc_game tags get BlackElo]
58  if { $welo == "0"} { set welo "-"}
59  if { $belo == "0"} { set belo "-"}
60  ttk::label $w.fgameinfo.l1 -text "[sc_game tags get White] ($welo) - [sc_game tags get Black] ($belo)"
61  set result [sc_game tags get Result]
62  if { $result == "1" } { set result "1-0"}
63  if { $result == "0" } { set result "0-1"}
64  if { $result == "=" } { set result "1/2 - 1/2"}
65  ttk::label $w.fgameinfo.l2 -text "$result"
66  pack $w.fgameinfo.l1 $w.fgameinfo.l2
67  pack $w.fgameinfo -expand 1 -fill both
68 
69  ttk::frame $w.fparam
70  ttk::label $w.fparam.ltime1 -text "[::tr Time] ([::tr sec])"
71  spinbox $w.fparam.time1 -values { 5 10 15 30 45 60 90 120 } -command { set ::reviewgame::timeShort [$::reviewgame::window.fparam.time1 get] }
72  $w.fparam.time1 set $::reviewgame::timeShort
73  ttk::label $w.fparam.ltime2 -text "[::tr GameReviewTimeExtended] ([ ::tr min])"
74  spinbox $w.fparam.time2 -values { 1 2 3 4 5} -command { set ::reviewgame::timeExtended [$::reviewgame::window.fparam.time1 get] }
75  $w.fparam.time2 set $::reviewgame::timeExtended
76  ttk::label $w.fparam.lmargin -text "[::tr GameReviewMargin]"
77  spinbox $w.fparam.margin -from 0.1 -to 1.0 -increment 0.1 -command { set ::reviewgame::margin [$::reviewgame::window.fparam.time1 get] }
78  $w.fparam.margin set $::reviewgame::margin
79 
80  set row 0
81  grid $w.fparam.ltime1 -column 0 -row $row -sticky nw
82  grid $w.fparam.time1 -column 1 -row $row -sticky nw
83  incr row
84  grid $w.fparam.ltime2 -column 0 -row $row -sticky nw
85  grid $w.fparam.time2 -column 1 -row $row -sticky nw
86  incr row
87  grid $w.fparam.lmargin -column 0 -row $row -sticky nw
88  grid $w.fparam.margin -column 1 -row $row -sticky nw
89  incr row
90 
91  ttk::checkbutton $w.fparam.cbproceed -text "[::tr GameReviewAutoContinue]" -variable ::reviewgame::autoProceed
92  grid $w.fparam.cbproceed -column 0 -row $row -columnspan 2 -sticky nw
93 
94  pack $w.fparam -expand 1 -fill both
95 
96  ttk::frame $w.finfo
97  pack $w.finfo -expand 1 -fill both
98  ttk::progressbar $w.finfo.pb -orient horizontal -length 100 -value 0 -mode determinate
99  ttk::label $w.finfo.pblabel -image tb_stop -compound left
100  ttk::label $w.finfo.sc1 -text "-"
101  ttk::label $w.finfo.sc2 -text "-"
102  ttk::label $w.finfo.sc3 -foreground blue -text "-"
103  ttk::button $w.finfo.proceed -textvar ::tr(Continue) -command ::reviewgame::proceed
104  ttk::button $w.finfo.extended -text "[::tr GameReviewReCalculate]" -command ::reviewgame::extendedTime
105 
106  set row 0
107  grid $w.finfo.pb -column 0 -row $row -sticky nw
108  incr row
109  grid $w.finfo.pblabel -column 0 -row $row -sticky nw
110  incr row
111  grid $w.finfo.proceed -column 0 -row $row -sticky nw
112  grid $w.finfo.extended -column 1 -row $row -sticky nw
113  incr row
114  grid $w.finfo.sc1 -column 0 -row $row -columnspan 2 -sticky nw
115  incr row
116  grid $w.finfo.sc2 -column 0 -row $row -columnspan 2 -sticky nw
117  incr row
118  grid $w.finfo.sc3 -column 0 -row $row -columnspan 2 -sticky nw
119  incr row
120 
121  ttk::button $w.finfo.sol -text [::tr ShowSolution] -command ::reviewgame::showSolution
122  grid $w.finfo.sol -column 0 -row $row -sticky nw
123  incr row
124 
125  # Display statistics
126  ttk::label $w.finfo.stats -text ""
127  grid $w.finfo.stats -column 0 -row $row -sticky nw -columnspan 3
128 
129  ttk::frame $w.fbuttons
130  pack $w.fbuttons -fill x
131  ttk::button $w.fbuttons.close -textvar ::tr(Abort) -command ::reviewgame::endTraining
132  pack $w.fbuttons.close -expand 1 -fill x
133 
134  set ::reviewgame::boardFlipped [::board::isFlipped .main.board]
135 
136  bind $w <Destroy> "if {\[string equal $w %W\]} {::reviewgame::endTraining}"
137  bind $w <Configure> "recordWinSize $w"
138  bind $w <F1> { helpWindow ReviewGame }
140  set ::reviewgame::prevFen [sc_pos fen]
141  set ::reviewgame::movesLikePlayer 0
142  set ::reviewgame::movesLikeEngine 0
143  set ::reviewgame::numberMovesPlayed 0
144  ::setPlayMode "::reviewgame::callback"
147 
148 }
149 
150 proc ::reviewgame::callback {cmd} {
151  switch $cmd {
152  stop { destroy $::reviewgame::window}
153  }
154  return 0
155 }
156 
157 ################################################################################
158 #
159 ################################################################################
160 proc ::reviewgame::showSolution {} {
161  set w $::reviewgame::window
162  $w.finfo.sol configure -text "[ sc_game info nextMove]"
163  set ::reviewgame::solutionDisplayed 1
164 }
165 ################################################################################
166 #
167 ################################################################################
168 proc ::reviewgame::endTraining {} {
169  set w $::reviewgame::window
170 
171  if {$::reviewgame::pushedgame} {
172  sc_game pop
173  }
174 
175  after cancel ::reviewgame::mainLoop
176  set ::reviewgame::bailout 1
177  set ::reviewgame::sequence 0
178  after cancel ::reviewgame::stopAnalyze
180  focus .
181  bind $w <Destroy> {}
182  destroy $w
184  ::setPlayMode ""
185 
186  catch { ::uci::closeUCIengine $::reviewgame::engineSlot}
187 }
188 ################################################################################
189 #
190 ################################################################################
191 proc ::reviewgame::isPlayerTurn {} {
192  if { [sc_pos side] == "white" && ![::board::isFlipped .main.board] || [sc_pos side] == "black" && [::board::isFlipped .main.board] } {
193  return 1
194  }
195  return 0
196 }
197 ################################################################################
198 # Handle the case where position was changed not during normal play but certainly with
199 # move back / forward / rewind commands
200 ################################################################################
201 proc ::reviewgame::abnormalContinuation {} {
206  updateBoard -pgn
207  if { [sc_pos side] == "white" && [::board::isFlipped .main.board] || [sc_pos side] == "black" && ![::board::isFlipped .main.board] } {
208  ::board::flip .main.board
209  }
210  set ::reviewgame::prevFen [sc_pos fen]
213 }
214 ################################################################################
215 # waits for the user to play and check the move played
216 ################################################################################
217 proc ::reviewgame::mainLoop {} {
218  global ::reviewgame::prevScore ::reviewgame::prevLine ::reviewgame::analysisEngine ::reviewgame::nextEngineMove
219  global ::reviewgame::sequence ::reviewgame::useExtendedTime
220  set w $::reviewgame::window
221 
222  after cancel ::reviewgame::mainLoop
223 
224  if { ! [ checkConsistency] } { puts "ERROR checkConsistency returns false" ; return}
225 
226  if { $useExtendedTime } {
227  set ::reviewgame::thinkingTime [expr $::reviewgame::timeExtended * 60]
228  } else {
229  set ::reviewgame::thinkingTime $::reviewgame::timeShort
230  }
231 
232  # in start position, it must be user's turn
233  if { ! [::reviewgame::isPlayerTurn] && $sequence == 0} {
234  if { [ sc_game info nextMoveNT] != ""} {
236  }
237  }
238 
239  $w.finfo.proceed configure -state disabled
240  $w.finfo.sol configure -state disabled
241 
242  # Phase 1 : analyze the move really played during the game
243  if {$sequence == 0} {
244  $w.finfo.sc1 configure -text ""
245  $w.finfo.sc2 configure -text ""
246  set ::reviewgame::movePlayed [ sc_game info nextMoveNT]
247  if {$::reviewgame::movePlayed == ""} {
248  return
249  }
250  $w.finfo.pblabel configure -image tb_stop -text "[::tr GameReviewAnalyzingMovePlayedDuringTheGame]"
251  ::reviewgame::startAnalyze $::reviewgame::thinkingTime $::reviewgame::movePlayed
252  vwait ::reviewgame::sequence
253  if { $::reviewgame::bailout } { return}
254  }
255 
256  # Phase 2 : find the best engine move in current position
257  if { $sequence == 1 } {
258  $w.finfo.pblabel configure -image tb_stop -text "[::tr GameReviewAnalyzingThePosition]"
259  ::reviewgame::startAnalyze $::reviewgame::thinkingTime
260  vwait ::reviewgame::sequence
261  if { $::reviewgame::bailout } { return}
262  }
263 
264  $w.finfo.pblabel configure -image tb_play -text "[::tr GameReviewEnterYourMove]"
265  $w.finfo.sol configure -state normal
266 
267  # is this player's turn (which always plays from bottom of the board) ?
268  if { [::reviewgame::isPlayerTurn] } {
269  after 1000 ::reviewgame::mainLoop
270  return
271  }
272 
273  $w.finfo.sol configure -text "[::tr ShowSolution]"
274 
276 
277  $w.finfo.proceed configure -state normal
278  $w.finfo.extended configure -state normal
280  set ::reviewgame::useExtendedTime 0
281  after 1000 ::reviewgame::mainLoop
282 }
283 ################################################################################
284 #
285 ################################################################################
286 proc ::reviewgame::checkPlayerMove {} {
287  global ::reviewgame::prevScore ::reviewgame::prevLine ::reviewgame::analysisEngine ::reviewgame::nextEngineMove
288  global ::reviewgame::sequence ::reviewgame::useExtendedTime
289  set w $::reviewgame::window
290 
291  incr ::reviewgame::numberMovesPlayed
292 
293  # Phase 3 : ponder on user's move if different of best engine move and move played
294  # We know user has played
295  set user_move [sc_game info previousMoveNT]
296 
297  # ponder on user's move if he did not play the same move as in match
298  if {$user_move != $::reviewgame::movePlayed} {
299  $w.finfo.pblabel configure -image tb_stop -text "[::tr GameReviewCheckingYourMove]"
300  $w.finfo.sc3 configure -text ""
301  ::reviewgame::startAnalyze $::reviewgame::thinkingTime ;#$user_move
302  vwait ::reviewgame::sequence
303  if { $::reviewgame::bailout } { return}
304  $w.finfo.pblabel configure -image tb_stop -text "[::tr GameReviewYourMoveWasAnalyzed]"
305  # display user's score
306  $w.finfo.sc3 configure -text "[::tr GameReviewScoreOfYourMove] : $analysisEngine(score,2)"
307  }
308 
309  # User guessed the correct move played in game
310  if {$user_move == $::reviewgame::movePlayed } {
311 
312  set ::reviewgame::sequence 0
313 
314  $w.finfo.sc3 configure -text "[::tr GameReviewYouPlayedSameMove]" -foreground "sea green"
315  if { ! $::reviewgame::solutionDisplayed } {
316  incr ::reviewgame::movesLikePlayer
317  }
318  # Starting with Scid 4.1, when the move entered already exists it is not added but we simply move forward, so the code below is useless
319  # set var [sc_var number]
320  # sc_var exit
321  # sc_var delete $var
322  # sc_move forward
323  # updateBoard -animate -pgn
324 
325  # display played move score
326  $w.finfo.sc2 configure -text "[::tr GameReviewGameMoveScore] : $analysisEngine(score,1) [::trans $::reviewgame::movePlayed]"
327  # display engine's score
328  $w.finfo.sc1 configure -text "[::tr GameReviewEngineScore] : $analysisEngine(score,2) [::trans [lindex $analysisEngine(moves,2) 0]]"
329  if { $::reviewgame::autoProceed } {
330  # guessed the correct move played during the game, so continue directly
331  proceed
332  }
333  $w.finfo.pblabel configure -image tb_play -text ""
334  set sequence 0
335  } elseif { $user_move == [ lindex $analysisEngine(moves,2) 0] || [ isGoodScore $analysisEngine(score,2) $analysisEngine(score,3)] } {
336 
337  set ::reviewgame::sequence 0
338 
339  # User guessed engine's move
340  if {$user_move == [ lindex $analysisEngine(moves,2) 0]} {
341  $w.finfo.sc3 configure -text "[::tr GameReviewYouPlayedLikeTheEngine]" -foreground "sea green"
342  incr ::reviewgame::movesLikeEngine
343 
344  } else {
345  $w.finfo.sc3 configure -text "[::tr GameReviewNotEngineMoveButGoodMove] : [::trans $user_move] ($analysisEngine(score,3))" -foreground blue
346  }
347  sc_var exit
348  sc_move forward
349  updateBoard -animate -pgn
350  # display played move score
351  $w.finfo.sc2 configure -text "[::tr GameReviewGameMoveScore] : $analysisEngine(score,1) [::trans $::reviewgame::movePlayed]"
352  # display engine's score
353  $w.finfo.sc1 configure -text "[::tr GameReviewEngineScore] $analysisEngine(score,2) [::trans [lindex $analysisEngine(moves,2) 0]]"
354  } else {
355 
356  # user played a bad move : comment it and restart the process
357 
358  set ::reviewgame::sequence 2
359 
360  $w.finfo.sc3 configure -text "[::tr GameReviewMoveNotGood] $analysisEngine(score,3) \n([::trans $analysisEngine(moves,3)])" -foreground red
361  sc_pos addNag "?"
362 
363  # Instead of adding info in comments, add real variations
364  # sc_pos setComment "($analysisEngine(score,3)) $analysisEngine(moves,3) Engine : ($analysisEngine(score,2)) \n[::trans $analysisEngine(moves,2)]"
365  sc_pos setComment "($analysisEngine(score,3))"
366  sc_move addSan $analysisEngine(moves,3)
367  sc_var exit
368  sc_var create
369  sc_pos setComment "Engine : ($analysisEngine(score,2))"
370  sc_move addSan $analysisEngine(moves,2)
371  sc_var exit
372  updateBoard -pgn
373 
374  # allows a re-calculation
375  $w.finfo.extended configure -state normal
376 
377  # display played move score
378  $w.finfo.sc2 configure -text "[::tr GameReviewGameMoveScore] : $analysisEngine(score,1)"
379  # display engine's score
380  $w.finfo.sc1 configure -text "[::tr GameReviewEngineScore] $analysisEngine(score,2)"
381  set ::reviewgame::sequence 2
382  # after 1000 ::reviewgame::mainLoop
383  # return
384  }
385 
386 }
387 ################################################################################
388 #
389 ################################################################################
390 proc ::reviewgame::updateStats {} {
391 
392  set l $::reviewgame::window.finfo.stats
393  if { ![::board::isFlipped .main.board] } {
394  set player [sc_game info white]
395  } else {
396  set player [sc_game info black]
397  }
398 
399  $l configure -text "[::tr GameReviewMovesPlayedLike] $player : $::reviewgame::movesLikePlayer / $::reviewgame::numberMovesPlayed\n[::tr GameReviewMovesPlayedEngine] : $::reviewgame::movesLikeEngine / $::reviewgame::numberMovesPlayed"
400 }
401 ################################################################################
402 #
403 ################################################################################
404 proc ::reviewgame::isGoodScore {engine player} {
405  global ::reviewgame::margin
406  if { ![::board::isFlipped .main.board] } {
407  # if player plays white
408  if {$player >= [expr $engine - $margin]} {
409  return 1
410  }
411  } else {
412  if {$player <= [expr $engine + $margin]} {
413  return 1
414  }
415  }
416  return 0
417 }
418 ################################################################################
419 ## resetValues
420 # Resets global data.
421 ################################################################################
422 proc ::reviewgame::resetValues {} {
423  set ::reviewgame::prevScore 0
424  set ::reviewgame::prevLine ""
425  set ::reviewgame::nextEngineMove ""
426  set ::reviewgame::prevFen ""
427  set ::reviewgame::sequence 0
428  set ::reviewgame::analysisEngine(analyzeMode) 0
429  set ::reviewgame::bailout 0
430  set ::reviewgame::useExtendedTime 0
431  set ::reviewgame::solutionDisplayed 0
432 }
433 
434 ################################################################################
435 # Will start engine
436 # in case of an error, return 0, or 1 if the engine is ok
437 ################################################################################
438 proc ::reviewgame::launchengine {} {
439  global ::reviewgame::analysisEngine
440 
441  ::uci::resetUciInfo $::reviewgame::engineSlot
442 
443  set analysisEngine(analyzeMode) 0
444 
445  # find engine
446  set engineFound 0
447  set index 0
448  foreach e $::engines(list) {
449  if {[lindex $e 7] != 0} {
450  set engineFound 1
451  break
452  }
453  incr index
454  }
455  if { ! $engineFound } {
456  return 0
457  }
458 
459  ::uci::startEngine $index $::reviewgame::engineSlot ;# start engine in analysis mode
460  return 1
461 }
462 
463 # ======================================================================
464 # sendToEngine:
465 # Send a command to a running analysis engine.
466 # ======================================================================
467 proc ::reviewgame::sendToEngine {text} {
468  ::uci::sendToEngine $::reviewgame::engineSlot $text
469 }
470 
471 # ======================================================================
472 # startAnalyzeMode:
473 # Put the engine in analyze mode, from current position after move played (in UCI format), time is in seconds
474 # ======================================================================
475 proc ::reviewgame::startAnalyze { analysisTime { move "" } } {
476  global ::reviewgame::analysisEngine ::reviewgame::engineSlot
477 
478  set pb $::reviewgame::window.finfo.pb
479  set length [$pb cget -maximum]
480  set ::reviewgame::progressBarTimer [expr ( $analysisTime * 1000 * $::reviewgame::progressBarStep ) / $length]
481  after $::reviewgame::progressBarTimer ::reviewgame::updateProgressBar
482 
483  # Check that the engine has not already had analyze mode started:
484  if {$analysisEngine(analyzeMode)} {
486  }
487  set analysisEngine(analyzeMode) 1
488  after cancel ::reviewgame::stopAnalyze
489 
490  # we want to ponder on a particular move, hence we need to switch to a temporary position so
491  # UCI code can correctly format the variations
492  if {$move != ""} {
493  sc_game push copyfast
494  set ::reviewgame::pushedgame 1
495  # ::uci::sc_move_add $move
496  sc_move addSan $move
497  set ::analysis(fen$engineSlot) [sc_pos fen]
498  } else {
499  set ::analysis(fen$engineSlot) ""
500  set ::reviewgame::pushedgame 0
501  }
502 
503  ::reviewgame::sendToEngine "position fen [sc_pos fen] $move"
504  ::reviewgame::sendToEngine "go infinite"
505  after [expr 1000 * $analysisTime] "::reviewgame::stopAnalyze $move"
506 }
507 # ======================================================================
508 # stopAnalyzeMode:
509 # Stop the engine analyze mode
510 # ======================================================================
511 proc ::reviewgame::stopAnalyze { { move "" } } {
512  global ::reviewgame::analysisEngine ::reviewgame::sequence
513 
514  # Check that the engine has already had analyze mode started:
515  if { ! $analysisEngine(analyzeMode) } { return}
516 
517  after cancel ::reviewgame::updateProgressBar
518  if { [winfo exists $::reviewgame::window.finfo.pb]} {
519  $::reviewgame::window.finfo.pb configure -value 0
520  }
521 
522  incr ::reviewgame::sequence
523  set pv [lindex $::analysis(multiPV$::reviewgame::engineSlot) 0]
524  set analysisEngine(score,$sequence) [lindex $pv 1]
525  set analysisEngine(moves,$sequence) [lindex $pv 2]
526 
527  set analysisEngine(analyzeMode) 0
529 
530  if {$move != ""} {
531  sc_game pop
532  }
533 }
534 ################################################################################
535 #
536 ################################################################################
537 proc ::reviewgame::proceed {} {
538  # next cycle
541  set ::reviewgame::prevFen [sc_pos fen]
542  after 1000 ::reviewgame::mainLoop
543 }
544 ################################################################################
545 # Rethink on the position with extended time
546 ################################################################################
547 proc ::reviewgame::extendedTime {} {
548 
549  # if already calculating, do nothing
550  if { $::reviewgame::analysisEngine(analyzeMode)} {
551  return
552  }
553 
554  if { ![::reviewgame::isPlayerTurn] } {
556  }
557 
558  set ::reviewgame::useExtendedTime 1
559  set ::reviewgame::sequence 0
561 
562 }
563 ################################################################################
564 #
565 ################################################################################
566 proc ::reviewgame::updateProgressBar {} {
567  $::reviewgame::window.finfo.pb step $::reviewgame::progressBarStep
568  after $::reviewgame::progressBarTimer ::reviewgame::updateProgressBar
569 }
570 ################################################################################
571 #
572 ################################################################################
573 proc ::reviewgame::checkConsistency {} {
574  if { $::reviewgame::boardFlipped != [::board::isFlipped .main.board] } {
575  tk_messageBox -type ok -icon warning -title "Scid" -message "Choose the side BEFORE starting the exercise"
576  return 0
577  }
578  return 1
579 }
580 
581 ################################################################################
582 # returns 1 if the player is allowed to enter a move (pondering is done)
583 ################################################################################
584 proc ::reviewgame::playerCanMove {} {
585  if { ! [winfo exists $::reviewgame::window] } { return 1}
586 
587  if { ! [::reviewgame::isPlayerTurn] } {
588  return 0
589  } elseif { $::reviewgame::sequence == 2 } {
590  return 1
591  }
592 
593  return 0
594 }
595 
596 ###
597 ### End of file: reviewgame.tcl
598 ###