--- analysis.c.orig Thu Jul 4 21:57:20 2002 +++ analysis.c Tue Jul 23 19:24:02 2002 @@ -131,8 +131,7 @@ return LUCK_NONE; } -static skilltype Skill( float r ) { - +extern skilltype Skill( float r ) { /* FIXME if the move is correct according to the selected evaluator but incorrect according to 0-ply, then return SKILL_GOOD or SKILL_VERYGOOD */ @@ -1586,3 +1585,5 @@ return 5; } + + --- analysis.h.orig Mon Jul 1 21:02:25 2002 +++ analysis.h Tue Jul 23 19:24:02 2002 @@ -105,4 +105,7 @@ extern float getMWCFromError ( const statcontext *psc, float *ar ); +extern skilltype +Skill( float r ); + #endif --- backgammon.h.orig Thu Jul 4 22:11:28 2002 +++ backgammon.h Tue Jul 23 19:24:21 2002 @@ -708,4 +708,12 @@ CommandSwapPlayers ( char * ), CommandTake( char * ), CommandTrainTD( char * ); + + +extern int fTutor; +extern void CommandSetTutor( char * ), + CommandShowTutor( char * ); + +extern int GiveAdvice ( skilltype Skill ); + #endif --- gnubg.c.orig Thu Jul 4 22:00:54 2002 +++ gnubg.c Tue Jul 23 19:24:33 2002 @@ -175,6 +176,7 @@ int fAutoAnalysis = FALSE; int fInvertMET = FALSE; int fConfirmSave = TRUE; +int fTutor = FALSE; char aaszPaths[ PATH_MET + 1 ][ 2 ][ 255 ]; char *szCurrentFileName = NULL; @@ -974,6 +976,8 @@ N_("Control training parameters"), NULL, acSetTraining }, { "turn", CommandSetTurn, N_("Set which player is on roll"), szPLAYER, &cPlayer }, + { "tutor", CommandSetTutor, N_("Give Advice on moves and cube play"), + szONOFF, &cOnOff }, { NULL, NULL, NULL, NULL, NULL } }, acShowStatistics[] = { { "game", CommandShowStatisticsGame, @@ -1067,6 +1071,8 @@ { "version", CommandShowVersion, N_("Describe this version of GNU Backgammon"), NULL, NULL }, + { "tutor", CommandShowTutor, N_("Give Advice on Moves"), + NULL, NULL }, { "warranty", CommandShowWarranty, N_("Various kinds of warranty you do not have"), NULL, NULL }, { NULL, NULL, NULL, NULL, NULL } @@ -3695,6 +3701,7 @@ fprintf( pf, "set cache %d\n", cCache ); fprintf( pf, "set clockwise %s\n" + "set tutor %s\n" "set confirm new %s\n" "set confirm save %s\n" "set cube use %s\n" @@ -3704,6 +3711,7 @@ "set display %s\n" "set egyptian %s\n", fClockwise ? "on" : "off", + fTutor ? "on" : "off", fConfirm ? "on" : "off", fConfirmSave ? "on" : "off", fCubeUse ? "on" : "off", @@ -5734,4 +5751,71 @@ } +/* ask for confirmation if this is a sub-optimal play + * returns TRUE if player wants to re-think the move + */ + +static int GetAdviceAnswer( char *sz ) { + + char *pch; +#if USE_GTK + if( fX ) + return GtkTutor ( sz ); +#endif + + if( fInterrupt ) + return FALSE; + + while( 1 ) { + pch = GetInput( sz ); + + if( pch ) + switch( *pch ) { + case 'y': + case 'Y': + free( pch ); + return TRUE; + case 'n': + case 'N': + free( pch ); + return FALSE; + default: + free( pch ); + } + + if( fInterrupt ) + return FALSE; + + outputl( _("Please answer `y' or `n'.") ); + } +} + +extern int GiveAdvice( skilltype Skill ) { + + char *sz; + + /* should never happen */ + if ( !fTutor ) + return FALSE; + + switch (Skill) { + + case SKILL_VERYBAD: + sz = _( "You may be about to make a very bad play. Are you sure? " ); + break; + + case SKILL_BAD: + sz = _( "You may be about to make a bad play. Are you sure? " ); + break; + + case SKILL_DOUBTFUL: + sz = _( "You may be about to make a doubtful play. Are you sure? " ); + break; + + default: + return (TRUE); + } + + return GetAdviceAnswer( sz ); +} --- gtkgame.c.orig Tue Jul 23 20:00:30 2002 +++ gtkgame.c Tue Jul 23 20:02:33 2002 @@ -176,6 +176,7 @@ CMD_SET_RNG_USER, CMD_SET_TURN_0, CMD_SET_TURN_1, + CMD_SET_TUTOR, CMD_SHOW_COPYING, CMD_SHOW_ENGINE, CMD_SHOW_EXPORT, @@ -189,6 +190,7 @@ CMD_SHOW_STATISTICS_MATCH, CMD_SHOW_STATISTICS_SESSION, CMD_SHOW_THORP, + CMD_SHOW_TUTOR, CMD_SHOW_VERSION, CMD_SHOW_WARRANTY, CMD_SWAP_PLAYERS, @@ -265,6 +267,7 @@ "set rng user", NULL, /* set turn 0 */ NULL, /* set turn 1 */ + "set tutor", "show copying", "show engine", "show export", @@ -278,6 +281,7 @@ "show statistics match", "show statistics session", "show thorp", + "show tutor", "show version", "show warranty", "swap players", @@ -342,6 +346,7 @@ GtkWidget *pwBoard, *pwMain, *pwMenuBar; static GtkWidget *pwStatus, *pwProgress, *pwGame, *pwGameList, *pom, *pwAnnotation, *pwAnalysis, *pwCommentary, *pwHint, *pwSetCube; +static GtkWidget *pwTutorial; static moverecord *pmrAnnotation; static GtkAccelGroup *pagMain; static GtkStyle *psGameList, *psCurrent; @@ -2279,6 +2284,7 @@ gtk_widget_destroy( gtk_widget_get_toplevel( pw ) ); } + extern GtkWidget *CreateDialog( char *szTitle, int fQuestion, GtkSignalFunc pf, void *p ) { #include "gnu.xpm" @@ -2405,6 +2411,123 @@ return Message( szPrompt, TRUE ); } +/* + * put up a tutor window with a message about how bad the intended + * play is. Allow selections: + * Continue (play it anyway) + * Cancel (rethink) + * returns TRUE if play it anyway + */ + + + +static void TutorEnd( GtkWidget *pw, int *pf ) { + + if( pf ) + *pf = TRUE; + + fTutor = FALSE; + gtk_widget_destroy( gtk_widget_get_toplevel( pw ) ); +} + +extern int GtkTutor ( char *sz ) { + + int f = FALSE, fRestoreNextTurn; + GdkPixmap *ppm; + GtkWidget *pwTutorDialog, *pwOK, *pwCancel, *pwEndTutor, *pwHbox, *pwButtons, + *pwPixmap, *pwPrompt; + GtkAccelGroup *pag; + +#include "gnu.xpm" +#include "question.xpm" + + pwTutorDialog = gtk_dialog_new(); + pwOK = gtk_button_new_with_label( _("Play Anyway") ); + pwCancel = gtk_button_new_with_label( _("Rethink") ); + pwEndTutor = gtk_button_new_with_label ( _("End Tutor Mode") ); + pwHbox = gtk_hbox_new( FALSE, 0 ); + pwButtons = gtk_hbutton_box_new(); + pag = gtk_accel_group_new(); + + ppm = gdk_pixmap_colormap_create_from_xpm_d( NULL, + gtk_widget_get_colormap( pwTutorDialog ), NULL, NULL, + question_xpm); + pwPixmap = gtk_pixmap_new( ppm, NULL ); + gtk_misc_set_padding( GTK_MISC( pwPixmap ), 8, 8 ); + + gtk_button_box_set_layout( GTK_BUTTON_BOX( pwButtons ), + GTK_BUTTONBOX_SPREAD ); + + gtk_box_pack_start( GTK_BOX( pwHbox ), pwPixmap, FALSE, FALSE, 0 ); + gtk_container_add( GTK_CONTAINER( GTK_DIALOG( pwTutorDialog )->vbox ), + pwHbox ); + gtk_container_add( GTK_CONTAINER( GTK_DIALOG( pwTutorDialog )->action_area ), + pwButtons ); + + gtk_container_add( GTK_CONTAINER( pwButtons ), pwOK ); + gtk_signal_connect( GTK_OBJECT( pwOK ), "clicked", + GTK_SIGNAL_FUNC( OK ), (void *) &f ); + + gtk_container_add( GTK_CONTAINER( pwButtons ), pwCancel ); + gtk_signal_connect_object( GTK_OBJECT( pwCancel ), "clicked", + GTK_SIGNAL_FUNC( gtk_widget_destroy ), + GTK_OBJECT( pwTutorDialog ) ); + + gtk_container_add( GTK_CONTAINER( pwButtons ), pwEndTutor ); + gtk_signal_connect( GTK_OBJECT( pwEndTutor ), "clicked", + GTK_SIGNAL_FUNC( TutorEnd ), (void *) &f ); + +#if GTK_CHECK_VERSION(1,3,15) + gtk_window_add_accel_group( GTK_WINDOW( pwTutorDialog ), pag ); +#else + gtk_accel_group_attach( pag, GTK_OBJECT( pwTutorDialog ) ); +#endif + gtk_widget_add_accelerator( pwCancel, "clicked", pag, + GDK_Escape, 0, 0 ); + + gtk_window_set_title( GTK_WINDOW( pwTutorDialog ), + _("GNU Backgammon - Tutor") ); + + GTK_WIDGET_SET_FLAGS( pwOK, GTK_CAN_DEFAULT ); + gtk_widget_grab_default( pwOK ); + + pwPrompt = gtk_label_new( sz ); + + gtk_misc_set_padding( GTK_MISC( pwPrompt ), 8, 8 ); + gtk_label_set_justify( GTK_LABEL( pwPrompt ), GTK_JUSTIFY_LEFT ); + gtk_label_set_line_wrap( GTK_LABEL( pwPrompt ), TRUE ); + gtk_container_add( GTK_CONTAINER( DialogArea( pwTutorDialog, DA_MAIN ) ), + pwPrompt ); + + gtk_window_set_policy( GTK_WINDOW( pwTutorDialog ), FALSE, FALSE, FALSE ); + gtk_window_set_modal( GTK_WINDOW( pwTutorDialog ), TRUE ); + gtk_window_set_transient_for( GTK_WINDOW( pwTutorDialog ), + GTK_WINDOW( pwMain ) ); + gtk_signal_connect( GTK_OBJECT( pwTutorDialog ), "destroy", + GTK_SIGNAL_FUNC( gtk_main_quit ), NULL ); + + gtk_widget_show_all( pwTutorDialog ); + + /* This dialog should be REALLY modal -- disable "next turn" idle + processing and stdin handler, to avoid reentrancy problems. */ + if( ( fRestoreNextTurn = nNextTurn ) ) + gtk_idle_remove( nNextTurn ); + + GTKDisallowStdin(); + gtk_main(); + GTKAllowStdin(); + + if( fRestoreNextTurn ) + nNextTurn = gtk_idle_add( NextTurnNotify, NULL ); + + /* if tutor mode was disabled, update the checklist */ + if ( !fTutor) { + GTKSet ( (void *) &fTutor); + } + + return f; +} + extern void GTKOutput( char *sz ) { if( !sz || !*sz ) @@ -6848,7 +6971,7 @@ typedef struct _optionswidget { GtkWidget *pwAutoAnalyse, *pwAutoBearoff, *pwAutoCrawford, *pwAutoGame, - *pwAutoMove, *pwAutoRoll; + *pwAutoMove, *pwAutoRoll, *pwTutor; GtkAdjustment *padjCubeBeaver, *padjCubeAutomatic; GtkWidget *pwCubeUsecube, *pwCubeJacoby, *pwCubeInvert; GtkWidget *pwGameClockwise, *pwGameNackgammon, *pwGameEgyptian; @@ -6927,6 +7050,9 @@ pow->pwAutoRoll = gtk_check_button_new_with_label (_("Roll")); gtk_box_pack_start (GTK_BOX (pwVBox), pow->pwAutoRoll, FALSE, FALSE, 0); + pow->pwTutor = gtk_check_button_new_with_label (_("Tutor")); + gtk_box_pack_start (GTK_BOX (pwVBox), pow->pwTutor, FALSE, FALSE, 0); + pwFrame = gtk_frame_new (_("Cube")); gtk_box_pack_start (GTK_BOX (pwHBoxMain), pwFrame, TRUE, TRUE, 0); gtk_container_set_border_width (GTK_CONTAINER (pwFrame), 4); @@ -7134,7 +7260,8 @@ CHECKUPDATE(pow->pwAutoRoll,fAutoRoll, "set automatic roll %s") CHECKUPDATE(pow->pwAutoMove,fAutoMove, "set automatic move %s") CHECKUPDATE(pow->pwAutoAnalyse,fAutoAnalysis, "set automatic analysis %s") - + CHECKUPDATE(pow->pwTutor, fTutor, "warn about errors in play %s") + CHECKUPDATE(pow->pwCubeUsecube,fCubeUse, "set cube use %s") CHECKUPDATE(pow->pwCubeJacoby,fJacoby, "set jacoby %s") CHECKUPDATE(pow->pwCubeInvert,fInvertMET, "set invert met %s") @@ -7219,6 +7346,8 @@ fAutoMove ); gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( pow->pwAutoRoll ), fAutoRoll ); + gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( pow->pwTutor ), + fTutor ); gtk_adjustment_set_value ( pow->padjCubeBeaver, nBeavers ); gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( pow->pwCubeUsecube ), --- play.c.orig Thu Jul 4 22:20:33 2002 +++ play.c Tue Jul 23 19:45:58 2002 @@ -135,7 +135,7 @@ } - /* tutor-mode */ + /* auto-analysis-mode */ if ( fAutoAnalysis && f ) { @@ -2105,6 +2105,26 @@ TurnDone(); } +static skilltype ShouldDrop( moverecord *pmr ) { + + int fAnalyseCubeSave = fAnalyseCube; + matchstate msx; + + if ((pmr == 0) || + (pmr->d.mt != MOVE_DROP) || + (ms.gs != GAME_PLAYING) || + !ms.fDoubled || + (ap[ ms.fTurn ].pt != PLAYER_HUMAN )) + return SKILL_NONE; + + fAnalyseCube = TRUE; + memcpy ( &msx, &ms, sizeof ( matchstate ) ); + AnalyzeMove ( pmr, &msx, NULL, FALSE ); + fAnalyseCube = fAnalyseCubeSave; + + return pmr->d.st; +} + extern void CommandDrop( char *sz ) { moverecord *pmr; @@ -2121,10 +2141,6 @@ return; } - if( fDisplay ) - outputf( _("%s refuses the cube and gives up %d point%s.\n"), - ap[ ms.fTurn ].szName, ms.nCube, ms.nCube == 1 ? "" : "s" ); - pmr = malloc( sizeof( pmr->d ) ); pmr->d.mt = MOVE_DROP; pmr->d.sz = NULL; @@ -2132,6 +2148,14 @@ pmr->d.esDouble.et = EVAL_NONE; pmr->d.st = SKILL_NONE; + if ( fTutor && !GiveAdvice ( ShouldDrop ( pmr ) )) + return; + + + if( fDisplay ) + outputf( _("%s refuses the cube and gives up %d point%s.\n"), + ap[ ms.fTurn ].szName, ms.nCube, ms.nCube == 1 ? "" : "s" ); + autoAnalyseMove ( pmr, &ms ); AddMoveRecord( pmr ); @@ -2248,6 +2272,29 @@ /* FIXME */ } +static skilltype GoodMove (movenormal *p) { + + matchstate msx; + int fAnalyseMoveSaved = fAnalyseMove; + moverecord *pmr = (moverecord *) p; + + /* should never happen, but if it does, tutoring + * is a non-starter + */ + if ((pmr == 0) || + (pmr->mt != MOVE_NORMAL) || + (ap[ pmr->n.fPlayer ].pt != PLAYER_HUMAN)) + return SKILL_NONE; + + /* ensure we're analyzing moves */ + fAnalyseMove = 1; + memcpy ( &msx, &ms, sizeof ( matchstate ) ); + AnalyzeMove ( pmr, &msx, NULL, FALSE ); + fAnalyseMove = fAnalyseMoveSaved; + + return pmr->n.stMove; +} + extern void CommandMove( char *sz ) { @@ -2382,7 +2429,10 @@ pmn->stMove = SKILL_NONE; memcpy( pmn->anMove, ml.amMoves[ i ].anMove, sizeof( pmn->anMove ) ); - + + if ( fTutor && !GiveAdvice ( GoodMove( pmn ) )) + return; + #ifdef USE_GTK /* There's no point delaying here. */ if( nTimeout ) { @@ -3030,6 +3080,68 @@ TurnDone(); } +/* evaluate wisdom of not having doubled/redoubled */ + +static skilltype ShouldDouble (void) { + + float arDouble[ 4 ], aarOutput[ 2 ][ NUM_ROLLOUT_OUTPUTS ]; + float aarStdDev[ 2 ][ NUM_ROLLOUT_OUTPUTS ]; + cubeinfo ci; + cubedecision cd; + float rDeltaEquity; + int fAnalyseCubeSave = fAnalyseCube; + + /* reasons that doubling is not an issue */ + if( (ms.gs != GAME_PLAYING) || + ms.anDice[ 0 ] || + ms.fDoubled || + ms.fResigned || + (ap[ ms.fTurn ].pt != PLAYER_HUMAN )) { + + return (SKILL_NONE); + } + + GetMatchStateCubeInfo( &ci, &ms ); + + fAnalyseCube = TRUE; + if( !GetDPEq ( NULL, NULL, &ci ) ) { + fAnalyseCube = fAnalyseCubeSave; + return (SKILL_NONE); + } + + /* Give hint on cube action */ + + ProgressStart( _("Considering cube action...") ); + if ( GeneralCubeDecisionE ( aarOutput, ms.anBoard, &ci, + &esEvalCube.ec ) < 0 ) { + ProgressEnd(); + fAnalyseCube = fAnalyseCubeSave; + return (SKILL_NONE);; + } + ProgressEnd(); + + cd = FindCubeDecision ( arDouble, aarOutput, &ci ); + + switch ( cd ) { + case DOUBLE_TAKE: + case DOUBLE_BEAVER: + case REDOUBLE_TAKE: + + rDeltaEquity = arDouble [OUTPUT_NODOUBLE] - arDouble [OUTPUT_TAKE]; + break; + + case DOUBLE_PASS: + case REDOUBLE_PASS: + + rDeltaEquity = arDouble [OUTPUT_NODOUBLE] - arDouble [OUTPUT_NODOUBLE]; + break; + + default: + return (SKILL_NONE); + } + + return Skill (rDeltaEquity); +} extern void CommandRoll( char *sz ) { @@ -3037,7 +3149,7 @@ movelist ml; movenormal *pmn; moverecord *pmr; - + if( ms.gs != GAME_PLAYING ) { outputl( _("No game in progress (type `new game' to start one).") ); @@ -3069,6 +3181,9 @@ return; } + if ( fTutor && !GiveAdvice( ShouldDouble() )) + return; + if( RollDice( ms.anDice ) < 0 ) return; @@ -3151,6 +3266,26 @@ } +static skilltype ShouldTake ( moverecord *pmr ) { + + int fAnalyseCubeSave = fAnalyseCube; + matchstate msx; + + if ((pmr == 0) || + (pmr->d.mt != MOVE_TAKE) || + (ms.gs != GAME_PLAYING) || + !ms.fDoubled || + ( ap[ ms.fTurn ].pt != PLAYER_HUMAN )) + return SKILL_NONE; + + fAnalyseCube = TRUE; + memcpy ( &msx, &ms, sizeof ( matchstate ) ); + AnalyzeMove ( pmr, &msx, NULL, FALSE ); + fAnalyseCube = fAnalyseCubeSave; + + return pmr->d.st; +} + extern void CommandTake( char *sz ) { moverecord *pmr; @@ -3167,10 +3302,6 @@ return; } - if( fDisplay ) - outputf( _("%s accepts the cube at %d.\n"), ap[ ms.fTurn ].szName, - ms.nCube << 1 ); - pmr = malloc( sizeof( pmr->d ) ); pmr->d.mt = MOVE_TAKE; pmr->d.sz = NULL; @@ -3178,6 +3309,13 @@ pmr->d.esDouble.et = EVAL_NONE; pmr->d.st = SKILL_NONE; + if ( fTutor && !GiveAdvice ( ShouldTake ( pmr ) )) + return; + + if( fDisplay ) + outputf( _("%s accepts the cube at %d.\n"), ap[ ms.fTurn ].szName, + ms.nCube << 1 ); + autoAnalyseMove ( pmr, &ms ); AddMoveRecord( pmr ); --- set.c.orig Sun Jun 30 15:11:19 2002 +++ set.c Tue Jul 23 19:29:02 2002 @@ -2460,3 +2460,11 @@ } +extern void CommandSetTutor( char *sz) { + + SetToggle ("tutor", &fTutor, sz, + _("Warnings are given for \'doubtful\', \'bad\', or " + "\'very bad\' moves."), + _("No warnings are given for \'doubtful\', \'bad\', or " + "\'very bad\' moves.") ); +} --- show.c.orig Sat Jun 15 19:19:21 2002 +++ show.c Tue Jul 23 19:24:56 2002 @@ -1435,3 +1435,14 @@ } } + +extern void CommandShowTutor( char *sz ) { + + if( fTutor ) + outputl( _("Warnings are given for \'doubtful\', \'bad\', or " + "\'very bad\' moves.") ); + else + outputl( _("No warnings are given for \'doubtful\', \'bad\', or " + "\'very bad\' moves.") ); +} +