Beef up variant detection
authorH.G. Muller <h.g.muller@hccnet.nl>
Sun, 20 Oct 2013 13:00:57 +0000 (15:00 +0200)
committerH.G. Muller <h.g.muller@hccnet.nl>
Sun, 22 Dec 2013 22:32:06 +0000 (23:32 +0100)
Testing whether an engine support a variant is now greatly improved.
Names that are substrings of supported variants are no longer recognized,
including base variants the engine only reports with a size-override prefix.
When the size is nonstandard, prefixes are always used on the searched name.
XBoard's New Variant dialog will refuse to close if the size overrides
(even if they specify defaults) do not correspond to a supported variant
of the first engine, but pops up an error box instead. If the problem is
that only one or more non-standard sizes are supported, this box reports
the first of them that is supported.
 GenericReadout() had to be fixed for this, as it was not setting options
that it already thought had the correct value, so that on a second try
the size-override controls were effectively ignored.

backend.c
backend.h
dialogs.c

index 4c9968f..a9b94ad 100644 (file)
--- a/backend.c
+++ b/backend.c
@@ -225,7 +225,7 @@ void DisplayTwoMachinesTitle P(());
 static void ExcludeClick P((int index));
 void ToggleSecond P((void));
 void PauseEngine P((ChessProgramState *cps));
-static int NonStandardBoardSize P((void));
+static int NonStandardBoardSize P((VariantClass v, int w, int h, int s));
 
 #ifdef WIN32
        extern void ConsoleCreate();
@@ -275,6 +275,7 @@ ChessSquare pieceSweep = EmptySquare;
 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
 int promoDefaultAltered;
 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
+static int initPing = -1;
 
 /* States for ics_getting_history */
 #define H_FALSE 0
@@ -8668,7 +8669,8 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
     }
 
     if (!strncmp(message, "setup ", 6) && 
-       (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown || NonStandardBoardSize())
+       (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
+          NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
                                        ) { // [HGM] allow first engine to define opening position
       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
@@ -8799,6 +8801,14 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
        }
     }
     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
+       if(initPing == cps->lastPong) {
+           if(gameInfo.variant == VariantUnknown) {
+               DisplayError(_("Engine did not send setup for non-standard variant"), 0);
+               *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
+               GameEnds(GameUnfinished, NULL, GE_XBOARD);
+           }
+           initPing = -1;
+        }
        return;
     }
     if(!strncmp(message, "highlight ", 10)) {
@@ -10148,40 +10158,74 @@ SendEgtPath (ChessProgramState *cps)
 }
 
 static int
-NonStandardBoardSize ()
-{
-      /* [HGM] Awkward testing. Should really be a table */
-      int overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
-      if( gameInfo.variant == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
-      if( gameInfo.variant == VariantXiangqi )
-           overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
-      if( gameInfo.variant == VariantShogi )
-           overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
-      if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
-           overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
-      if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
-          gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
-           overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
-      if( gameInfo.variant == VariantCourier )
-           overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
-      if( gameInfo.variant == VariantSuper )
-           overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
-      if( gameInfo.variant == VariantGreat )
-           overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
-      if( gameInfo.variant == VariantSChess )
-           overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
-      if( gameInfo.variant == VariantGrand )
-           overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
-      if( gameInfo.variant == VariantChu )
-           overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 12 || gameInfo.holdingsSize != 0;
-      return overruled;
+NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
+{
+      int width = 8, height = 8, holdings = 0;             // most common sizes
+      if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
+      // correct the deviations default for each variant
+      if( v == VariantXiangqi ) width = 9,  height = 10;
+      if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
+      if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
+      if( v == VariantCapablanca || v == VariantCapaRandom ||
+          v == VariantGothic || v == VariantFalcon || v == VariantJanus )
+                                width = 10;
+      if( v == VariantCourier ) width = 12;
+      if( v == VariantSuper )                            holdings = 8;
+      if( v == VariantGreat )   width = 10,              holdings = 8;
+      if( v == VariantSChess )                           holdings = 7;
+      if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
+      if( v == VariantChu )     width = 12, height = 12;
+      return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
+             boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
+             holdingsSize >= 0 && holdingsSize != holdings;
+}
+
+char variantError[MSG_SIZ];
+
+char *
+SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
+{     // returns error message (recognizable by upper-case) if engine does not support the variant
+      char *p, *variant = VariantName(v);
+      static char b[MSG_SIZ];
+      if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
+          snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
+                                               holdingsSize, variant); // cook up sized variant name
+           /* [HGM] varsize: try first if this deviant size variant is specifically known */
+           if(StrStr(list, b) == NULL) {
+               // specific sized variant not known, check if general sizing allowed
+               if(proto != 1 && StrStr(list, "boardsize") == NULL) {
+                   snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
+                            boardWidth, boardHeight, holdingsSize, engine);
+                   return NULL;
+               }
+               /* [HGM] here we really should compare with the maximum supported board size */
+           }
+      } else snprintf(b, MSG_SIZ,"%s", variant);
+      if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
+      p = StrStr(list, b);
+      while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
+      if(p == NULL) {
+          // occurs not at all in list, or only as sub-string
+          snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
+          if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
+              int l = strlen(variantError);
+              char *q;
+              while(p != list && p[-1] != ',') p--;
+              q = strchr(p, ',');
+              if(q) *q = NULLCHAR;
+              snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
+              if(q) *q= ',';
+          }
+          return NULL;
+      }
+      return b;
 }
 
 void
 InitChessProgram (ChessProgramState *cps, int setup)
 /* setup needed to setup FRC opening position */
 {
-    char buf[MSG_SIZ], b[MSG_SIZ];
+    char buf[MSG_SIZ], *b;
     if (appData.noChessProgram) return;
     hintRequested = FALSE;
     bookRequested = FALSE;
@@ -10203,33 +10247,15 @@ InitChessProgram (ChessProgramState *cps, int setup)
     if (gameInfo.variant != VariantNormal &&
        gameInfo.variant != VariantLoadable
         /* [HGM] also send variant if board size non-standard */
-        || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
-                                            ) {
-      char *v = VariantName(gameInfo.variant);
-      if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
-        /* [HGM] in protocol 1 we have to assume all variants valid */
-       snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
-       DisplayFatalError(buf, 0, 1);
+        || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
+
+      b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
+                           gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
+      if (b == NULL) {
+       DisplayFatalError(variantError, 0, 1);
        return;
       }
 
-      if(NonStandardBoardSize()) { /* [HGM] make prefix for non-standard board size. */
-       snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
-                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
-           /* [HGM] varsize: try first if this defiant size variant is specifically known */
-           if(StrStr(cps->variants, b) == NULL) {
-               // specific sized variant not known, check if general sizing allowed
-               if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
-                   if(StrStr(cps->variants, "boardsize") == NULL) {
-                    snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
-                            gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
-                       DisplayFatalError(buf, 0, 1);
-                       return;
-                   }
-                   /* [HGM] here we really should compare with the maximum supported board size */
-               }
-           }
-      } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
       snprintf(buf, MSG_SIZ, "variant %s\n", b);
       SendToProgram(buf, cps);
     }
@@ -10269,7 +10295,7 @@ InitChessProgram (ChessProgramState *cps, int setup)
        SendToProgram("easy\n", cps);
     }
     if (cps->usePing) {
-      snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
+      snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
       SendToProgram(buf, cps);
     }
     cps->initDone = TRUE;
@@ -11308,7 +11334,8 @@ FeedMovesToProgram (ChessProgramState *cps, int upto)
     if(currentlyInitializedVariant != gameInfo.variant) {
       char buf[MSG_SIZ];
         // [HGM] variantswitch: make engine aware of new variant
-       if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
+       if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
+                             gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
                return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
        snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
        SendToProgram(buf, cps);
@@ -14342,7 +14369,8 @@ TwoMachinesEvent P((void))
     }
     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
 
-    if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
+    if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
+                         gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
        startingEngine = FALSE;
        DisplayError("second engine does not play this", 0);
        return;
index 741a9e9..0560a4b 100644 (file)
--- a/backend.h
+++ b/backend.h
@@ -116,6 +116,7 @@ extern Boolean bookUp;
 extern int tinyLayout, smallLayout;
 extern Boolean mcMode;
 extern int dragging;
+extern char variantError[];
 
 void MarkMenuItem P((char *menuRef, int state));
 char *CmailMsg P((void));
@@ -451,6 +452,7 @@ void MoveHistorySet P(( char movelist[][2*MOVE_LEN], int first, int last, int cu
 void MakeEngineOutputTitle P((void));
 void LoadTheme P((void));
 void CreateBookEvent P((void));
+char *SupportedVariant P((char *list, VariantClass v, int w, int h, int s, int proto, char *engine));
 
 /* A point in time */
 typedef struct {
index 1a918ed..9f7eb6b 100644 (file)
--- a/dialogs.c
+++ b/dialogs.c
@@ -184,12 +184,14 @@ GenericReadout (Option *opts, int selected)
                    if(x < opts[i].min) x = opts[i].min;
                    if(opts[i].type == Fractional)
                        *(float*) opts[i].target = x; // engines never have float options!
-                   else if(opts[i].value != x) {
-                       opts[i].value = x;
+                   else {
                        if(currentCps) {
+                         if(opts[i].value != x) { // only to engine if changed
                            snprintf(buf, MSG_SIZ,  "option %s=%.0f\n", opts[i].name, x);
                            SendToProgram(buf, currentCps);
+                         }
                        } else *(int*) opts[i].target = x;
+                       opts[i].value = x;
                    }
                    break;
                case CheckBox:
@@ -406,6 +408,7 @@ OptionsProc ()
 static void Pick P((int n));
 
 static char warning[MSG_SIZ];
+static int ranksTmp, filesTmp, sizeTmp;
 
 static Option variantDescriptors[] = {
 { VariantNormal,        0, 135, NULL, (void*) &Pick, "#FFFFFF", NULL, Button, N_("Normal")},
@@ -421,9 +424,9 @@ static Option variantDescriptors[] = {
 { VariantAtomic,        0, 135, NULL, (void*) &Pick, "#FFFFFF", NULL, Button, N_("Atomic")},
 { VariantTwoKings,SAME_ROW,135, NULL, (void*) &Pick, "#FFFFFF", NULL, Button, N_("Two kings")},
 { 0, 0, 0, NULL, NULL, NULL, NULL, Label, N_("Board size (-1 = default for selected variant):")},
-{ 0, -1, BOARD_RANKS-1, NULL, (void*) &appData.NrRanks, "", NULL, Spin, N_("Number of Board Ranks:") },
-{ 0, -1, BOARD_FILES, NULL, (void*) &appData.NrFiles, "", NULL, Spin, N_("Number of Board Files:") },
-{ 0, -1, BOARD_RANKS-1, NULL, (void*) &appData.holdingsSize, "", NULL, Spin, N_("Holdings Size:") },
+{ 0, -1, BOARD_RANKS-1, NULL, (void*) &ranksTmp, "", NULL, Spin, N_("Number of Board Ranks:") },
+{ 0, -1, BOARD_FILES,   NULL, (void*) &filesTmp, "", NULL, Spin, N_("Number of Board Files:") },
+{ 0, -1, BOARD_RANKS-1, NULL, (void*) &sizeTmp,  "", NULL, Spin, N_("Holdings Size:") },
 { 0, 0, 275, NULL, NULL, NULL, NULL, Label, warning },
 { 0, 0, 275, NULL, NULL, NULL, NULL, Label, N_("Variants marked with * can only be played\nwith legality testing off.")},
 { 0, SAME_ROW, 0, NULL, NULL, NULL, NULL, Break, ""},
@@ -471,27 +474,28 @@ Pick (int n)
 {
        VariantClass v = variantDescriptors[n].value;
        if(v == VariantUnknown) safeStrCpy(engineVariant, variantDescriptors[n].name, MSG_SIZ); else *engineVariant = NULLCHAR;
+       GenericReadout(variantDescriptors, -1); // read new ranks and file settings
        if(!appData.noChessProgram) {
-           char *name = VariantName(v), buf[MSG_SIZ];
-           if (first.protocolVersion > 1 && StrStr(first.variants, name) == NULL) {
-               /* [HGM] in protocol 2 we check if variant is suported by engine */
-             snprintf(buf, MSG_SIZ,  _("Variant %s not supported by %s"), name, first.tidy);
-               DisplayError(buf, 0);
+           char buf[MSG_SIZ];
+           if (!SupportedVariant(first.variants, v, filesTmp, ranksTmp, sizeTmp, first.protocolVersion, first.tidy)) {
+               DisplayError(variantError, 0);
                return; /* ignore OK if first engine does not support it */
            } else
-           if (second.initDone && second.protocolVersion > 1 && StrStr(second.variants, name) == NULL) {
-             snprintf(buf, MSG_SIZ,  _("Warning: second engine (%s) does not support this!"), second.tidy);
+           if (second.initDone &&
+               !SupportedVariant(second.variants, v, filesTmp, ranksTmp, sizeTmp, second.protocolVersion, second.tidy)) {
+                snprintf(buf, MSG_SIZ,  _("Warning: second engine (%s) does not support this!"), second.tidy);
                DisplayError(buf, 0);   /* use of second engine is optional; only warn user */
            }
        }
 
-       GenericReadout(variantDescriptors, -1); // make sure ranks and file settings are read
-
        gameInfo.variant = v;
        appData.variant = VariantName(v);
 
        shuffleOpenings = FALSE; /* [HGM] shuffle: possible shuffle reset when we switch */
        startedFromPositionFile = FALSE; /* [HGM] loadPos: no longer valid in new variant */
+       appData.NrRanks = ranksTmp;
+       appData.NrFiles = filesTmp;
+       appData.holdingsSize = sizeTmp;
        appData.pieceToCharTable = NULL;
        appData.pieceNickNames = "";
        appData.colorNickNames = "";
@@ -505,6 +509,8 @@ NewVariantProc ()
 {
    static int start;
    int i, last;
+   char buf[MSG_SIZ];
+   ranksTmp = filesTmp = sizeTmp = -1; // prefer defaults over actual settings
    if(appData.noChessProgram) sprintf(warning, _("Only bughouse is not available in viewer mode.")); else
    sprintf(warning, _("All variants not supported by the first engine\n(currently %s) are disabled."), first.tidy);
    if(!start) while(variantDescriptors[start].type != -1) start++; // locate first spare
@@ -521,7 +527,9 @@ NewVariantProc ()
        ASSIGN(variantDescriptors[start+last+1].name, " ");
        variantDescriptors[start+last+1].type = Button;
    }
+   safeStrCpy(buf, engineVariant, MSG_SIZ); *engineVariant = NULLCHAR; // yeghh...
    GenericPopUp(variantDescriptors, _("New Variant"), TransientDlg, BoardWindow, MODAL, 0);
+   safeStrCpy(engineVariant, buf, MSG_SIZ); // must temporarily clear to avoid enabling all variant buttons
 }
 
 //------------------------------------------- Common Engine Options -------------------------------------