Fix display of uninitialized boards in background observe
[xboard.git] / backend.c
index 8c76afd..7a3c731 100644 (file)
--- a/backend.c
+++ b/backend.c
@@ -5,7 +5,7 @@
  * Massachusetts.
  *
  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
- * 2007, 2008, 2009 Free Software Foundation, Inc.
+ * 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
  *
  * Enhancements Copyright 2005 Alessandro Scotti
  *
@@ -167,6 +167,7 @@ int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
                   /*char*/int promoChar));
 void BackwardInner P((int target));
 void ForwardInner P((int target));
+int Adjudicate P((ChessProgramState *cps));
 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
 void EditPositionDone P((Boolean fakeRights));
 void PrintOpponents P((FILE *fp));
@@ -187,8 +188,9 @@ void DisplayMove P((int moveNumber));
 
 void ParseGameHistory P((char *game));
 void ParseBoard12 P((char *string));
+void KeepAlive P((void));
 void StartClocks P((void));
-void SwitchClocks P((void));
+void SwitchClocks P((int nr));
 void StopClocks P((void));
 void ResetClocks P((void));
 char *PGNDate P((void));
@@ -240,10 +242,17 @@ int endPV = -1;
 static int exiting = 0; /* [HGM] moved to top */
 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
+Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
+Boolean partnerBoardValid = 0;
+char partnerStatus[MSG_SIZ];
+Boolean partnerUp;
+Boolean originalFlip;
+Boolean twoBoards = 0;
 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
+Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
 int opponentKibitzes;
 int lastSavedGame; /* [HGM] save: ID of game */
 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
@@ -356,6 +365,7 @@ PosFlags(index)
   case VariantNoCastle:
   case VariantShatranj:
   case VariantCourier:
+  case VariantMakruk:
     flags &= ~F_ALL_CASTLE_OK;
     break;
   default:
@@ -489,10 +499,10 @@ ChessSquare  KnightmateArray[2][BOARD_FILES] = {
 };
 
 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
-    { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
+    { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
-    { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
-       BlackKing, BlackBishop, BlackKnight, BlackRook }
+    { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
+       BlackKing, BlackMarshall, BlackAlfil, BlackLance }
 };
 
 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
@@ -502,6 +512,13 @@ ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatr
         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
 };
 
+ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
+    { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
+        WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
+    { BlackRook, BlackKnight, BlackMan, BlackFerz,
+        BlackKing, BlackMan, BlackKnight, BlackRook }
+};
+
 
 #if (BOARD_FILES>=10)
 ChessSquare ShogiArray[2][BOARD_FILES] = {
@@ -888,6 +905,7 @@ InitBackEnd1()
       case VariantAtomic:     /* should work except for win condition */
       case Variant3Check:     /* should work except for win condition */
       case VariantShatranj:   /* should work except for all win conditions */
+      case VariantMakruk:     /* should work except for daw countdown */
       case VariantBerolina:   /* might work if TestLegality is off */
       case VariantCapaRandom: /* should work */
       case VariantJanus:      /* should work */
@@ -1112,6 +1130,8 @@ InitBackEnd3 P((void))
          AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
        fromUserISR =
          AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
+       if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
+           ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
     } else if (appData.noChessProgram) {
        SetNCPMode();
     } else {
@@ -1431,6 +1451,8 @@ read_from_player(isr, closure, message, count, error)
 void
 KeepAlive()
 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
+    if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
+    connectionAlive = FALSE; // only sticks if no response to 'date' command.
     SendToICS("date\n");
     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
 }
@@ -2048,6 +2070,204 @@ static int player2Rating = -1;
 ColorClass curColor = ColorNormal;
 int suppressKibitz = 0;
 
+// [HGM] seekgraph
+Boolean soughtPending = FALSE;
+Boolean seekGraphUp;
+#define MAX_SEEK_ADS 200
+#define SQUARE 0x80
+char *seekAdList[MAX_SEEK_ADS];
+int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
+float tcList[MAX_SEEK_ADS];
+char colorList[MAX_SEEK_ADS];
+int nrOfSeekAds = 0;
+int minRating = 1010, maxRating = 2800;
+int hMargin = 10, vMargin = 20, h, w;
+extern int squareSize, lineGap;
+
+void
+PlotSeekAd(int i)
+{
+       int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
+       xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
+       if(r < minRating+100 && r >=0 ) r = minRating+100;
+       if(r > maxRating) r = maxRating;
+       if(tc < 1.) tc = 1.;
+       if(tc > 95.) tc = 95.;
+       x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
+       y = ((double)r - minRating)/(maxRating - minRating)
+           * (h-vMargin-squareSize/8-1) + vMargin;
+       if(ratingList[i] < 0) y = vMargin + squareSize/4;
+       if(strstr(seekAdList[i], " u ")) color = 1;
+       if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
+          !strstr(seekAdList[i], "bullet") &&
+          !strstr(seekAdList[i], "blitz") &&
+          !strstr(seekAdList[i], "standard") ) color = 2;
+       if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
+       DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
+}
+
+void
+AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
+{
+       char buf[MSG_SIZ], *ext = "";
+       VariantClass v = StringToVariant(type);
+       if(strstr(type, "wild")) {
+           ext = type + 4; // append wild number
+           if(v == VariantFischeRandom) type = "chess960"; else
+           if(v == VariantLoadable) type = "setup"; else
+           type = VariantName(v);
+       }
+       sprintf(buf, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
+       if(nrOfSeekAds < MAX_SEEK_ADS-1) {
+           if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
+           ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
+           sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
+           tcList[nrOfSeekAds] = base + (2./3.)*inc;
+           seekNrList[nrOfSeekAds] = nr;
+           zList[nrOfSeekAds] = 0;
+           seekAdList[nrOfSeekAds++] = StrSave(buf);
+           if(plot) PlotSeekAd(nrOfSeekAds-1);
+       }
+}
+
+void
+EraseSeekDot(int i)
+{
+    int x = xList[i], y = yList[i], d=squareSize/4, k;
+    DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
+    if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
+    // now replot every dot that overlapped
+    for(k=0; k<nrOfSeekAds; k++) if(k != i) {
+       int xx = xList[k], yy = yList[k];
+       if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
+           DrawSeekDot(xx, yy, colorList[k]);
+    }
+}
+
+void
+RemoveSeekAd(int nr)
+{
+       int i;
+       for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
+           EraseSeekDot(i);
+           if(seekAdList[i]) free(seekAdList[i]);
+           seekAdList[i] = seekAdList[--nrOfSeekAds];
+           seekNrList[i] = seekNrList[nrOfSeekAds];
+           ratingList[i] = ratingList[nrOfSeekAds];
+           colorList[i]  = colorList[nrOfSeekAds];
+           tcList[i] = tcList[nrOfSeekAds];
+           xList[i]  = xList[nrOfSeekAds];
+           yList[i]  = yList[nrOfSeekAds];
+           zList[i]  = zList[nrOfSeekAds];
+           seekAdList[nrOfSeekAds] = NULL;
+           break;
+       }
+}
+
+Boolean
+MatchSoughtLine(char *line)
+{
+    char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
+    int nr, base, inc, u=0; char dummy;
+
+    if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
+       sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
+       (u=1) &&
+       (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
+        sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
+       // match: compact and save the line
+       AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
+       return TRUE;
+    }
+    return FALSE;
+}
+
+int
+DrawSeekGraph()
+{
+    if(!seekGraphUp) return FALSE;
+    int i;
+    h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
+    w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
+
+    DrawSeekBackground(0, 0, w, h);
+    DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
+    DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
+    for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
+       int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
+       yy = h-1-yy;
+       DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
+       if(i%500 == 0) {
+           char buf[MSG_SIZ];
+           sprintf(buf, "%d", i);
+           DrawSeekText(buf, hMargin+squareSize/8+7, yy);
+       }
+    }
+    DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
+    for(i=1; i<100; i+=(i<10?1:5)) {
+       int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
+       DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
+       if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
+           char buf[MSG_SIZ];
+           sprintf(buf, "%d", i);
+           DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
+       }
+    }
+    for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
+    return TRUE;
+}
+
+int SeekGraphClick(ClickType click, int x, int y, int moving)
+{
+    static int lastDown = 0, displayed = 0, lastSecond;
+    if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
+       if(click == Release || moving) return FALSE;
+       nrOfSeekAds = 0;
+       soughtPending = TRUE;
+       SendToICS(ics_prefix);
+       SendToICS("sought\n"); // should this be "sought all"?
+    } else { // issue challenge based on clicked ad
+       int dist = 10000; int i, closest = 0, second = 0;
+       for(i=0; i<nrOfSeekAds; i++) {
+           int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
+           if(d < dist) { dist = d; closest = i; }
+           second += (d - zList[i] < 120); // count in-range ads
+           if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
+       }
+       if(dist < 120) {
+           char buf[MSG_SIZ];
+           second = (second > 1);
+           if(displayed != closest || second != lastSecond) {
+               DisplayMessage(second ? "!" : "", seekAdList[closest]);
+               lastSecond = second; displayed = closest;
+           }
+           if(click == Press) {
+               if(moving == 2) zList[closest] = 100; // right-click; push to back on press
+               lastDown = closest;
+               return TRUE;
+           } // on press 'hit', only show info
+           if(moving == 2) return TRUE; // ignore right up-clicks on dot
+           sprintf(buf, "play %d\n", seekNrList[closest]);
+           SendToICS(ics_prefix);
+           SendToICS(buf);
+           return TRUE; // let incoming board of started game pop down the graph
+       } else if(click == Release) { // release 'miss' is ignored
+           zList[lastDown] = 100; // make future selection of the rejected ad more difficult
+           if(moving == 2) { // right up-click
+               nrOfSeekAds = 0; // refresh graph
+               soughtPending = TRUE;
+               SendToICS(ics_prefix);
+               SendToICS("sought\n"); // should this be "sought all"?
+           }
+           return TRUE;
+       } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
+       // press miss or release hit 'pop down' seek graph
+       seekGraphUp = FALSE;
+       DrawPosition(TRUE, NULL);
+    }
+    return TRUE;
+}
+
 void
 read_from_ics(isr, closure, data, count, error)
      InputSourceRef isr;
@@ -2085,6 +2305,8 @@ read_from_ics(isr, closure, data, count, error)
     char talker[MSG_SIZ]; // [HGM] chat
     int channel;
 
+    connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
+
     if (appData.debugMode) {
       if (!error) {
        fprintf(debugFP, "<ICS: ");
@@ -2294,12 +2516,16 @@ read_from_ics(isr, closure, data, count, error)
                  sprintf(str,
                          "/set-quietly interface %s\n/set-quietly style 12\n",
                          programVersion);
+                 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
+                     strcat(str, "/set-2 51 1\n/set seek 1\n");
                } else if (ics_type == ICS_CHESSNET) {
                  sprintf(str, "/style 12\n");
                } else {
                  strcpy(str, "alias $ @\n$set interface ");
                  strcat(str, programVersion);
                  strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
+                 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
+                     strcat(str, "$iset seekremove 1\n$set seek 1\n");
 #ifdef WIN32
                  strcat(str, "$iset nohighlight 1\n");
 #endif
@@ -2320,6 +2546,7 @@ read_from_ics(isr, closure, data, count, error)
                        sprintf(mess, "%s%s", talker, parse);
                        OutputChatMessage(chattingPartner, mess);
                        chattingPartner = -1;
+                       next_out = i+1; // [HGM] suppress printing in ICS window
                    } else
                    if(!suppressKibitz) // [HGM] kibitz
                        AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
@@ -2343,12 +2570,12 @@ read_from_ics(isr, closure, data, count, error)
                                pvInfoList[forwardMostMove-1].score = 100*score;
                            }
                            OutputKibitz(suppressKibitz, parse);
-                           next_out = i+1; // [HGM] suppress printing in ICS window
                        } else {
                            char tmp[MSG_SIZ];
                            sprintf(tmp, _("your opponent kibitzes: %s"), parse);
                            SendToPlayer(tmp, strlen(tmp));
                        }
+                       next_out = i+1; // [HGM] suppress printing in ICS window
                    }
                    started = STARTED_NONE;
                } else {
@@ -2364,6 +2591,7 @@ read_from_ics(isr, closure, data, count, error)
                    continue;
                }
                started = STARTED_NONE;
+               if(suppressKibitz) next_out = i+1;
            }
 
             /* Kludge to deal with rcmd protocol */
@@ -2421,6 +2649,50 @@ read_from_ics(isr, closure, data, count, error)
              continue;
            }
 
+           oldi = i;
+           // [HGM] seekgraph: recognize sought lines and end-of-sought message
+           if(appData.seekGraph) {
+               if(soughtPending && MatchSoughtLine(buf+i)) {
+                   i = strstr(buf+i, "rated") - buf;
+                   if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
+                   next_out = leftover_start = i;
+                   started = STARTED_CHATTER;
+                   suppressKibitz = TRUE;
+                   continue;
+               }
+               if((gameMode == IcsIdle || gameMode == BeginningOfGame)
+                       && looking_at(buf, &i, "* ads displayed")) {
+                   soughtPending = FALSE;
+                   seekGraphUp = TRUE;
+                   DrawSeekGraph();
+                   continue;
+               }
+               if(appData.autoRefresh) {
+                   if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
+                       int s = (ics_type == ICS_ICC); // ICC format differs
+                       if(seekGraphUp)
+                       AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]), 
+                             star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
+                       looking_at(buf, &i, "*% "); // eat prompt
+                       if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
+                       if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
+                       next_out = i; // suppress
+                       continue;
+                   }
+                   if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
+                       char *p = star_match[0];
+                       while(*p) {
+                           if(seekGraphUp) RemoveSeekAd(atoi(p));
+                           while(*p && *p++ != ' '); // next
+                       }
+                       looking_at(buf, &i, "*% "); // eat prompt
+                       if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
+                       next_out = i;
+                       continue;
+                   }
+               }
+           }
+
            /* skip formula vars */
            if (started == STARTED_NONE &&
                buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
@@ -2429,7 +2701,6 @@ read_from_ics(isr, closure, data, count, error)
              continue;
            }
 
-           oldi = i;
            // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
            if (appData.autoKibitz && started == STARTED_NONE && 
                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
@@ -2438,6 +2709,8 @@ read_from_ics(isr, closure, data, count, error)
                   (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
                    StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
                        suppressKibitz = TRUE;
+                       if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
+                       next_out = i;
                        if((StrStr(star_match[0], gameInfo.white) == star_match[0]
                                && (gameMode == IcsPlayingWhite)) ||
                           (StrStr(star_match[0], gameInfo.black) == star_match[0]
@@ -2452,22 +2725,32 @@ read_from_ics(isr, closure, data, count, error)
                        } 
                        continue;
                } else
-               if(looking_at(buf, &i, "kibitzed to *\n") && atoi(star_match[0])) {
+               if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
+                   looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
+                        && atoi(star_match[0])) {
                    // suppress the acknowledgements of our own autoKibitz
+                   char *p;
+                   if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
+                   if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
                    SendToPlayer(star_match[0], strlen(star_match[0]));
-                   looking_at(buf, &i, "*% "); // eat prompt
+                   if(looking_at(buf, &i, "*% ")) // eat prompt
+                       suppressKibitz = FALSE;
                    next_out = i;
+                   continue;
                }
            } // [HGM] kibitz: end of patch
 
-//if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
-
            // [HGM] chat: intercept tells by users for which we have an open chat window
            channel = -1;
            if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
                                           looking_at(buf, &i, "* whispers:") ||
+                                          looking_at(buf, &i, "* shouts:") ||
+                                          looking_at(buf, &i, "* c-shouts:") ||
+                                          looking_at(buf, &i, "--> * ") ||
                                           looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
-                                          looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
+                                          looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
+                                          looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
+                                          looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
                int p;
                sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
                chattingPartner = -1;
@@ -2475,27 +2758,41 @@ read_from_ics(isr, closure, data, count, error)
                if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
                for(p=0; p<MAX_CHAT; p++) {
                    if(channel == atoi(chatPartner[p])) {
-                   talker[0] = '['; strcat(talker, "]");
+                   talker[0] = '['; strcat(talker, "] ");
+                   Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
                    chattingPartner = p; break;
                    }
                } else
                if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
                for(p=0; p<MAX_CHAT; p++) {
-                   if(!strcmp("WHISPER", chatPartner[p])) {
-                       talker[0] = '['; strcat(talker, "]");
+                   if(!strcmp("whispers", chatPartner[p])) {
+                       talker[0] = '['; strcat(talker, "] ");
+                       chattingPartner = p; break;
+                   }
+               } else
+               if(buf[i-3] == 't' || buf[oldi+2] == '>') // shout, c-shout or it; look if there is a 'shouts' chatbox
+               for(p=0; p<MAX_CHAT; p++) {
+                   if(!strcmp("shouts", chatPartner[p])) {
+                       if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
+                       else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
+                       else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
                        chattingPartner = p; break;
                    }
                }
                if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
                for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
-                   talker[0] = 0;
+                   talker[0] = 0; Colorize(ColorTell, FALSE);
                    chattingPartner = p; break;
                }
                if(chattingPartner<0) i = oldi; else {
+                   Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
+                   if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
+                   if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
                    started = STARTED_COMMENT;
                    parse_pos = 0; parse[0] = NULLCHAR;
-                   savingComment = TRUE;
+                   savingComment = 3 + chattingPartner; // counts as TRUE
                    suppressKibitz = TRUE;
+                   continue;
                }
            } // [HGM] chat: end of patch
 
@@ -2689,6 +2986,8 @@ read_from_ics(isr, closure, data, count, error)
                    memcpy(parse, &buf[oldi], parse_pos);
                    parse[parse_pos] = NULLCHAR;
                    started = STARTED_COMMENT;
+                   if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
+                       chattingPartner = savingComment - 3; // kludge to remember the box
                } else {
                    started = STARTED_CHATTER;
                }
@@ -2856,6 +3155,11 @@ read_from_ics(isr, closure, data, count, error)
            if (looking_at(buf, &i, "% ") ||
                ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
                 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
+               if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
+                   soughtPending = FALSE;
+                   seekGraphUp = TRUE;
+                   DrawSeekGraph();
+               }
                if(suppressKibitz) next_out = i;
                savingComment = FALSE;
                suppressKibitz = 0;
@@ -3051,10 +3355,10 @@ read_from_ics(isr, closure, data, count, error)
                    looking_at(buf, &i, "It is not your move")) {
                    /* Illegal move */
                    if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
-                       currentMove = --forwardMostMove;
+                       currentMove = forwardMostMove-1;
                        DisplayMove(currentMove - 1); /* before DMError */
                        DrawPosition(FALSE, boards[currentMove]);
-                       SwitchClocks();
+                       SwitchClocks(forwardMostMove-1); // [HGM] race
                        DisplayBothClocks();
                    }
                    DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
@@ -3161,6 +3465,7 @@ read_from_ics(isr, closure, data, count, error)
                        ZippyGameStart(whitename, blackname);
                    }
 #endif /*ZIPPY*/
+                   partnerBoardValid = FALSE; // [HGM] bughouse
                    continue;
                }
 
@@ -3201,6 +3506,7 @@ read_from_ics(isr, closure, data, count, error)
                    Reset(TRUE, TRUE);
                }
 #endif /*ZIPPY*/
+               if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
                continue;
            }
 
@@ -3297,8 +3603,8 @@ read_from_ics(isr, closure, data, count, error)
                    if (appData.debugMode)
                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
                                                         parse, currentMove);
-                   if (sscanf(parse, " game %d", &gamenum) == 1 &&
-                       gamenum == ics_gamenum) {
+                   if (sscanf(parse, " game %d", &gamenum) == 1) {
+                     if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
                        if (gameInfo.variant == VariantNormal) {
                           /* [HGM] We seem to switch variant during a game!
                            * Presumably no holdings were displayed, so we have
@@ -3347,9 +3653,22 @@ read_from_ics(isr, closure, data, count, error)
                                    gameInfo.white, white_holding,
                                    gameInfo.black, black_holding);
                        }
-
+                       if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
                         DrawPosition(FALSE, boards[currentMove]);
                        DisplayTitle(str);
+                     } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
+                       sscanf(parse, "game %d white [%s black [%s <- %s",
+                              &gamenum, white_holding, black_holding,
+                              new_piece);
+                        white_holding[strlen(white_holding)-1] = NULLCHAR;
+                        black_holding[strlen(black_holding)-1] = NULLCHAR;
+                        /* [HGM] copy holdings to partner-board holdings area */
+                        CopyHoldings(partnerBoard, white_holding, WhitePawn);
+                        CopyHoldings(partnerBoard, black_holding, BlackPawn);
+                        if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
+                        if(partnerUp) DrawPosition(FALSE, partnerBoard);
+                        if(twoBoards) { partnerUp = 0; flipView = !flipView; DrawPosition(TRUE, boards[currentMove]); } // [HGM] dual: redraw own
+                     }
                    }
                    /* Suppress following prompt */
                    if (looking_at(buf, &i, "*% ")) {
@@ -3499,6 +3818,28 @@ ParseBoard12(string)
        break;
     }
     
+    if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
+        && newGameMode == IcsObserving && appData.bgObserve) {
+      // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
+      for (k = 0; k < ranks; k++) {
+        for (j = 0; j < files; j++)
+          board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
+        if(gameInfo.holdingsWidth > 1) {
+             board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
+             board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
+        }
+      }
+      if(appData.dualBoard) { twoBoards = partnerUp = 1; flipView = !flipView; InitDrawingSizes(-2,0); } // [HGM] dual
+      CopyBoard(partnerBoard, board);
+      if(partnerUp) DrawPosition(FALSE, partnerBoard);
+      if(twoBoards) { partnerUp = 0; flipView = !flipView; DrawPosition(TRUE, boards[currentMove]); } // [HGM] dual: redraw own game!
+      sprintf(partnerStatus, "W: %d:%d B: %d:%d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
+                (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
+      DisplayMessage(partnerStatus, "");
+       partnerBoardValid = TRUE;
+      return;
+    }
+
     /* Modify behavior for initial board display on move listing
        of wild games.
        */
@@ -4017,7 +4358,7 @@ ParseBoard12(string)
   }
     }
 
-   
+
     /* Display the board */
     if (!pausing && !appData.noGUI) {
       
@@ -4027,7 +4368,10 @@ ParseBoard12(string)
             ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
              ClearPremoveHighlights();
 
-      DrawPosition(FALSE, boards[currentMove]);
+      j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
+       if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
+      DrawPosition(j, boards[currentMove]);
+
       DisplayMove(moveNum - 1);
       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
            !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
@@ -4199,7 +4543,7 @@ SendMoveToICS(moveType, fromX, fromY, toX, toY)
       case BlackPromotionChancellor:
       case WhitePromotionArchbishop:
       case BlackPromotionArchbishop:
-        if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
+        if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
             sprintf(user_move, "%c%c%c%c=%c\n",
                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
                PieceToChar(WhiteFerz));
@@ -4232,6 +4576,73 @@ SendMoveToICS(moveType, fromX, fromY, toX, toY)
 }
 
 void
+UploadGameEvent()
+{   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
+    int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
+    static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
+    if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
+       DisplayError("You cannot do this while you are playing or observing", 0);
+       return;
+    }
+    if(gameMode != IcsExamining) { // is this ever not the case?
+       char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
+
+       if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
+           sprintf(command, "match %s", ics_handle);
+       } else { // on FICS we must first go to general examine mode
+           strcpy(command, "examine\nbsetup"); // and specify variant within it with bsetups
+       }
+       if(gameInfo.variant != VariantNormal) {
+           // try figure out wild number, as xboard names are not always valid on ICS
+           for(i=1; i<=36; i++) {
+               sprintf(buf, "wild/%d", i);
+               if(StringToVariant(buf) == gameInfo.variant) break;
+           }
+           if(i<=36 && ics_type == ICS_ICC) sprintf(buf, "%s w%d\n", command, i);
+           else if(i == 22) sprintf(buf, "%s fr\n", command);
+           else sprintf(buf, "%s %s\n", command, VariantName(gameInfo.variant));
+       } else sprintf(buf, "%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
+       SendToICS(ics_prefix);
+       SendToICS(buf);
+       if(startedFromSetupPosition || backwardMostMove != 0) {
+         fen = PositionToFEN(backwardMostMove, NULL);
+         if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
+           sprintf(buf, "loadfen %s\n", fen);
+           SendToICS(buf);
+         } else { // FICS: everything has to set by separate bsetup commands
+           p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
+           sprintf(buf, "bsetup fen %s\n", fen);
+           SendToICS(buf);
+           if(!WhiteOnMove(backwardMostMove)) {
+               SendToICS("bsetup tomove black\n");
+           }
+           i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
+           sprintf(buf, "bsetup wcastle %s\n", castlingStrings[i]);
+           SendToICS(buf);
+           i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
+           sprintf(buf, "bsetup bcastle %s\n", castlingStrings[i]);
+           SendToICS(buf);
+           i = boards[backwardMostMove][EP_STATUS];
+           if(i >= 0) { // set e.p.
+               sprintf(buf, "bsetup eppos %c\n", i+AAA);
+               SendToICS(buf);
+           }
+           bsetup++;
+         }
+       }
+      if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
+           SendToICS("bsetup done\n"); // switch to normal examining.
+    }
+    for(i = backwardMostMove; i<last; i++) {
+       char buf[20];
+       sprintf(buf, "%s\n", parseList[i]);
+       SendToICS(buf);
+    }
+    SendToICS(ics_prefix);
+    SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
+}
+
+void
 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
      int rf, ff, rt, ft;
      char promoChar;
@@ -4315,6 +4726,8 @@ AlphaRank(char *move, int n)
     }
 }
 
+char yy_textstr[8000];
+
 /* Parser for moves from gnuchess, ICS, or user typein box */
 Boolean
 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
@@ -4327,7 +4740,7 @@ ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
     if (appData.debugMode) {
         fprintf(debugFP, "move to parse: %s\n", move);
     }
-    *moveType = yylexstr(moveNum, move);
+    *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
 
     switch (*moveType) {
       case WhitePromotionChancellor:
@@ -4416,7 +4829,7 @@ ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
 
 
 void
-ParsePV(char *pv)
+ParsePV(char *pv, Boolean storeComments)
 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
   int fromX, fromY, toX, toY; char promoChar;
   ChessMove moveType;
@@ -4425,18 +4838,36 @@ ParsePV(char *pv)
 
   endPV = forwardMostMove;
   do {
-    while(*pv == ' ') pv++;
-    if(*pv == '(') pv++; // first (ponder) move can be in parentheses
+    while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
+    if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
 if(appData.debugMode){
-fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv);
+fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, yy_textstr, pv);
 }
     if(!valid && nr == 0 &&
        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ 
         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
+        // Hande case where played move is different from leading PV move
+        CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
+        CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
+        ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
+        if(!CompareBoards(boards[endPV], boards[endPV+2])) {
+          endPV += 2; // if position different, keep this
+          moveList[endPV-1][0] = fromX + AAA;
+          moveList[endPV-1][1] = fromY + ONE;
+          moveList[endPV-1][2] = toX + AAA;
+          moveList[endPV-1][3] = toY + ONE;
+          parseList[endPV-1][0] = NULLCHAR;
+          strcpy(moveList[endPV-2], "_0_0"); // suppress premove highlight on takeback move
+        }
+      }
+    pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
+    if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
+    if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
+    if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
+       valid++; // allow comments in PV
+       continue;
     }
-    while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators
-    if(moveType == Comment) { valid++; continue; } // allow comments in PV
     nr++;
     if(endPV+1 > framePtr) break; // no space, truncate
     if(!valid) break;
@@ -4447,7 +4878,13 @@ fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+
     moveList[endPV-1][1] = fromY + ONE;
     moveList[endPV-1][2] = toX + AAA;
     moveList[endPV-1][3] = toY + ONE;
-    parseList[endPV-1][0] = NULLCHAR;
+    if(storeComments)
+       CoordsToAlgebraic(boards[endPV - 1],
+                            PosFlags(endPV - 1),
+                            fromY, fromX, toY, toX, promoChar,
+                            parseList[endPV - 1]);
+    else
+       parseList[endPV-1][0] = NULLCHAR;
   } while(valid);
   currentMove = endPV;
   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
@@ -4462,16 +4899,19 @@ Boolean
 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
 {
        int startPV;
+       char *p;
 
        if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
        lastX = x; lastY = y;
        while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
        startPV = index;
-      while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
-      index = startPV;
-       while(buf[index] && buf[index] != '\n') index++;
+       while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
+       if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
+       index = startPV;
+       do{ while(buf[index] && buf[index] != '\n') index++;
+       } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
        buf[index] = 0;
-       ParsePV(buf+startPV);
+       ParsePV(buf+startPV, FALSE);
        *start = startPV; *end = index-1;
        return TRUE;
 }
@@ -4481,7 +4921,7 @@ LoadPV(int x, int y)
 { // called on right mouse click to load PV
   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
   lastX = x; lastY = y;
-  ParsePV(lastPV[which]); // load the PV of the thinking engine in the boards array.
+  ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
   return TRUE;
 }
 
@@ -4787,6 +5227,12 @@ InitPosition(redraw)
       nrCastlingRights = 0;
       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
       break;
+    case VariantMakruk:
+      pieces = makrukArray;
+      nrCastlingRights = 0;
+      startedFromSetupPosition = TRUE;
+      SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk"); 
+      break;
     case VariantTwoKings:
       pieces = twoKingsArray;
       break;
@@ -4898,6 +5344,7 @@ InitPosition(redraw)
 
     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
     if(pawnRow < 1) pawnRow = 1;
+    if(gameInfo.variant == VariantMakruk) pawnRow = 2;
 
     /* User pieceToChar list overrules defaults */
     if(appData.pieceToCharTable != NULL)
@@ -5062,6 +5509,8 @@ SendBoard(cps, moveNum)
     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
 }
 
+static int autoQueen; // [HGM] oneclick
+
 int
 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
 {
@@ -5083,6 +5532,8 @@ HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
     if(gameInfo.variant == VariantShogi) {
         promotionZoneSize = 3;
         highestPromotingPiece = (int)WhiteFerz;
+    } else if(gameInfo.variant == VariantMakruk) {
+        promotionZoneSize = 3;
     }
 
     // next weed out all moves that do not touch the promotion zone at all
@@ -5126,11 +5577,11 @@ HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
     }
 
     // we either have a choice what to promote to, or (in Shogi) whether to promote
-    if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
+    if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
        *promoChoice = PieceToChar(BlackFerz);  // no choice
        return FALSE;
     }
-    if(appData.alwaysPromoteToQueen) { // predetermined
+    if(autoQueen) { // predetermined
        if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
             *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
        else *promoChoice = PieceToChar(BlackQueen);
@@ -5271,6 +5722,64 @@ OKToStartUserMove(x, y)
     return TRUE;
 }
 
+Boolean
+OnlyMove(int *x, int *y, Boolean captures) {
+    DisambiguateClosure cl;
+    if (appData.zippyPlay) return FALSE;
+    switch(gameMode) {
+      case MachinePlaysBlack:
+      case IcsPlayingWhite:
+      case BeginningOfGame:
+       if(!WhiteOnMove(currentMove)) return FALSE;
+       break;
+      case MachinePlaysWhite:
+      case IcsPlayingBlack:
+       if(WhiteOnMove(currentMove)) return FALSE;
+       break;
+      default:
+       return FALSE;
+    }
+    cl.pieceIn = EmptySquare; 
+    cl.rfIn = *y;
+    cl.ffIn = *x;
+    cl.rtIn = -1;
+    cl.ftIn = -1;
+    cl.promoCharIn = NULLCHAR;
+    Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
+    if( cl.kind == NormalMove ||
+       cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
+       cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
+       cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
+       cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
+      fromX = cl.ff;
+      fromY = cl.rf;
+      *x = cl.ft;
+      *y = cl.rt;
+      return TRUE;
+    }
+    if(cl.kind != ImpossibleMove) return FALSE;
+    cl.pieceIn = EmptySquare;
+    cl.rfIn = -1;
+    cl.ffIn = -1;
+    cl.rtIn = *y;
+    cl.ftIn = *x;
+    cl.promoCharIn = NULLCHAR;
+    Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
+    if( cl.kind == NormalMove ||
+       cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
+       cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
+       cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
+       cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
+      fromX = cl.ff;
+      fromY = cl.rf;
+      *x = cl.ft;
+      *y = cl.rt;
+      autoQueen = TRUE; // act as if autoQueen on when we click to-square
+      return TRUE;
+    }
+    return FALSE;
+}
+
 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
 int lastLoadGameUseList = FALSE;
@@ -5435,8 +5944,6 @@ UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
          return WhiteDrop; /* Not needed to specify white or black yet */
     }
 
-    userOfferedDraw = FALSE;
-       
     /* [HGM] always test for legality, to get promotion info */
     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
                                          fromY, fromX, toY, toX, promoChar);
@@ -5551,6 +6058,8 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
 
   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
 
+  if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
+
   if (gameMode == BeginningOfGame) {
     if (appData.noChessProgram) {
       gameMode = EditGame;
@@ -5575,6 +6084,12 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
   if (appData.icsActive) {
     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
        gameMode == IcsExamining) {
+      if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
+        SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
+       SendToICS("draw ");
+        SendMoveToICS(moveType, fromX, fromY, toX, toY);
+      }
+      // also send plain move, in case ICS does not understand atomic claims
       SendMoveToICS(moveType, fromX, fromY, toX, toY);
       ics_user_moved = 1;
     }
@@ -5626,6 +6141,8 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
     break;
   }
 
+  userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
+       
   if(bookHit) { // [HGM] book: simulate book reply
        static char bookMove[MSG_SIZ]; // a bit generous?
 
@@ -5706,6 +6223,12 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
     static int second = 0, promotionChoice = 0;
     char promoChoice = NULLCHAR;
 
+    if(appData.seekGraph && appData.icsActive && loggedOn &&
+       (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
+       SeekGraphClick(clickType, xPix, yPix, 0);
+       return;
+    }
+
     if (clickType == Press) ErrorPopDown();
     MarkTargetSquares(1);
 
@@ -5746,7 +6269,10 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
        return;
 
+    autoQueen = appData.alwaysPromoteToQueen;
+
     if (fromX == -1) {
+      if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
        if (clickType == Press) {
            /* First square */
            if (OKToStartUserMove(x, y)) {
@@ -5761,6 +6287,7 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
            }
        }
        return;
+      }
     }
 
     /* fromX != -1 */
@@ -5786,6 +6313,7 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
             !(fromP == BlackKing && toP == BlackRook && frc))) {
            /* Clicked again on same color piece -- changed his mind */
            second = (x == fromX && y == fromY);
+          if(!second || !OnlyMove(&x, &y, TRUE)) {
            if (appData.highlightDragging) {
                SetHighlights(x, y, -1, -1);
            } else {
@@ -5798,6 +6326,7 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
                DragPieceBegin(xPix, yPix);
            }
            return;
+          }
        }
        // ignore clicks on holdings
        if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
@@ -5856,13 +6385,13 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
             
            if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
                n = PieceToNumber(piece - (int)BlackPawn);
-               if(n > gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
+               if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
                boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
                boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
            } else
            if(x == BOARD_RGHT+1 && piece < BlackPawn) {
                n = PieceToNumber(piece);
-               if(n > gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
+               if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
                boards[currentMove][n][BOARD_WIDTH-1] = piece;
                boards[currentMove][n][BOARD_WIDTH-2]++;
            }
@@ -5906,6 +6435,87 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
     }
 }
 
+int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
+{   // front-end-free part taken out of PieceMenuPopup
+    int whichMenu; int xSqr, ySqr;
+
+    if(seekGraphUp) { // [HGM] seekgraph
+       if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
+       if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
+       return -2;
+    }
+
+    if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
+        && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
+       if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
+       if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
+       if(action == Press)   {
+           originalFlip = flipView;
+           flipView = !flipView; // temporarily flip board to see game from partners perspective
+           DrawPosition(TRUE, partnerBoard);
+           DisplayMessage(partnerStatus, "");
+           partnerUp = TRUE;
+       } else if(action == Release) {
+           flipView = originalFlip;
+           DrawPosition(TRUE, boards[currentMove]);
+           partnerUp = FALSE;
+       }
+       return -2;
+    }
+
+    xSqr = EventToSquare(x, BOARD_WIDTH);
+    ySqr = EventToSquare(y, BOARD_HEIGHT);
+    if (action == Release) UnLoadPV(); // [HGM] pv
+    if (action != Press) return -2; // return code to be ignored
+    switch (gameMode) {
+      case IcsExamining:
+       if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
+      case EditPosition:
+       if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
+       if (xSqr < 0 || ySqr < 0) return -1;\r
+       whichMenu = 0; // edit-position menu
+       break;
+      case IcsObserving:
+       if(!appData.icsEngineAnalyze) return -1;
+      case IcsPlayingWhite:
+      case IcsPlayingBlack:
+       if(!appData.zippyPlay) goto noZip;
+      case AnalyzeMode:
+      case AnalyzeFile:
+      case MachinePlaysWhite:
+      case MachinePlaysBlack:
+      case TwoMachinesPlay: // [HGM] pv: use for showing PV
+       if (!appData.dropMenu) {
+         LoadPV(x, y);
+         return 2; // flag front-end to grab mouse events
+       }
+       if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
+           gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
+      case EditGame:
+      noZip:
+       if (xSqr < 0 || ySqr < 0) return -1;
+       if (!appData.dropMenu || appData.testLegality &&
+           gameInfo.variant != VariantBughouse &&
+           gameInfo.variant != VariantCrazyhouse) return -1;
+       whichMenu = 1; // drop menu
+       break;
+      default:
+       return -1;
+    }
+
+    if (((*fromX = xSqr) < 0) ||
+       ((*fromY = ySqr) < 0)) {
+       *fromX = *fromY = -1;
+       return -1;
+    }
+    if (flipView)
+      *fromX = BOARD_WIDTH - 1 - *fromX;
+    else
+      *fromY = BOARD_HEIGHT - 1 - *fromY;
+
+    return whichMenu;
+}
+
 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
 {
 //    char * hint = lastHint;
@@ -5932,308 +6542,19 @@ void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cp
     SetProgramStats( &stats );
 }
 
-char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
-{   // [HGM] book: this routine intercepts moves to simulate book replies
-    char *bookHit = NULL;
-
-    //first determine if the incoming move brings opponent into his book
-    if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
-       bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
-    if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
-    if(bookHit != NULL && !cps->bookSuspend) {
-       // make sure opponent is not going to reply after receiving move to book position
-       SendToProgram("force\n", cps);
-       cps->bookSuspend = TRUE; // flag indicating it has to be restarted
-    }
-    if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
-    // now arrange restart after book miss
-    if(bookHit) {
-       // after a book hit we never send 'go', and the code after the call to this routine
-       // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
-       char buf[MSG_SIZ];
-       if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
-       sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
-       SendToProgram(buf, cps);
-       if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
-    } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
-       SendToProgram("go\n", cps);
-       cps->bookSuspend = FALSE; // after a 'go' we are never suspended
-    } else { // 'go' might be sent based on 'firstMove' after this routine returns
-       if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
-           SendToProgram("go\n", cps); 
-       cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
-    }
-    return bookHit; // notify caller of hit, so it can take action to send move to opponent
-}
-
-char *savedMessage;
-ChessProgramState *savedState;
-void DeferredBookMove(void)
-{
-       if(savedState->lastPing != savedState->lastPong)
-                   ScheduleDelayedEvent(DeferredBookMove, 10);
-       else
-       HandleMachineMove(savedMessage, savedState);
-}
-
-void
-HandleMachineMove(message, cps)
-     char *message;
-     ChessProgramState *cps;
-{
-    char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
-    char realname[MSG_SIZ];
-    int fromX, fromY, toX, toY;
-    ChessMove moveType;
-    char promoChar;
-    char *p;
-    int machineWhite;
-    char *bookHit;
-
-    cps->userError = 0;
-
-FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
-    /*
-     * Kludge to ignore BEL characters
-     */
-    while (*message == '\007') message++;
-
-    /*
-     * [HGM] engine debug message: ignore lines starting with '#' character
-     */
-    if(cps->debug && *message == '#') return;
-
-    /*
-     * Look for book output
-     */
-    if (cps == &first && bookRequested) {
-       if (message[0] == '\t' || message[0] == ' ') {
-           /* Part of the book output is here; append it */
-           strcat(bookOutput, message);
-           strcat(bookOutput, "  \n");
-           return;
-       } else if (bookOutput[0] != NULLCHAR) {
-           /* All of book output has arrived; display it */
-           char *p = bookOutput;
-           while (*p != NULLCHAR) {
-               if (*p == '\t') *p = ' ';
-               p++;
-           }
-           DisplayInformation(bookOutput);
-           bookRequested = FALSE;
-           /* Fall through to parse the current output */
-       }
-    }
-
-    /*
-     * Look for machine move.
-     */
-    if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
-       (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
-    {
-        /* This method is only useful on engines that support ping */
-        if (cps->lastPing != cps->lastPong) {
-         if (gameMode == BeginningOfGame) {
-           /* Extra move from before last new; ignore */
-           if (appData.debugMode) {
-               fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
-           }
-         } else {
-           if (appData.debugMode) {
-               fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
-                       cps->which, gameMode);
-           }
-
-            SendToProgram("undo\n", cps);
-         }
-         return;
-       }
-
-       switch (gameMode) {
-         case BeginningOfGame:
-           /* Extra move from before last reset; ignore */
-           if (appData.debugMode) {
-               fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
-           }
-           return;
-
-         case EndOfGame:
-         case IcsIdle:
-         default:
-           /* Extra move after we tried to stop.  The mode test is
-              not a reliable way of detecting this problem, but it's
-              the best we can do on engines that don't support ping.
-           */
-           if (appData.debugMode) {
-               fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
-                       cps->which, gameMode);
-           }
-           SendToProgram("undo\n", cps);
-           return;
-
-         case MachinePlaysWhite:
-         case IcsPlayingWhite:
-           machineWhite = TRUE;
-           break;
-
-         case MachinePlaysBlack:
-         case IcsPlayingBlack:
-           machineWhite = FALSE;
-           break;
-
-         case TwoMachinesPlay:
-           machineWhite = (cps->twoMachinesColor[0] == 'w');
-           break;
-       }
-       if (WhiteOnMove(forwardMostMove) != machineWhite) {
-           if (appData.debugMode) {
-               fprintf(debugFP,
-                       "Ignoring move out of turn by %s, gameMode %d"
-                       ", forwardMost %d\n",
-                       cps->which, gameMode, forwardMostMove);
-           }
-           return;
-       }
-
-    if (appData.debugMode) { int f = forwardMostMove;
-        fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
-                boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
-                boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
-    }
-        if(cps->alphaRank) AlphaRank(machineMove, 4);
-        if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
-                              &fromX, &fromY, &toX, &toY, &promoChar)) {
-           /* Machine move could not be parsed; ignore it. */
-            sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
-                   machineMove, cps->which);
-           DisplayError(buf1, 0);
-            sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
-                    machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
-           if (gameMode == TwoMachinesPlay) {
-             GameEnds(machineWhite ? BlackWins : WhiteWins,
-                       buf1, GE_XBOARD);
-           }
-           return;
-       }
-
-        /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
-        /* So we have to redo legality test with true e.p. status here,  */
-        /* to make sure an illegal e.p. capture does not slip through,   */
-        /* to cause a forfeit on a justified illegal-move complaint      */
-        /* of the opponent.                                              */
-        if( gameMode==TwoMachinesPlay && appData.testLegality
-            && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
-                                                              ) {
-           ChessMove moveType;
-           moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
-                             fromY, fromX, toY, toX, promoChar);
-           if (appData.debugMode) {
-                int i;
-                for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
-                    boards[forwardMostMove][CASTLING][i], castlingRank[i]);
-                fprintf(debugFP, "castling rights\n");
-           }
-            if(moveType == IllegalMove) {
-                sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
-                        machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
-                GameEnds(machineWhite ? BlackWins : WhiteWins,
-                           buf1, GE_XBOARD);
-               return;
-           } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
-           /* [HGM] Kludge to handle engines that send FRC-style castling
-              when they shouldn't (like TSCP-Gothic) */
-           switch(moveType) {
-             case WhiteASideCastleFR:
-             case BlackASideCastleFR:
-               toX+=2;
-               currentMoveString[2]++;
-               break;
-             case WhiteHSideCastleFR:
-             case BlackHSideCastleFR:
-               toX--;
-               currentMoveString[2]--;
-               break;
-            default: ; // nothing to do, but suppresses warning of pedantic compilers
-           }
-        }
-       hintRequested = FALSE;
-       lastHint[0] = NULLCHAR;
-       bookRequested = FALSE;
-       /* Program may be pondering now */
-       cps->maybeThinking = TRUE;
-       if (cps->sendTime == 2) cps->sendTime = 1;
-       if (cps->offeredDraw) cps->offeredDraw--;
-
-#if ZIPPY
-       if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
-           first.initDone) {
-         SendMoveToICS(moveType, fromX, fromY, toX, toY);
-         ics_user_moved = 1;
-         if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
-               char buf[3*MSG_SIZ];
-
-               sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
-                       programStats.score / 100.,
-                       programStats.depth,
-                       programStats.time / 100.,
-                       (unsigned int)programStats.nodes,
-                       (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
-                       programStats.movelist);
-               SendToICS(buf);
-if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
-         }
-       }
-#endif
-       /* currentMoveString is set as a side-effect of ParseOneMove */
-       strcpy(machineMove, currentMoveString);
-       strcat(machineMove, "\n");
-       strcpy(moveList[forwardMostMove], machineMove);
-
-        /* [AS] Save move info and clear stats for next move */
-        pvInfoList[ forwardMostMove ].score = programStats.score;
-        pvInfoList[ forwardMostMove ].depth = programStats.depth;
-        pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
-        ClearProgramStats();
-        thinkOutput[0] = NULLCHAR;
-        hiddenThinkOutputState = 0;
-
-       MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
-
-        /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
-        if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
-            int count = 0;
-
-            while( count < adjudicateLossPlies ) {
-                int score = pvInfoList[ forwardMostMove - count - 1 ].score;
-
-                if( count & 1 ) {
-                    score = -score; /* Flip score for winning side */
-                }
-
-                if( score > adjudicateLossThreshold ) {
-                    break;
-                }
-
-                count++;
-            }
-
-            if( count >= adjudicateLossPlies ) {
-               ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
-
-                GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
-                    "Xboard adjudication", 
-                    GE_XBOARD );
-
-                return;
-            }
-        }
-
-       if( gameMode == TwoMachinesPlay ) {
-         // [HGM] some adjudications useful with buggy engines
-            int k, count = 0; static int bare = 1;
-         if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
-
-
+int
+Adjudicate(ChessProgramState *cps)
+{      // [HGM] some adjudications useful with buggy engines
+       // [HGM] adjudicate: made into separate routine, which now can be called after every move
+       //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
+       //       Actually ending the game is now based on the additional internal condition canAdjudicate.
+       //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
+       int k, count = 0; static int bare = 1;
+       ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
+       Boolean canAdjudicate = !appData.icsActive;
+
+       // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
+       if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
            if( appData.testLegality )
            {   /* [HGM] Some more adjudications for obstinate engines */
                int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
@@ -6293,12 +6614,13 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
                if(gameInfo.variant == VariantAtomic && NrK < 2) {
                    // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
                     boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
-                    if(appData.checkMates) {
-                        SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
+                    if(canAdjudicate && appData.checkMates) {
+                        if(engineOpponent)
+                          SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
                                                        "Xboard adjudication: King destroyed", GE_XBOARD );
-                         return;
+                         return 1;
                     }
                }
 
@@ -6306,24 +6628,26 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
                 if( NrW == 1 || NrPieces - NrW == 1) {
                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
                     boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
-                    if(appData.checkMates) {
-                        SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
+                    if(canAdjudicate && appData.checkMates) {
+                        if(engineOpponent)
+                          SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
                                                        "Xboard adjudication: Bare king", GE_XBOARD );
-                         return;
+                         return 1;
                     }
                  } else
                   if( gameInfo.variant == VariantShatranj && --bare < 0)
                   {    /* bare King */
                        boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
-                       if(appData.checkMates) {
+                       if(canAdjudicate && appData.checkMates) {
                            /* but only adjudicate if adjudication enabled */
-                           SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
+                           if(engineOpponent)
+                             SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
                            ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
                            GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
                                                        "Xboard adjudication: Bare king", GE_XBOARD );
-                           return;
+                           return 1;
                        }
                  }
                 } else bare = 1;
@@ -6378,11 +6702,12 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
                    default:
                        result = (ChessMove) 0;
                }
-                if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
-                   SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
+                if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
+                   if(engineOpponent)
+                     SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
                    ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
                    GameEnds( result, reason, GE_XBOARD );
-                   return;
+                   return 1;
                }
 
                 /* Next absolutely insufficient mating material. */
@@ -6395,13 +6720,15 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
                      /* always flag draws, for judging claims */
                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
 
-                     if(appData.materialDraws) {
+                     if(canAdjudicate && appData.materialDraws) {
                          /* but only adjudicate them if adjudication enabled */
-                        SendToProgram("force\n", cps->other); // suppress reply
-                        SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
+                        if(engineOpponent) {
+                          SendToProgram("force\n", engineOpponent); // suppress reply
+                          SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
+                        }
                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
-                         return;
+                         return 1;
                      }
                 }
 
@@ -6412,26 +6739,30 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
                    || NrWN==2 || NrBN==2     /* KNNK */
                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
                   ) ) {
-                     if(--moveCount < 0 && appData.trivialDraws)
+                     if(canAdjudicate && --moveCount < 0 && appData.trivialDraws)
                      {    /* if the first 3 moves do not show a tactical win, declare draw */
-                         SendToProgram("force\n", cps->other); // suppress reply
-                         SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
+                         if(engineOpponent) {
+                           SendToProgram("force\n", engineOpponent); // suppress reply
+                           SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
+                         }
                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
-                          return;
+                          return 1;
                      }
                 } else moveCount = 6;
            }
-         }
+       }
          
-         if (appData.debugMode) { int i;
+       if (appData.debugMode) { int i;
            fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
                    forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
                    appData.drawRepeats);
            for( i=forwardMostMove; i>=backwardMostMove; i-- )
              fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
            
-         }
+       }
+
+       // Repetition draws and 50-move rule can be applied independently of legality testing
 
                 /* Check for rep-draws */
                 count = 0;
@@ -6459,11 +6790,13 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
                                    rights++;
                         }
-                        if( rights == 0 && ++count > appData.drawRepeats-2
+                        if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2
                             && appData.drawRepeats > 1) {
                              /* adjudicate after user-specified nr of repeats */
-                            SendToProgram("force\n", cps->other); // suppress reply
-                            SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
+                            if(engineOpponent) {
+                              SendToProgram("force\n", engineOpponent); // suppress reply
+                              SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
+                            }
                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
                             if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
                                // [HGM] xiangqi: check for forbidden perpetuals
@@ -6479,7 +6812,7 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
                                if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
                                    GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
                                           "Xboard adjudication: perpetual checking", GE_XBOARD );
-                                   return;
+                                   return 1;
                                }
                                if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
                                    break; // (or we would have caught him before). Abort repetition-checking loop.
@@ -6490,7 +6823,7 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
                                    if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
                                        GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
                                                      "Xboard adjudication: perpetual chasing", GE_XBOARD );
-                                       return;
+                                       return 1;
                                    }
                                    if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
                                        break; // Abort repetition-checking loop.
@@ -6498,7 +6831,7 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
                                // if neither of us is checking or chasing all the time, or both are, it is draw
                             }
                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
-                             return;
+                             return 1;
                         }
                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
@@ -6517,12 +6850,14 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
                 if( count >= 100)
                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
                          /* this is used to judge if draw claims are legal */
-                if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
-                        SendToProgram("force\n", cps->other); // suppress reply
-                        SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
+                if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
+                        if(engineOpponent) {
+                          SendToProgram("force\n", engineOpponent); // suppress reply
+                          SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
+                        }
                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
-                         return;
+                         return 1;
                 }
 
                 /* if draw offer is pending, treat it as a draw claim
@@ -6530,7 +6865,7 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
                  * claim draws before making their move to avoid a race
                  * condition occurring after their move
                  */
-                if( cps->other->offeredDraw || cps->offeredDraw ) {
+               if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
                          char *p = NULL;
                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
                              p = "Draw claim: 50-move rule";
@@ -6538,26 +6873,332 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
                              p = "Draw claim: 3-fold repetition";
                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
                              p = "Draw claim: insufficient mating material";
-                         if( p != NULL ) {
-                            SendToProgram("force\n", cps->other); // suppress reply
-                            SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
-                             GameEnds( GameIsDrawn, p, GE_XBOARD );
+                         if( p != NULL && canAdjudicate) {
+                            if(engineOpponent) {
+                              SendToProgram("force\n", engineOpponent); // suppress reply
+                              SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
+                            }
                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
-                             return;
+                             GameEnds( GameIsDrawn, p, GE_XBOARD );
+                             return 1;
                          }
                 }
 
-
-               if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
-                   SendToProgram("force\n", cps->other); // suppress reply
-                   SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
+               if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
+                   if(engineOpponent) {
+                     SendToProgram("force\n", engineOpponent); // suppress reply
+                     SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
+                   }
                    ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
-
                    GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
-
-                   return;
+                   return 1;
                }
+       return 0;
+}
+
+char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
+{   // [HGM] book: this routine intercepts moves to simulate book replies
+    char *bookHit = NULL;
+
+    //first determine if the incoming move brings opponent into his book
+    if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
+       bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
+    if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
+    if(bookHit != NULL && !cps->bookSuspend) {
+       // make sure opponent is not going to reply after receiving move to book position
+       SendToProgram("force\n", cps);
+       cps->bookSuspend = TRUE; // flag indicating it has to be restarted
+    }
+    if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
+    // now arrange restart after book miss
+    if(bookHit) {
+       // after a book hit we never send 'go', and the code after the call to this routine
+       // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
+       char buf[MSG_SIZ];
+       if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
+       sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
+       SendToProgram(buf, cps);
+       if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
+    } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
+       SendToProgram("go\n", cps);
+       cps->bookSuspend = FALSE; // after a 'go' we are never suspended
+    } else { // 'go' might be sent based on 'firstMove' after this routine returns
+       if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
+           SendToProgram("go\n", cps); 
+       cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
+    }
+    return bookHit; // notify caller of hit, so it can take action to send move to opponent
+}
+
+char *savedMessage;
+ChessProgramState *savedState;
+void DeferredBookMove(void)
+{
+       if(savedState->lastPing != savedState->lastPong)
+                   ScheduleDelayedEvent(DeferredBookMove, 10);
+       else
+       HandleMachineMove(savedMessage, savedState);
+}
+
+void
+HandleMachineMove(message, cps)
+     char *message;
+     ChessProgramState *cps;
+{
+    char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
+    char realname[MSG_SIZ];
+    int fromX, fromY, toX, toY;
+    ChessMove moveType;
+    char promoChar;
+    char *p;
+    int machineWhite;
+    char *bookHit;
+
+    cps->userError = 0;
+
+FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
+    /*
+     * Kludge to ignore BEL characters
+     */
+    while (*message == '\007') message++;
+
+    /*
+     * [HGM] engine debug message: ignore lines starting with '#' character
+     */
+    if(cps->debug && *message == '#') return;
+
+    /*
+     * Look for book output
+     */
+    if (cps == &first && bookRequested) {
+       if (message[0] == '\t' || message[0] == ' ') {
+           /* Part of the book output is here; append it */
+           strcat(bookOutput, message);
+           strcat(bookOutput, "  \n");
+           return;
+       } else if (bookOutput[0] != NULLCHAR) {
+           /* All of book output has arrived; display it */
+           char *p = bookOutput;
+           while (*p != NULLCHAR) {
+               if (*p == '\t') *p = ' ';
+               p++;
+           }
+           DisplayInformation(bookOutput);
+           bookRequested = FALSE;
+           /* Fall through to parse the current output */
+       }
+    }
+
+    /*
+     * Look for machine move.
+     */
+    if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
+       (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
+    {
+        /* This method is only useful on engines that support ping */
+        if (cps->lastPing != cps->lastPong) {
+         if (gameMode == BeginningOfGame) {
+           /* Extra move from before last new; ignore */
+           if (appData.debugMode) {
+               fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
+           }
+         } else {
+           if (appData.debugMode) {
+               fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
+                       cps->which, gameMode);
+           }
+
+            SendToProgram("undo\n", cps);
+         }
+         return;
+       }
+
+       switch (gameMode) {
+         case BeginningOfGame:
+           /* Extra move from before last reset; ignore */
+           if (appData.debugMode) {
+               fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
+           }
+           return;
+
+         case EndOfGame:
+         case IcsIdle:
+         default:
+           /* Extra move after we tried to stop.  The mode test is
+              not a reliable way of detecting this problem, but it's
+              the best we can do on engines that don't support ping.
+           */
+           if (appData.debugMode) {
+               fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
+                       cps->which, gameMode);
+           }
+           SendToProgram("undo\n", cps);
+           return;
+
+         case MachinePlaysWhite:
+         case IcsPlayingWhite:
+           machineWhite = TRUE;
+           break;
+
+         case MachinePlaysBlack:
+         case IcsPlayingBlack:
+           machineWhite = FALSE;
+           break;
+
+         case TwoMachinesPlay:
+           machineWhite = (cps->twoMachinesColor[0] == 'w');
+           break;
+       }
+       if (WhiteOnMove(forwardMostMove) != machineWhite) {
+           if (appData.debugMode) {
+               fprintf(debugFP,
+                       "Ignoring move out of turn by %s, gameMode %d"
+                       ", forwardMost %d\n",
+                       cps->which, gameMode, forwardMostMove);
+           }
+           return;
+       }
+
+    if (appData.debugMode) { int f = forwardMostMove;
+        fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
+                boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
+                boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
+    }
+        if(cps->alphaRank) AlphaRank(machineMove, 4);
+        if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
+                              &fromX, &fromY, &toX, &toY, &promoChar)) {
+           /* Machine move could not be parsed; ignore it. */
+            sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
+                   machineMove, cps->which);
+           DisplayError(buf1, 0);
+            sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
+                    machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
+           if (gameMode == TwoMachinesPlay) {
+             GameEnds(machineWhite ? BlackWins : WhiteWins,
+                       buf1, GE_XBOARD);
+           }
+           return;
+       }
+
+        /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
+        /* So we have to redo legality test with true e.p. status here,  */
+        /* to make sure an illegal e.p. capture does not slip through,   */
+        /* to cause a forfeit on a justified illegal-move complaint      */
+        /* of the opponent.                                              */
+        if( gameMode==TwoMachinesPlay && appData.testLegality
+            && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
+                                                              ) {
+           ChessMove moveType;
+           moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
+                             fromY, fromX, toY, toX, promoChar);
+           if (appData.debugMode) {
+                int i;
+                for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
+                    boards[forwardMostMove][CASTLING][i], castlingRank[i]);
+                fprintf(debugFP, "castling rights\n");
+           }
+            if(moveType == IllegalMove) {
+                sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
+                        machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
+                GameEnds(machineWhite ? BlackWins : WhiteWins,
+                           buf1, GE_XBOARD);
+               return;
+           } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
+           /* [HGM] Kludge to handle engines that send FRC-style castling
+              when they shouldn't (like TSCP-Gothic) */
+           switch(moveType) {
+             case WhiteASideCastleFR:
+             case BlackASideCastleFR:
+               toX+=2;
+               currentMoveString[2]++;
+               break;
+             case WhiteHSideCastleFR:
+             case BlackHSideCastleFR:
+               toX--;
+               currentMoveString[2]--;
+               break;
+            default: ; // nothing to do, but suppresses warning of pedantic compilers
+           }
         }
+       hintRequested = FALSE;
+       lastHint[0] = NULLCHAR;
+       bookRequested = FALSE;
+       /* Program may be pondering now */
+       cps->maybeThinking = TRUE;
+       if (cps->sendTime == 2) cps->sendTime = 1;
+       if (cps->offeredDraw) cps->offeredDraw--;
+
+       /* currentMoveString is set as a side-effect of ParseOneMove */
+       strcpy(machineMove, currentMoveString);
+       strcat(machineMove, "\n");
+       strcpy(moveList[forwardMostMove], machineMove);
+
+       MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
+
+        /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
+        if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
+            int count = 0;
+
+            while( count < adjudicateLossPlies ) {
+                int score = pvInfoList[ forwardMostMove - count - 1 ].score;
+
+                if( count & 1 ) {
+                    score = -score; /* Flip score for winning side */
+                }
+
+                if( score > adjudicateLossThreshold ) {
+                    break;
+                }
+
+                count++;
+            }
+
+            if( count >= adjudicateLossPlies ) {
+               ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
+
+                GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
+                    "Xboard adjudication", 
+                    GE_XBOARD );
+
+                return;
+            }
+        }
+
+       if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
+
+#if ZIPPY
+       if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
+           first.initDone) {
+         if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
+               SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
+               SendToICS("draw ");
+               SendMoveToICS(moveType, fromX, fromY, toX, toY);
+         }
+         SendMoveToICS(moveType, fromX, fromY, toX, toY);
+         ics_user_moved = 1;
+         if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
+               char buf[3*MSG_SIZ];
+
+               sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
+                       programStats.score / 100.,
+                       programStats.depth,
+                       programStats.time / 100.,
+                       (unsigned int)programStats.nodes,
+                       (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
+                       programStats.movelist);
+               SendToICS(buf);
+if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
+         }
+       }
+#endif
+
+        /* [AS] Save move info and clear stats for next move */
+        pvInfoList[ forwardMostMove-1 ].score = programStats.score;
+        pvInfoList[ forwardMostMove-1 ].depth = programStats.depth;
+        pvInfoList[ forwardMostMove-1 ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
+        ClearProgramStats();
+        thinkOutput[0] = NULLCHAR;
+        hiddenThinkOutputState = 0;
 
        bookHit = NULL;
        if (gameMode == TwoMachinesPlay) {
@@ -6835,9 +7476,9 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
            gameMode = EditGame;
            ModeHighlight();
        }
-       currentMove = --forwardMostMove;
+       currentMove = forwardMostMove-1;
        DisplayMove(currentMove-1); /* before DisplayMoveError */
-       SwitchClocks();
+       SwitchClocks(forwardMostMove-1); // [HGM] race
        DisplayBothClocks();
        sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
                parseList[currentMove], cps->which);
@@ -7548,6 +8189,7 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board)
      Board board;
 {
   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
+  int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
 
     /* [HGM] compute & store e.p. status and castling rights for new position */
     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
@@ -7594,7 +8236,7 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board)
     }
 
   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
-  if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
+  if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
          
   if (fromX == toX && fromY == toY) return;
@@ -7645,7 +8287,7 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board)
         board[toY][toX+1] = board[fromY][BOARD_LEFT];
         board[fromY][BOARD_LEFT] = EmptySquare;
     } else if (board[fromY][fromX] == WhitePawn
-               && toY == BOARD_HEIGHT-1
+               && toY >= BOARD_HEIGHT-promoRank
                && gameInfo.variant != VariantXiangqi
                ) {
        /* white pawn promotion */
@@ -7709,13 +8351,13 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board)
        board[fromY][0] = EmptySquare;
        board[toY][2] = BlackRook;
     } else if (board[fromY][fromX] == BlackPawn
-              && toY == 0
+              && toY < promoRank
                && gameInfo.variant != VariantXiangqi
                ) {
        /* black pawn promotion */
-       board[0][toX] = CharToPiece(ToLower(promoChar));
-       if (board[0][toX] == EmptySquare) {
-           board[0][toX] = BlackQueen;
+       board[toY][toX] = CharToPiece(ToLower(promoChar));
+       if (board[toY][toX] == EmptySquare) {
+           board[toY][toX] = BlackQueen;
        }
         if(gameInfo.variant==VariantBughouse ||
            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
@@ -7909,8 +8551,8 @@ MakeMove(fromX, fromY, toX, toY, promoChar)
     }
     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
-    forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
-    SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
+    // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
+    SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
     gameInfo.result = GameUnfinished;
@@ -8262,12 +8904,15 @@ GameEnds(result, resultDetails, whosays)
 
     if(endingGame) return; /* [HGM] crash: forbid recursion */
     endingGame = 1;
+    if(twoBoards) { twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0); } // [HGM] dual
 
     if (appData.debugMode) {
       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
              result, resultDetails ? resultDetails : "(null)", whosays);
     }
 
+    fromX = fromY = -1; // [HGM] abort any move the user is entering.
+
     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
        /* If we are playing on ICS, the server decides when the
           game is over, but the engine can offer to draw, claim 
@@ -11538,7 +12183,8 @@ EditPositionMenuEvent(selection, x, y)
       case BlackQueen:
         if(gameInfo.variant == VariantShatranj ||
            gameInfo.variant == VariantXiangqi  ||
-           gameInfo.variant == VariantCourier    )
+           gameInfo.variant == VariantCourier  ||
+           gameInfo.variant == VariantMakruk     )
             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
         goto defaultlabel;
 
@@ -11560,13 +12206,13 @@ EditPositionMenuEvent(selection, x, y)
                 int n;
                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
                     n = PieceToNumber(selection - BlackPawn);
-                    if(n > gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
+                    if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
                     boards[0][BOARD_HEIGHT-1-n][1]++;
                 } else
                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
                     n = PieceToNumber(selection);
-                    if(n > gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
+                    if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
                     boards[0][n][BOARD_WIDTH-1] = selection;
                     boards[0][n][BOARD_WIDTH-2]++;
                 }
@@ -11733,6 +12379,7 @@ DrawEvent()
        
         SendToICS(ics_prefix);
        SendToICS("draw\n");
+        userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
     } else if (cmailMsgLoaded) {
        if (currentMove == cmailOldMove &&
            commentList[cmailOldMove] != NULL &&
@@ -12058,9 +12705,9 @@ ToNrEvent(int to)
 }
 
 void
-RevertEvent()
+RevertEvent(Boolean annotate)
 {
-    if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
+    if(PopTail(annotate)) { // [HGM] vari: restore old game tail
        return;
     }
     if (gameMode != IcsExamining) {
@@ -13575,7 +14222,7 @@ DecrementClocks()
    from the color that is *not* on move now.
 */
 void
-SwitchClocks()
+SwitchClocks(int newMoveNr)
 {
     long lastTickLength;
     TimeMark now;
@@ -13585,7 +14232,7 @@ SwitchClocks()
 
     if (StopClockTimer() && appData.clockMode) {
        lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
-       if (WhiteOnMove(forwardMostMove)) {
+       if (!WhiteOnMove(forwardMostMove)) {
            if(blackNPS >= 0) lastTickLength = 0;
            blackTimeRemaining -= lastTickLength;
            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
@@ -13602,6 +14249,7 @@ SwitchClocks()
        }
        flagged = CheckFlags();
     }
+    forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
     CheckTimeControl();
 
     if (flagged || !appData.clockMode) return;
@@ -13964,7 +14612,7 @@ PositionToFEN(move, overrideCastling)
   }
 
   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
-     gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
+     gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
     /* En passant target square */
     if (move > backwardMostMove) {
         fromX = moveList[move - 1][0] - AAA;
@@ -14139,10 +14787,12 @@ ParseFEN(board, blackPlaysFirst, fen)
     }   /* assume possible unless obviously impossible */
     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
-    if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
+    if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
+                                 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
-    if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
+    if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
+                                 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
     FENrulePlies = 0;
 
     while(*p==' ') p++;
@@ -14157,12 +14807,18 @@ ParseFEN(board, blackPlaysFirst, fen)
              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
-        char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
+        char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
 
         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
         }
+        if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
+            whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
+        if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
+                                     && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
+        if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
+                                     && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
         switch(c) {
           case'K':
               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
@@ -14170,7 +14826,7 @@ ParseFEN(board, blackPlaysFirst, fen)
               board[CASTLING][2] = whiteKingFile;
               break;
           case'Q':
-              for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
+              for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
               board[CASTLING][2] = whiteKingFile;
               break;
@@ -14180,7 +14836,7 @@ ParseFEN(board, blackPlaysFirst, fen)
               board[CASTLING][5] = blackKingFile;
               break;
           case'q':
-              for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
+              for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
               board[CASTLING][5] = blackKingFile;
           case '-':
@@ -14212,6 +14868,8 @@ ParseFEN(board, blackPlaysFirst, fen)
               }
         }
       }
+      for(i=0; i<nrCastlingRights; i++)
+        if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
     if (appData.debugMode) {
         fprintf(debugFP, "FEN castling rights:");
         for(i=0; i<nrCastlingRights; i++)
@@ -14224,7 +14882,7 @@ ParseFEN(board, blackPlaysFirst, fen)
 
     /* read e.p. field in games that know e.p. capture */
     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
-       gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
+       gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
       if(*p=='-') {
         p++; board[EP_STATUS] = EP_NONE;
       } else {
@@ -14410,7 +15068,7 @@ PushTail(int firstMove, int lastMove)
        }
 
        storedGames++;
-       forwardMostMove = currentMove; // truncte game so we can start variation
+       forwardMostMove = firstMove; // truncate game so we can start variation
        if(storedGames == 1) GreyRevert(FALSE);
 }
 
@@ -14422,6 +15080,7 @@ PopTail(Boolean annotate)
 
        if(appData.icsActive) return FALSE; // only in local mode
        if(!storedGames) return FALSE; // sanity
+       CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
 
        storedGames--;
        ToNrEvent(savedFirst[storedGames]); // sets currentMove
@@ -14435,11 +15094,12 @@ PopTail(Boolean annotate)
                             sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
                        else sprintf(moveBuf, " %s", SavePart(parseList[i]));
                        strcat(buf, moveBuf);
+                       if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
                        if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
                }
                strcat(buf, ")");
        }
-       for(i=1; i<nrMoves; i++) { // copy last variation back
+       for(i=1; i<=nrMoves; i++) { // copy last variation back
            CopyBoard(boards[currentMove+i], boards[framePtr+i]);
            for(j=0; j<MOVE_LEN; j++)
                moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
@@ -14480,3 +15140,35 @@ CleanupTail()
        framePtr = MAX_MOVES-1;
        storedGames = 0;
 }
+
+void
+LoadVariation(int index, char *text)
+{       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
+       char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
+       int level = 0, move;
+
+       if(gameMode != EditGame && gameMode != AnalyzeMode) return;
+       // first find outermost bracketing variation
+       while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
+           if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
+               if(*p == '{') wait = '}'; else
+               if(*p == '[') wait = ']'; else
+               if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
+               if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
+           }
+           if(*p == wait) wait = NULLCHAR; // closing ]} found
+           p++;
+       }
+       if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
+       if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
+       end[1] = NULLCHAR; // clip off comment beyond variation
+       ToNrEvent(currentMove-1);
+       PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
+       // kludge: use ParsePV() to append variation to game
+       move = currentMove;
+       ParsePV(start, TRUE);
+       forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
+       ClearPremoveHighlights();
+       CommentPopDown();
+       ToNrEvent(currentMove+1);
+}