Added internal wrapping ability.
[xboard.git] / backend.c
old mode 100644 (file)
new mode 100755 (executable)
index 73aed9d..5b8dcfa
--- a/backend.c
+++ b/backend.c
@@ -77,6 +77,7 @@
 #if STDC_HEADERS
 # include <stdlib.h>
 # include <string.h>
+# include <stdarg.h>
 #else /* not STDC_HEADERS */
 # if HAVE_STRING_H
 #  include <string.h>
@@ -147,11 +148,11 @@ void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
                         char *buf, int count, int error));
 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
                      char *buf, int count, int error));
+void ics_printf P((char *format, ...));
 void SendToICS P((char *s));
 void SendToICSDelayed P((char *s, long msdelay));
 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
                      int toX, int toY));
-void InitPosition P((int redraw));
 void HandleMachineMove P((char *message, ChessProgramState *cps));
 int AutoPlayOneMove P((void));
 int LoadGameOneMove P((ChessMove readAhead));
@@ -183,7 +184,6 @@ void FeedMovesToProgram P((ChessProgramState *cps, int upto));
 void ResurrectChessProgram P((void));
 void DisplayComment P((int moveNumber, char *text));
 void DisplayMove P((int moveNumber));
-void DisplayAnalysis P((void));
 
 void ParseGameHistory P((char *game));
 void ParseBoard12 P((char *string));
@@ -230,6 +230,7 @@ char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comm
 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
+void ics_update_width P((int new_width));
 extern char installDir[MSG_SIZ];
 
 extern int tinyLayout, smallLayout;
@@ -243,6 +244,9 @@ VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
 int opponentKibitzes;
 int lastSavedGame; /* [HGM] save: ID of game */
+char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
+extern int chatCount;
+int chattingPartner;
 
 /* States for ics_getting_history */
 #define H_FALSE 0
@@ -297,26 +301,6 @@ static char * safeStrCpy( char * dst, const char * src, size_t count )
     return dst;
 }
 
-#if 0
-//[HGM] for future use? Conditioned out for now to suppress warning.
-static char * safeStrCat( char * dst, const char * src, size_t count )
-{
-    size_t  dst_len;
-
-    assert( dst != NULL );
-    assert( src != NULL );
-    assert( count > 0 );
-
-    dst_len = strlen(dst);
-
-    assert( count > dst_len ); /* Buffer size must be greater than current length */
-
-    safeStrCpy( dst + dst_len, src, count - dst_len );
-
-    return dst;
-}
-#endif
-
 /* Some compiler can't cast u64 to double
  * This function do the job for us:
 
@@ -457,16 +441,17 @@ AppData appData;
 
 Board boards[MAX_MOVES];
 /* [HGM] Following 7 needed for accurate legality tests: */
-char  epStatus[MAX_MOVES];
-char  castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
-char  castlingRank[BOARD_SIZE]; // and corresponding ranks
-char  initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
+signed char  epStatus[MAX_MOVES];
+signed char  castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
+signed char  castlingRank[BOARD_SIZE]; // and corresponding ranks
+signed char  initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
 int   initialRulePlies, FENrulePlies;
 char  FENepStatus;
 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
 int loadFlag = 0; 
 int shuffleOpenings;
+int mute; // mute all sounds
 
 ChessSquare  FIDEArray[2][BOARD_SIZE] = {
     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
@@ -828,20 +813,9 @@ InitBackEnd1()
        programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
        sprintf(programVersion, "%s", PACKAGE_STRING);
     } else {
-#if 0
-       char *p, *q;
-       q = first.program;
-       while (*q != ' ' && *q != NULLCHAR) q++;
-       p = q;
-       while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] backslash added */
-       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING + (q - p));
-       sprintf(programVersion, "%s + ", PACKAGE_STRING);
-       strncat(programVersion, p, q - p);
-#else
-       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
-       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
-       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
-#endif
+      /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
+      programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
+      sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
     }
 
     if (!appData.icsActive) {
@@ -1007,71 +981,58 @@ ParseTimeControl(tc, ti, mps)
      int ti;
      int mps;
 {
-#if 0
-    int matched, min, sec;
-
-    matched = sscanf(tc, "%d:%d", &min, &sec);
-    if (matched == 1) {
-       timeControl = min * 60 * 1000;
-    } else if (matched == 2) {
-       timeControl = (min * 60 + sec) * 1000;
-    } else {
-       return FALSE;
-    }
-#else
-    long tc1;
-    long tc2;
-    char buf[MSG_SIZ];
-
-    if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
-    if(ti > 0) {
-        if(mps)
-             sprintf(buf, "+%d/%s+%d", mps, tc, ti);
-        else sprintf(buf, "+%s+%d", tc, ti);
-    } else {
-        if(mps)
+  long tc1;
+  long tc2;
+  char buf[MSG_SIZ];
+  
+  if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
+  if(ti > 0) {
+    if(mps)
+      sprintf(buf, "+%d/%s+%d", mps, tc, ti);
+    else sprintf(buf, "+%s+%d", tc, ti);
+  } else {
+    if(mps)
              sprintf(buf, "+%d/%s", mps, tc);
-        else sprintf(buf, "+%s", tc);
-    }
-    fullTimeControlString = StrSave(buf);
-
-    if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
-        return FALSE;
-    }
-
-    if( *tc == '/' ) {
-        /* Parse second time control */
-        tc++;
-
-        if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
-            return FALSE;
-        }
-
-        if( tc2 == 0 ) {
-            return FALSE;
-        }
-
-        timeControl_2 = tc2 * 1000;
-    }
-    else {
-        timeControl_2 = 0;
-    }
-
-    if( tc1 == 0 ) {
-        return FALSE;
+    else sprintf(buf, "+%s", tc);
+  }
+  fullTimeControlString = StrSave(buf);
+  
+  if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
+    return FALSE;
+  }
+  
+  if( *tc == '/' ) {
+    /* Parse second time control */
+    tc++;
+    
+    if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
+      return FALSE;
     }
-
-    timeControl = tc1 * 1000;
-#endif
-
-    if (ti >= 0) {
-       timeIncrement = ti * 1000;  /* convert to ms */
-       movesPerSession = 0;
-    } else {
-       timeIncrement = 0;
-       movesPerSession = mps;
+    
+    if( tc2 == 0 ) {
+      return FALSE;
     }
-    return TRUE;
+    
+    timeControl_2 = tc2 * 1000;
+  }
+  else {
+    timeControl_2 = 0;
+  }
+  
+  if( tc1 == 0 ) {
+    return FALSE;
+  }
+  
+  timeControl = tc1 * 1000;
+  
+  if (ti >= 0) {
+    timeIncrement = ti * 1000;  /* convert to ms */
+    movesPerSession = 0;
+  } else {
+    timeIncrement = 0;
+    movesPerSession = mps;
+  }
+  return TRUE;
 }
 
 void
@@ -1081,6 +1042,7 @@ InitBackEnd2()
        fprintf(debugFP, "%s\n", programVersion);
     }
 
+    set_cont_sequence(appData.wrapContSeq);
     if (appData.matchGames > 0) {
        appData.matchMode = TRUE;
     } else if (appData.matchMode) {
@@ -1452,6 +1414,26 @@ read_from_player(isr, closure, message, count, error)
     }
 }
 
+void
+KeepAlive()
+{   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
+    SendToICS("date\n");
+    if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
+}
+
+/* added routine for printf style output to ics */
+void ics_printf(char *format, ...)
+{
+    char buffer[MSG_SIZ];
+    va_list args;
+
+    va_start(args, format);
+    vsnprintf(buffer, sizeof(buffer), format, args);
+    buffer[sizeof(buffer)-1] = '\0';
+    SendToICS(buffer);
+    va_end(args);
+}
+
 void
 SendToICS(s)
      char *s;
@@ -1570,6 +1552,10 @@ StringToVariant(e)
         while( *e++ != '_');
     }
 
+    if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
+       v = VariantNormal;
+       found = TRUE;
+    } else
     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
       if (StrCaseStr(e, variantNames[i])) {
        v = (VariantClass) i;
@@ -1954,8 +1940,6 @@ void
 VariantSwitch(Board board, VariantClass newVariant)
 {
    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
-   int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
-//   Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
 
    startedFromPositionFile = FALSE;
    if(gameInfo.variant == newVariant) return;
@@ -1972,69 +1956,65 @@ VariantSwitch(Board board, VariantClass newVariant)
     * case we want to add those holdings to the already received position.
     */
 
-
-  if (appData.debugMode) {
-    fprintf(debugFP, "Switch board from %s to %s\n",
-               VariantName(gameInfo.variant), VariantName(newVariant));
-    setbuf(debugFP, NULL);
-  }
-    shuffleOpenings = 0;       /* [HGM] shuffle */
-    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
-    switch(newVariant) {
-            case VariantShogi:
-              newWidth = 9;  newHeight = 9;
-              gameInfo.holdingsSize = 7;
-            case VariantBughouse:
-            case VariantCrazyhouse:
-              newHoldingsWidth = 2; break;
-            default:
-              newHoldingsWidth = gameInfo.holdingsSize = 0;
-    }
-
-    if(newWidth  != gameInfo.boardWidth  ||
-       newHeight != gameInfo.boardHeight ||
-       newHoldingsWidth != gameInfo.holdingsWidth ) {
-
-        /* shift position to new playing area, if needed */
-        if(newHoldingsWidth > gameInfo.holdingsWidth) {
-           for(i=0; i<BOARD_HEIGHT; i++) 
-               for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
-                   board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
-                                                     board[i][j];
-           for(i=0; i<newHeight; i++) {
-               board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
-               board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
-           }
-        } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
-           for(i=0; i<BOARD_HEIGHT; i++)
-               for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
-                   board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
-                                                 board[i][j];
-        }
-
-        gameInfo.boardWidth  = newWidth;
-        gameInfo.boardHeight = newHeight;
-        gameInfo.holdingsWidth = newHoldingsWidth;
-        gameInfo.variant = newVariant;
-        InitDrawingSizes(-2, 0);
-
-        /* [HGM] The following should definitely be solved in a better way */
-#if 0
-        CopyBoard(board, tempBoard); /* save position in case it is board[0] */
-        for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];
-        saveEP = epStatus[0];
-#endif
-        InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
-#if 0
-        epStatus[0] = saveEP;
-        for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];
-        CopyBoard(tempBoard, board); /* restore position received from ICS   */
-#endif
-    } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
-
-    forwardMostMove = oldForwardMostMove;
-    backwardMostMove = oldBackwardMostMove;
-    currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
+   
+   if (appData.debugMode) {
+     fprintf(debugFP, "Switch board from %s to %s\n",
+            VariantName(gameInfo.variant), VariantName(newVariant));
+     setbuf(debugFP, NULL);
+   }
+   shuffleOpenings = 0;       /* [HGM] shuffle */
+   gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
+   switch(newVariant) 
+     {
+     case VariantShogi:
+       newWidth = 9;  newHeight = 9;
+       gameInfo.holdingsSize = 7;
+     case VariantBughouse:
+     case VariantCrazyhouse:
+       newHoldingsWidth = 2; break;
+     case VariantGreat:
+       newWidth = 10;
+     case VariantSuper:
+       newHoldingsWidth = 2;
+       gameInfo.holdingsSize = 8;
+       return;
+     case VariantGothic:
+     case VariantCapablanca:
+     case VariantCapaRandom:
+       newWidth = 10;
+     default:
+       newHoldingsWidth = gameInfo.holdingsSize = 0;
+     };
+   
+   if(newWidth  != gameInfo.boardWidth  ||
+      newHeight != gameInfo.boardHeight ||
+      newHoldingsWidth != gameInfo.holdingsWidth ) {
+     
+     /* shift position to new playing area, if needed */
+     if(newHoldingsWidth > gameInfo.holdingsWidth) {
+       for(i=0; i<BOARD_HEIGHT; i++) 
+        for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
+          board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
+            board[i][j];
+       for(i=0; i<newHeight; i++) {
+        board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
+        board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
+       }
+     } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
+       for(i=0; i<BOARD_HEIGHT; i++)
+        for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
+          board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
+            board[i][j];
+     }
+     gameInfo.boardWidth  = newWidth;
+     gameInfo.boardHeight = newHeight;
+     gameInfo.holdingsWidth = newHoldingsWidth;
+     gameInfo.variant = newVariant;
+     InitDrawingSizes(-2, 0);
+     InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
+   } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
+   
+   DrawPosition(TRUE, boards[currentMove]);
 }
 
 static int loggedOn = FALSE;
@@ -2044,6 +2024,7 @@ int gs_gamenum;
 char gs_kind[MSG_SIZ];
 static char player1Name[128] = "";
 static char player2Name[128] = "";
+static char cont_seq[] = "\n\\   ";
 static int player1Rating = -1;
 static int player2Rating = -1;
 /*----------------------------*/
@@ -2076,6 +2057,8 @@ read_from_ics(isr, closure, data, count, error)
     static int firstTime = TRUE, intfSet = FALSE;
     static ColorClass prevColor = ColorNormal;
     static int savingComment = FALSE;
+    static int cmatch = 0; // continuation sequence match
+    char *bp;
     char str[500];
     int i, oldi;
     int buf_len;
@@ -2083,6 +2066,8 @@ read_from_ics(isr, closure, data, count, error)
     int tkind;
     int backup;    /* [DM] For zippy color lines */
     char *p;
+    char talker[MSG_SIZ]; // [HGM] chat
+    int channel;
 
     if (appData.debugMode) {
       if (!error) {
@@ -2104,17 +2089,66 @@ read_from_ics(isr, closure, data, count, error)
              buf[i] = buf[leftover_start + i];
        }
 
-       /* Copy in new characters, removing nulls and \r's */
-       buf_len = leftover_len;
-       for (i = 0; i < count; i++) {
-           if (data[i] != NULLCHAR && data[i] != '\r')
-             buf[buf_len++] = data[i];
-           if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' && 
-                               buf[buf_len-3]==' '  && buf[buf_len-2]==' '  && buf[buf_len-1]==' ') {
-               buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
-               buf[buf_len++] = ' '; // replace by space (assumes ICS does not break lines within word)
-           }
-       }
+    /* copy new characters into the buffer */
+    bp = buf + leftover_len;
+    buf_len=leftover_len;
+    for (i=0; i<count; i++)
+    {
+        // ignore these
+        if (data[i] == '\r')
+            continue;
+
+        // join lines split by ICS?
+        if (!appData.noJoin)
+        {
+            /*
+                Joining just consists of finding matches against the
+                continuation sequence, and discarding that sequence
+                if found instead of copying it.  So, until a match
+                fails, there's nothing to do since it might be the
+                complete sequence, and thus, something we don't want
+                copied.
+            */
+            if (data[i] == cont_seq[cmatch])
+            {
+                cmatch++;
+                if (cmatch == strlen(cont_seq))
+                {
+                    cmatch = 0; // complete match.  just reset the counter
+
+                    /*
+                        it's possible for the ICS to not include the space
+                        at the end of the last word, making our [correct]
+                        join operation fuse two separate words.  the server
+                        does this when the space occurs at the width setting.
+                    */
+                    if (!buf_len || buf[buf_len-1] != ' ')
+                    {
+                        *bp++ = ' ';
+                        buf_len++;
+                    }
+                }
+                continue;
+            }
+            else if (cmatch)
+            {
+                /*
+                    match failed, so we have to copy what matched before
+                    falling through and copying this character.  In reality,
+                    this will only ever be just the newline character, but
+                    it doesn't hurt to be precise.
+                */
+                strncpy(bp, cont_seq, cmatch);
+                bp += cmatch;
+                buf_len += cmatch;
+                cmatch = 0;
+            }
+        }
+
+        // copy this char
+        *bp++ = data[i];
+        buf_len++;
+    }
 
        buf[buf_len] = NULLCHAR;
        next_out = leftover_len;
@@ -2242,7 +2276,6 @@ read_from_ics(isr, closure, data, count, error)
                  sprintf(str,
                          "/set-quietly interface %s\n/set-quietly style 12\n",
                          programVersion);
-
                } else if (ics_type == ICS_CHESSNET) {
                  sprintf(str, "/style 12\n");
                } else {
@@ -2255,6 +2288,7 @@ read_from_ics(isr, closure, data, count, error)
                  strcat(str, "$iset lock 1\n$style 12\n");
                }
                SendToICS(str);
+               NotifyFrontendLogin();
                intfSet = TRUE;
            }
 
@@ -2263,6 +2297,12 @@ read_from_ics(isr, closure, data, count, error)
                parse[parse_pos++] = buf[i];
                if (buf[i] == '\n') {
                    parse[parse_pos] = NULLCHAR;
+                   if(chattingPartner>=0) {
+                       char mess[MSG_SIZ];
+                       sprintf(mess, "%s%s", talker, parse);
+                       OutputChatMessage(chattingPartner, mess);
+                       chattingPartner = -1;
+                   } else
                    if(!suppressKibitz) // [HGM] kibitz
                        AppendComment(forwardMostMove, StripHighlight(parse));
                    else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
@@ -2399,6 +2439,45 @@ read_from_ics(isr, closure, data, count, error)
                }
            } // [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, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
+                                          looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
+               int p;
+               sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
+               chattingPartner = -1;
+
+               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, "]");
+                   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, "]");
+                       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;
+                   chattingPartner = p; break;
+               }
+               if(chattingPartner<0) i = oldi; else {
+                   started = STARTED_COMMENT;
+                   parse_pos = 0; parse[0] = NULLCHAR;
+                   savingComment = TRUE;
+                   suppressKibitz = TRUE;
+               }
+           } // [HGM] chat: end of patch
+
            if (appData.zippyTalk || appData.zippyPlay) {
                 /* [DM] Backup address for color zippy lines */
                 backup = i;
@@ -2417,14 +2496,15 @@ read_from_ics(isr, closure, data, count, error)
        #endif
 #endif
            } // [DM] 'else { ' deleted
-               if (/* Don't color "message" or "messages" output */
-                   (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
-                   looking_at(buf, &i, "*. * at *:*: ") ||
-                   looking_at(buf, &i, "--* (*:*): ") ||
+               if (
                    /* Regular tells and says */
                    (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
                    looking_at(buf, &i, "* (your partner) tells you: ") ||
                    looking_at(buf, &i, "* says: ") ||
+                   /* Don't color "message" or "messages" output */
+                   (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
+                   looking_at(buf, &i, "*. * at *:*: ") ||
+                   looking_at(buf, &i, "--* (*:*): ") ||
                    /* Message notifications (same color as tells) */
                    looking_at(buf, &i, "* has left a message ") ||
                    looking_at(buf, &i, "* just sent you a message:\n") ||
@@ -2773,18 +2853,10 @@ read_from_ics(isr, closure, data, count, error)
                                  }
                                  SendTimeRemaining(&first, TRUE);
                                }
-#if 0
-                               if (first.useColors) {
-                                 SendToProgram("white\ngo\n", &first);
-                               } else {
-                                 SendToProgram("go\n", &first);
-                               }
-#else
                                if (first.useColors) {
                                  SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
                                }
                                bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
-#endif
                                first.maybeThinking = TRUE;
                            } else {
                                if (first.usePlayother) {
@@ -2805,18 +2877,10 @@ read_from_ics(isr, closure, data, count, error)
                                  }
                                  SendTimeRemaining(&first, FALSE);
                                }
-#if 0
-                               if (first.useColors) {
-                                 SendToProgram("black\ngo\n", &first);
-                               } else {
-                                 SendToProgram("go\n", &first);
-                               }
-#else
                                if (first.useColors) {
                                  SendToProgram("black\n", &first);
                                }
                                bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
-#endif
                                first.maybeThinking = TRUE;
                            } else {
                                if (first.usePlayother) {
@@ -3768,12 +3832,6 @@ ParseBoard12(string)
     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
   }
-#if 0
-           if (appData.testLegality && appData.debugMode) {
-               sprintf(str, "Illegal move \"%s\" from ICS", move_str);
-               DisplayError(str, 0);
-           }
-#endif
            strcpy(parseList[moveNum - 1], move_str);
            strcat(parseList[moveNum - 1], " ");
            strcat(parseList[moveNum - 1], elapsed_time);
@@ -3947,6 +4005,12 @@ AnalysisPeriodicEvent(force)
     programStats.ok_to_send = 0;
 }
 
+void ics_update_width(new_width)
+       int new_width;
+{
+       ics_printf("set width %d\n", new_width);
+}
+
 void
 SendMoveToProgram(moveNum, cps)
      int moveNum;
@@ -4087,6 +4151,8 @@ SendMoveToICS(moveType, fromX, fromY, toX, toY)
        break;
     }
     SendToICS(user_move);
+    if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
+       ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
 }
 
 void
@@ -4497,7 +4563,6 @@ InitPosition(redraw)
     oldh = gameInfo.holdingsWidth,
     oldv = gameInfo.variant;
 
-    currentMove = forwardMostMove = backwardMostMove = 0;
     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
 
     /* [AS] Initialize pv info list [HGM] and game status */
@@ -4713,17 +4778,6 @@ InitPosition(redraw)
        initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
        initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
      }
-#if 0
-    if(gameInfo.variant == VariantFischeRandom) {
-      if( appData.defaultFrcPosition < 0 ) {
-        ShuffleFRC( initialPosition );
-      }
-      else {
-        SetupFRC( initialPosition, appData.defaultFrcPosition );
-      }
-      startedFromSetupPosition = TRUE;
-    } else 
-#else
   if (appData.debugMode) {
     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
   }
@@ -4731,7 +4785,6 @@ InitPosition(redraw)
        SetUpShuffle(initialPosition, appData.defaultFrcPosition);
        startedFromSetupPosition = TRUE;
     }
-#endif
     if(startedFromPositionFile) {
       /* [HGM] loadPos: use PositionFile for every new game */
       CopyBoard(initialPosition, filePosition);
@@ -4831,24 +4884,29 @@ SendBoard(cps, moveNum)
 }
 
 int
-IsPromotion(fromX, fromY, toX, toY)
-     int fromX, fromY, toX, toY;
+HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
 {
+    /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
     /* [HGM] add Shogi promotions */
     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
     ChessSquare piece;
+    ChessMove moveType;
+    Boolean premove;
+
+    if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
+    if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
+
+    if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
+      !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
+       return FALSE;
 
-    if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
-      !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
-   /* [HGM] Note to self: line above also weeds out drops */
     piece = boards[currentMove][fromY][fromX];
     if(gameInfo.variant == VariantShogi) {
         promotionZoneSize = 3;
-        highestPromotingPiece = (int)WhiteKing;
-        /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
-           and if in normal chess we then allow promotion to King, why not
-           allow promotion of other piece in Shogi?                         */
+        highestPromotingPiece = (int)WhiteFerz;
     }
+
+    // next weed out all moves that do not touch the promotion zone at all
     if((int)piece >= BlackPawn) {
         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
              return FALSE;
@@ -4857,7 +4915,62 @@ IsPromotion(fromX, fromY, toX, toY)
         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
     }
-    return ( (int)piece <= highestPromotingPiece );
+
+    if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
+
+    // weed out mandatory Shogi promotions
+    if(gameInfo.variant == VariantShogi) {
+       if(piece >= BlackPawn) {
+           if(toY == 0 && piece == BlackPawn ||
+              toY == 0 && piece == BlackQueen ||
+              toY <= 1 && piece == BlackKnight) {
+               *promoChoice = '+';
+               return FALSE;
+           }
+       } else {
+           if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
+              toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
+              toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
+               *promoChoice = '+';
+               return FALSE;
+           }
+       }
+    }
+
+    // weed out obviously illegal Pawn moves
+    if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
+       if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
+       if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
+       if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
+       if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
+       // note we are not allowed to test for valid (non-)capture, due to premove
+    }
+
+    // we either have a choice what to promote to, or (in Shogi) whether to promote
+    if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
+       *promoChoice = PieceToChar(BlackFerz);  // no choice
+       return FALSE;
+    }
+    if(appData.alwaysPromoteToQueen) { // predetermined
+       if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
+            *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
+       else *promoChoice = PieceToChar(BlackQueen);
+       return FALSE;
+    }
+
+    // suppress promotion popup on illegal moves that are not premoves
+    premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
+             gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
+    if(appData.testLegality && !premove) {
+       moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
+                       epStatus[currentMove], castlingRights[currentMove],
+                       fromY, fromX, toY, toX, NULLCHAR);
+       if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
+          moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
+           return FALSE;
+    }
+
+    return TRUE;
 }
 
 int
@@ -4991,39 +5104,15 @@ int lastLoadGameUseList = FALSE;
 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
 ChessMove lastLoadGameStart = (ChessMove) 0;
 
-
 ChessMove
-UserMoveTest(fromX, fromY, toX, toY, promoChar)
+UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
      int fromX, fromY, toX, toY;
      int promoChar;
+     Boolean captureOwn;
 {
     ChessMove moveType;
     ChessSquare pdown, pup;
 
-    if (fromX < 0 || fromY < 0) return ImpossibleMove;
-    if ((fromX == toX) && (fromY == toY)) {
-        return ImpossibleMove;
-    }
-
-    /* [HGM] suppress all moves into holdings area and guard band */
-    if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
-            return ImpossibleMove;
-
-    /* [HGM] <sameColor> moved to here from winboard.c */
-    /* note: this code seems to exist for filtering out some obviously illegal premoves */
-    pdown = boards[currentMove][fromY][fromX];
-    pup = boards[currentMove][toY][toX];
-    if (    gameMode != EditPosition &&
-            (WhitePawn <= pdown && pdown < BlackPawn &&
-             WhitePawn <= pup && pup < BlackPawn  ||
-             BlackPawn <= pdown && pdown < EmptySquare &&
-             BlackPawn <= pup && pup < EmptySquare 
-            ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
-                    (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
-                     pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1  ) 
-        )           )
-         return ImpossibleMove;
-
     /* Check if the user is playing in turn.  This is complicated because we
        let the user "pick up" a piece before it is his turn.  So the piece he
        tried to pick up may have been captured by the time he puts it down!
@@ -5142,6 +5231,9 @@ UserMoveTest(fromX, fromY, toX, toY, promoChar)
         return ImpossibleMove;
     }
 
+    pdown = boards[currentMove][fromY][fromX];
+    pup = boards[currentMove][toY][toX];
+
     /* [HGM] If move started in holdings, it means a drop */
     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
          if( pup != EmptySquare ) return ImpossibleMove;
@@ -5163,7 +5255,6 @@ UserMoveTest(fromX, fromY, toX, toY, promoChar)
     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
                           epStatus[currentMove], castlingRights[currentMove],
                                          fromY, fromX, toY, toX, promoChar);
-
     /* [HGM] but possibly ignore an IllegalMove result */
     if (appData.testLegality) {
        if (moveType == IllegalMove || moveType == ImpossibleMove) {
@@ -5379,12 +5470,192 @@ UserMoveEvent(fromX, fromY, toX, toY, promoChar)
        FinishMove if the first part succeeded. Calls that do not need
        to do anything in between, can call this routine the old way. 
     */
-    ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);
+    ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
-    if(moveType != ImpossibleMove)
+    if(moveType == AmbiguousMove)
+       DrawPosition(FALSE, boards[currentMove]);
+    else if(moveType != ImpossibleMove && moveType != Comment)
         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
 }
 
+void LeftClick(ClickType clickType, int xPix, int yPix)
+{
+    int x, y;
+    Boolean saveAnimate;
+    static int second = 0, promotionChoice = 0;
+    char promoChoice = NULLCHAR;
+
+    if (clickType == Press) ErrorPopDown();
+
+    x = EventToSquare(xPix, BOARD_WIDTH);
+    y = EventToSquare(yPix, BOARD_HEIGHT);
+    if (!flipView && y >= 0) {
+       y = BOARD_HEIGHT - 1 - y;
+    }
+    if (flipView && x >= 0) {
+       x = BOARD_WIDTH - 1 - x;
+    }
+
+    if(promotionChoice) { // we are waiting for a click to indicate promotion piece
+       if(clickType == Release) return; // ignore upclick of click-click destination
+       promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
+       if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
+       if(gameInfo.holdingsWidth && 
+               (WhiteOnMove(currentMove) 
+                       ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
+                       : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
+           // click in right holdings, for determining promotion piece
+           ChessSquare p = boards[currentMove][y][x];
+           if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
+           if(p != EmptySquare) {
+               FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
+               fromX = fromY = -1;
+               return;
+           }
+       }
+       DrawPosition(FALSE, boards[currentMove]);
+       return;
+    }
+
+    /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
+    if(clickType == Press
+            && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
+              || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
+              || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
+       return;
+
+    if (fromX == -1) {
+       if (clickType == Press) {
+           /* First square */
+           if (OKToStartUserMove(x, y)) {
+               fromX = x;
+               fromY = y;
+               second = 0;
+               DragPieceBegin(xPix, yPix);
+               if (appData.highlightDragging) {
+                   SetHighlights(x, y, -1, -1);
+               }
+           }
+       }
+       return;
+    }
+
+    /* fromX != -1 */
+    if (clickType == Press && gameMode != EditPosition) {
+       ChessSquare fromP;
+       ChessSquare toP;
+       int frc;
+
+       // ignore off-board to clicks
+       if(y < 0 || x < 0) return;
+
+       /* Check if clicking again on the same color piece */
+       fromP = boards[currentMove][fromY][fromX];
+       toP = boards[currentMove][y][x];
+       frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
+       if ((WhitePawn <= fromP && fromP <= WhiteKing &&
+            WhitePawn <= toP && toP <= WhiteKing &&
+            !(fromP == WhiteKing && toP == WhiteRook && frc) &&
+            !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
+           (BlackPawn <= fromP && fromP <= BlackKing && 
+            BlackPawn <= toP && toP <= BlackKing &&
+            !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
+            !(fromP == BlackKing && toP == BlackRook && frc))) {
+           /* Clicked again on same color piece -- changed his mind */
+           second = (x == fromX && y == fromY);
+           if (appData.highlightDragging) {
+               SetHighlights(x, y, -1, -1);
+           } else {
+               ClearHighlights();
+           }
+           if (OKToStartUserMove(x, y)) {
+               fromX = x;
+               fromY = y;
+               DragPieceBegin(xPix, yPix);
+           }
+           return;
+       }
+       // ignore to-clicks in holdings
+       if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
+    }
+
+    if (clickType == Release && (x == fromX && y == fromY ||
+       x < BOARD_LEFT || x >= BOARD_RGHT)) {
+
+       // treat drags into holding as click on start square
+       x = fromX; y = fromY;
+
+       DragPieceEnd(xPix, yPix);
+       if (appData.animateDragging) {
+           /* Undo animation damage if any */
+           DrawPosition(FALSE, NULL);
+       }
+       if (second) {
+           /* Second up/down in same square; just abort move */
+           second = 0;
+           fromX = fromY = -1;
+           ClearHighlights();
+           gotPremove = 0;
+           ClearPremoveHighlights();
+       } else {
+           /* First upclick in same square; start click-click mode */
+           SetHighlights(x, y, -1, -1);
+       }
+       return;
+    }
+
+    /* we now have a different from- and to-square */
+    /* Completed move */
+    toX = x;
+    toY = y;
+    saveAnimate = appData.animate;
+    if (clickType == Press) {
+       /* Finish clickclick move */
+       if (appData.animate || appData.highlightLastMove) {
+           SetHighlights(fromX, fromY, toX, toY);
+       } else {
+           ClearHighlights();
+       }
+    } else {
+       /* Finish drag move */
+       if (appData.highlightLastMove) {
+           SetHighlights(fromX, fromY, toX, toY);
+       } else {
+           ClearHighlights();
+       }
+       DragPieceEnd(xPix, yPix);
+       /* Don't animate move and drag both */
+       appData.animate = FALSE;
+    }
+    if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
+       SetHighlights(fromX, fromY, toX, toY);
+       if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
+           // [HGM] super: promotion to captured piece selected from holdings
+           ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
+           promotionChoice = TRUE;
+           // kludge follows to temporarily execute move on display, without promoting yet
+           boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
+           boards[currentMove][toY][toX] = p;
+           DrawPosition(FALSE, boards[currentMove]);
+           boards[currentMove][fromY][fromX] = p; // take back, but display stays
+           boards[currentMove][toY][toX] = q;
+           DisplayMessage("Click in holdings to choose piece", "");
+           return;
+       }
+       PromotionPopUp();
+    } else {
+       UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
+       if (!appData.highlightLastMove || gotPremove) ClearHighlights();
+       if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
+       fromX = fromY = -1;
+    }
+    appData.animate = saveAnimate;
+    if (appData.animate || appData.animateDragging) {
+       /* Undo animation damage if needed */
+       DrawPosition(FALSE, NULL);
+    }
+}
+
 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
 {
 //    char * hint = lastHint;
@@ -5647,7 +5918,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
          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, %1.0f knps) PV=%s\n",
+               sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
                        programStats.score / 100.,
                        programStats.depth,
                        programStats.time / 100.,
@@ -5655,6 +5926,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
                        (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
@@ -5898,16 +6170,16 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
                 } else moveCount = 6;
            }
          }
-#if 1
-    if (appData.debugMode) { int i;
-      fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
-              forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
-              appData.drawRepeats);
-      for( i=forwardMostMove; i>=backwardMostMove; i-- )
-           fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
+         
+         if (appData.debugMode) { int i;
+           fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
+                   forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
+                   appData.drawRepeats);
+           for( i=forwardMostMove; i>=backwardMostMove; i-- )
+             fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
+           
+         }
 
-    }
-#endif
                 /* Check for rep-draws */
                 count = 0;
                 for(k = forwardMostMove-2;
@@ -5916,17 +6188,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
                     k-=2)
                 {   int rights=0;
-#if 0
-    if (appData.debugMode) {
-      fprintf(debugFP, " loop\n");
-    }
-#endif
                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
-#if 0
-    if (appData.debugMode) {
-      fprintf(debugFP, "match\n");
-    }
-#endif
                         /* compare castling rights */
                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
@@ -5944,16 +6206,6 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
                                    rights++;
                         }
-#if 0
-    if (appData.debugMode) {
-      for(i=0; i<nrCastlingRights; i++)
-      fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);
-    }
-
-    if (appData.debugMode) {
-      fprintf(debugFP, " %d %d\n", rights, k);
-    }
-#endif
                         if( rights == 0 && ++count > appData.drawRepeats-2
                             && appData.drawRepeats > 1) {
                              /* adjudicate after user-specified nr of repeats */
@@ -6305,17 +6557,30 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
        if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
            gameMode == IcsIdle) return;
        if (forwardMostMove <= backwardMostMove) return;
-#if 0
-       /* Following removed: it caused a bug where a real illegal move
-          message in analyze mored would be ignored. */
-       if (cps == &first && programStats.ok_to_send == 0) {
-           /* Bogus message from Crafty responding to "."  This filtering
-              can miss some of the bad messages, but fortunately the bug 
-              is fixed in current Crafty versions, so it doesn't matter. */
-           return;
-       }
-#endif
        if (pausing) PauseEvent();
+      if(appData.forceIllegal) {
+           // [HGM] illegal: machine refused move; force position after move into it
+          SendToProgram("force\n", cps);
+          if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
+               // we have a real problem now, as SendBoard will use the a2a3 kludge
+               // when black is to move, while there might be nothing on a2 or black
+               // might already have the move. So send the board as if white has the move.
+               // But first we must change the stm of the engine, as it refused the last move
+               SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
+               if(WhiteOnMove(forwardMostMove)) {
+                   SendToProgram("a7a6\n", cps); // for the engine black still had the move
+                   SendBoard(cps, forwardMostMove); // kludgeless board
+               } else {
+                   SendToProgram("a2a3\n", cps); // for the engine white still had the move
+                   CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
+                   SendBoard(cps, forwardMostMove+1); // kludgeless board
+               }
+          } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
+           if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
+                gameMode == TwoMachinesPlay)
+              SendToProgram("go\n", cps);
+           return;
+      } else
        if (gameMode == PlayFromGameFile) {
            /* Stop reading this game file */
            gameMode = EditGame;
@@ -6552,13 +6817,6 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
             /* [HGM] in two-machine mode we delay relaying draw offer      */
             /* until after we also have move, to see if it is really claim */
            }
-#if 0
-              else {
-               if (cps->other->sendDrawOffers) {
-                   SendToProgram("draw\n", cps->other);
-               }
-           }
-#endif
        } else if (gameMode == MachinePlaysWhite ||
                   gameMode == MachinePlaysBlack) {
          if (userOfferedDraw) {
@@ -6699,7 +6957,6 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
                    DisplayMove(currentMove - 1);
-                   DisplayAnalysis();
                }
                return;
 
@@ -6727,7 +6984,6 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
                if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
                    DisplayMove(currentMove - 1);
-                   DisplayAnalysis();
                }
                return;
            } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
@@ -6752,7 +7008,6 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
 
                 SendProgramStatsToFrontend( cps, &programStats );
 
-               DisplayAnalysis();
                return;
 
            } else if (strncmp(message,"++",2) == 0) {
@@ -6788,7 +7043,6 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
                if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
                    DisplayMove(currentMove - 1);
-                   DisplayAnalysis();
                }
                return;
            }
@@ -7391,9 +7645,6 @@ MakeMove(fromX, fromY, toX, toY, promoChar)
                        0, 1);
       return;
     }
-    SwitchClocks();
-    timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;
-    timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;
     if (commentList[forwardMostMove+1] != NULL) {
        free(commentList[forwardMostMove+1]);
        commentList[forwardMostMove+1] = NULL;
@@ -7403,6 +7654,9 @@ MakeMove(fromX, fromY, toX, toY, promoChar)
     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1], 
                                castlingRights[forwardMostMove+1], &epStatus[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 !
+    timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
+    timeRemaining[1][forwardMostMove] = blackTimeRemaining;
     gameInfo.result = GameUnfinished;
     if (gameInfo.resultDetails != NULL) {
        free(gameInfo.resultDetails);
@@ -7489,7 +7743,7 @@ void SendEgtPath(ChessProgramState *cps)
                while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
                c = *r; *r = 0;             // temporarily null-terminate path info
                    *--q = 0;               // strip of trailig ':' from name
-                   sprintf(buf, "egtbpath %s %s\n", name+1, s);
+                   sprintf(buf, "egtpath %s %s\n", name+1, s);
                *r = c;
                SendToProgram(buf,cps);     // send egtbpath command for this format
            }
@@ -8218,6 +8472,7 @@ Reset(redraw, init)
     gameMode = BeginningOfGame;
     ModeHighlight();
     if(appData.icsActive) gameInfo.variant = VariantNormal;
+    currentMove = forwardMostMove = backwardMostMove = 0;
     InitPosition(redraw);
     for (i = 0; i < MAX_MOVES; i++) {
        if (commentList[i] != NULL) {
@@ -9295,26 +9550,8 @@ LoadPosition(f, positionNumber, title)
        DisplayError(_("Position not found in file"), 0);
        return FALSE;
     }
-#if 0
-    switch (line[0]) {
-      case '#':  case 'x':
-      default:
-       fenMode = FALSE;
-       break;
-      case 'p':  case 'n':  case 'b':  case 'r':  case 'q':  case 'k':
-      case 'P':  case 'N':  case 'B':  case 'R':  case 'Q':  case 'K':
-      case '1':  case '2':  case '3':  case '4':  case '5':  case '6':
-      case '7':  case '8':  case '9':
-      case 'H':  case 'A':  case 'M':  case 'h':  case 'a':  case 'm':
-      case 'E':  case 'F':  case 'G':  case 'e':  case 'f':  case 'g':
-      case 'C':  case 'W':             case 'c':  case 'w': 
-       fenMode = TRUE;
-       break;
-    }
-#else
     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
-#endif
 
     if (pn >= 2) {
        if (fenMode || line[0] == '#') pn--;
@@ -9627,23 +9864,13 @@ SaveGamePGN(f)
            fprintf(f, " ");
            linelen++;
        }
-       fprintf(f, numtext);
+       fprintf(f, "%s", numtext);
        linelen += numlen;
 
        /* Get move */
        strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
        movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
-#if 0
-       // SavePart already does this!
-        if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
-               int p = movelen - 1;
-               if(move_buffer[p] == ' ') p--;
-               if(move_buffer[p] == ')') { // [HGM] pgn: strip off ICS time if we have extended info
-                   while(p && move_buffer[--p] != '(');
-                   if(p && move_buffer[p-1] == ' ') move_buffer[movelen=p-1] = 0;
-               }
-        }
-#endif
+
        /* Print move */
        blank = linelen > 0 && movelen > 0;
        if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
@@ -9655,7 +9882,7 @@ SaveGamePGN(f)
            fprintf(f, " ");
            linelen++;
        }
-       fprintf(f, move_buffer);
+       fprintf(f, "%s", move_buffer);
        linelen += movelen;
 
         /* [AS] Add PV info if present */
@@ -9663,7 +9890,6 @@ SaveGamePGN(f)
             /* [HGM] add time */
             char buf[MSG_SIZ]; int seconds = 0;
 
-#if 1
             if(i >= backwardMostMove) {
                if(WhiteOnMove(i))
                        seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
@@ -9673,9 +9899,6 @@ SaveGamePGN(f)
                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
             }
             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
-#else
-            seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time
-#endif
 
             if( seconds <= 0) buf[0] = 0; else
             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
@@ -9703,7 +9926,7 @@ SaveGamePGN(f)
                fprintf(f, " ");
                linelen++;
            }
-           fprintf(f, move_buffer);
+           fprintf(f, "%s", move_buffer);
            linelen += movelen;
         }
 
@@ -10222,24 +10445,10 @@ ExitEvent(status)
     if (icsPR != NoProc) {
       DestroyChildProcess(icsPR, TRUE);
     }
-#if 0
-    /* Save game if resource set and not already saved by GameEnds() */
-    if ((gameInfo.resultDetails == NULL || errorExitFlag )
-                             && forwardMostMove > 0) {
-      if (*appData.saveGameFile != NULLCHAR) {
-       SaveGameToFile(appData.saveGameFile, TRUE);
-      } else if (appData.autoSaveGames) {
-       AutoSaveGame();
-      }
-      if (*appData.savePositionFile != NULLCHAR) {
-       SavePositionToFile(appData.savePositionFile);
-      }
-    }
-    GameEnds((ChessMove) 0, NULL, GE_PLAYER);
-#else
+
     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
-#endif
+
     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
     /* make sure this other one finishes before killing it!                  */
     if(endingGame) { int count = 0;
@@ -10380,8 +10589,7 @@ AnalyzeModeEvent()
        first.analyzing = TRUE;
        /*first.maybeThinking = TRUE;*/
        first.maybeThinking = FALSE; /* avoid killing GNU Chess */
-       AnalysisPopUp(_("Analysis"),
-                     _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
+       EngineOutputPopUp();
     }
     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
     pausing = FALSE;
@@ -10407,8 +10615,7 @@ AnalyzeFileEvent()
        first.analyzing = TRUE;
        /*first.maybeThinking = TRUE;*/
        first.maybeThinking = FALSE; /* avoid killing GNU Chess */
-       AnalysisPopUp(_("Analysis"),
-                     _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
+       EngineOutputPopUp();
     }
     gameMode = AnalyzeFile;
     pausing = FALSE;
@@ -10480,6 +10687,7 @@ MachineWhiteEvent()
     SetMachineThinkingEnables();
     first.maybeThinking = TRUE;
     StartClocks();
+    firstMove = FALSE;
 
     if (appData.autoFlipView && !flipView) {
       flipView = !flipView;
@@ -10905,15 +11113,25 @@ ExitAnalyzeMode()
       SendToProgram("exit\n", &first);
       first.analyzing = FALSE;
     }
-    AnalysisPopDown();
     thinkOutput[0] = NULLCHAR;
 }
 
 void
 EditPositionDone()
 {
+    int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
+
     startedFromSetupPosition = TRUE;
     InitChessProgram(&first, FALSE);
+    castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
+    if(boards[0][0][BOARD_WIDTH>>1] == king) {
+       castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
+       castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
+    } else castlingRights[0][2] = -1;
+    if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
+       castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
+       castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
+    } else castlingRights[0][5] = -1;
     SendToProgram("force\n", &first);
     if (blackPlaysFirst) {
        strcpy(moveList[0], "");
@@ -12199,7 +12417,8 @@ ReceiveFromProgram(isr, closure, message, count, error)
                   sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
                   sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
                   sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
-                  sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')
+                  sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
+                  sscanf(message, "pong %c", &c)!=1   && start != '#')
                        { quote = "# "; print = (appData.engineComments == 2); }
                message[0] = start; // restore original message
        }
@@ -12549,7 +12768,11 @@ ParseFeatures(args, cps)
     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
-       ParseOption(&(cps->option[cps->nrOptions++]), cps); // [HGM] options: add option feature
+       if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
+           sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
+           SendToProgram(buf, cps);
+           continue;
+       }
        if(cps->nrOptions >= MAX_OPTIONS) {
            cps->nrOptions--;
            sprintf(buf, "%s engine has too many options\n", cps->which);
@@ -12590,7 +12813,7 @@ PeriodicUpdatesEvent(newState)
     appData.periodicUpdates=newState;
 
     /* Display type changes, so update it now */
-    DisplayAnalysis();
+//    DisplayAnalysis();
 
     /* Get the ball rolling again... */
     if (newState) {
@@ -12729,92 +12952,6 @@ DisplayMove(moveNumber)
     }
 }
 
-void
-DisplayAnalysisText(text)
-     char *text;
-{
-    char buf[MSG_SIZ];
-
-    if (gameMode == AnalyzeMode || gameMode == AnalyzeFile 
-               || appData.icsEngineAnalyze) {
-       sprintf(buf, "Analysis (%s)", first.tidy);
-       AnalysisPopUp(buf, text);
-    }
-}
-
-static int
-only_one_move(str)
-     char *str;
-{
-    while (*str && isspace(*str)) ++str;
-    while (*str && !isspace(*str)) ++str;
-    if (!*str) return 1;
-    while (*str && isspace(*str)) ++str;
-    if (!*str) return 1;
-    return 0;
-}
-
-void
-DisplayAnalysis()
-{
-    char buf[MSG_SIZ];
-    char lst[MSG_SIZ / 2];
-    double nps;
-    static char *xtra[] = { "", " (--)", " (++)" };
-    int h, m, s, cs;
-  
-    if (programStats.time == 0) {
-       programStats.time = 1;
-    }
-  
-    if (programStats.got_only_move) {
-       safeStrCpy(buf, programStats.movelist, sizeof(buf));
-    } else {
-        safeStrCpy( lst, programStats.movelist, sizeof(lst));
-
-        nps = (u64ToDouble(programStats.nodes) /
-             ((double)programStats.time /100.0));
-
-       cs = programStats.time % 100;
-       s = programStats.time / 100;
-       h = (s / (60*60));
-       s = s - h*60*60;
-       m = (s/60);
-       s = s - m*60;
-
-       if (programStats.moves_left > 0 && appData.periodicUpdates) {
-         if (programStats.move_name[0] != NULLCHAR) {
-           sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
-                   programStats.depth,
-                   programStats.nr_moves-programStats.moves_left,
-                   programStats.nr_moves, programStats.move_name,
-                   ((float)programStats.score)/100.0, lst,
-                   only_one_move(lst)?
-                   xtra[programStats.got_fail] : "",
-                   (u64)programStats.nodes, (int)nps, h, m, s, cs);
-         } else {
-           sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
-                   programStats.depth,
-                   programStats.nr_moves-programStats.moves_left,
-                   programStats.nr_moves, ((float)programStats.score)/100.0,
-                   lst,
-                   only_one_move(lst)?
-                   xtra[programStats.got_fail] : "",
-                   (u64)programStats.nodes, (int)nps, h, m, s, cs);
-         }
-       } else {
-           sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
-                   programStats.depth,
-                   ((float)programStats.score)/100.0,
-                   lst,
-                   only_one_move(lst)?
-                   xtra[programStats.got_fail] : "",
-                   (u64)programStats.nodes, (int)nps, h, m, s, cs);
-       }
-    }
-    DisplayAnalysisText(buf);
-}
-
 void
 DisplayComment(moveNumber, text)
      int moveNumber;
@@ -13541,6 +13678,14 @@ PositionToFEN(move, overrideCastling)
        } else {
            *p++ = '-';
        }
+    } else if(move == backwardMostMove) {
+       // [HGM] perhaps we should always do it like this, and forget the above?
+       if(epStatus[move] >= 0) {
+           *p++ = epStatus[move] + AAA;
+           *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
+       } else {
+           *p++ = '-';
+       }
     } else {
        *p++ = '-';
     }
@@ -13823,4 +13968,111 @@ EditPositionPasteFEN(char *fen)
       DrawPosition(FALSE, boards[currentMove]);
     }
   }
-}
\ No newline at end of file
+}
+
+static char cseq[12] = "\\   ";
+
+Boolean set_cont_sequence(char *new_seq)
+{
+    int len;
+    Boolean ret;
+
+    // handle bad attempts to set the sequence
+       if (!new_seq)
+               return 0; // acceptable error - no debug
+
+    len = strlen(new_seq);
+    ret = (len > 0) && (len < sizeof(cseq));
+    if (ret)
+        strcpy(cseq, new_seq);
+    else if (appData.debugMode)
+        fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %d)\n", new_seq, sizeof(cseq)-1);
+    return ret;
+}
+
+/*
+    reformat a source message so words don't cross the width boundary.  internal
+    newlines are not removed.  returns the wrapped size (no null character unless
+    included in source message).  If dest is NULL, only calculate the size required
+    for the dest buffer.  lp argument indicats line position upon entry, and it's
+    passed back upon exit.
+*/
+int wrap(char *dest, char *src, int count, int width, int *lp)
+{
+    int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
+
+    cseq_len = strlen(cseq);
+    old_line = line = *lp;
+    ansi = len = clen = 0;
+
+    for (i=0; i < count; i++)
+    {
+        if (src[i] == '\033')
+            ansi = 1;
+
+        // if we hit the width, back up
+        if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
+        {
+            // store i & len in case the word is too long
+            old_i = i, old_len = len;
+
+            // find the end of the last word
+            while (i && src[i] != ' ' && src[i] != '\n')
+            {
+                i--;
+                len--;
+            }
+
+            // word too long?  restore i & len before splitting it
+            if ((old_i-i+clen) >= width)
+            {
+                i = old_i;
+                len = old_len;
+            }
+
+            // extra space?
+            if (i && src[i-1] == ' ')
+                len--;
+
+            if (src[i] != ' ' && src[i] != '\n')
+            {
+                i--;
+                if (len)
+                    len--;
+            }
+
+            // now append the newline and continuation sequence
+            if (dest)
+                dest[len] = '\n';
+            len++;
+            if (dest)
+                strncpy(dest+len, cseq, cseq_len);
+            len += cseq_len;
+            line = cseq_len;
+            clen = cseq_len;
+            continue;
+        }
+
+        if (dest)
+            dest[len] = src[i];
+        len++;
+        if (!ansi)
+            line++;
+        if (src[i] == '\n')
+            line = 0;
+        if (src[i] == 'm')
+            ansi = 0;
+    }
+    if (dest && appData.debugMode)
+    {
+        fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
+            count, width, line, len, *lp);
+        show_bytes(debugFP, src, count);
+        fprintf(debugFP, "\ndest: ");
+        show_bytes(debugFP, dest, len);
+        fprintf(debugFP, "\n");
+    }
+    *lp = dest ? line : old_line;
+
+    return len;
+}