Implement feature dice
authorH.G.Muller <hgm@hgm-xboard.(none)>
Tue, 26 May 2020 17:23:45 +0000 (19:23 +0200)
committerH.G.Muller <hgm@hgm-xboard.(none)>
Tue, 26 May 2020 17:23:45 +0000 (19:23 +0200)
An engine can now request XBoard to make a 'dice roll', by sending a
command "dice N", where N is the number of faces on the die. XBoard
will reply to such a request with "pips K/N", where K is a random
number 1...N, and N the N of the request. Multiple dice rolls can be
listed in a single dice or pips command, separated by spaces.
  In Two Machines mode only the requests of the engine that is on move
will be honored (as it is assumed that both engines would put in a
request for the same dice roll), but the 'pips' reply will then be
sent to both engines. (So the opponent can verify whether the opponent
did not cheat by specifying wrong N or ignoring K.)
  When playing against a human the engine can request for both sides
to make it easy for the human. The accumulated results of all rolls
during the turn will be shown to the user in the title bar of the board
window, to also exposes cheating by requesting until you like the result.
When the user's roll is displayed, the previous series of rolls (from
the engine's turn) is shown behind it in parentheses, to make sure the
user gets the chance to see it when the engine moves instantly.
  A Boolean feature dice is added, to enable the engine to see if
it is safe to send the 'dice' command (based on acceptance of the
feature), and for the GUI whether it is safe to send the 'pips'
to the opponent of the requestor. On rejection of the feature the engine
would have to use an alternative way (either generate the randoms by
itself, or use 'askuser' to request the user to roll the dice and enter
the result, with no guarantees against cheating by the active party).

backend.c
backend.h

index af59ce3..048fd0a 100644 (file)
--- a/backend.c
+++ b/backend.c
@@ -8938,7 +8938,7 @@ static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
 static ChessProgramState *stalledEngine;
 static char stashedInputMove[MSG_SIZ], abortEngineThink, startPieceToChar[MSG_SIZ];
 static Boolean prelude;
-static char preludeText[MSG_SIZ];
+static char preludeText[MSG_SIZ], diceRoll[MSG_SIZ];
 
 void
 HandleMachineMove (char *message, ChessProgramState *cps)
@@ -9416,6 +9416,31 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
       }
       return;
     }
+    if(!strncmp(message, "dice ", 5)) { // [HGM] dice: provide dice rolls
+      if(gameMode != TwoMachinesPlay || (cps->twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
+       static char previousRoll[MSG_SIZ];
+       char buf[MSG_SIZ], *p = message + 5;
+       int n, k, l;
+       sprintf(buf, "pips");
+       while(sscanf(p, "%d", &n) == 1) {
+         k = (random() + 256*random() & 0xFFFF)*n >> 16;
+         l = strlen(buf);
+         snprintf(buf + l, MSG_SIZ - 1 - l, " %d/%d", k+1, n);
+         while(*++p > ' ') {}
+       }
+       l = strlen(diceRoll);
+       if(l == 0) safeStrCpy(previousRoll, diceRoll+1, MSG_SIZ); // remember series from previous turn
+       snprintf(diceRoll + l, MSG_SIZ - l, "%s", buf + 4);       // accumulate rolls for display
+       strcat(buf, "\n"); SendToProgram(buf, cps);
+       if(gameMode != TwoMachinesPlay) {
+         int human = (gameMode == MachinePlaysWhite) != WhiteOnMove(forwardMostMove);
+         if(human) snprintf(buf, MSG_SIZ, "Your dice roll:%s (mine %s)", diceRoll, previousRoll);
+         else      snprintf(buf, MSG_SIZ, "My dice roll:%s", diceRoll);
+         DisplayTitle(buf);
+       } else if(cps->other->dice) SendToProgram(buf, cps->other);
+      }
+      return;
+    }
     /* [HGM] Allow engine to set up a position. Don't ask me why one would
      * want this, I was asked to put it in, and obliged.
      */
@@ -10852,6 +10877,7 @@ MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
     ChessSquare p = boards[forwardMostMove][toY][toX];
 //    forwardMostMove++; // [HGM] bare: moved downstream
 
+    diceRoll[0] = NULLCHAR; // [HGM] dice: consumed by this move
     if(kill2X >= 0) x = kill2X, y = kill2Y; else
     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
     (void) CoordsToAlgebraic(boards[forwardMostMove],
@@ -12373,6 +12399,7 @@ Reset (int redraw, int init)
     second.maybeThinking = FALSE;
     first.bookSuspend = FALSE; // [HGM] book
     second.bookSuspend = FALSE;
+    diceRoll[0] = NULLCHAR;
     thinkOutput[0] = NULLCHAR;
     lastHint[0] = NULLCHAR; hintSrc = 0;
     ClearGameInfo(&gameInfo);
@@ -17696,6 +17723,7 @@ ParseFeatures (char *args, ChessProgramState *cps)
     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
+    if (BoolFeature(&p, "dice", &cps->dice, cps)) continue; // [HGM] dice
     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
     if (IntFeature(&p, "done", &val, cps)) {
       FeatureDone(cps, val);
index d9392c4..738947d 100644 (file)
--- a/backend.h
+++ b/backend.h
@@ -376,6 +376,7 @@ typedef struct XB_CPS {
     char *egtFormats; /* [HGM] EGT: supported tablebase formats             */
     int bookSuspend;  /* [HGM] book: go was deferred because of book hit    */
     int pause;        /* [HGM] pause: 1=supports it, 2=actually paused      */
+    int dice;         /* [HGM] dice: engine understands pips command        */
     int highlight;    /* [HGM] engine wants to get lift and put commands    */
     int nrOptions;    /* [HGM] options: remembered option="..." features    */
 #define MAX_OPTIONS 200