diff --git a/tcl/enginecomm.tcl b/tcl/enginecomm.tcl index 094498583..5e8366518 100644 --- a/tcl/enginecomm.tcl +++ b/tcl/enginecomm.tcl @@ -761,13 +761,9 @@ proc ::uci::parseline {id line} { } if {[string match "bestmove *" $line]} { - lassign [split $line] -> ::engconn(InfoBestMove_$id) ponder ponder_move - #TODO: - # lassign [lsearch -inline -index 0 $::engconn(options_$id) "Ponder"] -> do_ponder - # if {$do_ponder eq "true" && $ponder eq "ponder"} - # set ::engconn(waitReply_$id) "Go?" - # ::engine::rawsend $id position ... - # ::engine::rawsend $id go ponder ... + # assign ponder move as well + # starting ponder should not be done here because other parameter like time or depth not available here + set ::engconn(InfoBestMove_$id) [lrange [split $line] 1 3] return 1 } diff --git a/tcl/lang/deutsch.tcl b/tcl/lang/deutsch.tcl index 74b0ee0b1..c8f6fa365 100644 --- a/tcl/lang/deutsch.tcl +++ b/tcl/lang/deutsch.tcl @@ -186,7 +186,7 @@ menuText D ToolsTracker "Figurenverteilung" 7 \ {Figurenverteilungsfenster öffnen} menuText D ToolsTraining "Training" 0 {Trainingswerkzeuge (Taktik, Eröffnungen,...) } menuText D ToolsTacticalGame "Trainingspartie" 0 {Trainingspartie spielen} -menuText D ToolsSeriousGame "Ernste Partie" 0 {Ernste Partie spielen} +menuText D ToolsSeriousGame "Partie spielen" 0 {Eine Partie gegen eine Engine spielen} menuText D ToolsTrainOpenings "Eröffnungen" 0 {Eröffnungsrepertoire trainieren} menuText D ToolsTrainReviewGame "Partie nachspielen" 0 {Finden von Zügen wie in vorgegebener Partie} menuText D ToolsTrainTactics "Taktik" 0 {Taktische Stellungen lösen} diff --git a/tcl/lang/english.tcl b/tcl/lang/english.tcl index 07bf3e09c..b04f3e03d 100644 --- a/tcl/lang/english.tcl +++ b/tcl/lang/english.tcl @@ -194,7 +194,7 @@ menuText E ToolsOpenRecentBaseAsTree "Open recent base as tree" 0 {Open a rece menuText E ToolsTracker "Piece Tracker" 6 {Open the Piece Tracker window} menuText E ToolsTraining "Training" 0 {Training tools (tactics, openings,...) } menuText E ToolsTacticalGame "Tactical game" 0 {Play a game with tactics} -menuText E ToolsSeriousGame "Serious game" 0 {Play a serious game} +menuText E ToolsSeriousGame "Play against engine" 0 {Play a game against an engine} menuText E ToolsTrainOpenings "Openings" 0 {Train with a repertoire} menuText E ToolsTrainReviewGame "Review game" 0 {Guess moves played in a game} menuText E ToolsTrainTactics "Tactics" 0 {Solve tactics} diff --git a/tcl/menus.tcl b/tcl/menus.tcl index e66623ef3..efcd2a9d0 100644 --- a/tcl/menus.tcl +++ b/tcl/menus.tcl @@ -213,7 +213,6 @@ set m .menu.play menu $m -postcommand "updateMenuStates $m" .menu add cascade -label Play -menu $m $m add command -label ToolsSeriousGame -command ::sergame::config -$m add command -label ToolsTacticalGame -command ::tacgame::config $m add command -label ToolsTrainFics -command ::fics::config $m add separator menu $m.training @@ -250,6 +249,8 @@ $m add command -label ToolsStartEngine1 \ -command "::enginewin::start 1" -accelerator "F2" $m add command -label ToolsStartEngine2 \ -command "::enginewin::start 2" -accelerator "F3" +$m add command -label "Annotate Game(s)" -command "::annotation::doAnnotate" +$m add command -label "Finish Game" -command "::finishgame::finishGameDialog" $m add command -label ToolsAnalysis -command "makeAnalysisWin 1" $m add separator $m add checkbutton -label ToolsFilterGraph \ diff --git a/tcl/options.tcl b/tcl/options.tcl index 102563427..623b0b171 100644 --- a/tcl/options.tcl +++ b/tcl/options.tcl @@ -125,19 +125,26 @@ proc InitDefaultFonts {} { } proc InitDefaultAnnotate {} { - set ::isBatchOpening 0 - set ::isBatchOpeningMoves 12 - set ::isBatch 0 - set ::markTacticalExercises 0 - set ::isAnnotateVar 0 - set ::isShortAnnotation 0 - set ::addScoreToShortAnnotations 0 - set ::addAnnotatorTag 0 - set ::annotateMoves all - set ::annotateBlunders blundersonly - set ::scoreAllMoves 0 - # Blunder Threshold - set ::blunderThreshold 1.0 + set ::annotation::options(typ) "movetime" + set ::annotation::options(movetime) 1000 + set ::annotation::options(time) 1 + set ::annotation::options(depth) 20 + set ::annotation::options(engine) "" + set ::annotation::options(blunderThreshold) 0.5 + set ::annotation::options(annotateMoves) all + set ::annotation::options(annotateBlunders) blundersonly + set ::annotation::options(scoreAllMoves) 1 + set ::annotation::options(useAnalysisBook) 0 + set ::annotation::options(AnalysisBookName) "" + set ::annotation::options(tacticalExercises) 0 + set ::annotation::options(addAnnotatorTag) 1 + set ::annotation::options(OpeningErrors) 0 + set ::annotation::options(OpeningMoves) 8 + set ::annotation::options(annotateShort) 1 + set ::annotation::options(addScoreToShortAnnotations) 1 + set ::annotation::options(batchMode) 0 + set ::annotation::options(batchEnd) 0 + set ::annotation::options(anzVariation) 1 } InitDefaultFonts @@ -234,24 +241,6 @@ set windowsDock 1 set ::tactics::analysisTime 3 -set ::tacgame::threshold 0.9 -set ::tacgame::blunderwarning false -set ::tacgame::blunderwarningvalue 0.0 -set ::tacgame::levelMin 1200 -set ::tacgame::levelMax 2200 -set ::tacgame::levelFixed 1500 -set ::tacgame::randomLevel 0 -set ::tacgame::isLimitedAnalysisTime 1 -set ::tacgame::showblunder 1 -set ::tacgame::showblundervalue 1 -set ::tacgame::showblunderfound 1 -set ::tacgame::showmovevalue 1 -set ::tacgame::showevaluation 1 -set ::tacgame::isLimitedAnalysisTime 1 -set ::tacgame::analysisTime 10 -set ::tacgame::openingType new -set ::tacgame::chosenOpening 0 - # Analysis command: to start chess analysis engine. set analysisCommand "" if {$windowsOS} { @@ -328,22 +317,30 @@ set ::pinfo::dnburl "http://d-nb.info/gnd" set ::novag::referee "OFF" # Defaults for serious game training -set ::sergame::isOpening 0 -set ::sergame::chosenOpening 0 -set ::sergame::chosenEngine 0 -set ::sergame::useBook 1 -set ::sergame::bookToUse "" -set ::sergame::startFromCurrent 0 -set ::sergame::coachIsWatching 0 -set ::sergame::timeMode "timebonus" -set ::sergame::depth 3 -set ::sergame::movetime 0 -set ::sergame::nodes 10000 -set ::sergame::ponder 0 -set ::uci::uciInfo(wtime3) [expr 5 * 60 * 1000 ] -set ::uci::uciInfo(winc3) [expr 10 * 1000 ] -set ::uci::uciInfo(btime3) [expr 5 * 60 * 1000 ] -set ::uci::uciInfo(binc3) [expr 10 * 1000 ] +set ::sergame::options(isOpening) 0 +set ::sergame::options(chosenOpening) 0 +set ::sergame::options(useBook) 1 +set ::sergame::options(bookToUse) "" +set ::sergame::options(startFromCurrent) 0 +set ::sergame::options(coachIsWatching) 0 +set ::sergame::options(timeMode) "timebonus" +set ::sergame::options(depth) 3 +set ::sergame::options(movetime) 1000 +set ::sergame::options(nodes) 10000 +set ::sergame::options(engineName) "" +set ::sergame::options(coachName) "" +set ::sergame::options(storeEval) 0 +set ::sergame::options(coachTypeMove) 0 +set ::sergame::options(coachTypeTactic) 0 +set ::sergame::options(useCoachEngine) 0 +set ::sergame::options(threshold) 0.6 +set ::sergame::options(tacTime) 5 +set ::sergame::options(ponder) 0 +set ::sergame::options(isLimitedAnalysisTime) 1 +set ::sergame::options(wtime) [expr 5 * 60 * 1000 ] +set ::sergame::options(winc) [expr 10 * 1000 ] +set ::sergame::options(btime) [expr 5 * 60 * 1000 ] +set ::sergame::options(binc) [expr 10 * 1000 ] # Defaults for initial directories: set initialDir(base) "." @@ -650,24 +647,17 @@ proc options.write {} { engineCoach1 engineCoach2 scidBooksDir scidBasesDir ::book::lastBook \ ::utils::sound::soundFolder ::utils::sound::announceNew \ ::utils::sound::announceForward ::utils::sound::announceBack \ - ::tacgame::threshold ::tacgame::blunderwarning ::tacgame::blunderwarningvalue \ - ::tacgame::levelMin ::tacgame::levelMax ::tacgame::levelFixed ::tacgame::randomLevel \ - ::tacgame::isLimitedAnalysisTime ::tacgame::showblunder ::tacgame::showblundervalue \ - ::tacgame::showblunderfound ::tacgame::showmovevalue ::tacgame::showevaluation \ - ::tacgame::isLimitedAnalysisTime ::tacgame::analysisTime ::tacgame::openingType ::tacgame::chosenOpening \ - ::sergame::chosenOpening ::sergame::chosenEngine ::sergame::useBook ::sergame::bookToUse \ - ::sergame::startFromCurrent ::sergame::coachIsWatching ::sergame::timeMode \ - ::sergame::depth ::sergame::movetime ::sergame::nodes ::sergame::ponder ::sergame::isOpening \ - ::uci::uciInfo(wtime3) ::uci::uciInfo(winc3) ::uci::uciInfo(btime3) ::uci::uciInfo(binc3) \ boardfile_lite boardfile_dark \ FilterMaxMoves FilterMinMoves FilterStepMoves FilterMaxElo FilterMinElo FilterStepElo \ - FilterMaxYear FilterMinYear FilterStepYear FilterGuessELO lookTheme ThemePackageFile autoResizeBoard \ - isBatchOpening isBatchOpeningMoves isBatch \ - markTacticalExercises scoreAllMoves \ - isAnnotateVar isShortAnnotation addScoreToShortAnnotations annotateBlunders\ - addAnnotatorTag annotateMoves } { + FilterMaxYear FilterMinYear FilterStepYear FilterGuessELO lookTheme ThemePackageFile autoResizeBoard } { puts $optionF "set $i [list [set $i]]" } + foreach i [lsort [array names ::sergame::options]] { + puts $optionF "set ::sergame::options($i) [list $::sergame::options($i)]" + } + foreach i [lsort [array names ::annotation::options]] { + puts $optionF "set ::annotation::options($i) [list $::annotation::options($i)]" + } puts $optionF "" foreach i [lsort [array names winWidth]] { diff --git a/tcl/start.tcl b/tcl/start.tcl index 51cae61a3..10d187d16 100644 --- a/tcl/start.tcl +++ b/tcl/start.tcl @@ -219,6 +219,7 @@ InitTooltip # initialized here, so that default values can be set up and # altered when the user options file is loaded. # +#ToDo remove tacgame from list, but prevent error from options.dat foreach ns { ::icon ::splash @@ -242,7 +243,7 @@ foreach ns { ::tools::graphs ::tools::graphs::filter ::tools::graphs::absfilter ::tools::graphs::rating ::tools::graphs::score ::tb ::optable - ::board ::move + ::board ::move ::annotation ::tacgame ::sergame ::opening ::tactics ::calvar ::uci ::fics ::reviewgame ::novag ::config ::docking ::pinfo @@ -744,6 +745,9 @@ tools/optable.tcl tools/preport.tcl tools/pinfo.tcl tools/analysis.tcl +tools/enginenowin.tcl +tools/annotate.tcl +tools/finishgame.tcl tools/wbdetect.tcl tools/graphs.tcl tools/ptracker.tcl @@ -756,7 +760,6 @@ move.tcl main.tcl tools/uci.tcl end.tcl -tools/tacgame.tcl tools/sergame.tcl tools/calvar.tcl tools/fics.tcl diff --git a/tcl/tools/annotate.tcl b/tcl/tools/annotate.tcl new file mode 100644 index 000000000..a5871c9ec --- /dev/null +++ b/tcl/tools/annotate.tcl @@ -0,0 +1,588 @@ +### +### annotate.tcl: part of Scid. +### This file is part of Scid (Shane's Chess Information Database). +### Copyright (C) 2025 Uwe Klimmek +### uses code from Fulvio Benini https://github.com/benini/chess_accuracy and analysis.tcl +########################################################################################## +### Annotate Dialog: uses a chess engine to analyze and annotate a chess game. + +namespace eval ::annotation { + + set _Data(BookSlot) 1 + + proc doAnnotate {} { + global ::annotation::options ::annotation::_Data + set w .annotationDialog + # Do not do anything if the window exists + if { [winfo exists $w] } { + raise $w + focus $w + return + } + + #Workaround for error in trace var for arrays + set ::annotateBlunderThreshold $options(blunderThreshold) + set ::annotateTime $options(time) + trace variable ::annotateBlunderThreshold w {::utils::validate::Regexp {^[0-9]*\.?[0-9]*$}} + trace variable ::annotateTime w {::utils::validate::Regexp {^[0-9]*\.?[0-9]*$}} + + win::createDialog $w + ::setTitle $w "Scid: $::tr(Annotate)" + catch {grab $w} + wm resizable $w 0 0 + set f [ttk::frame $w.f] + pack $f -expand 1 + + ttk::labelframe $f.annotate -text $::tr(GameReview) + ttk::frame $f.annotate.typ + ttk::radiobutton $f.annotate.typ.label -text $::tr(AnnotateTime) -variable ::annotation::options(typ) -value "movetime" + ttk::radiobutton $f.annotate.typ.ldepth -text "Depth per move" -variable ::annotation::options(typ) -value "depth" + ttk::spinbox $f.annotate.typ.spDelay -width 5 -textvariable ::annotateTime -from 0.1 -to 999 -validate key -justify right + ttk::spinbox $f.annotate.typ.depth -width 5 -textvariable ::annotation::options(depth) -from 2 -to 999 -validate key -justify right + ttk::radiobutton $f.annotate.allmoves -text $::tr(AnnotateAllMoves) -variable ::annotation::options(annotateBlunders) -value allmoves + ttk::radiobutton $f.annotate.blundersonly -text $::tr(AnnotateBlundersOnly) -variable ::annotation::options(annotateBlunders) -value blundersonly + ttk::frame $f.annotate.blunderbox + ttk::label $f.annotate.blunderbox.label -text $::tr(BlundersThreshold:) + ttk::spinbox $f.annotate.blunderbox.spBlunder -width 4 -textvariable ::annotateBlunderThreshold \ + -from 0.1 -to 3.0 -increment 0.1 -justify right + ttk::checkbutton $f.annotate.cbBook -text $::tr(UseBook) -variable ::annotation::options(useAnalysisBook) + ::engineNoWin::createEngineOptionsFrame $f annotateEngine ::annotation::options(engine) 3 ::annotation::eng_messages + + # load book names + lassign [getBookList $options(AnalysisBookName)] idx tmp + # No book found + if { $idx < 0 } { + set options(useAnalysisBook) 0 + $f.annotate.cbBook configure -state disabled + } + if { $options(AnalysisBookName) eq "" } { set options(AnalysisBookName) [lindex $tmp $idx] } + ttk::combobox $f.annotate.comboBooks -width 12 -values $tmp -textvariable ::annotation::options(AnalysisBookName) + catch { $f.annotate.comboBooks current $idx } + pack $f.annotate.blunderbox.label -side left -padx { 20 0 } + pack $f.annotate.blunderbox.spBlunder -side left -anchor w + pack $f.annotate.typ -side top -anchor w + pack $f.annotateEngine -in $f.annotate -side top -anchor w + pack $f.annotate.allmoves $f.annotate.blundersonly $f.annotate.blunderbox -side top -anchor w + pack $f.annotate.cbBook -side top -anchor w + pack $f.annotate.comboBooks -side top -anchor w -padx 20 + grid $f.annotate.typ.label -row 0 -column 0 -sticky w + grid $f.annotate.typ.ldepth -row 1 -column 0 -sticky w + grid $f.annotate.typ.spDelay -row 0 -column 1 -sticky w + grid $f.annotate.typ.depth -row 1 -column 1 -sticky w + bind $w { .configAnnotation.f.buttons.cancel invoke } + bind $w { .configAnnotation.f.buttons.ok invoke } + + ttk::labelframe $f.av -text $::tr(AnnotateWhich) + ttk::radiobutton $f.av.all -text $::tr(AnnotateAll) -variable ::annotation::options(annotateMoves) -value all + ttk::radiobutton $f.av.white -text $::tr(AnnotateWhite) -variable ::annotation::options(annotateMoves) -value white + ttk::radiobutton $f.av.black -text $::tr(AnnotateBlack) -variable ::annotation::options(annotateMoves) -value black + ttk::checkbutton $f.av.vars -text "Store two variations" -variable ::annotation::options(anzVariation) -onvalue 2 -offvalue 1 + pack $f.av.all $f.av.white $f.av.black $f.av.vars -side top -fill x -anchor w + + ttk::labelframe $f.comment -text $::tr(Comments) + # Checkmark to enable all-move-scoring + ttk::checkbutton $f.comment.scoreAll -text $::tr(ScoreAllMoves) -variable ::annotation::options(scoreAllMoves) + ttk::checkbutton $f.comment.cbShortAnnotation -text $::tr(ShortAnnotations) -variable ::annotation::options(annotateShort) + ttk::checkbutton $f.comment.cbAddScore -text $::tr(AddScoreToShortAnnotations) -variable ::annotation::options(addScoreToShortAnnotations) + ttk::checkbutton $f.comment.cbAddAnnotatorTag -text $::tr(addAnnotatorTag) -variable ::annotation::options(addAnnotatorTag) + ttk::checkbutton $f.comment.cbMarkTactics -text $::tr(MarkTacticalExercises) -variable ::annotation::options(tacticalExercises) + pack $f.comment.scoreAll $f.comment.cbShortAnnotation $f.comment.cbAddScore \ + $f.comment.cbAddAnnotatorTag $f.comment.cbMarkTactics -fill x -anchor w + # batch annotation of consecutive games, and optional opening errors finder + ttk::labelframe $f.batch -text "Batch Annotation" + ttk::frame $f.buttons + ttk::frame $f.running + ttk::label $f.running.line1 -textvariable ::annotation::_Data(msg1) -width 60 + ttk::label $f.running.line2 -textvariable ::annotation::_Data(msg2) -width 10 + ttk::label $f.running.line3 -textvariable ::annotation::_Data(msg3) -width 10 + ttk::progressbar $f.running.progress -variable ::annotation::_Data(progress) -orient horizontal -length 600 + ttk::progressbar $f.running.games -variable ::annotation::options(games) -orient horizontal -length 600 + grid $f.running.line1 -row 0 -column 1 -sticky w -pady { 0 10 } + grid $f.running.line2 -row 1 -column 0 -sticky w + grid $f.running.line3 -row 2 -column 0 -sticky w + grid $f.running.games -row 1 -column 1 -sticky w + grid $f.running.progress -row 2 -column 1 -sticky w + grid $f.annotate -row 0 -column 0 -pady { 0 10 } -sticky nswe -padx { 0 10 } + grid $f.comment -row 0 -column 1 -pady { 0 10 } -sticky nswe -padx { 10 0 } + grid $f.av -row 1 -column 0 -pady { 10 0 } -sticky nswe -padx { 0 10 } + grid $f.batch -row 1 -column 1 -pady { 10 0 } -sticky nswe -padx { 10 0 } + grid $f.buttons -row 3 -column 1 -sticky we + + set options(batchEnd) [sc_base numGames $::curr_db] + if {$options(batchEnd) <1} { set options(batchEnd) 1 } + ttk::checkbutton $f.batch.cbBatch -text $::tr(AnnotateSeveralGames) -variable ::annotation::options(batchMode) + ttk::spinbox $f.batch.spBatchEnd -width 8 -textvariable ::annotation::options(batchEnd) \ + -from 1 -to $options(batchEnd) -increment 1 -validate all -validatecommand { regexp {^[0-9]+$} %P } + ttk::checkbutton $f.batch.cbBatchOpening -text $::tr(FindOpeningErrors) -variable ::annotation::options(OpeningErrors) + ttk::spinbox $f.batch.spBatchOpening -width 2 -textvariable ::annotation::options(OpeningMoves) \ + -from 10 -to 20 -increment 1 -validate all -validatecommand { regexp {^[0-9]+$} %P } + ttk::label $f.batch.lBatchOpening -text $::tr(moves) + pack $f.batch.cbBatch -side top -anchor w -pady { 0 0 } + pack $f.batch.spBatchEnd -side top -padx 20 -anchor w + pack $f.batch.cbBatchOpening -side top -anchor w + pack $f.batch.spBatchOpening -side left -anchor w -padx { 20 4 } + pack $f.batch.lBatchOpening -side left + + ttk::button $f.buttons.cancel -text $::tr(Cancel) -command { + if { $::autoplayMode } { + set ::autoplayMode 0 + } else { + ::engineNoWin::closeEngine annotateEngine + destroy .annotationDialog + } + } + ttk::button $f.buttons.ok -text "Annotate" -command { + if {$::annotateTime < 0.1} { set ::annotateTime 0.1 } + set ::annotation::options(movetime) [expr {int($::annotateTime * 1000.0)}] + set ::annotation::options(blunderThreshold) $::annotateBlunderThreshold + set ::annotation::options(time) $::annotateTime + if { [::engineNoWin::initEngine annotateEngine $::annotation::options(engine) \ + [list ::annotation::eng_messages annotateEngine .annotationDialog.f.engpara]] } { + ::annotation::runAnnotation + } + } + pack $f.buttons.cancel $f.buttons.ok -side right -padx 5 -pady 5 + focus $f.annotate.typ.spDelay + bind $w { focus . } + } + + # reset values for every game + proc initGameAnnotation { } { + global ::annotation::options ::annotation::_Data + #reset engine + ::engine::send annotateEngine NewGame [list analysis post_pv post_wdl [sc_game variant]] + # calc amount of moves to analyze for progressbar + set firstmove [llength [sc_game moves]] + sc_game push copyfast + catch { sc_move forward 300 } + set anz [expr {[llength [sc_game moves]] - $firstmove + 1}] + sc_game pop + .annotationDialog.f.running.progress configure -maximum $anz + #reset values + set _Data(prevscore1) 0 + set _Data(prevscore2) 0 + set _Data(score) 0 + set _Data(scoremate) 0 + set _Data(prevscoremate) 0 + set _Data(prevmoves1) "" + set _Data(prevmoves2) "" + set _Data(moves) "" + set _Data(progress) 1 + set _Data(msg1) "$::tr(game) [sc_game number]: [sc_game info white] - [sc_game info black]" + set _Data(msg2) "$::tr(game) $options(games)" + set _Data(msg3) "$::tr(move) 1" + if { $options(addAnnotatorTag) } { + appendAnnotator "$options(engine) $options(typ) $options($options(typ))" + } + } + + proc annotateGame { } { + global ::annotation::options ::annotation::_Data + initGameAnnotation + makeBookAnnotation + # Annotate all remaining moves of the game + while { 1 } { + set _Data(PV1) [list "" "" ""] + set _Data(PV2) [list "" "" ""] + ::engine::send annotateEngine Go [list [sc_game UCI_currentPos] [list $options(typ) $options($options(typ))]] + vwait ::annotation::_Data(move_done) + addAnnotation + incr _Data(progress) + set _Data(msg3) "$::tr(move) $_Data(progress)" + if {[sc_pos isAt end] || ! $::autoplayMode } break + sc_move forward + ::notify::PosChanged -pgn + } + } + + proc runAnnotation { } { + global ::annotation::options + # make sure, we have 2 best lines + ::engine::send annotateEngine SetOptions [list {MultiPV 2}] + set f .annotationDialog.f + grid forget $f.annotate $f.comment $f.av $f.batch $f.optsannotateEngine + pack forget $f.buttons.ok + if {!$options(batchMode)} { grid forget $f.running.games $f.running.line2 } + # show progressbar and game infos + set options(games) 1 + set gameNo [sc_game number] + $f.running.games configure -maximum [expr {$options(batchEnd) - $gameNo + 1}] + grid $f.running -row 2 -column 0 -columnspan 2 -sticky we + + # tactical positions is selected, must be in multipv mode + if {$options(tacticalExercises)} { ::engine::send annotateEngine SetOptions [list {MultiPV 4}] } + + set ::autoplayMode 1 + set gameNo [sc_game number] + if { $gameNo == 0 } { return } + annotateGame + while {$options(batchMode)} { + sc_game save $gameNo + incr gameNo + incr options(games) + if { ! $::autoplayMode || $gameNo > $options(batchEnd) } { break } + sc_game load $gameNo + annotateGame + } + set ::autoplayMode 0 + ::engineNoWin::closeEngine annotateEngine + ::notify::PosChanged -pgn + destroy .annotationDialog + } + + ################################################################################ + # Part of annotation process : will check the moves if they are in te book, and add a comment + # when going out of it + ################################################################################ + proc makeBookAnnotation { } { + global ::annotation::options ::annotation::_Data + if {$options(useAnalysisBook)} { + set prevbookmoves "" + set bn [ file join $::scidBooksDir $options(AnalysisBookName) ] + sc_book load $bn $_Data(BookSlot) + + lassign [sc_book moves $_Data(BookSlot)] bookmoves + while {[string length $bookmoves] != 0 && ![sc_pos isAt vend]} { + # we are in book, so move immediately forward + ::move::Forward + set prevbookmoves $bookmoves + lassign [sc_book moves $_Data(BookSlot)] bookmoves + } + sc_book close $_Data(BookSlot) + + if { [ string match -nocase "*[sc_game info previousMoveNT]*" $prevbookmoves ] != 1 } { + if {$prevbookmoves != ""} { + sc_pos setComment "[sc_pos getComment] $::tr(LastBookMove) [::trans $prevbookmoves]" + } else { + sc_pos setComment "[sc_pos getComment] $::tr(LastBookMove)" + } + # last move was out of book: it needs to be analyzed, so take back + sc_move back + } else { + sc_pos setComment "[sc_pos getComment] $::tr(MoveOutOfBook)" + } + if { $options(OpeningErrors) && ([sc_pos moveNumber] < $options(OpeningMoves) ) } { + appendAnnotator "opBlunder [sc_pos moveNumber] ([sc_pos side])" + } + } + } + + ################################################################################ + # will append arg to current game Annotator tag + ################################################################################ + proc appendAnnotator { s } { + # Get the current collection of extra tags + set extra [sc_game tags get "Extra"] + set annot 0 + set other "" + set nExtra {} + # Walk through the extra tags, just copying the crap we do not need + # If we meet the existing annotator tag, add our name to the list + foreach line $extra { + if { $annot == 1 } { + lappend nExtra "Annotator \"$line, $s\"\n" + set annot 2 + } elseif { $other != "" } { + lappend nExtra "$other \"$line\"\n" + set other "" + } elseif {[string match "Annotator" $line]} { + set annot 1 + } else { + set other $line + } + } + # First annotator: Create a tag + if { $annot == 0 } { + lappend nExtra "Annotator \"$s\"\n" + } + # Put the extra tags back to the game + sc_game tags set -extra $nExtra + } + + proc addAnnotation { } { + global ::annotation::options ::annotation::_Data + # Let's try to assess the situation: + # We are here, now that the engine has analyzed the position reached by + # our last move. Currently it is the opponent to move: + set tomove [sc_pos side] + set gamemove [sc_game info previousMoveUCI] + + # And this is his best line: + lassign $_Data(PV1) score score_type _Data(moves) + if { $gamemove eq "" || $score eq "" } { set _Data(prevscore1) $score; return } + set moves $_Data(moves) + set bestMoveIsMate 0 + if { $score_type eq "mate" } { + # We do not want to insert a best-line variation into the game + # if we did play along that line. Even not when annotating all moves. + # It simply makes no sense to do so (unless we are debugging the engine!) + # Sooner or later the game will deviate anyway; a variation at that point will + # do nicely and is probably more accurate as well. + set bestMoveIsMate 1 + set _Data(scoremate) $score + set score [expr { $score < 0 ? -127 : 127 }] + set _Data(score) $score + } else { + set _Data(score) $score + set _Data(scoremate) 0 + } + + # We will add a closing line at the end of variation or game + set addClosingLine 0 + if { [sc_pos isAt vend] } { + set addClosingLine 1 + } + + # This is the score we could have had if we had played our best move + set prevscore $_Data(prevscore1) + + # Note that the engine's judgement is in relative terms, a negative score + # being favorable to opponent, a positive score favorable to player + # Looking primarily for blunders, we are interested in the score decay, + # which, for white, is (previous-current) + set deltamove [expr {$prevscore + $score}] + # and whether the game was already lost for us + set gameIsLost [expr {$prevscore < (0.0 - $::informant("+--"))}] + + # Invert this logic for black + if { $tomove == "white" } { + set gameIsLost [expr {$prevscore > $::informant("+--")}] + } + + # Set an "isBlunder" filter. + # Let's mark moves with a decay greater than the threshold. + set isBlunder 0 + if { $deltamove > $options(blunderThreshold) } { + set isBlunder 2 + } elseif { $deltamove > 0 } { + set isBlunder 1 + } + set absdeltamove [expr { abs($deltamove) } ] + + # to parse scores if the engine's name contains - or + chars (see sc_game_scores) + set engine_name [string map {"-" " " "+" " "} $options(engine)] + + # Prepare score strings for the opponent + if { $_Data(scoremate) != 0 } { + set text [format "M%d" [expr abs($_Data(scoremate))]] + } else { + set wscore [format "%+.2f" $score] + if { $tomove eq "black" } {set wscore [expr 0.0 - $wscore] } + set text "\[%eval $wscore\]" + } + + # See if we have the threshold filter activated. + # If so, take only bad moves and missed mates until the position is lost anyway + # Or that we must annotate all moves + if { ( $options(annotateBlunders) == "blundersonly" + && ($isBlunder > 1 || ($isBlunder > 0 && [expr abs($score)] >= 327.0)) + && ! $gameIsLost) + || ($options(annotateBlunders) == "allmoves") } { + if { $isBlunder > 0 } { + # Add move score nag, and possibly an exercise + if { $absdeltamove > $::informant("??") } { + markExercise $prevscore $score "??" + } elseif { $absdeltamove > $::informant("?") } { + markExercise $prevscore $score "?" + } elseif { $absdeltamove > $::informant("?!") } { + sc_pos addNag "?!" + } + } elseif { $absdeltamove > $::informant("!?") } { + sc_pos addNag "!?" + } + + # Add score comment and engine name if needed + if { ! $options(annotateShort) } { + sc_pos setComment "[sc_pos getComment] $engine_name: $text" + } elseif { $options(addScoreToShortAnnotations) || $options(scoreAllMoves) } { + sc_pos setComment "[sc_pos getComment] $text" + } + + # Add position score nag + sc_pos addNag [scoreToNag $score] + # Add the variation + sc_move back + if { $options(annotateBlunders) == "blundersonly" } { + # Add a diagram tag, but avoid doubles + if { [string first "D" "[sc_pos getNags]"] == -1 } { + sc_pos addNag "D" + } + } + + if { $_Data(prevmoves1) != "" && ( $options(annotateMoves) == "all" || + $options(annotateMoves) == "white" && $tomove == "black" || + $options(annotateMoves) == "black" && $tomove == "white" )} { + set n 1 + while { $n <= $options(anzVariation) && $_Data(prevmoves$n) ne "" } { + sc_var create + # Add the starting move + sc_move addSan [lrange $_Data(prevmoves$n) 0 0] + # Add its score + if { ! $options(annotateShort) || $options(addScoreToShortAnnotations) } { + # And for the (missed?) chance + if { $_Data(prevscoremate) != 0 } { + set prevtext [format "M%d" [expr abs($_Data(prevscoremate))]] + } else { + set wprevscore [format "%+.2f" $_Data(prevscore$n)] + if { $tomove eq "white" } {set wprevscore [expr 0.0 - $wprevscore] } + set prevtext "\[%eval $wprevscore\]" + } + sc_pos setComment "$prevtext" + } + # Add remaining moves + sc_move addSan [lrange $_Data(prevmoves$n) 1 end] + # Add position NAG, unless the line ends in mate + if { $n == 1 && $_Data(prevscoremate) == 0 } { + sc_pos addNag [scoreToNag $prevscore] + } + sc_var exit + incr n + } + } + sc_move forward + } else { + if { $isBlunder == 0 && $absdeltamove > $::informant("!?") } { + sc_pos addNag "!?" + } + if { $options(scoreAllMoves) } { + # Add a score mark anyway + sc_pos setComment "[sc_pos getComment] $text" + } + } + + if { $addClosingLine } { + sc_move back + sc_var create + sc_move addSan $gamemove + if { ($_Data(scoremate) == 0) && ( ! $options(annotateShort) || $options(addScoreToShortAnnotations)) } { + sc_pos setComment "$text" + } + sc_move addSan $moves + if { $_Data(scoremate) == 0 } { + sc_pos addNag [scoreToNag $score] + } + sc_var exit + # Now up to the end of the game + ::move::Forward + } + set _Data(prevscore1) $_Data(score) + set _Data(prevmoves1) $_Data(moves) + lassign $_Data(PV2) _Data(prevscore2) score_type _Data(prevmoves2) + set _Data(prevscoremate) $_Data(scoremate) + updateBoard -pgn + } + + ################################################################################ + # Will add **** to any position considered as a tactical shot + # check at which depth the tactical shot is found + ################################################################################ + proc markExercise { prevscore score nag} { + global ::annotation::options ::annotation::_Data + sc_pos addNag $nag + if { ! $options(tacticalExercises)} { return 0 } + + set deltamove [expr {$score + $prevscore}] + # filter tactics so only those with high gains are kept + if { [expr abs($deltamove)] < $::informant("+/-") } { return 0 } + # dismiss games where the result is already clear (high score,and we continue in the same way) + if { [expr $prevscore * $score] >= 0} { + if { [expr abs($prevscore) ] > $::informant("+--") } { return 0 } + if { [expr abs($prevscore)] > $::informant("+-") && [expr abs($score) ] < [expr 2 * abs($prevscore)]} { return 0 } + } + + # The best move is much better than others. + set sc2 [lindex $_Data(PV2) 0] + if { [expr abs( $score - $sc2 )] < 1.5 } { return 0 } + + # The best move does not lose position. + if {([sc_pos side] == "black") && ($score < [expr 0.0 - $::informant("+/-")]) } { return 0 } + if {([sc_pos side] == "white") && ($score > $::informant("+/-")) } { return 0} + + # Move is not obvious: check that it is not the first move guessed at low depths + set pv [ lindex [ lindex $_Data(PV1) 2 ] 0 ] + # bm0 must SAN, pv is UCI: convert + set bm0 [string range [lindex $pv 0] 0 4] + set bm0 [sc_pos coordToSAN $_Data(position) $bm0] + set bm0 [string range $bm0 [expr [string first "." $bm0] + 1] end] + + foreach depth {1 2 3} { + set res [ sc_pos analyze -time 1000 -hashkb 32 -pawnkb 1 -searchdepth $depth ] + set bm$depth [lindex $res 1] + } + if { $bm0 == $bm1 && $bm0 == $bm2 && $bm0 == $bm3 } { + return 0 + } + + # find what time is needed to get the solution (use internal analyze function) + set timer {1 2 5 10 50 100 200 1000} + set movelist {} + for {set t 0} {$t < [llength $timer]} { incr t} { + set res [sc_pos analyze -time [lindex $timer $t] -hashkb 1 -pawnkb 1 -mindepth 0] + set move_analyze [lindex $res 1] + lappend movelist $move_analyze + } + + # find at what timing the right move was reliably found + # only the move is checked, not if the score is close to the expected one + for {set t [expr [llength $timer] -1]} {$t >= 0} { incr t -1} { + if { [lindex $movelist $t] != $bm0 } { + break + } + } + set difficulty [expr $t +2] + + # If the base opened is read only, like a PGN file, avoids an exception + catch { sc_base gameflag [sc_base current] [sc_game number] set T } + sc_pos setComment "****D${difficulty} [format %.1f $prevscore]->[format %.1f $score] [sc_pos getComment]" + updateBoard + return 1 + } + + proc ::annotation::eng_messages {id w msg} { + global ::annotation::_Data + lassign $msg msgType msgData + switch $msgType { + "InfoConfig" { + if { $::autoplayMode } { return } + set msgData [lindex $msgData 2] + ::engineNoWin::initEngineOptions $id $w $msgData + } + "InfoPV" { + lassign $msgData multipv depth seldepth nodes nps hashfull tbhits time score score_type score_wdl pv + if { $score_type ne "mate" } { set score [expr {$score / 100.0}] } + set _Data(PV$multipv) [list $score $score_type $pv] + } + "InfoBestMove" { + lassign $msgData _Data(bestmove) + set _Data(move_done) 1 + } + "InfoGo" { + lassign $msgData _Data(position) + } + "InfoDisconnected" { + ::engineNoWin::disconnected $id $msgData + set ::autoplayMode 0 + } + } + } + # Informant index strings + array set ana_informantList { 0 "+=" 1 "+/-" 2 "+-" 3 "+--" } + # Nags. Note the slight inconsistency for the "crushing" symbol (see game.cpp) + array set ana_nagList { 0 "=" 1 "+=" 2 "+/-" 3 "+-" 4 "+--" 5 "=" 6 "=+" 7 "-/+" 8 "-+" 9 "--+" } + ################################################################################ + # + ################################################################################ + proc scoreToNag {score} { + global ana_informantList ana_nagList + # Find the score in the informant map + set tmp [expr { abs( $score ) }] + for { set i 0 } { $i < 4 } { incr i } { + if { $tmp < $::informant("$ana_informantList($i)") } { break } + } + # Jump into negative counterpart + if { $score < 0.0 } { + set i [expr {$i + 5}] + } + return $ana_nagList($i) + } +} diff --git a/tcl/tools/enginenowin.tcl b/tcl/tools/enginenowin.tcl new file mode 100644 index 000000000..3befcf26f --- /dev/null +++ b/tcl/tools/enginenowin.tcl @@ -0,0 +1,104 @@ +### +### enginenowin.tcl: part of Scid. +### This file is part of Scid (Shane's Chess Information Database). +### Copyright (C) 2025 Uwe Klimmek +### uses code from Fulvio Benini https://github.com/benini/chess_accuracy and analysis.tcl +########################################################################################## +### procs for using engines without a engine window + +# engineNoWin will be used by annotate and finish game +namespace eval ::engineNoWin {} +# Open the engine and configure it +proc ::engineNoWin::initEngine { id engine callback } { + if { [info exists ::enginewin::engConfig_$id] } { return 1 } + set config [::enginecfg::get $engine] + lassign $config name cmd args wdir elo time url uci options + set ::enginewin::engConfig_$id $config + ::engine::setLogCmd $id {} + ::engine::connect $id $callback $cmd $args + if { $options ne "" } { ::engine::send $id SetOptions $options } + return 1 +} + +proc ::engineNoWin::closeEngine { id } { + ::engine::close $id + unset -nocomplain ::enginewin::engConfig_$id +} + +proc ::engineNoWin::changeEngine {id w enginevar callback} { + ::engine::close $id + $w.text configure -state normal + $w.text delete 1.0 end + foreach wchild [winfo children $w.text] { destroy $wchild } + catch { unset ::enginewin::engConfig_$id } + set engine [set $enginevar] + ::engineNoWin::initEngine $id $engine [list $callback $id $w] +} + +proc ::engineNoWin::showHideOptionsFrame {id w enginevar callback col} { + if { [winfo ismapped $w] } { grid forget $w ; return } + grid $w -row 0 -column $col -rowspan 5 -sticky ne -padx 10 + set engine [set $enginevar] + ::engineNoWin::initEngine $id $engine [list $callback $id $w] +} + +#create frame for select and edit engine options +#engType: all, uci or winboard +proc ::engineNoWin::createEngineOptionsFrame {f id var col callback {engTyp "uci"}} { + ttk::frame $f.$id + set allEngList [::enginecfg::names ] + if { $engTyp ne "all"} { + set engList {} + foreach name $allEngList { + set typ [lindex [::enginecfg::get $name] 7] + if { $engTyp == "uci" && $typ || $engTyp == "winboard" && ! $typ } { + lappend engList $name + } + } + } else { + set engList $allEngList + } + if { [set $var] eq "" } { set $var [lindex $engList 0] } + ttk::combobox $f.$id.eng -width 20 -state readonly -values $engList -textvariable $var + bind $f.$id.eng <> "::engineNoWin::changeEngine $id $f.opts$id $var $callback" + ttk::button $f.$id.opts -image ::icon::filter_adv -style Toolbutton \ + -command "::engineNoWin::showHideOptionsFrame $id $f.opts$id $var $callback $col" + pack $f.$id.eng $f.$id.opts -side left -padx { 0 5 } + ttk::labelframe $f.opts$id -text "Engine Parameter" + ttk::label $f.opts$id.l -textvariable $var + ttk::button $f.opts$id.x -image tb_close -style Toolbutton -command "grid forget $f.opts$id" + ttk_text $f.opts$id.text -wrap none -padx 4 + autoscrollBars both $f.opts$id $f.opts$id.text 1 + $f.opts$id.text configure -state normal -wrap word -width 60 -height 18 + ttk::button $f.opts$id.save -text "Save Setup" -command "::engineNoWin::saveEngineSetup $id" + grid $f.opts$id.l -row 0 -column 0 -sticky w + grid $f.opts$id.x -row 0 -column 1 -sticky e + grid $f.opts$id.save -row 2 -column 0 -columnspan 2 -sticky e -pady { 5 0 } + bind $f.$id "catch { unset ::enginewin::engConfig_$id }; ::engine::close $id" +} + +proc ::engineNoWin::initEngineOptions {id w options} { + upvar ::enginewin::engConfig_$id engConfig_ + if { ! [winfo exists $w.text.reset] } { + lset ::enginewin::engConfig_$id 8 $options + ::enginecfg::createOptionWidgets $id $w $options + ::engine::replyInfoConfig $id + } else { + lset ::enginewin::engConfig_$id 8 $options + ::enginecfg::updateOptionWidgets $id $w $options {} + $w.text configure -state disabled + } +} + +proc ::engineNoWin::saveEngineSetup { id } { + upvar ::enginewin::engConfig_$id engConfig_ + ::enginecfg::save [set ::enginewin::engConfig_$id] +} + +proc ::engineNoWin::disconnected { id data } { + upvar ::enginewin::engConfig_$id engConfig_ + lassign $data errorMsg + lassign [set ::enginewin::engConfig_$id] engine + if {$errorMsg eq ""} { set errorMsg "The connection with the engine $id $engine terminated unexpectedly." } + tk_messageBox -icon warning -type ok -parent . -message $errorMsg +} diff --git a/tcl/tools/finishgame.tcl b/tcl/tools/finishgame.tcl new file mode 100644 index 000000000..b3d541d7e --- /dev/null +++ b/tcl/tools/finishgame.tcl @@ -0,0 +1,242 @@ +### +### finishgame.tcl: part of Scid. +### This file is part of Scid (Shane's Chess Information Database). +### Copyright (C) 2025 Uwe Klimmek +### uses code from Fulvio Benini https://github.com/benini/chess_accuracy and analysis.tcl +########################################################################################## +### finishGame Dialog: uses a chess engine to play a game + +namespace eval ::finishgame { + + set ::finishGame(annotate) 1 + set ::finishGame(annotateShort) 1 + set ::finishGame(enginewhite) "" + set ::finishGame(engineblack) "" + set ::finishGame(cmdwhite) movetime + set ::finishGame(cmdblack) movetime + set ::finishGame(cmdValuewhite) 2 + set ::finishGame(cmdValueblack) 2 + set ::finishGame(msg) "" + + ################################################################################ + # will ask engine(s) to play the game till the end + ################################################################################ + proc finishGameDialog { } { + if { $::autoplayMode } { return } + + # UCI engines + # On exit save values in options.dat + ::options.store ::finishGame(annotate) + ::options.store ::finishGamen(annotateShort) + ::options.store ::finishGame(enginewhite) + ::options.store ::finishGame(engineblack) + + set w .configFinishGame + win::createDialog $w + wm resizable $w 0 0 + ::setTitle $w "Scid: $::tr(FinishGame)" + + ttk::labelframe $w.wh_f -text "$::tr(White)" -padding 5 + grid $w.wh_f -column 0 -row 0 -columnspan 2 -sticky we -pady 8 + foreach psize $::boardSizes { + if {$psize >= 40} { break } + } + ttk::label $w.wh_f.p -image wk$psize + grid $w.wh_f.p -column 0 -row 0 -rowspan 3 + ttk::spinbox $w.wh_f.cv -width 3 -textvariable ::finishGame(cmdValuewhite) -from 1 -to 999 -justify right + ttk::radiobutton $w.wh_f.c1 -text $::tr(seconds) -variable ::finishGame(cmdwhite) -value "movetime" + ttk::radiobutton $w.wh_f.c2 -text $::tr(FixedDepth) -variable ::finishGame(cmdwhite) -value "depth" + ::engineNoWin::createEngineOptionsFrame $w fgEnginewhite ::finishGame(enginewhite) 4 ::finishgame::eng_messages + grid $w.fgEnginewhite -in $w.wh_f -column 1 -row 0 -columnspan 3 -sticky w + grid $w.wh_f.cv -column 1 -row 2 -sticky w + grid $w.wh_f.c1 -column 2 -row 2 -sticky w -padx 6 + grid $w.wh_f.c2 -column 3 -row 2 -sticky w + + ttk::labelframe $w.bk_f -text "$::tr(Black)" -padding 5 + grid $w.bk_f -column 0 -row 1 -columnspan 2 -sticky we -pady 8 + ttk::label $w.bk_f.p -image bk$psize + grid $w.bk_f.p -column 0 -row 0 -rowspan 3 + ttk::spinbox $w.bk_f.cv -width 3 -textvariable ::finishGame(cmdValueblack) -from 1 -to 999 -justify right + ttk::radiobutton $w.bk_f.c1 -text $::tr(seconds) -variable ::finishGame(cmdblack) -value "movetime" + ttk::radiobutton $w.bk_f.c2 -text $::tr(FixedDepth) -variable ::finishGame(cmdblack) -value "depth" + ::engineNoWin::createEngineOptionsFrame $w fgEngineblack ::finishGame(engineblack) 5 ::finishgame::eng_messages + grid $w.fgEngineblack -in $w.bk_f -column 1 -row 0 -columnspan 3 -sticky w + grid $w.bk_f.cv -column 1 -row 2 -sticky w + grid $w.bk_f.c1 -column 2 -row 2 -sticky w -padx 6 + grid $w.bk_f.c2 -column 3 -row 2 -sticky w + + ttk::checkbutton $w.finishGame -text $::tr(Annotate) -variable ::finishGame(annotate) + grid $w.finishGame -column 0 -row 2 -sticky w -padx 5 -pady 8 + ttk::checkbutton $w.finishGameShort -text $::tr(ShortAnnotations) -variable ::finishGame(annotateShort) + grid $w.finishGameShort -column 1 -row 2 -sticky w -padx 5 -pady 8 + ttk::label $w.line1 -textvariable ::finishGame(msg) -width 60 + + ttk::frame $w.fbuttons + ttk::button $w.fbuttons.cancel -text $::tr(Cancel) -command { + if { $::autoplayMode } { + set ::autoplayMode 0 + } else { + ::engineNoWin::closeEngine fgEnginewhite + ::engineNoWin::closeEngine fgEngineblack + destroy .configFinishGame + } + } + + ttk::button $w.fbuttons.ok -text "OK" -command { + if { [::engineNoWin::initEngine fgEnginewhite $::finishGame(enginewhite) \ + [list ::finishgame::eng_messages fgEnginewhite .configFinishGame.optsfgEnginewhite]] && + [::engineNoWin::initEngine fgEngineblack $::finishGame(engineblack) \ + [list ::finishgame::eng_messages fgEngineblack .configFinishGame.optsfgEngineblack]] } { + ::finishgame::runFinishGame + } + } + packbuttons right $w.fbuttons.cancel $w.fbuttons.ok + grid $w.fbuttons -row 3 -column 1 -columnspan 2 -sticky we + focus $w.fbuttons.ok + bind $w { .configFinishGame.cancel invoke } + bind $w { .configFinishGame.ok invoke } + bind $w { focus . } + grab $w + } + + # Open the engine and configure it + proc initfgEngine { color engine } { + set id fgEngine$color + if { [info exists ::enginewin::engConfig_$id] } { return "ok" } + set config [::enginecfg::get $engine] + lassign $config name cmd args wdir elo time url uci options + if { ! $uci } { return "Only UCI-Engines are supported!" } + set ::enginewin::engConfig_$id [list $name $cmd $args $wdir $elo $time $url $uci {}] + ::engine::setLogCmd $id {} + ::engine::connect $id [list ::finishgame::eng_messages $id] $cmd {} + lappend options "MultiPV 2" + ::engine::send $id SetOptions $options + return "ok" + } + + proc ::finishgame::annotate { tomove } { + lassign $::finishGame(PV1) score score_type pv + if { $tomove eq "black" } {set score [expr 0.0 - $score] } + if {! $::finishGame(annotateShort) } { + sc_var create + # Add the starting move + sc_move addSan $pv + sc_var exit + } + if {$::finishGame(annotate) } { + set tmp [sc_pos getComment] + if { $score_type eq "mate" } { + set score "M$score" + } else { + set score "\[%eval $score\]" + } + sc_pos setComment "$tmp $score" + } + } + + proc ::finishgame::runFinishGame { } { + set w .configFinishGame + grid forget $w.wh_f $w.bk_f $w.finishGame $w.finishGameShort $w.optsfgEnginewhite $w.optsfgEngineblack + pack forget $w.fbuttons.ok + grid $w.line1 -row 2 -column 0 -columnspan 2 -sticky we + + set ::autoplayMode 1 + set repetition {} + set moves 0 + set material 0 + set pawns "" + set tomove [sc_pos side] + set value(white) $::finishGame(cmdValuewhite) + set value(black) $::finishGame(cmdValueblack) + if { $::finishGame(cmdwhite) eq "movetime" } { set value(white) [expr {$::finishGame(cmdValuewhite) * 1000 }] } + if { $::finishGame(cmdblack) eq "movetime" } { set value(black) [expr {$::finishGame(cmdValueblack) * 1000 }] } + + sc_var create + while {$::autoplayMode} { + ::engine::send fgEngine$tomove Go [list [sc_game UCI_currentPos] [list $::finishGame(cmd$tomove) $value($tomove)]] + vwait ::finishGame(moveDone) + if { [catch { sc_move addSan $::finishGame(bestmove) }] } { + set ::autoplayMode 0 + } else { + ::finishgame::annotate $tomove + lassign [checkRepetition $repetition] isRepetition repetition + lassign [checkfiftyMoveRule $moves $material $pawns] isFifty moves material pawns + if { $isRepetition || $isFifty } { + if { $isFifty } { + set text "50-moves rule" + } else { + set text "3-fold repetition" + } + set tmp [sc_pos getComment] + sc_pos setComment "$tmp $text" + set ::autoplayMode 0 + } + } + sc_move forward + ::notify::PosChanged -pgn + set tomove [expr {$tomove eq "white" ? "black" : "white"}] + } + sc_var exit + + set ::autoplayMode 0 + set tmp [sc_pos getComment] + sc_pos setComment "$tmp\n\n$::tr(FinishGame) $::tr(White): $::finishGame(enginewhite) $::finishGame(cmdwhite) $::finishGame(cmdValuewhite)\n\n$::tr(Black): $::finishGame(engineblack) $::finishGame(cmdblack) $::finishGame(cmdValueblack)" + ::engineNoWin::closeEngine fgEnginewhite + ::engineNoWin::closeEngine fgEngineblack + ::notify::PosChanged -pgn + destroy .configFinishGame + } + + proc ::finishgame::eng_messages {id w msg} { + lassign $msg msgType msgData + switch $msgType { + "InfoConfig" { + if { $::autoplayMode } { return } + set msgData [lindex $msgData 2] + ::engineNoWin::initEngineOptions $id $w $msgData + } + "InfoPV" { + lassign $msgData multipv depth seldepth nodes nps hashfull tbhits time score score_type score_wdl pv + if { $score_type ne "mate" } { set score [expr {$score / 100.0}] } + set ::finishGame(PV$multipv) [list $score $score_type $pv] + set ::finishGame(msg) $::finishGame(PV$multipv) + } + "InfoBestMove" { + lassign $msgData ::finishGame(bestmove) + set ::finishGame(moveDone) 1 + } + "InfoGo" { + lassign $msgData ::annotate(position) + } + "InfoDisconnected" { + ::engineNoWin::disconnected $id $msgData + set ::autoplayMode 0 + } + } + } +} + +################################################################################ +# add current position for 3fold repetition detection and returns 1 if +# the position is a repetition +################################################################################ +proc checkRepetition { journal } { + set elt [lrange [split [sc_pos fen]] 0 2] + set isRep 0 + # append the position only if different from the last element + if { $elt != [ lindex $journal end ] } { lappend journal $elt } + # 3fold repetion detected + if { [llength [lsearch -all $journal $elt] ] >=3 } { set isRep 1 } + return [list $isRep $journal] +} + +proc checkfiftyMoveRule { moves prevmaterial prevpawns } { + set isFiftyRule 0 + set elt [string range [sc_pos board] 0 63] + incr moves + set material [string length [string map {"." ""} $elt]] + set pawns [string map {"n" "." "b" "." "r" "." "q" "." "k" "." "N" "." "B" "." "R" "." "Q" "." "K" "." } $elt] + if { $pawns ne $prevpawns || $material ne $prevmaterial } { set moves 0 } + if { $moves >= 100 || $material == 2 } { set isFiftyRule 1 } + return [list $isFiftyRule $moves $material $pawns] +} diff --git a/tcl/tools/sergame.tcl b/tcl/tools/sergame.tcl index 1e9851d72..e8dead26b 100644 --- a/tcl/tools/sergame.tcl +++ b/tcl/tools/sergame.tcl @@ -3,32 +3,26 @@ ### Copyright (C) 2007 Pascal Georges ### ################################################################################ -# The number used for the engine playing a serious game is 3 +# Use new engine interface for serious game and combine a coach engine ################################################################################ namespace eval sergame { - # DEBUG - set ::uci::uciInfo(log_stdout3) 0 - # if true, follow a specific opening - set openingMovesList {} - set openingMovesHash {} - set openingMoves "" - set outOfOpening 0 - array set engineListBox {} - set engineName "" - set bookSlot 2 - set storeEval 0 - + set _Data(openingMovesList) {} + set _Data(openingMovesHash) {} + set _Data(outOfOpening) 0 + set _Data(bookSlot) 2 + set _Data(tacticBlunder) "" + set _Data(actTacTime) 0 # list of fen positions played to detect 3 fold repetition - set lFen {} + set _Data(lFen) {} ################################################################################ # ################################################################################ proc config {} { - global ::sergame::configWin ::sergame::chosenOpening + global ::sergame::_Data ::sergame::options ::sergame::configWin set w ".configSerGameWin" if {[winfo exists $w]} { @@ -42,86 +36,70 @@ namespace eval sergame { bind $w { helpWindow SeriousGame } setWinLocation $w - ttk::frame $w.fconfig -padding 10 + ttk::frame $w.fconfig + ttk::frame $w.fconfig2 ttk::frame $w.fbuttons - ttk::labelframe $w.fengines -text $::tr(Engine) + ttk::labelframe $w.fengines -text "$::tr(Player) - $::tr(Engine)" + ttk::labelframe $w.coach -text "Coaching" ttk::labelframe $w.ftime -text $::tr(TimeMode) ttk::labelframe $w.fopening -text $::tr(Opening) - grid $w.fengines -row 0 -column 0 -pady { 0 10 } -sticky we -padx { 0 10 } - grid $w.fopening -row 0 -column 1 -pady { 0 10 } -sticky nswe -padx { 10 0 } - grid $w.ftime -row 1 -column 0 -pady { 10 0 } -sticky nswe -padx { 0 10 } - grid $w.fconfig -row 1 -column 1 -pady { 10 0 } -sticky we -padx { 10 0 } - grid $w.fbuttons -row 2 -column 1 -sticky we + grid $w.fengines -row 0 -column 0 -pady { 0 10 } -sticky nswe -padx { 0 10 } + grid $w.coach -row 0 -column 1 -pady { 0 10 } -sticky nswe -padx { 0 10 } + grid $w.fopening -row 1 -column 0 -pady { 0 10 } -sticky nswe -padx { 0 10 } + grid $w.ftime -row 1 -column 1 -pady { 0 10 } -sticky nswe -padx { 0 10 } + grid $w.fconfig -row 2 -column 0 -pady { 0 10 } -sticky we -padx { 0 10 } + grid $w.fconfig2 -row 2 -column 1 -pady { 0 10 } -sticky we -padx { 0 10 } + grid $w.fbuttons -row 3 -column 1 -sticky se # builds the list of UCI engines - ttk::frame $w.fengines.fEnginesList - ttk::treeview $w.fengines.fEnginesList.lbEngines -columns {0} -show {} -selectmode browse \ - -yscrollcommand "$w.fengines.fEnginesList.ybar set" - $w.fengines.fEnginesList.lbEngines column 0 -width 100 - $w.fengines.fEnginesList.lbEngines configure -height 5 - ttk::scrollbar $w.fengines.fEnginesList.ybar -command "$w.fengines.fEnginesList.lbEngines yview" - pack $w.fengines.fEnginesList.ybar -side right -fill y - pack $w.fengines.fEnginesList.lbEngines -side left -fill x -expand 1 - pack $w.fengines.fEnginesList -expand yes -fill x -side top - + ::engineNoWin::createEngineOptionsFrame $w seriousEngine ::sergame::options(engineName) 5 ::sergame::eng_messages + # ponder + ttk::checkbutton $w.fengines.ponder -text $::tr(Ponder) -variable ::sergame::options(ponder) + pack $w.seriousEngine -in $w.fengines -side top -pady 5 -anchor w -padx 4 + pack $w.fengines.ponder -side top -anchor w + # Engine plays for the upper side + set _Data(playerColor) [expr {[::board::isFlipped .main.board] ? "black" : "white"}] + ttk::frame $w.fengines.player + ttk::label $w.fengines.player.l -text "$::tr(Player) $::tr(GlistColor)" + ttk::radiobutton $w.fengines.player.w -text $::tr(white) -value "white" -variable ::sergame::_Data(playerColor) + ttk::radiobutton $w.fengines.player.b -text $::tr(black) -value "black" -variable ::sergame::_Data(playerColor) + pack $w.fengines.player.l $w.fengines.player.w $w.fengines.player.b -side left + pack $w.fengines.player -side top -anchor w - set i 0 - set idx 0 - foreach e $::engines(list) { - if { [lindex $e 7] != 1} { incr idx ; continue } - set ::sergame::engineListBox($i) $idx - set name [lindex $e 0] - $w.fengines.fEnginesList.lbEngines insert {} end -id $idx -values [list $name] - incr i - incr idx - } - - # Engine configuration (limit strength for example) - ttk::button $w.fengines.bEngineConfig -text $::tr(ConfigureUCIengine) -command { - set sel [.configSerGameWin.fengines.fEnginesList.lbEngines selection] - set index $::sergame::engineListBox($sel) - set engineData [lindex $::engines(list) $index] - set name [lindex $engineData 0] - set cmd [ toAbsPath [lindex $engineData 1] ] - set args [lindex $engineData 2] - set dir [ toAbsPath [lindex $engineData 3] ] - set options [lindex $engineData 8] - ::uci::uciConfig 3 [ toAbsPath $cmd ] $args [ toAbsPath $dir ] $options - } - pack $w.fengines.bEngineConfig -side top -pady 5 -anchor e -padx 4 - - # if no engines defined, bail out - if {$i == 0} { - tk_messageBox -type ok -message "No UCI engine defined" -icon error - destroy $w - return - } - - $w.fengines.fEnginesList.lbEngines selection set $::sergame::chosenEngine - $w.fengines.fEnginesList.lbEngines see $::sergame::chosenEngine - + # coach engine + ttk::frame $w.coach.en + ttk::checkbutton $w.coach.en.coach -text "$::tr(Engine)" -variable ::sergame::options(useCoachEngine) + ::utils::tooltip::Set $w.coach.en.coach "Use a separate (strong) engine for coaching if the playing engine is weak." + ::engineNoWin::createEngineOptionsFrame $w coachEngine ::sergame::options(coachName) 6 ::sergame::eng_messages + pack $w.coach.en.coach -in $w.coach.en -side left -pady 5 -anchor w -padx 4 + pack $w.coachEngine -in $w.coach.en -side left -pady 5 -anchor w -padx 4 + ttk::frame $w.coach.cb + ttk::checkbutton $w.coach.cb.coach -text "Bad move warning" -variable ::sergame::options(coachTypeMove) + ::utils::tooltip::Set $w.coach.cb.coach "Coach warns if player made a bad move. Player can take back this move." + ttk::checkbutton $w.coach.cb.fullCoach -text "Mark engine blunder" -variable ::sergame::options(coachTypeTactic) \ + -command { if { $::sergame::options(coachTypeTactic) } { set ::sergame::options(useCoachEngine) 1 } } + ::utils::tooltip::Set $w.coach.cb.fullCoach "Gives a hint (in InfoBar) that engines has blundered. Needs coaching engine." + pack $w.coach.cb.coach $w.coach.cb.fullCoach -side left -padx 4 + ttk::frame $w.coach.th + ttk::label $w.coach.th.l -text $::tr(moveblunderthreshold) + ttk::spinbox $w.coach.th.val -width 3 -from 0.4 -to 5.0 -increment 0.1 -textvariable ::sergame::options(threshold) -validate all -validatecommand { regexp {^[0-9]\.[0-9]$} %P } + pack $w.coach.th.l $w.coach.th.val -side left -anchor w -padx 4 + ttk::frame $w.coach.ad + ttk::checkbutton $w.coach.ad.l -text $::tr(limitanalysis) -variable ::sergame::options(isLimitedAnalysisTime) + ttk::spinbox $w.coach.ad.val -width 3 -from 1 -to 360 -increment 1 -textvariable ::sergame::options(tacTime) -validate all -validatecommand { regexp {^[0-9]$} %P } + pack $w.coach.ad.l $w.coach.ad.val -side left -anchor w -padx 4 + pack $w.coach.cb $w.coach.th $w.coach.en $w.coach.ad -side top -anchor w -padx 4 + + ttk::checkbutton $w.fconfig.cbUseBook -text $::tr(UseBook) -variable ::sergame::options(useBook) # load book names - ttk::checkbutton $w.fconfig.cbUseBook -text $::tr(UseBook) -variable ::sergame::useBook - set bookPath $::scidBooksDir - set bookList [ lsort -dictionary [ glob -nocomplain -directory $bookPath *.bin ] ] - if { [llength $bookList] == 0 } { + lassign [getBookList $options(bookToUse)] idx tmp + if { $idx < 0 } { $w.fconfig.cbUseBook configure -state disabled - set ::sergame::useBook 0 + set options(useBook) 0 } - set i 0 - set idx 0 - set tmp {} - foreach file $bookList { - lappend tmp [ file tail $file ] - if { $::sergame::bookToUse == [ file tail $file ]} { - set idx $i - } - incr i - } - ttk::combobox $w.fconfig.combo -width 12 -values $tmp - catch { ch$w.fconfig.combo current $idx } + catch { $w.fconfig.combo current $idx } set row 0 @@ -129,7 +107,7 @@ namespace eval sergame { ttk::frame $w.ftime.timebonus pack $w.ftime.timebonus -side top -fill x - ttk::radiobutton $w.ftime.timebonus.rb1 -text $::tr(TimeBonus) -value "timebonus" -variable ::sergame::timeMode + ttk::radiobutton $w.ftime.timebonus.rb1 -text $::tr(TimeBonus) -value "timebonus" -variable ::sergame::options(timeMode) grid $w.ftime.timebonus.rb1 -row $row -column 0 -sticky w -rowspan 2 ttk::label $w.ftime.timebonus.whitelabel -text $::tr(White) @@ -155,34 +133,34 @@ namespace eval sergame { ttk::label $w.ftime.timebonus.blacklseconds -text $::tr(TimeSec) grid $w.ftime.timebonus.blacklseconds -row $row -column 5 - $w.ftime.timebonus.whitespminutes set [expr $::uci::uciInfo(wtime3) / (60 * 1000)] - $w.ftime.timebonus.whitespseconds set [expr $::uci::uciInfo(winc3) / 1000] - $w.ftime.timebonus.blackspminutes set [expr $::uci::uciInfo(btime3) / (60 * 1000)] - $w.ftime.timebonus.blackspseconds set [expr $::uci::uciInfo(binc3) / 1000 ] + $w.ftime.timebonus.whitespminutes set [expr $options(wtime) / (60 * 1000)] + $w.ftime.timebonus.whitespseconds set [expr $options(winc) / 1000] + $w.ftime.timebonus.blackspminutes set [expr $options(btime) / (60 * 1000)] + $w.ftime.timebonus.blackspseconds set [expr $options(binc) / 1000 ] # Fixed depth ttk::frame $w.ftime.depth - ttk::radiobutton $w.ftime.depth.button -text $::tr(FixedDepth) -value "depth" -variable ::sergame::timeMode -width 16 + ttk::radiobutton $w.ftime.depth.button -text $::tr(FixedDepth) -value "depth" -variable ::sergame::options(timeMode) -width 16 ttk::spinbox $w.ftime.depth.value -background white -width 3 -from 1 -to 20 -increment 1 -validate all -validatecommand { regexp {^[0-9]+$} %P } - $w.ftime.depth.value set $::sergame::depth + $w.ftime.depth.value set $options(depth) pack $w.ftime.depth -side top -fill x pack $w.ftime.depth.button -side left pack $w.ftime.depth.value -side left ttk::frame $w.ftime.nodes - ttk::radiobutton $w.ftime.nodes.button -text "$::tr(Nodes) (x1000)" -value "nodes" -variable ::sergame::timeMode -width 16 + ttk::radiobutton $w.ftime.nodes.button -text "$::tr(Nodes) (x1000)" -value "nodes" -variable ::sergame::options(timeMode) -width 16 ttk::spinbox $w.ftime.nodes.value -background white -width 3 -from 5 -to 10000 -increment 5 -validate all -validatecommand { regexp {^[0-9]+$} %P } - $w.ftime.nodes.value set [ expr $::sergame::nodes /1000] + $w.ftime.nodes.value set [ expr $options(nodes) /1000] pack $w.ftime.nodes -side top -fill x pack $w.ftime.nodes.button -side left pack $w.ftime.nodes.value -side left ttk::frame $w.ftime.movetime - ttk::radiobutton $w.ftime.movetime.button -text $::tr(SecondsPerMove) -value "movetime" -variable ::sergame::timeMode -width 16 + ttk::radiobutton $w.ftime.movetime.button -text $::tr(SecondsPerMove) -value "movetime" -variable ::sergame::options(timeMode) -width 16 ttk::spinbox $w.ftime.movetime.value -background white -width 3 -from 1 -to 120 -increment 1 -validate all -validatecommand { regexp {^[0-9]+$} %P } - $w.ftime.movetime.value set [ expr $::sergame::movetime /1000] + $w.ftime.movetime.value set [ expr $options(movetime) /1000] pack $w.ftime.movetime -side top -fill x pack $w.ftime.movetime.button -side left @@ -192,63 +170,56 @@ namespace eval sergame { pack $w.fconfig.combo -side top -anchor w -padx 20 -fill x # New game or use current position ? - ttk::checkbutton $w.fconfig.cbPosition -text $::tr(StartFromCurrentPosition) -variable ::sergame::startFromCurrent - pack $w.fconfig.cbPosition -side top -anchor w - - # ponder - ttk::checkbutton $w.fconfig.cbPonder -text $::tr(Ponder) -variable ::sergame::ponder - pack $w.fconfig.cbPonder -side top -anchor w - - # Warn if the user makes weak/bad moves - ttk::checkbutton $w.fconfig.cbCoach -text $::tr(CoachIsWatching) -variable ::sergame::coachIsWatching - pack $w.fconfig.cbCoach -side top -anchor w + ttk::checkbutton $w.fconfig2.cbPosition -text $::tr(StartFromCurrentPosition) -variable ::sergame::options(startFromCurrent) #Should the evaluation of the position stored in the comment? - ttk::checkbutton $w.fconfig.storeEval -text $::tr(AddScoreToShortAnnotations) -variable ::sergame::storeEval - pack $w.fconfig.storeEval -side top -anchor w + ttk::checkbutton $w.fconfig2.storeEval -text $::tr(AddScoreToShortAnnotations) -variable ::sergame::options(storeEval) + pack $w.fconfig2.cbPosition $w.fconfig2.storeEval -side top -anchor w # choose a specific opening - ttk::checkbutton $w.fopening.cbOpening -text $::tr(SpecificOpening) -variable ::sergame::isOpening + ttk::checkbutton $w.fopening.cbOpening -text $::tr(SpecificOpening) -variable ::sergame::options(isOpening) ttk::frame $w.fopening.fOpeningList ttk::treeview $w.fopening.fOpeningList.lbOpening -columns {0} -show {} -selectmode browse \ -yscrollcommand "$w.fopening.fOpeningList.ybar set" $w.fopening.fOpeningList.lbOpening column 0 -width 250 $w.fopening.fOpeningList.lbOpening configure -height 5 set idx 0 - foreach o $::tacgame::openingList { + foreach o $::sergame::openingList { $w.fopening.fOpeningList.lbOpening insert {} end -id $idx -values [list $o] incr idx } - $w.fopening.fOpeningList.lbOpening selection set $::sergame::chosenOpening - $w.fopening.fOpeningList.lbOpening see $::sergame::chosenOpening + $w.fopening.fOpeningList.lbOpening selection set $options(chosenOpening) + $w.fopening.fOpeningList.lbOpening see $options(chosenOpening) ttk::scrollbar $w.fopening.fOpeningList.ybar -command "$w.fopening.fOpeningList.lbOpening yview" + pack $w.fopening.cbOpening -fill x -side top pack $w.fopening.fOpeningList.ybar -side right -fill y pack $w.fopening.fOpeningList.lbOpening -side left -fill both -expand 1 pack $w.fopening.fOpeningList -fill both -side top - pack $w.fopening.cbOpening -fill x -side top ttk::button $w.fbuttons.close -text $::tr(Play) -command { focus . - set ::sergame::chosenEngine [.configSerGameWin.fengines.fEnginesList.lbEngines selection] - set ::sergame::engineName [.configSerGameWin.fengines.fEnginesList.lbEngines set $::sergame::chosenEngine 0] - set ::sergame::chosenOpening [.configSerGameWin.fopening.fOpeningList.lbOpening selection] - if {$::sergame::useBook} { - set ::sergame::bookToUse [.configSerGameWin.fconfig.combo get] - if {$::sergame::bookToUse == "" } { - set ::sergame::useBook 0 + set ::sergame::options(chosenOpening) [.configSerGameWin.fopening.fOpeningList.lbOpening selection] + if {$::sergame::options(useBook)} { + set ::sergame::options(bookToUse) [.configSerGameWin.fconfig.combo get] + if {$::sergame::options(bookToUse) == "" } { + set ::sergame::options(useBook) 0 } } - set ::uci::uciInfo(wtime3) [expr [.configSerGameWin.ftime.timebonus.whitespminutes get]*1000*60] - set ::uci::uciInfo(btime3) [expr [.configSerGameWin.ftime.timebonus.blackspminutes get]*1000*60] - set ::uci::uciInfo(winc3) [expr [.configSerGameWin.ftime.timebonus.whitespseconds get]*1000] - set ::uci::uciInfo(binc3) [expr [.configSerGameWin.ftime.timebonus.blackspseconds get]*1000] - set ::uci::uciInfo(fixeddepth3) [.configSerGameWin.ftime.depth.value get] - set ::uci::uciInfo(fixednodes3) [expr [.configSerGameWin.ftime.nodes.value get]*1000] - set ::uci::uciInfo(movetime3) [expr [.configSerGameWin.ftime.movetime.value get]*1000] + set ::sergame::options(wtime) [expr [.configSerGameWin.ftime.timebonus.whitespminutes get]*1000*60] + set ::sergame::options(btime) [expr [.configSerGameWin.ftime.timebonus.blackspminutes get]*1000*60] + set ::sergame::options(winc) [expr [.configSerGameWin.ftime.timebonus.whitespseconds get]*1000] + set ::sergame::options(binc) [expr [.configSerGameWin.ftime.timebonus.blackspseconds get]*1000] + set ::sergame::options(fixeddepth) [.configSerGameWin.ftime.depth.value get] + set ::sergame::options(fixednodes) [expr [.configSerGameWin.ftime.nodes.value get]*1000] + set ::sergame::options(movetime) [expr [.configSerGameWin.ftime.movetime.value get]*1000] + set ::sergame::options(depth) [.configSerGameWin.ftime.depth.value get] + set ::sergame::options(nodes) [expr [.configSerGameWin.ftime.nodes.value get]*1000] + set ::sergame::options(movetime) [expr [.configSerGameWin.ftime.movetime.value get]*1000] + bind .configSerGameWin.seriousEngine "" destroy .configSerGameWin - ::sergame::play $::sergame::chosenEngine + ::sergame::play seriousEngine } ttk::button $w.fbuttons.cancel -textvar ::tr(Cancel) -command "focus .; destroy $w" @@ -264,121 +235,227 @@ namespace eval sergame { ################################################################################ # ################################################################################ - proc play { engine {n 3} } { - global ::sergame::chosenOpening ::sergame::isOpening ::tacgame::openingList ::sergame::openingMovesList \ - ::sergame::openingMovesHash ::sergame::openingMoves ::sergame::outOfOpening + proc play { engine } { + global ::sergame::_Data ::sergame::options - if {$::sergame::isOpening || !$::sergame::startFromCurrent} { + set callback [list ::sergame::eng_messages $engine nop] + if { ! [::engineNoWin::initEngine $engine $options(engineName) $callback] } { + tk_messageBox -title Scid -icon info -type ok -message "The UCI-Engines could not be started." + return + } + if {$options(isOpening) || !$options(startFromCurrent)} { if {[::game::Clear] eq "cancel"} { return } } - set ::sergame::lFen {} - - ::uci::startEngine $::sergame::engineListBox($engine) $n - set engineData [lindex $::engines(list) $::sergame::engineListBox($engine)] - foreach {option} [lindex $engineData 8] { - array set ::uciOptions$n $option - } - ::uci::sendUCIoptions $n + set _Data(lFen) {} + set _Data(prevscore) "" + set _Data(score) 0.0 + set _Data(ponder) "" + set _Data(takeback) 0 - set ::uci::uciInfo(prevscore$n) 0.0 - set ::uci::uciInfo(score$n) 0.0 - set ::uci::uciInfo(ponder$n) "" - - if {$::sergame::startFromCurrent} { - set isOpening 0 + if {$options(startFromCurrent)} { + set options(isOpening) 0 } # ponder - if {$::sergame::ponder} { - ::sergame::sendToEngine $n "setoption name Ponder value true" - } else { - ::sergame::sendToEngine $n "setoption name Ponder value false" - } + set ponder false + if {$options(ponder)} { set ponder true } + ::engine::send $engine SetOptions [list {Ponder true}] # if will follow a specific opening line - if {$isOpening} { - set fields [split [lindex $openingList $chosenOpening] ":"] - set openingName [lindex $fields 0] - set openingMoves [string trim [lindex $fields 1]] - set openingMovesList "" - set openingMovesHash "" - set outOfOpening 0 - foreach m [split $openingMoves] { + if {$options(isOpening)} { + set fields [split [lindex $::sergame::openingList $options(chosenOpening)] ":"] +# set openingName [lindex $fields 0] + set _Data(openingMoves) [string trim [lindex $fields 1]] + set _Data(openingMovesList) "" + set _Data(openingMovesHash) "" + set _Data(outOfOpening) 0 + foreach m [split $_Data(openingMoves)] { # in case of multiple adjacent spaces in opening line if {$m =={}} { continue } set p [string trim $m] - lappend openingMovesList [string trim [regsub {^[1-9]+\.} $p ""] ] + lappend _Data(openingMovesList) [string trim [regsub {^[1-9]+\.} $p ""] ] } - lappend openingMovesHash [sc_pos hash] - foreach m $openingMovesList { + lappend _Data(openingMovesHash) [sc_pos hash] + foreach m $_Data(openingMovesList) { if {[catch {sc_move addSan $m}]} { } - lappend openingMovesHash [sc_pos hash] + lappend _Data(openingMovesHash) [sc_pos hash] } + #goto start pos and clear the moves + sc_move start + sc_game truncate } - - # Engine plays for the upper side - if {[::board::isFlipped .main.board]} { - set ::sergame::playerColor "black" - set ::sergame::engineColor "white" - } else { - set ::sergame::playerColor "white" - set ::sergame::engineColor "black" + set _Data(engineColor) [expr {$_Data(playerColor) eq "white" ? "black" : "white"}] + if { (![::board::isFlipped .main.board] && $_Data(playerColor) eq "black") || \ + ([::board::isFlipped .main.board] && $_Data(playerColor) eq "white") } { + board::flip .main.board } - if {!$::sergame::startFromCurrent} { + if {!$options(startFromCurrent)} { # create a new game if a DB is opened sc_game tags set -event "Serious game" - sc_game tags set -$::sergame::playerColor "Player" - sc_game tags set -$::sergame::engineColor "$::sergame::engineName" + sc_game tags set -$_Data(playerColor) $::tr(Player) + sc_game tags set -$_Data(engineColor) "$options(engineName)" sc_game tags set -date [::utils::date::today] + if {$options(timeMode) eq "timebonus"} { + sc_game tags set -extra [list "TimeControlWhite \"[expr $options(wtime)/60000]+[expr $options(winc)/1000]\"" \ + "TimeControlBlack \"[expr $options(btime)/60000]+[expr $options(binc)/1000]\""] + } + if { $options(coachTypeMove) || $options(coachTypeTactic) } { + sc_game tags set -event "Coached game" + set co "Coached Game: " + if { $options(coachTypeMove) } { append co "Bad Move Warning; " } + if { $options(coachTypeTactic) } { append co "Engine Blunder Information; " } + append co "Blunder Threshold: $options(threshold)" + sc_pos setComment $co + } } - set ::sergame::waitPlayerMove 0 - set ::sergame::wentOutOfBook 0 + set _Data(waitPlayerMove) 0 + set _Data(wentOutOfBook) 0 ::setPlayMode "::sergame::callback" ::notify::GameChanged - clocks init $n + if { $options(coachTypeTactic) || $options(useCoachEngine) } { + set options(useCoachEngine) 1 + set callback [list ::sergame::coachEng_messages coachEngine nop] + if { ! [::engineNoWin::initEngine coachEngine $::sergame::options(coachName) $callback] } { + set options(useCoachEngine) 0 + } + } + clocks init clocks start - ::sergame::engineGo $n + ::sergame::playLoop + } + + proc ::sergame::eng_messages {id w msg} { + global ::sergame::_Data ::sergame::options + lassign $msg msgType msgData + switch $msgType { + "InfoConfig" { + if { ! [winfo exists $w] } { return } + set msgData [lindex $msgData 2] + ::engineNoWin::initEngineOptions $id $w $msgData + } + "InfoPV" { + if { ! $options(useCoachEngine) } { + # no coach engine then use score from playing engine + lassign $msgData multipv depth seldepth nodes nps hashfull tbhits time score score_type score_wdl pv + if { $multipv == 1 } { + set _Data(score) [expr $score / 100.0] + if { $score_type eq "mate" } { + if { $score > 0 } { + set _Data(score) 128.0 + } else { + set _Data(score) -128.0 + } + } + } + } + } + "InfoBestMove" { + lassign $msgData _Data(bestmove) ponder _Data(ponder) + } + "InfoDisconnected" { + lassign $msgData errorMsg + if {$errorMsg eq ""} { set errorMsg "The connection with the engine terminated unexpectedly." } + tk_messageBox -icon warning -type ok -parent . -message $errorMsg + ::sergame::abortGame + } + } + } + proc ::sergame::coachEng_messages {id w msg} { + global ::sergame::_Data ::sergame::options + lassign $msg msgType msgData + switch $msgType { + "InfoConfig" { + if { ! [winfo exists $w] } { return } + set msgData [lindex $msgData 2] + ::engineNoWin::initEngineOptions $id $w $msgData + } + "InfoPV" { + lassign $msgData multipv depth seldepth nodes nps hashfull tbhits time score score_type score_wdl pv + if { $multipv == 1 } { + set _Data(bestCoachmove) [lrange $pv 0 0] + set _Data(score) [expr $score / 100.0] + if { $score_type eq "mate" } { + if { $score > 0 } { + set _Data(score) 128.0 + } else { + set _Data(score) -128.0 + } + } + } + } + "InfoBestMove" { + lassign $msgData _Data(bestCoachmove) + } + "InfoDisconnected" { + lassign $msgData errorMsg + if {$errorMsg eq ""} { set errorMsg "The connection with the engine terminated unexpectedly." } + tk_messageBox -icon warning -type ok -parent . -message $errorMsg + ::sergame::abortGame + } + } } proc callback {cmd args} { + global ::sergame::_Data switch $cmd { premove { # TODO: currently we just return true if it is the engine turn. - return [expr { ! $::sergame::waitPlayerMove }] + return [expr { ! $_Data(waitPlayerMove) }] } stop { ::sergame::abortGame } } return 0 } - proc abortGame { { n 3 } } { + proc setResult {} { + set w .askResult + ::win::createDialog $w + wm resizable $w 0 0 + wm title $w "Scid: [tr Result]" + ttk::button $w.win -text " 1-0 " -command { sc_game tags set -result 1; destroy .askResult } + ttk::button $w.loss -text " 0-1 " -command { sc_game tags set -result 0; destroy .askResult } + ttk::button $w.draw -text "1/2-1/2" -command { sc_game tags set -result =; destroy .askResult } + ttk::button $w.undef -text " * " -command { sc_game tags set -result *; destroy .askResult } + pack $w.win $w.draw $w.loss $w.undef -side left -padx 10 + tk::PlaceWindow $w + grab $w + tkwait window $w + } + + proc abortGame { } { + global ::sergame::_Data ::sergame::options ::setPlayMode "" - after cancel ::sergame::engineGo $n + after cancel ::sergame::playLoop clocks stop - set ::sergame::lFen {} - if { $::uci::uciInfo(pipe$n) != ""} { - ::uci::closeUCIengine $n - set ::uci::uciInfo(bestmove$n) "abort" - } + set _Data(lFen) {} + ::engine::send seriousEngine StopGo + ::engineNoWin::closeEngine seriousEngine + set _Data(bestmove) "abort" + if { $options(useCoachEngine) } { + ::engine::send coachEngine StopGo + ::engineNoWin::closeEngine coachEngine + } + # if { [sc_game tag get Result] eq "*" } { setResult } ::notify::GameChanged } - proc clocks {cmd {n 3}} { - if {$::sergame::timeMode != "timebonus"} { return } + proc clocks {cmd} { + global ::sergame::options + if {$options(timeMode) != "timebonus"} { return } switch $cmd { init { ::gameclock::new "" 1 ::gameclock::new "" 2 - ::gameclock::setSec 1 [expr 0 - $::uci::uciInfo(wtime$n)/1000] - ::gameclock::setSec 2 [expr 0 - $::uci::uciInfo(btime$n)/1000] + ::gameclock::setSec 1 [expr 0 - $options(wtime)/1000] + ::gameclock::setSec 2 [expr 0 - $options(btime)/1000] } start { if { [sc_pos side] == "white" } { @@ -393,11 +470,11 @@ namespace eval sergame { } toggle { if {[::gameclock::stop 1]} { - ::gameclock::add 1 [expr $::uci::uciInfo(winc$n)/1000] + ::gameclock::add 1 [expr $options(winc)/1000] ::gameclock::storeTimeComment 1 ::gameclock::start 2 } elseif {[::gameclock::stop 2]} { - ::gameclock::add 2 [expr $::uci::uciInfo(binc$n)/1000] + ::gameclock::add 2 [expr $options(binc)/1000] ::gameclock::storeTimeComment 2 ::gameclock::start 1 } @@ -407,7 +484,12 @@ namespace eval sergame { } proc takeBack {takebackClockW takebackClockB} { + global ::sergame::_Data + sc_pos setComment "Player takes back this move" sc_move back 1 + set _Data(takeback) 1 + set _Data(prevscore) "" + set _Data(score) 0.0 if {$takebackClockW != ""} { ::gameclock::setSec 1 [expr 0 - $takebackClockW] ::gameclock::setSec 2 [expr 0 - $takebackClockB] @@ -416,74 +498,159 @@ namespace eval sergame { ::notify::PosChanged -pgn } - ################################################################################ - # - ################################################################################ - proc sendToEngine {n text} { - ::sergame::logEngine $n "Scid : $text" - catch {puts $::uci::uciInfo(pipe$n) $text} - } ################################################################################ # returns true if last move is a mate and stops clocks ################################################################################ proc endOfGame {} { - set move_done [sc_game info previousMove] + global ::sergame::_Data if { [string index [sc_game info previousMove] end ] == "#"} { - clocks stop + tk_messageBox -type ok -message "This is Mate!" -parent .main -icon info + set result 0 + if { [sc_pos side] == "black" } { set result 1 } + sc_game tags set -result $result + ::sergame::abortGame return 1 } return 0 } + + # start playing engine: ponder must be "" or "ponder" + proc startEngine { ponder } { + global ::sergame::_Data ::sergame::options + if {$options(timeMode) == "timebonus"} { + set wtime [expr [::gameclock::getSec 1] * 1000 ] + set btime [expr [::gameclock::getSec 2] * 1000 ] + set parameter "$ponder wtime $wtime btime $btime winc $options(winc) binc $options(binc)" + } elseif {$options(timeMode) == "depth"} { + set parameter "$ponder depth $options(fixeddepth)" + } elseif {$options(timeMode) == "movetime"} { + set parameter "$ponder movetime $options(movetime)" + } elseif {$options(timeMode) == "nodes"} { + set parameter "$ponder nodes $options(fixednodes)" + } + if { $ponder ne "" } { set ponder "moves $options(ponder)" } + ::engine::send seriousEngine Go [list "position fen [sc_pos fen] $ponder" $parameter] + } + + proc checkBlunder { delta } { + global ::sergame::options + set ret "" + if { $delta >= $options(threshold) } { + if {$delta > $::informant("?!") } { set ret [list "?!" "DubiousMovePlayedTakeBack"] } + if {$delta > $::informant("?") } { set ret [list "?" "WeakMovePlayedTakeBack"] } + if {$delta > $::informant("??") } { set ret [list "??" "BadMovePlayedTakeBack"] } + } + return $ret + } + proc checkEngineBlunder { } { + global ::sergame::_Data ::sergame::options + set delta [expr $_Data(score) + $_Data(prevscore)] + if { [sc_pos side] == $_Data(engineColor) } { set delta [expr 0.0 - $delta] } + lassign [checkBlunder $delta] _Data(tacticBlunder) + if { $_Data(tacticBlunder) ne "" } { + if { $_Data(engineColor) eq "white" } { + set from $_Data(prevscore) + set to [expr 0.0 - $_Data(score)] + } else { + set from [expr 0.0 - $_Data(prevscore)] + set to $_Data(score) + } + ::board::setInfoAlert .main.board "Engine blunders: $_Data(tacticBlunder) $from -> $to" "Show move" red \ + {::board::setInfoAlert .main.board "Try move $::sergame::_Data(bestCoachmove) Playing..." [tr Stop] red {{*}$::playMode stop}} + } + } + ################################################################################ # ################################################################################ - proc engineGo { n } { - global ::sergame::isOpening ::sergame::openingMovesList ::sergame::openingMovesHash ::sergame::openingMoves \ - ::sergame::timeMode ::sergame::outOfOpening + proc playLoop { } { + global ::sergame::_Data ::sergame::options - after cancel ::sergame::engineGo $n + after cancel ::sergame::playLoop if { [::sergame::endOfGame] } { return } - if { [sc_pos side] != $::sergame::engineColor } { - set ::sergame::waitPlayerMove 1 - after 1000 ::sergame::engineGo $n + if { [sc_pos side] != $_Data(engineColor) } { + # wait until player has moved + set _Data(waitPlayerMove) 1 + after 1000 ::sergame::playLoop + if { $options(useCoachEngine) && $options(coachTypeTactic) && $_Data(actTacTime) > 0 && $_Data(prevscore) != "" } { + #check for engine blunder with coach engine + incr _Data(actTacTime) -1 + if { $options(isLimitedAnalysisTime) && ! $_Data(actTacTime) } { + # make sure we have a move and evaluation from coach engine + while { $_Data(bestCoachmove) eq "" } { vwait ::sergame::_Data(bestCoachmove) } + ::engine::send coachEngine StopGo + } else { + checkEngineBlunder + } + } + if {![sc_pos isAt vend] && ! $_Data(takeback) } { + # postion has changed to earlier postion in the game. set takeback flag + set _Data(takeback) 2 + set _Data(prevscore) "" + set _Data(score) 0.0 + } return } + + if { $_Data(takeback) } { + # player has taken back his move or changed position and played an new move, ask for continuation + if {[info exists ::guessedAddMove]} { + sc_game undo; addMoveEx [lindex $::guessedAddMove 1] mainline + unset ::guessedAddMove + } + set _Data(takeback) 0 + } + + if { $options(useCoachEngine) } { + ::engine::send coachEngine StopGo + if { $_Data(tacticBlunder) ne "" } { + # engine blundered, add nag and correct eval comment + sc_move back + sc_pos addNag $_Data(tacticBlunder) + if { $options(storeEval) == 1 } { + set score $_Data(score) + if { $_Data(engineColor) eq "white" } { set score [expr 0.0 - $score] } + storeEvalComment $score + } + sc_move forward + } + } set takebackClockW "" set takebackClockB "" - if {$::sergame::waitPlayerMove} { + if {$_Data(waitPlayerMove)} { # The player moved - set ::sergame::waitPlayerMove 0 - if {$::sergame::timeMode == "timebonus"} { + set _Data(waitPlayerMove) 0 + if {$options(timeMode) == "timebonus"} { set takebackClockW [::gameclock::getSec 1] set takebackClockB [::gameclock::getSec 2] - clocks toggle $n + clocks toggle } - repetition + if { [repetition] } { [return } } # make a move corresponding to a specific opening, (it is engine's turn) - if {$isOpening && !$outOfOpening} { + if {$options(isOpening) && !$_Data(outOfOpening)} { set index 0 # Warn if the user went out of the opening line chosen - if { !$outOfOpening } { + if { !$_Data(outOfOpening) } { set ply [ expr [sc_pos moveNumber] * 2 - 1] if { [sc_pos side] == "white" } { set ply [expr $ply - 1] } - if { [lsearch $openingMovesHash [sc_pos hash]] == -1 && [llength $openingMovesList] >= $ply} { + if { [lsearch $_Data(openingMovesHash) [sc_pos hash]] == -1 && [llength $_Data(openingMovesList)] >= $ply} { clocks stop set answer [tk_messageBox -icon question -parent .main -title $::tr(OutOfOpening) -type yesno \ - -message "$::tr(NotFollowedLine) $openingMoves\n $::tr(DoYouWantContinue)" ] + -message "$::tr(NotFollowedLine) $_Data(openingMoves)\n $::tr(DoYouWantContinue)" ] if {$answer == no} { takeBack $takebackClockW $takebackClockB - after 1000 ::sergame::engineGo $n + after 1000 ::sergame::playLoop return } else { - set outOfOpening 1 + set _Data(outOfOpening) 1 } clocks start } @@ -491,117 +658,81 @@ namespace eval sergame { set hpos [sc_pos hash] # Find a corresponding position in the opening line - set length [llength $openingMovesHash] + set length [llength $_Data(openingMovesHash)] for {set i 0} { $i < [expr $length-1] } { incr i } { - set h [lindex $openingMovesHash $i] + set h [lindex $_Data(openingMovesHash) $i] if {$h == $hpos} { - set index [lsearch $openingMovesHash $h] - set move [lindex $openingMovesList $index] - # play the move - set action "replace" - if {![sc_pos isAt vend]} { set action [confirmReplaceMove] } - if {$action == "replace"} { - if {[catch {sc_move addSan $move}]} {} - } elseif {$action == "var"} { - sc_var create - if {[catch {sc_move addSan $move}]} {} - } elseif {$action == "mainline"} { - sc_var create - if {[catch {sc_move addSan $move}]} {} - sc_var promote - sc_move forward 1 - } + set index [lsearch $_Data(openingMovesHash) $h] + set move [lindex $_Data(openingMovesList) $index] + sc_move addSan $move - clocks toggle $n + clocks toggle updateBoard -pgn -animate - repetition - after 1000 ::sergame::engineGo $n + if { ! [repetition] } { + after 1000 ::sergame::playLoop + } return } } } # ------------------------------------------------------------- # use a book - if {$::sergame::useBook && ! $::sergame::wentOutOfBook} { - set move [ ::book::getMove $::sergame::bookToUse [sc_pos fen] $::sergame::bookSlot] + if {$options(useBook) && ! $_Data(wentOutOfBook)} { + set move [ ::book::getMove $options(bookToUse) [sc_pos fen] $_Data(bookSlot)] if {$move == ""} { - set ::sergame::wentOutOfBook 1 + set _Data(wentOutOfBook) 1 } else { sc_move addSan $move ::utils::sound::AnnounceNewMove $move # we made a book move so assume a score = 0 - set ::uci::uciInfo(prevscore$n) 0.0 - clocks toggle $n + set _Data(prevscore) "" + clocks toggle updateBoard -pgn -animate - repetition - after 1000 ::sergame::engineGo $n + if { ! [repetition] } { + after 1000 ::sergame::playLoop + } return } } # ------------------------------------------------------------- # check if the engine pondered on the right move - - if { $::sergame::ponder && $::uci::uciInfo(ponder$n) == [sc_game info previousMoveUCI]} { - ::sergame::sendToEngine $n "ponderhit" + if { $options(ponder) && $_Data(ponder) ne "" && $_Data(ponder) == [sc_game info previousMoveUCI]} { + ::engine::rawsend seriousEngine "ponderhit" } else { - - if { $::sergame::ponder } { - ::sergame::sendToEngine $n "stop" - } - set ::analysis(waitForReadyOk$n) 1 - ::sergame::sendToEngine $n "isready" - vwait ::analysis(waitForReadyOk$n) - ::sergame::sendToEngine $n "position fen [sc_pos fen]" - if {$timeMode == "timebonus"} { - set wtime [expr [::gameclock::getSec 1] * 1000 ] - set btime [expr [::gameclock::getSec 2] * 1000 ] - ::sergame::sendToEngine $n "go wtime $wtime btime $btime winc $::uci::uciInfo(winc$n) binc $::uci::uciInfo(binc$n)" - } elseif {$timeMode == "depth"} { - ::sergame::sendToEngine $n "go depth $::uci::uciInfo(fixeddepth$n)" - } elseif {$timeMode == "movetime"} { - ::sergame::sendToEngine $n "go movetime $::uci::uciInfo(movetime$n)" - } elseif {$timeMode == "nodes"} { - ::sergame::sendToEngine $n "go nodes $::uci::uciInfo(fixednodes$n)" - } + if { $options(ponder) } { ::engine::send seriousEngine StopGo } + startEngine "" + } + if { $options(useCoachEngine) } { + set _Data(bestCoachmove) "" + ::engine::send coachEngine Go [list "position fen [sc_pos fen]" "infinite"] } - set ::uci::uciInfo(bestmove$n) "" - vwait ::uci::uciInfo(bestmove$n) + set _Data(bestmove) "" + vwait ::sergame::_Data(bestmove) + if { $options(useCoachEngine) } { + # make sure we have a move and evaluation from coach engine + while { $_Data(bestCoachmove) eq "" } { vwait ::sergame::_Data(bestCoachmove) } + ::engine::send coachEngine StopGo + } # ------------------------------------------------------------- - # if weak move detected, propose the user to tack back - if { $::sergame::coachIsWatching && $::uci::uciInfo(prevscore$n) != "" } { - set blunder 0 - set delta [expr $::uci::uciInfo(score$n) - $::uci::uciInfo(prevscore$n)] - if {$delta > $::informant("?!") && $::sergame::engineColor == "white" || - $delta < [expr 0.0 - $::informant("?!")] && $::sergame::engineColor == "black" } { - set blunder 1 - } - - if {$delta > $::informant("?") && $::sergame::engineColor == "white" || - $delta < [expr 0.0 - $::informant("?")] && $::sergame::engineColor == "black" } { - set blunder 2 - } - - if {$delta > $::informant("??") && $::sergame::engineColor == "white" || - $delta < [expr 0.0 - $::informant("??")] && $::sergame::engineColor == "black" } { - set blunder 3 - } - - if {$blunder == 1} { - set tBlunder "DubiousMovePlayedTakeBack" - } elseif {$blunder == 2} { - set tBlunder "WeakMovePlayedTakeBack" - } elseif {$blunder == 3} { - set tBlunder "BadMovePlayedTakeBack" - } - - if {$blunder != 0} { + # if weak move detected, propose the user to take back + if { $options(coachTypeMove) && $_Data(prevscore) != "" } { + set delta [expr $_Data(score) - $_Data(prevscore)] + if { [sc_pos side] != $_Data(engineColor) } { set delta [expr 0.0 - $delta] } + lassign [checkBlunder $delta] nop tBlunder + if {$tBlunder ne ""} { clocks stop - set answer [tk_messageBox -icon question -parent .main -title "Scid" -type yesno -message $::tr($tBlunder) ] + set prevScore $_Data(prevscore) + set actScore $_Data(score) + if { $_Data(playerColor) eq "white" } { + set prevScore [expr 0.0 - $prevScore] + set actScore [expr 0.0 - $actScore] + } + set answer [tk_messageBox -icon question -parent .main -title "Scid" -type yesno -message "$::tr($tBlunder)\n$prevScore -> $actScore" ] if {$answer == yes} { takeBack $takebackClockW $takebackClockB - after 1000 ::sergame::engineGo $n + after 1000 ::sergame::playLoop return } clocks start @@ -609,53 +740,44 @@ namespace eval sergame { } # ------------------------------------------------------------- - if { $::uci::uciInfo(bestmove$n) == "abort" } { + if { $_Data(bestmove) == "abort" } { return } - ::uci::sc_move_add $::uci::uciInfo(bestmove$n) - ::utils::sound::AnnounceNewMove $::uci::uciInfo(bestmove$n) - set ::uci::uciInfo(prevscore$n) $::uci::uciInfo(score$n) - if { $::sergame::storeEval == 1 } { - storeEvalComment $::uci::uciInfo(score$n) + sc_move addSan $_Data(bestmove) + ::utils::sound::AnnounceNewMove $_Data(bestmove) + set _Data(prevscore) $_Data(score) + if { $options(storeEval) == 1 } { + set score $_Data(score) + if { $_Data(engineColor) eq "black" } { set score [expr 0.0 - $score] } + storeEvalComment $score } updateBoard -pgn -animate - repetition + if { [repetition] } { return } - clocks toggle $n + clocks toggle # ponder mode (the engine just played its move) - if {$::sergame::ponder && $::uci::uciInfo(ponder$n) != ""} { - ::sergame::sendToEngine $n "position fen [sc_pos fen] moves $::uci::uciInfo(ponder$n)" - set wtime [expr [::gameclock::getSec 1] * 1000 ] - set btime [expr [::gameclock::getSec 2] * 1000 ] - if {$timeMode == "timebonus"} { - ::sergame::sendToEngine $n "go ponder wtime $wtime btime $btime winc $::uci::uciInfo(winc$n) binc $::uci::uciInfo(binc$n)" - } elseif {$timeMode == "depth"} { - ::sergame::sendToEngine $n "go ponder depth $::uci::uciInfo(fixeddepth$n)" - } elseif {$timeMode == "movetime"} { - ::sergame::sendToEngine $n "go ponder movetime $::uci::uciInfo(movetime$n)" - } elseif {$timeMode == "nodes"} { - ::sergame::sendToEngine $n "go ponder nodes $::uci::uciInfo(fixednodes$n)" - } - } + if {$options(ponder) } { startEngine ponder } - after 1000 ::sergame::engineGo $n + if { $options(useCoachEngine) } { + set _Data(actTacTime) $options(tacTime) + set _Data(bestCoachmove) "" + ::engine::send coachEngine Go [list "position fen [sc_pos fen]" "infinite"] + } + after 1000 ::sergame::playLoop } ################################################################################ # add current position for 3fold repetition detection and returns 1 if # the position is a repetition ################################################################################ proc repetition {} { - set elt [lrange [split [sc_pos fen]] 0 2] - # append the position only if different from the last element - if { $elt != [ lindex $::sergame::lFen end ] } { - lappend ::sergame::lFen $elt - } - - if { [llength [lsearch -all $::sergame::lFen $elt] ] >=3 } { + global ::sergame::_Data + lassign [checkRepetition $_Data(lFen)] isRepetition _Data(lFen) + if { $isRepetition } { tk_messageBox -type ok -message $::tr(Draw) -parent .main -icon info - puts $::sergame::lFen + sc_game tags set -result = + ::sergame::abortGame return 1 } return 0 @@ -663,12 +785,68 @@ namespace eval sergame { ################################################################################ # ################################################################################ - proc logEngine {n text} { - if {$::uci::uciInfo(log_stdout$n)} { - puts stdout "$n $text" - } - } - + set openingList [ list \ + "$::tr(Reti): 1.Nf3" \ + "$::tr(English): 1.c4" \ + "$::tr(d4Nf6Miscellaneous): 1.d4 Nf6" \ + "$::tr(Trompowsky): 1.d4 Nf6 2.Bg5" \ + "$::tr(Budapest): 1.d4 Nf6 2.c4 e5" \ + "$::tr(OldIndian): 1.d4 Nf6 2.c4 d6" \ + "$::tr(BenkoGambit): 1.d4 Nf6 2.c4 c5 3.d5 b5" \ + "$::tr(ModernBenoni): 1.d4 Nf6 2.c4 c5 3.d5 e6" \ + "$::tr(DutchDefence): 1.d4 f5" \ + "1.e4" \ + "$::tr(Scandinavian): 1.e4 d5" \ + "$::tr(AlekhineDefence): 1.e4 Nf6" \ + "$::tr(Pirc): 1.e4 d6" \ + "$::tr(CaroKann): 1.e4 c6" \ + "$::tr(CaroKannAdvance): 1.e4 c6 2.d4 d5 3.e5" \ + "$::tr(Sicilian): 1.e4 c5" \ + "$::tr(SicilianAlapin): 1.e4 c5 2.c3" \ + "$::tr(SicilianClosed): 1.e4 c5 2.Nc3" \ + "$::tr(Sicilian): 1.e4 c5 2.Nf3 Nc6" \ + "$::tr(Sicilian): 1.e4 c5 2.Nf3 e6" \ + "$::tr(SicilianRauzer): 1.e4 c5 2.Nf3 d6 3.d4 cxd4 4.Nxd4 Nf6 5.Nc3 Nc6" \ + "$::tr(SicilianDragon): 1.e4 c5 2.Nf3 d6 3.d4 cxd4 4.Nxd4 Nf6 5.Nc3 g6 " \ + "$::tr(SicilianScheveningen): 1.e4 c5 2.Nf3 d6 3.d4 cxd4 4.Nxd4 Nf6 5.Nc3 e6" \ + "$::tr(SicilianNajdorf): 1.e4 c5 2.Nf3 d6 3.d4 cxd4 4.Nxd4 Nf6 5.Nc3 a6" \ + "$::tr(OpenGame): 1.e4 e5" \ + "$::tr(Vienna): 1.e4 e5 2.Nc3" \ + "$::tr(KingsGambit): 1.e4 e5 2.f4" \ + "$::tr(RussianGame): 1.e4 e5 2.Nf3 Nf6" \ + "$::tr(OpenGame): 1.e4 e5 2.Nf3 Nc6" \ + "$::tr(ItalianTwoKnights): 1.e4 e5 2.Nf3 Nc6 3.Bc4" \ + "$::tr(Spanish): 1.e4 e5 2.Nf3 Nc6 3.Bb5" \ + "$::tr(SpanishExchange): 1.e4 e5 2.Nf3 Nc6 3.Bb5 a6 4.Bxc6" \ + "$::tr(SpanishOpen): 1.e4 e5 2.Nf3 Nc6 3.Bb5 a6 4.Ba4 Nf6 5.O-O Nxe4" \ + "$::tr(SpanishClosed): 1.e4 e5 2.Nf3 Nc6 3.Bb5 a6 4.Ba4 Nf6 5.O-O Be7" \ + "$::tr(FrenchDefence): 1.e4 e6" \ + "$::tr(FrenchAdvance): 1.e4 e6 2.d4 d5 3.e5" \ + "$::tr(FrenchTarrasch): 1.e4 e6 2.d4 d5 3.Nd2" \ + "$::tr(FrenchWinawer): 1.e4 e6 2.d4 d5 3.Nc3 Bb4" \ + "$::tr(FrenchExchange): 1.e4 e6 2.d4 d5 3.exd5 exd5" \ + "$::tr(QueensPawn): 1.d4 d5" \ + "$::tr(Slav): 1.d4 d5 2.c4 c6" \ + "$::tr(QGA): 1.d4 d5 2.c4 dxc4" \ + "$::tr(QGD): 1.d4 d5 2.c4 e6" \ + "$::tr(QGDExchange): 1.d4 d5 2.c4 e6 3.cxd5 exd5" \ + "$::tr(SemiSlav): 1.d4 d5 2.c4 e6 3.Nc3 Nf6 4.Nf3 c6" \ + "$::tr(QGDwithBg5): 1.d4 d5 2.c4 e6 3.Nc3 Nf6 4.Bg5" \ + "$::tr(QGDOrthodox): 1.d4 d5 2.c4 e6 3.Nc3 Nf6 4.Bg5 Be7 5.e3 O-O 6.Nf3 Nbd7" \ + "$::tr(Grunfeld): 1.d4 Nf6 2.c4 g6 3.Nc3 d5" \ + "$::tr(GrunfeldExchange): 1.d4 Nf6 2.c4 g6 3.Nc3 d5 4.cxd5" \ + "$::tr(GrunfeldRussian): 1.d4 Nf6 2.c4 g6 3.Nc3 d5 4.Nf3 Bg7 5.Qb3" \ + "$::tr(Catalan): 1.d4 Nf6 2.c4 e6 3.g3 " \ + "$::tr(CatalanOpen): 1.d4 Nf6 2.c4 e6 3.g3 d5 4.Bg2 dxc4" \ + "$::tr(CatalanClosed): 1.d4 Nf6 2.c4 e6 3.g3 d5 4.Bg2 Be7" \ + "$::tr(QueensIndian): 1.d4 Nf6 2.c4 e6 3.Nf3 b6" \ + "$::tr(NimzoIndian): 1.d4 Nf6 2.c4 e6 3.Nc3 Bb4" \ + "$::tr(NimzoIndianClassical): 1.d4 Nf6 2.c4 e6 3.Nc3 Bb4 4.Qc2" \ + "$::tr(NimzoIndianRubinstein): 1.d4 Nf6 2.c4 e6 3.Nc3 Bb4 4.e3" \ + "$::tr(KingsIndian): 1.d4 Nf6 2.c4 g6" \ + "$::tr(KingsIndianSamisch): 1.d4 Nf6 2.c4 g6 4.e4 d6 5.f3" \ + "$::tr(KingsIndianMainLine): 1.d4 Nf6 2.c4 g6 4.e4 d6 5.Nf3" \ + ] } ### ### End of file: sergame.tcl diff --git a/tcl/windows/book.tcl b/tcl/windows/book.tcl index e411f0bb8..42fda9246 100644 --- a/tcl/windows/book.tcl +++ b/tcl/windows/book.tcl @@ -5,6 +5,27 @@ ###################################################################### ### Book window +# return index of actBook and a list of all books, return -1 if no books available +proc getBookList { actBook } { + set bookPath $::scidBooksDir + set bookList [ lsort -dictionary [ glob -nocomplain -directory $bookPath *.bin ] ] + # No book found + if { [llength $bookList] == 0 } { + return [list -1 {}] + } + set tmp {} + set idx 0 + set i 0 + foreach file $bookList { + lappend tmp [ file tail $file ] + if {$actBook == [ file tail $file ] } { + set idx $i + } + incr i + } + return [list $idx $tmp] +} + namespace eval book { set isOpen 0 set isReadonly 0 @@ -105,29 +126,15 @@ namespace eval book { if { $name == "" && $lastBook != "" } { set name $lastBook } - set bookPath $::scidBooksDir - set bookList [ lsort -dictionary [ glob -nocomplain -directory $bookPath *.bin ] ] - + lassign [getBookList $name] idx tmp # No book found - if { [llength $bookList] == 0 } { + if { $idx < 0 } { tk_messageBox -title "Scid" -type ok -icon error -message "No books found. Check books directory" set ::book::isOpen 0 set ::book::currentBook "" ::win::closeWindow $w return } - - set i 0 - set idx 0 - set tmp {} - foreach file $bookList { - set f [ file tail $file ] - lappend tmp $f - if {$name == $f} { - set idx $i - } - incr i - } ttk::combobox $w.f.combo -width 12 -values $tmp catch { $w.f.combo current $idx } @@ -270,11 +277,8 @@ namespace eval book { ttk::frame $w.f applyThemeColor_background $w # load book names - set bookPath $::scidBooksDir - set bookList [ lsort -dictionary [ glob -nocomplain -directory $bookPath *.bin ] ] - - # No book found - if { [llength $bookList] == 0 } { + lassign [getBookList $name] idx tmp + if { $idx < 0 } { tk_messageBox -title "Scid" -type ok -icon error -message "No books found. Check books directory" set ::book::isOpen 0 set ::book::currentBook "" @@ -282,18 +286,6 @@ namespace eval book { return } - set i 0 - set idx 0 - set tmp {} - foreach file $bookList { - set f [ file tail $file ] - lappend tmp $f - if {$name == $f} { - set idx $i - } - incr i - } - ttk::combobox $w.fcombo.combo -width 12 -values $tmp catch { $w.fcombo.combo current $idx } pack $w.fcombo.combo -expand yes -fill x