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