new developer release
[xboard.git] / backend.c
index b0d9351..ca839ca 100644 (file)
--- 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));
@@ -159,7 +160,7 @@ int LoadGameFromFile P((char *filename, int n, char *title, int useList));
 int LoadPositionFromFile P((char *filename, int n, char *title));
 int SavePositionToFile P((char *filename));
 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
-                 Board board, char *castle, char *ep));
+                                                                               Board board));
 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
 void ShowMove P((int fromX, int fromY, int toX, int toY));
 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
@@ -167,7 +168,7 @@ int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
 void BackwardInner P((int target));
 void ForwardInner P((int target));
 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
-void EditPositionDone P((void));
+void EditPositionDone P((Boolean fakeRights));
 void PrintOpponents P((FILE *fp));
 void PrintPosition P((FILE *fp, int move));
 void StartChessProgram P((ChessProgramState *cps));
@@ -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,10 +230,13 @@ 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;
 ChessProgramStats programStats;
+char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
+int endPV = -1;
 static int exiting = 0; /* [HGM] moved to top */
 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
@@ -243,6 +246,10 @@ 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;
+char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
 
 /* States for ics_getting_history */
 #define H_FALSE 0
@@ -297,26 +304,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:
 
@@ -369,6 +356,7 @@ PosFlags(index)
   case VariantNoCastle:
   case VariantShatranj:
   case VariantCourier:
+  case VariantMakruk:
     flags &= ~F_ALL_CASTLE_OK;
     break;
   default:
@@ -457,83 +445,102 @@ 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  castlingRank[BOARD_FILES]; // and corresponding ranks
+signed char  initialRights[BOARD_FILES];
 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;
-
-ChessSquare  FIDEArray[2][BOARD_SIZE] = {
+int mute; // mute all sounds
+
+// [HGM] vari: next 12 to save and restore variations
+#define MAX_VARIATIONS 10
+int framePtr = MAX_MOVES-1; // points to free stack entry
+int storedGames = 0;
+int savedFirst[MAX_VARIATIONS];
+int savedLast[MAX_VARIATIONS];
+int savedFramePtr[MAX_VARIATIONS];
+char *savedDetails[MAX_VARIATIONS];
+ChessMove savedResult[MAX_VARIATIONS];
+
+void PushTail P((int firstMove, int lastMove));
+Boolean PopTail P((Boolean annotate));
+void CleanupTail P((void));
+
+ChessSquare  FIDEArray[2][BOARD_FILES] = {
     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
        WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
        BlackKing, BlackBishop, BlackKnight, BlackRook }
 };
 
-ChessSquare twoKingsArray[2][BOARD_SIZE] = {
+ChessSquare twoKingsArray[2][BOARD_FILES] = {
     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
        WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
         BlackKing, BlackKing, BlackKnight, BlackRook }
 };
 
-ChessSquare  KnightmateArray[2][BOARD_SIZE] = {
+ChessSquare  KnightmateArray[2][BOARD_FILES] = {
     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
     { BlackRook, BlackMan, BlackBishop, BlackQueen,
         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
 };
 
-ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
-    { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
+ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
+    { 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_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
+ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
         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_SIZE>=10)
-ChessSquare ShogiArray[2][BOARD_SIZE] = {
+#if (BOARD_FILES>=10)
+ChessSquare ShogiArray[2][BOARD_FILES] = {
     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
 };
 
-ChessSquare XiangqiArray[2][BOARD_SIZE] = {
+ChessSquare XiangqiArray[2][BOARD_FILES] = {
     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
 };
 
-ChessSquare CapablancaArray[2][BOARD_SIZE] = {
+ChessSquare CapablancaArray[2][BOARD_FILES] = {
     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
 };
 
-ChessSquare GreatArray[2][BOARD_SIZE] = {
+ChessSquare GreatArray[2][BOARD_FILES] = {
     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
 };
 
-ChessSquare JanusArray[2][BOARD_SIZE] = {
+ChessSquare JanusArray[2][BOARD_FILES] = {
     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
@@ -541,7 +548,7 @@ ChessSquare JanusArray[2][BOARD_SIZE] = {
 };
 
 #ifdef GOTHIC
-ChessSquare GothicArray[2][BOARD_SIZE] = {
+ChessSquare GothicArray[2][BOARD_FILES] = {
     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
@@ -552,7 +559,7 @@ ChessSquare GothicArray[2][BOARD_SIZE] = {
 #endif // !GOTHIC
 
 #ifdef FALCON
-ChessSquare FalconArray[2][BOARD_SIZE] = {
+ChessSquare FalconArray[2][BOARD_FILES] = {
     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
@@ -562,23 +569,23 @@ ChessSquare FalconArray[2][BOARD_SIZE] = {
 #define FalconArray CapablancaArray
 #endif // !FALCON
 
-#else // !(BOARD_SIZE>=10)
+#else // !(BOARD_FILES>=10)
 #define XiangqiPosition FIDEArray
 #define CapablancaArray FIDEArray
 #define GothicArray FIDEArray
 #define GreatArray FIDEArray
-#endif // !(BOARD_SIZE>=10)
+#endif // !(BOARD_FILES>=10)
 
-#if (BOARD_SIZE>=12)
-ChessSquare CourierArray[2][BOARD_SIZE] = {
+#if (BOARD_FILES>=12)
+ChessSquare CourierArray[2][BOARD_FILES] = {
     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
 };
-#else // !(BOARD_SIZE>=12)
+#else // !(BOARD_FILES>=12)
 #define CourierArray CapablancaArray
-#endif // !(BOARD_SIZE>=12)
+#endif // !(BOARD_FILES>=12)
 
 
 Board initialPosition;
@@ -622,7 +629,7 @@ InitBackEnd1()
     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
 
     GetTimeMark(&programStartTime);
-    srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level
+    srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
 
     ClearProgramStats();
     programStats.ok_to_send = 1;
@@ -659,10 +666,10 @@ InitBackEnd1()
     {
         int i, j;
 
-        for( i=0; i<MAX_MOVES; i++ ) {
+        for( i=0; i<=framePtr; i++ ) {
             pvInfoList[i].depth = -1;
-            epStatus[i]=EP_NONE;
-            for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
+            boards[i][EP_STATUS] = EP_NONE;
+            for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
         }
     }
 
@@ -758,7 +765,7 @@ InitBackEnd1()
     /* [HGM] time odds: set factor for each machine */
     first.timeOdds  = appData.firstTimeOdds;
     second.timeOdds = appData.secondTimeOdds;
-    { int norm = 1;
+    { float norm = 1;
         if(appData.timeOddsMode) {
             norm = first.timeOdds;
             if(norm > second.timeOdds) norm = second.timeOdds;
@@ -809,7 +816,8 @@ InitBackEnd1()
 
     if (appData.icsActive) {
         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
-    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
+//    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
+    } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
        appData.clockMode = FALSE;
        first.sendTime = second.sendTime = 0;
     }
@@ -828,20 +836,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) {
@@ -899,6 +896,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 */
@@ -1007,71 +1005,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 +1066,7 @@ InitBackEnd2()
        fprintf(debugFP, "%s\n", programVersion);
     }
 
+    set_cont_sequence(appData.wrapContSeq);
     if (appData.matchGames > 0) {
        appData.matchMode = TRUE;
     } else if (appData.matchMode) {
@@ -1230,10 +1216,9 @@ InitBackEnd3 P((void))
             }
             /* [HGM] loadPos: make that every new game uses the setup */
             /* from file as long as we do not switch variant          */
-            if(!blackPlaysFirst) { int i;
+            if(!blackPlaysFirst) {
                 startedFromPositionFile = TRUE;
                 CopyBoard(filePosition, boards[0]);
-                for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
             }
        }
        if (initialMode == AnalyzeMode) {
@@ -1453,6 +1438,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 +1575,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;
@@ -1918,6 +1927,8 @@ CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
     ChessSquare piece;
 
     if(gameInfo.holdingsWidth < 2)  return;
+    if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
+       return; // prevent overwriting by pre-board holdings
 
     if( (int)lowestPiece >= BlackPawn ) {
         holdingsColumn = 0;
@@ -1946,7 +1957,6 @@ CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
         board[holdingsStartRow+j*direction][countsColumn]++;
     }
-
 }
 
 
@@ -1954,8 +1964,7 @@ 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;
+   Board oldBoard;
 
    startedFromPositionFile = FALSE;
    if(gameInfo.variant == newVariant) return;
@@ -1972,69 +1981,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;
+       break;
+     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);
+   } else gameInfo.variant = newVariant;
+   CopyBoard(oldBoard, board);   // remember correctly formatted board
+     InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
+   DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
 }
 
 static int loggedOn = FALSE;
@@ -2044,6 +2049,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;
 /*----------------------------*/
@@ -2059,7 +2065,7 @@ read_from_ics(isr, closure, data, count, error)
      int count;
      int error;
 {
-#define BUF_SIZE 8192
+#define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
 #define STARTED_NONE 0
 #define STARTED_MOVES 1
 #define STARTED_BOARD 2
@@ -2076,6 +2082,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 +2091,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) {
@@ -2094,7 +2104,8 @@ read_from_ics(isr, closure, data, count, error)
 
     if (appData.debugMode) { int f = forwardMostMove;
         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
-                castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
+                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 (count > 0) {
        /* If last read ended with a partial line that we couldn't parse,
@@ -2104,20 +2115,70 @@ 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;
+//     next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
+       next_out = 0;
        leftover_start = 0;
        
        i = 0;
@@ -2242,7 +2303,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 +2315,7 @@ read_from_ics(isr, closure, data, count, error)
                  strcat(str, "$iset lock 1\n$style 12\n");
                }
                SendToICS(str);
+               NotifyFrontendLogin();
                intfSet = TRUE;
            }
 
@@ -2263,19 +2324,25 @@ 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));
+                       AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
                    else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
-                       int nrDigit = 0, nrAlph = 0, i;
+                       int nrDigit = 0, nrAlph = 0, j;
                        if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
                        { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
                        parse[parse_pos] = NULLCHAR;
                        // try to be smart: if it does not look like search info, it should go to
                        // ICS interaction window after all, not to engine-output window.
-                       for(i=0; i<parse_pos; i++) { // count letters and digits
-                           nrDigit += (parse[i] >= '0' && parse[i] <= '9');
-                           nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
-                           nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
+                       for(j=0; j<parse_pos; j++) { // count letters and digits
+                           nrDigit += (parse[j] >= '0' && parse[j] <= '9');
+                           nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
+                           nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
                        }
                        if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
                            int depth=0; float score;
@@ -2285,6 +2352,7 @@ 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);
@@ -2293,7 +2361,7 @@ read_from_ics(isr, closure, data, count, error)
                    }
                    started = STARTED_NONE;
                } else {
-                   /* Don't match patterns against characters in chatter */
+                   /* Don't match patterns against characters in comment */
                    i++;
                    continue;
                }
@@ -2393,12 +2461,53 @@ read_from_ics(isr, closure, data, count, error)
                        } 
                        continue;
                } else
-               if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
-                   started = STARTED_CHATTER;
-                   suppressKibitz = TRUE;
+               if(looking_at(buf, &i, "kibitzed to *\n") && atoi(star_match[0])) {
+                   // suppress the acknowledgements of our own autoKibitz
+                   SendToPlayer(star_match[0], strlen(star_match[0]));
+                   looking_at(buf, &i, "*% "); // eat prompt
+                   next_out = i;
                }
            } // [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 +2526,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") ||
@@ -2755,7 +2865,9 @@ 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(suppressKibitz) next_out = i;
                savingComment = FALSE;
+               suppressKibitz = 0;
                switch (started) {
                  case STARTED_MOVES:
                  case STARTED_MOVES_NOHIDE:
@@ -2773,18 +2885,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 +2909,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) {
@@ -2839,7 +2935,7 @@ read_from_ics(isr, closure, data, count, error)
                        currentMove = forwardMostMove;
                        ClearHighlights();/*!!could figure this out*/
                        flipView = appData.flipView;
-                       DrawPosition(FALSE, boards[currentMove]);
+                       DrawPosition(TRUE, boards[currentMove]);
                        DisplayBothClocks();
                        sprintf(str, "%s vs. %s",
                                gameInfo.white, gameInfo.black);
@@ -3068,6 +3164,7 @@ read_from_ics(isr, closure, data, count, error)
                    strncmp(why, "Continuing ", 11) == 0) {
                    gs_gamenum = gamenum;
                    strcpy(gs_kind, strchr(why, ' ') + 1);
+                   VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
 #if ZIPPY
                    if (appData.zippyPlay) {
                        ZippyGameStart(whitename, blackname);
@@ -3170,16 +3267,14 @@ read_from_ics(isr, closure, data, count, error)
                      if (currentMove == 0 &&
                          gameMode == IcsPlayingWhite &&
                          appData.premoveWhite) {
-                       sprintf(str, "%s%s\n", ics_prefix,
-                               appData.premoveWhiteText);
+                       sprintf(str, "%s\n", appData.premoveWhiteText);
                        if (appData.debugMode)
                          fprintf(debugFP, "Sending premove:\n");
                        SendToICS(str);
                      } else if (currentMove == 1 &&
                                 gameMode == IcsPlayingBlack &&
                                 appData.premoveBlack) {
-                       sprintf(str, "%s%s\n", ics_prefix,
-                               appData.premoveBlackText);
+                       sprintf(str, "%s\n", appData.premoveBlackText);
                        if (appData.debugMode)
                          fprintf(debugFP, "Sending premove:\n");
                        SendToICS(str);
@@ -3196,8 +3291,10 @@ read_from_ics(isr, closure, data, count, error)
 
                    /* Usually suppress following prompt */
                    if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
+                       while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
                        if (looking_at(buf, &i, "*% ")) {
                            savingComment = FALSE;
+                           suppressKibitz = 0;
                        }
                    }
                    next_out = i;
@@ -3217,7 +3314,13 @@ read_from_ics(isr, closure, data, count, error)
                            * to move the position two files to the right to
                            * create room for them!
                            */
-                          VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
+                         VariantClass newVariant;
+                         switch(gameInfo.boardWidth) { // base guess on board width
+                               case 9:  newVariant = VariantShogi; break;
+                               case 10: newVariant = VariantGreat; break;
+                               default: newVariant = VariantCrazyhouse; break;
+                         }
+                          VariantSwitch(boards[currentMove], newVariant); /* temp guess */
                          /* Get a move list just to see the header, which
                             will tell us whether this is really bug or zh */
                          if (ics_getting_history == H_FALSE) {
@@ -3233,8 +3336,9 @@ read_from_ics(isr, closure, data, count, error)
                         white_holding[strlen(white_holding)-1] = NULLCHAR;
                         black_holding[strlen(black_holding)-1] = NULLCHAR;
                         /* [HGM] copy holdings to board holdings area */
-                        CopyHoldings(boards[currentMove], white_holding, WhitePawn);
-                        CopyHoldings(boards[currentMove], black_holding, BlackPawn);
+                        CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
+                        CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
+                        boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
 #if ZIPPY
                        if (appData.zippyPlay && first.initDone) {
                            ZippyHoldings(white_holding, black_holding,
@@ -3258,7 +3362,9 @@ read_from_ics(isr, closure, data, count, error)
                    }
                    /* Suppress following prompt */
                    if (looking_at(buf, &i, "*% ")) {
+                       if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
                        savingComment = FALSE;
+                       suppressKibitz = 0;
                    }
                    next_out = i;
                }
@@ -3268,12 +3374,13 @@ read_from_ics(isr, closure, data, count, error)
            i++;                /* skip unparsed character and loop back */
        }
        
-       if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
-           started != STARTED_HOLDINGS && i > next_out) {
-           SendToPlayer(&buf[next_out], i - next_out);
+       if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
+//         started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
+//         SendToPlayer(&buf[next_out], i - next_out);
+           started != STARTED_HOLDINGS && leftover_start > next_out) {
+           SendToPlayer(&buf[next_out], leftover_start - next_out);
            next_out = i;
        }
-       suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
        
        leftover_len = buf_len - leftover_start;
        /* if buffer ends with something we couldn't parse,
@@ -3327,6 +3434,7 @@ ParseBoard12(string)
     char promoChar;
     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
     char *bookHit = NULL; // [HGM] book
+    Boolean weird = FALSE, reqFlag = FALSE;
 
     fromX = fromY = toX = toY = -1;
     
@@ -3342,6 +3450,7 @@ ParseBoard12(string)
         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
            if(string[i] == ' ') { ranks++; files = 0; }
             else files++;
+           if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
            i++;
        }
        for(j = 0; j <i; j++) board_chars[j] = string[j];
@@ -3364,7 +3473,7 @@ ParseBoard12(string)
     /* Convert the move number to internal form */
     moveNum = (moveNum - 1) * 2;
     if (to_play == 'B') moveNum++;
-    if (moveNum >= MAX_MOVES) {
+    if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
                        0, 1);
       return;
@@ -3431,6 +3540,27 @@ ParseBoard12(string)
        ics_getting_history = H_FALSE;
        return;
     }
+
+   if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
+                                       weird && (int)gameInfo.variant <= (int)VariantShogi) {
+     /* [HGM] We seem to have switched variant unexpectedly
+      * Try to guess new variant from board size
+      */
+         VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
+         if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
+         if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
+         if(ranks == 8 && files == 12) newVariant = VariantCourier; else
+         if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
+         if(!weird) newVariant = VariantNormal;
+          VariantSwitch(boards[currentMove], newVariant); /* temp guess */
+         /* Get a move list just to see the header, which
+            will tell us whether this is really bug or zh */
+         if (ics_getting_history == H_FALSE) {
+           ics_getting_history = H_REQUESTED; reqFlag = TRUE;
+           sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
+           SendToICS(str);
+         }
+    }
     
     /* Take action if this is the first board of a new game, or of a
        different game than is currently being displayed.  */
@@ -3439,15 +3569,15 @@ ParseBoard12(string)
        
        /* Forget the old game and get the history (if any) of the new one */
        if (gameMode != BeginningOfGame) {
-         Reset(FALSE, TRUE);
+         Reset(TRUE, TRUE);
        }
        newGame = TRUE;
        if (appData.autoRaiseBoard) BoardToTop();
        prevMove = -3;
        if (gamenum == -1) {
            newGameMode = IcsIdle;
-       } else if (moveNum > 0 && newGameMode != IcsIdle &&
-                  appData.getMoveList) {
+       } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
+                  appData.getMoveList && !reqFlag) {
            /* Need to get game history */
            ics_getting_history = H_REQUESTED;
            sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
@@ -3483,7 +3613,7 @@ ParseBoard12(string)
        timeIncrement = increment * 1000;
        movesPerSession = 0;
        gameInfo.timeControl = TimeControlTagValue();
-        VariantSwitch(board, StringToVariant(gameInfo.event) );
+        VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
   if (appData.debugMode) {
     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
@@ -3563,6 +3693,7 @@ ParseBoard12(string)
       }
     }
     CopyBoard(boards[moveNum], board);
+    boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
     if (moveNum == 0) {
        startedFromSetupPosition =
          !CompareBoards(board, initialPosition);
@@ -3583,40 +3714,45 @@ ParseBoard12(string)
     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
 
-        for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
+        for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
             if(board[0][i] == WhiteRook) j = i;
-        initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
-        for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
+        initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
+        for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
             if(board[0][i] == WhiteRook) j = i;
-        initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
-        for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
+        initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
+        for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
-        initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
-        for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
+        initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
+        for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
-        initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
+        initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
 
        if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
-            if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
+            if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
             if(board[BOARD_HEIGHT-1][k] == bKing)
-                initialRights[5] = castlingRights[moveNum][5] = k;
+                initialRights[5] = boards[moveNum][CASTLING][5] = k;
+        if(gameInfo.variant == VariantTwoKings) {
+            // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
+            if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
+            if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
+        }
     } else { int r;
-        r = castlingRights[moveNum][0] = initialRights[0];
-        if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
-        r = castlingRights[moveNum][1] = initialRights[1];
-        if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
-        r = castlingRights[moveNum][3] = initialRights[3];
-        if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
-        r = castlingRights[moveNum][4] = initialRights[4];
-        if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
+        r = boards[moveNum][CASTLING][0] = initialRights[0];
+        if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
+        r = boards[moveNum][CASTLING][1] = initialRights[1];
+        if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
+        r = boards[moveNum][CASTLING][3] = initialRights[3];
+        if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
+        r = boards[moveNum][CASTLING][4] = initialRights[4];
+        if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
         /* wildcastle kludge: always assume King has rights */
-        r = castlingRights[moveNum][2] = initialRights[2];
-        r = castlingRights[moveNum][5] = initialRights[5];
+        r = boards[moveNum][CASTLING][2] = initialRights[2];
+        r = boards[moveNum][CASTLING][5] = initialRights[5];
     }
     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
-    epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
+    boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
 
     
     if (ics_getting_history == H_GOT_REQ_HEADER ||
@@ -3629,15 +3765,6 @@ ParseBoard12(string)
     /* Update currentMove and known move number limits */
     newMove = newGame || moveNum > forwardMostMove;
 
-    /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
-    if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
-        takeback = forwardMostMove - moveNum;
-        for (i = 0; i < takeback; i++) {
-             if (appData.debugMode) fprintf(debugFP, "take back move\n");
-             SendToProgram("undo\n", &first);
-        }
-    }
-
     if (newGame) {
        forwardMostMove = backwardMostMove = currentMove = moveNum;
        if (gameMode == IcsExamining && moveNum == 0) {
@@ -3650,6 +3777,20 @@ ParseBoard12(string)
        }
     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
               || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
+#if ZIPPY
+       /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
+       /* [HGM] applied this also to an engine that is silently watching        */
+       if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
+           (gameMode == IcsObserving || gameMode == IcsExamining) &&
+           gameInfo.variant == currentlyInitializedVariant) {
+         takeback = forwardMostMove - moveNum;
+         for (i = 0; i < takeback; i++) {
+           if (appData.debugMode) fprintf(debugFP, "take back move\n");
+           SendToProgram("undo\n", &first);
+         }
+       }
+#endif
+
        forwardMostMove = moveNum;
        if (!pausing || currentMove > forwardMostMove)
          currentMove = forwardMostMove;
@@ -3660,12 +3801,20 @@ ParseBoard12(string)
            forwardMostMove = pauseExamForwardMostMove;
            return;
        }
-       forwardMostMove = backwardMostMove = currentMove = moveNum;
        if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
+#if ZIPPY
+           if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
+               // [HGM] when we will receive the move list we now request, it will be
+               // fed to the engine from the first move on. So if the engine is not
+               // in the initial position now, bring it there.
+               InitChessProgram(&first, 0);
+           }
+#endif
            ics_getting_history = H_REQUESTED;
            sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
            SendToICS(str);
        }
+       forwardMostMove = backwardMostMove = currentMove = moveNum;
     }
     
     /* Update the clocks */
@@ -3693,7 +3842,8 @@ ParseBoard12(string)
   if (appData.debugMode) {
     if (appData.debugMode) { int f = forwardMostMove;
         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
-                castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
+                boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
+                boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
     }
     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
     fprintf(debugFP, "moveNum = %d\n", moveNum);
@@ -3739,11 +3889,10 @@ ParseBoard12(string)
          // end of long SAN patch
          if (valid) {
            (void) CoordsToAlgebraic(boards[moveNum - 1],
-                                    PosFlags(moveNum - 1), EP_UNKNOWN,
+                                    PosFlags(moveNum - 1),
                                     fromY, fromX, toY, toX, promoChar,
                                     parseList[moveNum-1]);
-            switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
-                             castlingRights[moveNum]) ) {
+            switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
              case MT_NONE:
              case MT_STALEMATE:
              default:
@@ -3768,12 +3917,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 +4090,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;
@@ -4059,7 +4208,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));
@@ -4087,6 +4236,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
@@ -4236,7 +4387,8 @@ ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
        if (appData.testLegality) {
          return (*moveType != IllegalMove);
        } else {
-         return !(fromX == fromY && toX == toY);
+         return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
+                       WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
        }
 
       case WhiteDrop:
@@ -4271,6 +4423,108 @@ ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
     }
 }
 
+
+void
+ParsePV(char *pv)
+{ // Parse a string of PV moves, and append to current game, behind forwardMostMove
+  int fromX, fromY, toX, toY; char promoChar;
+  ChessMove moveType;
+  Boolean valid;
+  int nr = 0;
+
+  endPV = forwardMostMove;
+  do {
+    while(*pv == ' ') pv++;
+    if(*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);
+}
+    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
+    }
+    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;
+    endPV++;
+    CopyBoard(boards[endPV], boards[endPV-1]);
+    ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
+    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;
+  } while(valid);
+  currentMove = endPV;
+  if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
+  SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
+                       moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
+  DrawPosition(TRUE, boards[currentMove]);
+}
+
+static int lastX, lastY;
+
+Boolean
+LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
+{
+       int startPV;
+
+       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++;
+       buf[index] = 0;
+       ParsePV(buf+startPV);
+       *start = startPV; *end = index-1;
+       return TRUE;
+}
+
+Boolean
+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.
+  return TRUE;
+}
+
+void
+UnLoadPV()
+{
+  if(endPV < 0) return;
+  endPV = -1;
+  currentMove = forwardMostMove;
+  ClearPremoveHighlights();
+  DrawPosition(TRUE, boards[currentMove]);
+}
+
+void
+MovePV(int x, int y, int h)
+{ // step through PV based on mouse coordinates (called on mouse move)
+  int margin = h>>3, step = 0;
+
+  if(endPV < 0) return;
+  // we must somehow check if right button is still down (might be released off board!)
+  if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
+  if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
+  if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
+  if(!step) return;
+  lastX = x; lastY = y;
+  if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
+  currentMove += step;
+  if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
+  SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
+                       moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
+  DrawPosition(FALSE, boards[currentMove]);
+}
+
+
 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
 // All positions will have equal probability, but the current method will not provide a unique
 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
@@ -4394,12 +4648,12 @@ void SetUpShuffle(Board board, int number)
            // Last King gets castling rights
            while(piecesLeft[(int)WhiteUnicorn]) {
                i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
-               initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
+               initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
            }
 
            while(piecesLeft[(int)WhiteKing]) {
                i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
-               initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
+               initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
            }
 
 
@@ -4414,9 +4668,9 @@ void SetUpShuffle(Board board, int number)
                if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
                        if(first) {
                                first=0;
-                               initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
+                               initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
                        }
-                       initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
+                       initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
                }
        }
        for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
@@ -4490,22 +4744,21 @@ void
 InitPosition(redraw)
      int redraw;
 {
-    ChessSquare (* pieces)[BOARD_SIZE];
+    ChessSquare (* pieces)[BOARD_FILES];
     int i, j, pawnRow, overrule,
     oldx = gameInfo.boardWidth,
     oldy = gameInfo.boardHeight,
     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 */
     {
-        for( i=0; i<MAX_MOVES; i++ ) {
+        for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
             pvInfoList[i].depth = 0;
-            epStatus[i]=EP_NONE;
-            for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
+            boards[i][EP_STATUS] = EP_NONE;
+            for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
         }
 
         initialRulePlies = 0; /* 50-move counter start */
@@ -4527,7 +4780,9 @@ InitPosition(redraw)
     gameInfo.boardHeight   = 8;
     gameInfo.holdingsSize  = 0;
     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
-    for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
+    for(i=0; i<BOARD_FILES-2; i++)
+      initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
+    initialPosition[EP_STATUS] = EP_NONE;
     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
 
     switch (gameInfo.variant) {
@@ -4541,6 +4796,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;
@@ -4561,12 +4822,12 @@ InitPosition(redraw)
       gameInfo.boardWidth = 10;
       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
       nrCastlingRights = 6;
-        castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
-        castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
-        castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
-        castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
-        castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
-        castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
+        initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
+        initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
+        initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
+        initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
+        initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
+        initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
       break;
     case VariantFalcon:
       pieces = FalconArray;
@@ -4593,7 +4854,6 @@ InitPosition(redraw)
       gameInfo.boardWidth  = 12;
       nrCastlingRights = 0;
       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
-      for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
       break;
     case VariantKnightmate:
       pieces = KnightmateArray;
@@ -4601,7 +4861,7 @@ InitPosition(redraw)
       break;
     case VariantFairy:
       pieces = fairyArray;
-      SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); 
+      SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
       break;
     case VariantGreat:
       pieces = GreatArray;
@@ -4629,7 +4889,6 @@ InitPosition(redraw)
     case VariantNoCastle:
       pieces = FIDEArray;
       nrCastlingRights = 0;
-      for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
       /* !!?unconstrained back-rank shuffle */
       shuffleOpenings = 1;
       break;
@@ -4649,11 +4908,12 @@ InitPosition(redraw)
         gameInfo.holdingsSize = i;
     }
     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
-    if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
-        DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
+    if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
+        DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
 
     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)
@@ -4698,12 +4958,12 @@ InitPosition(redraw)
         /* Variants with other castling rights must set them themselves above    */
         nrCastlingRights = 6;
        
-        castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
-        castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
-        castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
-        castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
-        castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
-        castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
+        initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
+        initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
+        initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
+        initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
+        initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
+        initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
      }
 
      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
@@ -4713,17 +4973,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,12 +4980,11 @@ InitPosition(redraw)
        SetUpShuffle(initialPosition, appData.defaultFrcPosition);
        startedFromSetupPosition = TRUE;
     }
-#endif
     if(startedFromPositionFile) {
       /* [HGM] loadPos: use PositionFile for every new game */
       CopyBoard(initialPosition, filePosition);
       for(i=0; i<nrCastlingRights; i++)
-          castlingRights[0][i] = initialRights[i] = fileRights[i];
+          initialRights[i] = filePosition[CASTLING][i];
       startedFromSetupPosition = TRUE;
     }
 
@@ -4831,24 +5079,31 @@ 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;
+    } else if(gameInfo.variant == VariantMakruk) {
+        promotionZoneSize = 3;
     }
+
+    // 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 +5112,61 @@ 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 || gameInfo.variant == VariantMakruk) {
+       *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),
+                       fromY, fromX, toY, toX, NULLCHAR);
+       if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
+          moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
+           return FALSE;
+    }
+
+    return TRUE;
 }
 
 int
@@ -4944,12 +5253,6 @@ OKToStartUserMove(x, y)
            /* Could disallow this or prompt for confirmation */
            cmailOldMove = -1;
        }
-       if (currentMove < forwardMostMove) {
-           /* Discarding moves */
-           /* Could prompt for confirmation here,
-              but I don't think that's such a good idea */
-           forwardMostMove = currentMove;
-       }
        break;
 
       case BeginningOfGame:
@@ -4978,6 +5281,7 @@ OKToStartUserMove(x, y)
        break;
     }
     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
+       && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
        && gameMode != AnalyzeFile && gameMode != Training) {
        DisplayMoveError(_("Displayed position is not current"));
        return FALSE;
@@ -4991,39 +5295,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!
@@ -5136,12 +5416,28 @@ UserMoveTest(fromX, fromY, toX, toY, promoChar)
            return AmbiguousMove;
        } else if (toX >= 0 && toY >= 0) {
            boards[0][toY][toX] = boards[0][fromY][fromX];
+           if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
+               if(boards[0][fromY][0] != EmptySquare) {
+                   if(boards[0][fromY][1]) boards[0][fromY][1]--;
+                   if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare; 
+               }
+           } else
+           if(fromX == BOARD_RGHT+1) {
+               if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
+                   if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
+                   if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; 
+               }
+           } else
            boards[0][fromY][fromX] = EmptySquare;
            return AmbiguousMove;
        }
         return ImpossibleMove;
     }
 
+    if(toX < 0 || toY < 0) 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;
@@ -5161,9 +5457,7 @@ UserMoveTest(fromX, fromY, toX, toY, promoChar)
        
     /* [HGM] always test for legality, to get promotion info */
     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) {
@@ -5171,7 +5465,7 @@ UserMoveTest(fromX, fromY, toX, toY, promoChar)
             return ImpossibleMove;
        }
     }
-if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
+
     return moveType;
     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
        function is made into one that returns an OK move type if FinishMove
@@ -5190,7 +5484,7 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
      /*char*/int promoChar;
 {
     char *bookHit = 0;
-if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
+
     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
        // [HGM] superchess: suppress promotions to non-available piece
        int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
@@ -5205,13 +5499,12 @@ if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", move
        move type in caller when we know the move is a legal promotion */
     if(moveType == NormalMove && promoChar)
         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
-if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
+
     /* [HGM] convert drag-and-drop piece drops to standard form */
-    if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
+    if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
           if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
                moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
-//         fromX = boards[currentMove][fromY][fromX];
           // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
           if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
           fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
@@ -5220,7 +5513,7 @@ if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", move
     }
 
     /* [HGM] <popupFix> The following if has been moved here from
-       UserMoveEvent(). Because it seemed to belon here (why not allow
+       UserMoveEvent(). Because it seemed to belong here (why not allow
        piece drops in training games?), and because it can only be
        performed after it is known to what we promote. */
     if (gameMode == Training) {
@@ -5229,9 +5522,9 @@ if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", move
        * If they don't match, display an error message.
        */
       int saveAnimate;
-      Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
+      Board testBoard;
       CopyBoard(testBoard, boards[currentMove]);
-      ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
+      ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
 
       if (CompareBoards(testBoard, boards[currentMove+1])) {
        ForwardInner(currentMove+1);
@@ -5260,8 +5553,9 @@ if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", move
 
   /* Ok, now we know that the move is good, so we can kill
      the previous line in Analysis Mode */
-  if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
-    forwardMostMove = currentMove;
+  if ((gameMode == AnalyzeMode || gameMode == EditGame) 
+                               && currentMove < forwardMostMove) {
+    PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
   }
 
   /* If we need the chess program but it's dead, restart it */
@@ -5294,7 +5588,7 @@ if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", move
     }
     ModeHighlight();
   }
-if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
+
   /* Relay move to ICS or chess engine */
   if (appData.icsActive) {
     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
@@ -5322,8 +5616,7 @@ if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", move
 
   switch (gameMode) {
   case EditGame:
-    switch (MateTest(boards[currentMove], PosFlags(currentMove),
-                     EP_UNKNOWN, castlingRights[currentMove]) ) {
+    switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
     case MT_NONE:
     case MT_CHECK:
       break;
@@ -5379,12 +5672,258 @@ 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
+Mark(board, flags, kind, rf, ff, rt, ft, closure)
+     Board board;
+     int flags;
+     ChessMove kind;
+     int rf, ff, rt, ft;
+     VOIDSTAR closure;
+{
+    typedef char Markers[BOARD_RANKS][BOARD_FILES];
+    Markers *m = (Markers *) closure;
+    if(rf == fromY && ff == fromX)
+       (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
+                        || kind == WhiteCapturesEnPassant
+                        || kind == BlackCapturesEnPassant);
+    else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
+}
+
+void
+MarkTargetSquares(int clear)
+{
+  int x, y;
+  if(!appData.markers || !appData.highlightDragging || 
+     !appData.testLegality || gameMode == EditPosition) return;
+  if(clear) {
+    for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
+  } else {
+    int capt = 0;
+    GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
+    if(PosFlags(0) & F_MANDATORY_CAPTURE) {
+      for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
+      if(capt)
+      for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
+    }
+  }
+  DrawPosition(TRUE, NULL);
+}
+
+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();
+    MarkTargetSquares(1);
+
+    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;
+               MarkTargetSquares(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;
+               MarkTargetSquares(0);
+               DragPieceBegin(xPix, yPix);
+           }
+           return;
+       }
+       // ignore clicks on holdings
+       if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
+    }
+
+    if (clickType == Release && 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 (possibly off-board) 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;
+    }
+
+    // moves into holding are invalid for now (except in EditPosition, adapting to-square)
+    if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
+       ChessSquare piece = boards[currentMove][fromY][fromX];
+       if(gameMode == EditPosition && piece != EmptySquare &&
+          fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
+           int n;
+            
+           if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
+               n = PieceToNumber(piece - (int)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; }
+               boards[currentMove][n][BOARD_WIDTH-1] = piece;
+               boards[currentMove][n][BOARD_WIDTH-2]++;
+           }
+           boards[currentMove][fromY][fromX] = EmptySquare;
+       }
+       ClearHighlights();
+       fromX = fromY = -1;
+       DrawPosition(TRUE, boards[currentMove]);
+       return;
+    }
+
+    // off-board moves should not be highlighted
+    if(x < 0 || x < 0) ClearHighlights();
+
+    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;
@@ -5406,6 +5945,8 @@ void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cp
         stats.an_move_count = cpstats->nr_moves;
     }
 
+    if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
+
     SetProgramStats( &stats );
 }
 
@@ -5467,6 +6008,8 @@ HandleMachineMove(message, cps)
     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
@@ -5572,7 +6115,8 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
 
     if (appData.debugMode) { int f = forwardMostMove;
         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
-                castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
+                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,
@@ -5600,12 +6144,11 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
                                                               ) {
            ChessMove moveType;
            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
-                        epStatus[forwardMostMove], castlingRights[forwardMostMove],
                              fromY, fromX, toY, toX, promoChar);
            if (appData.debugMode) {
                 int i;
                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
-                    castlingRights[forwardMostMove][i], castlingRank[i]);
+                    boards[forwardMostMove][CASTLING][i], castlingRank[i]);
                 fprintf(debugFP, "castling rights\n");
            }
             if(moveType == IllegalMove) {
@@ -5647,7 +6190,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 +6198,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
@@ -5704,7 +6248,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
 
        if( gameMode == TwoMachinesPlay ) {
          // [HGM] some adjudications useful with buggy engines
-            int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
+            int k, count = 0; static int bare = 1;
          if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
 
 
@@ -5766,7 +6310,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
                /* Some material-based adjudications that have to be made before stalemate test */
                if(gameInfo.variant == VariantAtomic && NrK < 2) {
                    // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
-                    epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
+                    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
                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
@@ -5779,7 +6323,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
                /* Bare King in Shatranj (loses) or Losers (wins) */
                 if( NrW == 1 || NrPieces - NrW == 1) {
                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
-                    epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
+                    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
                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
@@ -5790,7 +6334,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
                  } else
                   if( gameInfo.variant == VariantShatranj && --bare < 0)
                   {    /* bare King */
-                       epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
+                       boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
                        if(appData.checkMates) {
                            /* but only adjudicate if adjudication enabled */
                            SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
@@ -5804,17 +6348,16 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
 
 
             // don't wait for engine to announce game end if we can judge ourselves
-            switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
-                                       castlingRights[forwardMostMove]) ) {
+            switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
              case MT_CHECK:
                if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
                    int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
                    for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
-                       if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
+                       if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
                            checkCnt++;
                        if(checkCnt >= 2) {
                            reason = "Xboard adjudication: 3rd check";
-                           epStatus[forwardMostMove] = EP_CHECKMATE;
+                           boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
                            break;
                        }
                    }
@@ -5825,25 +6368,25 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
              case MT_STALEMATE:
              case MT_STAINMATE:
                reason = "Xboard adjudication: Stalemate";
-               if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
-                   epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
+               if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
+                   boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
                    if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
-                       epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
+                       boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
                    else if(gameInfo.variant == VariantSuicide) // in suicide it depends
-                       epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
+                       boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
                                                   ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
                                                                        EP_CHECKMATE : EP_WINS);
                    else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
-                       epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
+                       boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
                }
                break;
              case MT_CHECKMATE:
                reason = "Xboard adjudication: Checkmate";
-               epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
+               boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
                break;
            }
 
-               switch(i = epStatus[forwardMostMove]) {
+               switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
                    case EP_STALEMATE:
                        result = GameIsDrawn; break;
                    case EP_CHECKMATE:
@@ -5868,7 +6411,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
                 {    /* KBK, KNK, KK of KBKB with like Bishops */
 
                      /* always flag draws, for judging claims */
-                     epStatus[forwardMostMove] = EP_INSUF_DRAW;
+                     boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
 
                      if(appData.materialDraws) {
                          /* but only adjudicate them if adjudication enabled */
@@ -5898,62 +6441,42 @@ 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, 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]);
+           
+         }
 
-    }
-#endif
                 /* Check for rep-draws */
                 count = 0;
                 for(k = forwardMostMove-2;
                     k>=backwardMostMove && k>=forwardMostMove-100 &&
-                        epStatus[k] < EP_UNKNOWN &&
-                        epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
+                        (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
+                        (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= 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) )
+                        if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
+                             (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
                                 rights++; /* King lost rights, while rook still had them */
-                        if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
-                            if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
-                                castlingRights[forwardMostMove][1] != castlingRights[k][1] )
+                        if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
+                            if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
+                                boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
                                    rights++; /* but at least one rook lost them */
                         }
-                        if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
-                             (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
+                        if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
+                             (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
                                 rights++; 
-                        if( castlingRights[forwardMostMove][5] >= 0 ) {
-                            if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
-                                castlingRights[forwardMostMove][4] != castlingRights[k][4] )
+                        if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
+                            if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
+                                boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][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 */
@@ -5964,11 +6487,9 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
                                // [HGM] xiangqi: check for forbidden perpetuals
                                int m, ourPerpetual = 1, hisPerpetual = 1;
                                for(m=forwardMostMove; m>k; m-=2) {
-                                   if(MateTest(boards[m], PosFlags(m), 
-                                                       EP_NONE, castlingRights[m]) != MT_CHECK)
+                                   if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
                                        ourPerpetual = 0; // the current mover did not always check
-                                   if(MateTest(boards[m-1], PosFlags(m-1), 
-                                                       EP_NONE, castlingRights[m-1]) != MT_CHECK)
+                                   if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
                                        hisPerpetual = 0; // the opponent did not always check
                                }
                                if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
@@ -5998,21 +6519,21 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
                              return;
                         }
                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
-                             epStatus[forwardMostMove] = EP_REP_DRAW;
+                             boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
                     }
                 }
 
                 /* Now we test for 50-move draws. Determine ply count */
                 count = forwardMostMove;
                 /* look for last irreversble move */
-                while( epStatus[count] <= EP_NONE && count > backwardMostMove )
+                while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
                     count--;
                 /* if we hit starting position, add initial plies */
                 if( count == backwardMostMove )
                     count -= initialRulePlies;
                 count = forwardMostMove - count; 
                 if( count >= 100)
-                         epStatus[forwardMostMove] = EP_RULE_DRAW;
+                         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
@@ -6029,11 +6550,11 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
                  */
                 if( cps->other->offeredDraw || cps->offeredDraw ) {
                          char *p = NULL;
-                         if(epStatus[forwardMostMove] == EP_RULE_DRAW)
+                         if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
                              p = "Draw claim: 50-move rule";
-                         if(epStatus[forwardMostMove] == EP_REP_DRAW)
+                         if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
                              p = "Draw claim: 3-fold repetition";
-                         if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
+                         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
@@ -6135,7 +6656,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
      * want this, I was asked to put it in, and obliged.
      */
     if (!strncmp(message, "setboard ", 9)) {
-        Board initial_position; int i;
+        Board initial_position;
 
         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
 
@@ -6143,12 +6664,9 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
             DisplayError(_("Bad FEN received from engine"), 0);
             return ;
         } else {
-           Reset(FALSE, FALSE);
+           Reset(TRUE, FALSE);
            CopyBoard(boards[0], initial_position);
            initialRulePlies = FENrulePlies;
-           epStatus[0] = FENepStatus;
-           for( i=0; i<nrCastlingRights; i++ )
-                castlingRights[0][i] = FENcastlingRights[i];
            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
            else gameMode = MachinePlaysBlack;                 
            DrawPosition(FALSE, boards[currentMove]);
@@ -6164,6 +6682,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
        return;
     }
     if (!strncmp(message, "tellusererror ", 14)) {
+       cps->userError = 1;
        DisplayError(message + 14, 0);
        return;
     }
@@ -6305,17 +6824,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;
@@ -6373,7 +6905,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
            if (ParseOneMove(buf1, forwardMostMove, &moveType,
                                 &fromX, &fromY, &toX, &toY, &promoChar)) {
                (void) CoordsToAlgebraic(boards[forwardMostMove],
-                                   PosFlags(forwardMostMove), EP_UNKNOWN,
+                                   PosFlags(forwardMostMove),
                                    fromY, fromX, toY, toX, promoChar, buf1);
                snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
                DisplayInformation(buf2);
@@ -6552,13 +7084,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) {
@@ -6621,7 +7146,12 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
 
                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
                 if( cps->scoreIsAbsolute && 
-                    ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
+                    ( gameMode == MachinePlaysBlack ||
+                      gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
+                      gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
+                     (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
+                     !WhiteOnMove(currentMove)
+                    ) )
                 {
                     curscore = -curscore;
                 }
@@ -6638,9 +7168,12 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
 
                        if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
                        else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
-                       if(WhiteOnMove(forwardMostMove)) 
+                       if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
+                                               gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
                             whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
-                       else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
+                       if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
+                                               gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
+                            blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
                }
 
                /* Buffer overflow protection */
@@ -6648,8 +7181,8 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
                    if (strlen(buf1) >= sizeof(programStats.movelist)
                        && appData.debugMode) {
                        fprintf(debugFP,
-                               "PV is too long; using the first %d bytes.\n",
-                               sizeof(programStats.movelist) - 1);
+                               "PV is too long; using the first %u bytes.\n",
+                               (unsigned) sizeof(programStats.movelist) - 1);
                    }
 
                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
@@ -6699,7 +7232,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 +7259,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 +7283,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 +7318,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;
            }
@@ -7003,18 +7532,15 @@ ParseGameHistory(game)
            return;
        }
        (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
-                                EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
+                                fromY, fromX, toY, toX, promoChar,
                                 parseList[boardIndex]);
        CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
-        {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
        /* currentMoveString is set as a side-effect of yylex */
        strcpy(moveList[boardIndex], currentMoveString);
        strcat(moveList[boardIndex], "\n");
        boardIndex++;
-       ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex], 
-                                       castlingRights[boardIndex], &epStatus[boardIndex]);
-        switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
-                                 EP_UNKNOWN, castlingRights[boardIndex]) ) {
+       ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
+        switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
          case MT_NONE:
          case MT_STALEMATE:
          default:
@@ -7034,61 +7560,60 @@ ParseGameHistory(game)
 
 /* Apply a move to the given board  */
 void
-ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
+ApplyMove(fromX, fromY, toX, toY, promoChar, board)
      int fromX, fromY, toX, toY;
      int promoChar;
      Board board;
-     char *castling;
-     char *ep;
 {
   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 */
     { int i;
 
       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
-      oldEP = *ep;
-      *ep = EP_NONE;
+      oldEP = (signed char)board[EP_STATUS];
+      board[EP_STATUS] = EP_NONE;
 
       if( board[toY][toX] != EmptySquare ) 
-           *ep = EP_CAPTURE;  
+           board[EP_STATUS] = EP_CAPTURE;  
 
       if( board[fromY][fromX] == WhitePawn ) {
            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
-              *ep = EP_PAWN_MOVE;
+              board[EP_STATUS] = EP_PAWN_MOVE;
            if( toY-fromY==2) {
                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
                        gameInfo.variant != VariantBerolina || toX < fromX)
-                     *ep = toX | berolina;
+                     board[EP_STATUS] = toX | berolina;
                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
                        gameInfo.variant != VariantBerolina || toX > fromX) 
-                     *ep = toX;
+                     board[EP_STATUS] = toX;
           }
       } else 
       if( board[fromY][fromX] == BlackPawn ) {
            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
-              *ep = EP_PAWN_MOVE; 
+              board[EP_STATUS] = EP_PAWN_MOVE; 
            if( toY-fromY== -2) {
                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
                        gameInfo.variant != VariantBerolina || toX < fromX)
-                     *ep = toX | berolina;
+                     board[EP_STATUS] = toX | berolina;
                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
                        gameInfo.variant != VariantBerolina || toX > fromX) 
-                     *ep = toX;
+                     board[EP_STATUS] = toX;
           }
        }
 
        for(i=0; i<nrCastlingRights; i++) {
-           if(castling[i] == fromX && castlingRank[i] == fromY ||
-              castling[i] == toX   && castlingRank[i] == toY   
-             ) castling[i] = -1; // revoke for moved or captured piece
+           if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
+              board[CASTLING][i] == toX   && castlingRank[i] == toY   
+             ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
        }
 
     }
 
   /* [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;
@@ -7139,7 +7664,7 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
         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 */
@@ -7203,13 +7728,13 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
        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 */
@@ -7260,14 +7785,20 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
         p = (int) fromX;
         if(p < (int) BlackPawn) { /* white drop */
              p -= (int)WhitePawn;
+                p = PieceToNumber((ChessSquare)p);
              if(p >= gameInfo.holdingsSize) p = 0;
-             if(--board[p][BOARD_WIDTH-2] == 0)
+             if(--board[p][BOARD_WIDTH-2] <= 0)
                   board[p][BOARD_WIDTH-1] = EmptySquare;
+             if((int)board[p][BOARD_WIDTH-2] < 0)
+                       board[p][BOARD_WIDTH-2] = 0;
         } else {                  /* black drop */
              p -= (int)BlackPawn;
+                p = PieceToNumber((ChessSquare)p);
              if(p >= gameInfo.holdingsSize) p = 0;
-             if(--board[BOARD_HEIGHT-1-p][1] == 0)
+             if(--board[BOARD_HEIGHT-1-p][1] <= 0)
                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
+             if((int)board[BOARD_HEIGHT-1-p][1] < 0)
+                       board[BOARD_HEIGHT-1-p][1] = 0;
         }
       }
       if (captured != EmptySquare && gameInfo.holdingsSize > 0
@@ -7301,7 +7832,6 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
        }
       }
-
     } else if (gameInfo.variant == VariantAtomic) {
       if (captured != EmptySquare) {
        int y, x;
@@ -7386,23 +7916,22 @@ MakeMove(fromX, fromY, toX, toY, promoChar)
         fflush(serverMoves);
     }
 
-    if (forwardMostMove+1 >= MAX_MOVES) {
+    if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
                        0, 1);
       return;
     }
-    SwitchClocks();
-    timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;
-    timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;
+    UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
     if (commentList[forwardMostMove+1] != NULL) {
        free(commentList[forwardMostMove+1]);
        commentList[forwardMostMove+1] = NULL;
     }
     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
-    {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
-    ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1], 
-                               castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
+    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 !
+    timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
+    timeRemaining[1][forwardMostMove] = blackTimeRemaining;
     gameInfo.result = GameUnfinished;
     if (gameInfo.resultDetails != NULL) {
        free(gameInfo.resultDetails);
@@ -7411,12 +7940,10 @@ MakeMove(fromX, fromY, toX, toY, promoChar)
     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
                              moveList[forwardMostMove - 1]);
     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
-                            PosFlags(forwardMostMove - 1), EP_UNKNOWN,
+                            PosFlags(forwardMostMove - 1),
                             fromY, fromX, toY, toX, promoChar,
                             parseList[forwardMostMove - 1]);
-    switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
-                       epStatus[forwardMostMove], /* [HGM] use true e.p. */
-                            castlingRights[forwardMostMove]) ) {
+    switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
       case MT_NONE:
       case MT_STALEMATE:
       default:
@@ -7489,7 +8016,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
            }
@@ -7679,7 +8206,7 @@ TwoMachinesEventIfReady P((void))
 void
 NextMatchGame P((void))
 {
-    int index; /* [HGM] autoinc: step lod index during match */
+    int index; /* [HGM] autoinc: step load index during match */
     Reset(FALSE, TRUE);
     if (*appData.loadGameFile != NULLCHAR) {
        index = appData.loadGameIndex;
@@ -7814,15 +8341,15 @@ GameEnds(result, resultDetails, whosays)
                                             second.twoMachinesColor[0] ;
 
                // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
-               if(epStatus[forwardMostMove] == EP_CHECKMATE) {
+               if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
                    /* [HGM] verify: engine mate claims accepted if they were flagged */
                    trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
                } else
-               if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
+               if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
                    /* [HGM] verify: engine mate claims accepted if they were flagged */
                    trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
                } else
-               if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
+               if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
                    trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
                }
 
@@ -7833,7 +8360,7 @@ GameEnds(result, resultDetails, whosays)
                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
                      if (appData.debugMode) {
                        fprintf(debugFP, "result=%d sp=%d move=%d\n",
-                               result, epStatus[forwardMostMove], forwardMostMove);
+                               result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
                      }
                      if(result != trueResult) {
                              sprintf(buf, "False win claim: '%s'", resultDetails);
@@ -7841,9 +8368,9 @@ GameEnds(result, resultDetails, whosays)
                              resultDetails = buf;
                      }
                 } else
-                if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
+                if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
                     && (forwardMostMove <= backwardMostMove ||
-                        epStatus[forwardMostMove-1] > EP_DRAWS ||
+                        (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
                         (claimer=='b')==(forwardMostMove&1))
                                                                                   ) {
                       /* [HGM] verify: draws that were not flagged are false claims */
@@ -7860,7 +8387,7 @@ GameEnds(result, resultDetails, whosays)
               && result != GameIsDrawn)
            {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
                for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
-                       int p = (int)boards[forwardMostMove][i][j] - color;
+                       int p = (signed char)boards[forwardMostMove][i][j] - color;
                        if(p >= 0 && p <= (int)WhiteKing) k++;
                }
                if (appData.debugMode) {
@@ -8172,6 +8699,7 @@ Reset(redraw, init)
        fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
                redraw, init, gameMode);
     }
+    CleanupTail(); // [HGM] vari: delete any stored variations
     pausing = pauseExamInvalid = FALSE;
     startedFromSetupPosition = blackPlaysFirst = FALSE;
     firstMove = TRUE;
@@ -8218,6 +8746,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) {
@@ -8338,14 +8867,9 @@ LoadGameOneMove(readAhead)
        if (appData.debugMode) 
          fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
        p = yy_text;
-       if (*p == '{' || *p == '[' || *p == '(') {
-           p[strlen(p) - 1] = NULLCHAR;
-           p++;
-       }
 
        /* append the comment but don't display it */
-       while (*p == '\n') p++;
-       AppendComment(currentMove, p);
+       AppendComment(currentMove, p, FALSE);
        return TRUE;
 
       case WhiteCapturesEnPassant:
@@ -8432,8 +8956,7 @@ LoadGameOneMove(readAhead)
       case (ChessMove) 0:      /* end of file */
        if (appData.debugMode)
          fprintf(debugFP, "Parser hit end of file\n");
-       switch (MateTest(boards[currentMove], PosFlags(currentMove),
-                         EP_UNKNOWN, castlingRights[currentMove]) ) {
+       switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
          case MT_NONE:
          case MT_CHECK:
            break;
@@ -8468,8 +8991,7 @@ LoadGameOneMove(readAhead)
        /* Reached start of next game in file */
        if (appData.debugMode)
          fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
-       switch (MateTest(boards[currentMove], PosFlags(currentMove),
-                         EP_UNKNOWN, castlingRights[currentMove]) ) {
+       switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
          case MT_NONE:
          case MT_CHECK:
            break;
@@ -8628,8 +9150,7 @@ MakeRegisteredMove()
            MakeMove(fromX, fromY, toX, toY, promoChar);
            ShowMove(fromX, fromY, toX, toY);
              
-           switch (MateTest(boards[currentMove], PosFlags(currentMove),
-                             EP_UNKNOWN, castlingRights[currentMove]) ) {
+           switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
              case MT_NONE:
              case MT_CHECK:
                break;
@@ -8999,9 +9520,8 @@ LoadGame(f, gameNumber, title, useList)
           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
           {   int i;
               initialRulePlies = FENrulePlies;
-              epStatus[forwardMostMove] = FENepStatus;
               for( i=0; i< nrCastlingRights; i++ )
-                  initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
+                  initialRights[i] = initial_position[CASTLING][i];
           }
          yyboardindex = forwardMostMove;
          free(gameInfo.fen);
@@ -9017,12 +9537,7 @@ LoadGame(f, gameNumber, title, useList)
            if (appData.debugMode) 
              fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
            p = yy_text;
-           if (*p == '{' || *p == '[' || *p == '(') {
-               p[strlen(p) - 1] = NULLCHAR;
-               p++;
-           }
-           while (*p == '\n') p++;
-           AppendComment(currentMove, p);
+           AppendComment(currentMove, p, FALSE);
            yyboardindex = forwardMostMove;
            cm = (ChessMove) yylex();
        }
@@ -9123,12 +9638,7 @@ LoadGame(f, gameNumber, title, useList)
        if (appData.debugMode) 
          fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
        p = yy_text;
-       if (*p == '{' || *p == '[' || *p == '(') {
-           p[strlen(p) - 1] = NULLCHAR;
-           p++;
-       }
-       while (*p == '\n') p++;
-       AppendComment(currentMove, p);
+       AppendComment(currentMove, p, FALSE);
        yyboardindex = forwardMostMove;
        cm = (ChessMove) yylex();
     }
@@ -9295,26 +9805,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--;
@@ -9368,17 +9860,11 @@ LoadPosition(f, positionNumber, title)
        currentMove = forwardMostMove = backwardMostMove = 0;
        DisplayMessage("", _("White to play"));
     }
-          /* [HGM] copy FEN attributes as well */
-          {   int i;
-              initialRulePlies = FENrulePlies;
-              epStatus[forwardMostMove] = FENepStatus;
-              for( i=0; i< nrCastlingRights; i++ )
-                  castlingRights[forwardMostMove][i] = FENcastlingRights[i];
-          }
+    initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
     SendBoard(&first, forwardMostMove);
     if (appData.debugMode) {
 int i, j;
-  for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
+  for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
         fprintf(debugFP, "Load Position\n");
     }
@@ -9598,7 +10084,7 @@ SaveGamePGN(f)
        /* Print comments preceding this move */
        if (commentList[i] != NULL) {
            if (linelen > 0) fprintf(f, "\n");
-           fprintf(f, "{\n%s}\n", commentList[i]);
+           fprintf(f, "%s", commentList[i]);
            linelen = 0;
            newblock = TRUE;
        }
@@ -9627,23 +10113,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,27 +10131,15 @@ SaveGamePGN(f)
            fprintf(f, " ");
            linelen++;
        }
-       fprintf(f, move_buffer);
+       fprintf(f, "%s", move_buffer);
        linelen += movelen;
 
         /* [AS] Add PV info if present */
         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
             /* [HGM] add time */
-            char buf[MSG_SIZ]; int seconds = 0;
+            char buf[MSG_SIZ]; int seconds;
 
-#if 1
-            if(i >= backwardMostMove) {
-               if(WhiteOnMove(i))
-                       seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
-                                 + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
-               else
-                       seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
-                                  + 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
+            seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
 
             if( seconds <= 0) buf[0] = 0; else
             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
@@ -9703,7 +10167,7 @@ SaveGamePGN(f)
                fprintf(f, " ");
                linelen++;
            }
-           fprintf(f, move_buffer);
+           fprintf(f, "%s", move_buffer);
            linelen += movelen;
         }
 
@@ -9715,7 +10179,7 @@ SaveGamePGN(f)
 
     /* Print comments after last move */
     if (commentList[i] != NULL) {
-       fprintf(f, "{\n%s}\n", commentList[i]);
+       fprintf(f, "%s\n", commentList[i]);
     }
 
     /* Print result */
@@ -9804,7 +10268,7 @@ SaveGame(f, dummy, dummy2)
      int dummy;
      char *dummy2;
 {
-    if (gameMode == EditPosition) EditPositionDone();
+    if (gameMode == EditPosition) EditPositionDone(TRUE);
     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
     if (appData.oldSaveStyle)
       return SaveGameOldStyle(f);
@@ -9845,6 +10309,7 @@ SavePosition(f, dummy, dummy2)
     time_t tm;
     char *fen;
     
+    if (gameMode == EditPosition) EditPositionDone(TRUE);
     if (appData.oldSaveStyle) {
        tm = time((time_t *) NULL);
     
@@ -10222,24 +10687,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 +10831,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 +10857,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;
@@ -10438,7 +10887,7 @@ MachineWhiteEvent()
        EditGameEvent();
 
     if (gameMode == EditPosition) 
-        EditPositionDone();
+        EditPositionDone(TRUE);
 
     if (!WhiteOnMove(currentMove)) {
        DisplayError(_("It is not White's turn"), 0);
@@ -10480,6 +10929,7 @@ MachineWhiteEvent()
     SetMachineThinkingEnables();
     first.maybeThinking = TRUE;
     StartClocks();
+    firstMove = FALSE;
 
     if (appData.autoFlipView && !flipView) {
       flipView = !flipView;
@@ -10518,7 +10968,7 @@ MachineBlackEvent()
         EditGameEvent();
 
     if (gameMode == EditPosition) 
-        EditPositionDone();
+        EditPositionDone(TRUE);
 
     if (WhiteOnMove(currentMove)) {
        DisplayError(_("It is not Black's turn"), 0);
@@ -10625,7 +11075,7 @@ TwoMachinesEvent P((void))
        if (gameMode != EditGame) return;
        break;
       case EditPosition:
-       EditPositionDone();
+       EditPositionDone(TRUE);
        break;
       case AnalyzeMode:
       case AnalyzeFile:
@@ -10636,7 +11086,8 @@ TwoMachinesEvent P((void))
        break;
     }
 
-    forwardMostMove = currentMove;
+//    forwardMostMove = currentMove;
+    TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
     ResurrectChessProgram();   /* in case first program isn't running */
 
     if (second.pr == NULL) {
@@ -10717,7 +11168,9 @@ TwoMachinesEvent P((void))
 
        strcpy(bookMove, "move ");
        strcat(bookMove, bookHit);
-       HandleMachineMove(bookMove, &first);
+       savedMessage = bookMove; // args for deferred call
+       savedState = onmove;
+       ScheduleDelayedEvent(DeferredBookMove, 1);
     }
 }
 
@@ -10761,7 +11214,7 @@ IcsClientEvent()
        break;
 
       case EditPosition:
-       EditPositionDone();
+       EditPositionDone(TRUE);
        break;
 
       case AnalyzeMode:
@@ -10802,7 +11255,7 @@ EditGameEvent()
        }
        break;
       case EditPosition:
-       EditPositionDone();
+       EditPositionDone(TRUE);
        break;
       case AnalyzeMode:
       case AnalyzeFile:
@@ -10905,26 +11358,34 @@ ExitAnalyzeMode()
       SendToProgram("exit\n", &first);
       first.analyzing = FALSE;
     }
-    AnalysisPopDown();
     thinkOutput[0] = NULLCHAR;
 }
 
 void
-EditPositionDone()
+EditPositionDone(Boolean fakeRights)
 {
+    int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
+
     startedFromSetupPosition = TRUE;
     InitChessProgram(&first, FALSE);
+    if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
+      boards[0][EP_STATUS] = EP_NONE;
+      boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
+    if(boards[0][0][BOARD_WIDTH>>1] == king) {
+       boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
+       boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
+      } else boards[0][CASTLING][2] = NoRights;
+    if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
+       boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
+       boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
+      } else boards[0][CASTLING][5] = NoRights;
+    }
     SendToProgram("force\n", &first);
     if (blackPlaysFirst) {
        strcpy(moveList[0], "");
        strcpy(parseList[0], "");
        currentMove = forwardMostMove = backwardMostMove = 1;
        CopyBoard(boards[1], boards[0]);
-       /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
-       { int i;
-         epStatus[1] = epStatus[0];
-         for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
-       }
     } else {
        currentMove = forwardMostMove = backwardMostMove = 0;
     }
@@ -11057,9 +11518,20 @@ EditPositionMenuEvent(selection, x, y)
 
       case EmptySquare:
        if (gameMode == IcsExamining) {
+            if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
            SendToICS(buf);
        } else {
+            if(x < BOARD_LEFT || x >= BOARD_RGHT) {
+                if(x == BOARD_LEFT-2) {
+                    if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
+                    boards[0][y][1] = 0;
+                } else
+                if(x == BOARD_RGHT+1) {
+                    if(y >= gameInfo.holdingsSize) break;
+                    boards[0][y][BOARD_WIDTH-2] = 0;
+                } else break;
+            }
            boards[0][y][x] = EmptySquare;
            DrawPosition(FALSE, boards[0]);
        }
@@ -11085,7 +11557,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;
 
@@ -11098,12 +11571,28 @@ EditPositionMenuEvent(selection, x, y)
       default:
         defaultlabel:
        if (gameMode == IcsExamining) {
+            if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
            sprintf(buf, "%s%c@%c%c\n", ics_prefix,
                     PieceToChar(selection), AAA + x, ONE + y);
            SendToICS(buf);
        } else {
+            if(x < BOARD_LEFT || x >= BOARD_RGHT) {
+                int n;
+                if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
+                    n = PieceToNumber(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; }
+                    boards[0][n][BOARD_WIDTH-1] = selection;
+                    boards[0][n][BOARD_WIDTH-2]++;
+                }
+            } else
            boards[0][y][x] = selection;
-           DrawPosition(FALSE, boards[0]);
+           DrawPosition(TRUE, boards[0]);
        }
        break;
     }
@@ -11192,7 +11681,7 @@ DeclineEvent()
            StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
                   "Black offers a draw" : "White offers a draw")) {
 #ifdef NOTDEF
-           AppendComment(cmailOldMove, "Draw declined");
+           AppendComment(cmailOldMove, "Draw declined", TRUE);
            DisplayComment(cmailOldMove - 1, "Draw declined");
 #endif /*NOTDEF*/
        } else {
@@ -11274,7 +11763,7 @@ DrawEvent()
        } else if (currentMove == cmailOldMove + 1) {
            char *offer = WhiteOnMove(cmailOldMove) ?
              "White offers a draw" : "Black offers a draw";
-           AppendComment(currentMove, offer);
+           AppendComment(currentMove, offer, TRUE);
            DisplayComment(currentMove - 1, offer);
            cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
        } else {
@@ -11550,7 +12039,7 @@ void
 ToStartEvent()
 {
     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
-       /* to optimze, we temporarily turn off analysis mode while we undo
+       /* to optimize, we temporarily turn off analysis mode while we undo
         * all the moves. Otherwise we get analysis output after each undo.
         */ 
         if (first.analysisSupport) {
@@ -11591,6 +12080,9 @@ ToNrEvent(int to)
 void
 RevertEvent()
 {
+    if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
+       return;
+    }
     if (gameMode != IcsExamining) {
        DisplayError(_("You are not examining a game"), 0);
        return;
@@ -11688,6 +12180,7 @@ TruncateGameEvent()
 void
 TruncateGame()
 {
+    CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
     if (forwardMostMove > currentMove) {
        if (gameInfo.resultDetails != NULL) {
            free(gameInfo.resultDetails);
@@ -11745,7 +12238,7 @@ BookEvent()
        }
        break;
       case EditPosition:
-       EditPositionDone();
+       EditPositionDone(TRUE);
        break;
       case TwoMachinesPlay:
        return;
@@ -11850,6 +12343,14 @@ SetGameInfo()
 {
     /* This routine is used only for certain modes */
     VariantClass v = gameInfo.variant;
+    ChessMove r = GameUnfinished;
+    char *p = NULL;
+
+    if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
+       r = gameInfo.result; 
+       p = gameInfo.resultDetails; 
+       gameInfo.resultDetails = NULL;
+    }
     ClearGameInfo(&gameInfo);
     gameInfo.variant = v;
 
@@ -11902,6 +12403,8 @@ SetGameInfo()
        gameInfo.round = StrSave("-");
        gameInfo.white = StrSave("-");
        gameInfo.black = StrSave("-");
+       gameInfo.result = r;
+       gameInfo.resultDetails = p;
        break;
 
       case EditPosition:
@@ -11951,10 +12454,23 @@ ReplaceComment(index, text)
        commentList[index] = NULL;
        return;
     }
+  if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
+      *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
+      *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
     commentList[index] = (char *) malloc(len + 2);
     strncpy(commentList[index], text, len);
     commentList[index][len] = '\n';
     commentList[index][len + 1] = NULLCHAR;
+  } else { 
+    // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
+    char *p;
+    commentList[index] = (char *) malloc(len + 6);
+    strcpy(commentList[index], "{\n");
+    strncpy(commentList[index]+2, text, len);
+    commentList[index][len+2] = NULLCHAR;
+    while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
+    strcat(commentList[index], "\n}\n");
+  }
 }
 
 void
@@ -11973,13 +12489,15 @@ CrushCRs(text)
 }
 
 void
-AppendComment(index, text)
+AppendComment(index, text, addBraces)
      int index;
      char *text;
+     Boolean addBraces; // [HGM] braces: tells if we should add {}
 {
     int oldlen, len;
     char *old;
 
+if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
 
     CrushCRs(text);
@@ -11992,17 +12510,30 @@ AppendComment(index, text)
     if (commentList[index] != NULL) {
        old = commentList[index];
        oldlen = strlen(old);
-       commentList[index] = (char *) malloc(oldlen + len + 2);
+       while(commentList[index][oldlen-1] ==  '\n')
+         commentList[index][--oldlen] = NULLCHAR;
+       commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
        strcpy(commentList[index], old);
        free(old);
-       strncpy(&commentList[index][oldlen], text, len);
-       commentList[index][oldlen + len] = '\n';
-       commentList[index][oldlen + len + 1] = NULLCHAR;
+       // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
+       if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
+         if(addBraces) addBraces = FALSE; else { text++; len--; }
+         while (*text == '\n') { text++; len--; }
+         commentList[index][--oldlen] = NULLCHAR;
+      }
+       if(addBraces) strcat(commentList[index], "\n{\n");
+       else          strcat(commentList[index], "\n");
+       strcat(commentList[index], text);
+       if(addBraces) strcat(commentList[index], "\n}\n");
+       else          strcat(commentList[index], "\n");
     } else {
-       commentList[index] = (char *) malloc(len + 2);
-       strncpy(commentList[index], text, len);
-       commentList[index][len] = '\n';
-       commentList[index][len + 1] = NULLCHAR;
+       commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
+       if(addBraces)
+            strcpy(commentList[index], "{\n");
+       else commentList[index][0] = NULLCHAR;
+       strcat(commentList[index], text);
+       strcat(commentList[index], "\n");
+       if(addBraces) strcat(commentList[index], "}\n");
     }
 }
 
@@ -12046,21 +12577,24 @@ char *GetInfoFromComment( int index, char * text )
 
             if( s_emt != NULL ) {
             }
+               return text;
         }
         else {
             /* We expect something like: [+|-]nnn.nn/dd */
             int score_lo = 0;
 
+            if(*text != '{') return text; // [HGM] braces: must be normal comment
+
             sep = strchr( text, '/' );
             if( sep == NULL || sep < (text+4) ) {
                 return text;
             }
 
             time = -1; sec = -1; deci = -1;
-            if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
-               sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
-                sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
-                sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
+            if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
+               sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
+                sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
+                sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
                 return text;
             }
 
@@ -12095,6 +12629,7 @@ char *GetInfoFromComment( int index, char * text )
         pvInfoList[index-1].depth = depth;
         pvInfoList[index-1].score = score;
         pvInfoList[index-1].time  = 10*time; // centi-sec
+        if(*sep == '}') *sep = 0; else *--sep = '{';
     }
     return sep;
 }
@@ -12124,15 +12659,15 @@ SendToProgram(message, cps)
                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
        sprintf(buf, _("Error writing to %s chess program"), cps->which);
         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
-            if(epStatus[forwardMostMove] <= EP_DRAWS) {
+            if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
             } else {
                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
             }
-            gameInfo.resultDetails = buf;
+            gameInfo.resultDetails = StrSave(buf);
         }
-        DisplayFatalError(buf, error, 1);
+        if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
     }
 }
 
@@ -12155,16 +12690,16 @@ ReceiveFromProgram(isr, closure, message, count, error)
                    _("Error: %s chess program (%s) exited unexpectedly"),
                    cps->which, cps->program);
         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
-                if(epStatus[forwardMostMove] <= EP_DRAWS) {
+                if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
                 } else {
                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
                 }
-                gameInfo.resultDetails = buf;
+                gameInfo.resultDetails = StrSave(buf);
             }
            RemoveInputSource(cps->isr);
-           DisplayFatalError(buf, 0, 1);
+           if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
        } else {
            sprintf(buf,
                    _("Error reading from %s chess program (%s)"),
@@ -12177,7 +12712,7 @@ ReceiveFromProgram(isr, closure, message, count, error)
                 cps->pr = NoProc;
             }
 
-            DisplayFatalError(buf, error, 1);
+            if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
        }
        return;
     }
@@ -12199,7 +12734,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
        }
@@ -12321,7 +12857,7 @@ SendTimeRemaining(cps, machineWhite)
     /* [HGM] translate opponent's time by time-odds factor */
     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
     if (appData.debugMode) {
-        fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
+        fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
     }
 
     if (time <= 0) time = 1;
@@ -12567,7 +13103,7 @@ ParseFeatures(args, cps)
     /* unknown feature: complain and skip */
     q = p;
     while (*q && *q != '=') q++;
-    sprintf(buf, "rejected %.*s\n", q-p, p);
+    sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
     SendToProgram(buf, cps);
     p = q;
     if (*p == '=') {
@@ -12594,7 +13130,7 @@ PeriodicUpdatesEvent(newState)
     appData.periodicUpdates=newState;
 
     /* Display type changes, so update it now */
-    DisplayAnalysis();
+//    DisplayAnalysis();
 
     /* Get the ball rolling again... */
     if (newState) {
@@ -12608,7 +13144,7 @@ PonderNextMoveEvent(newState)
      int newState;
 {
     if (newState == appData.ponderNextMove) return;
-    if (gameMode == EditPosition) EditPositionDone();
+    if (gameMode == EditPosition) EditPositionDone(TRUE);
     if (newState) {
        SendToProgram("hard\n", &first);
        if (gameMode == TwoMachinesPlay) {
@@ -12631,7 +13167,7 @@ NewSettingEvent(option, command, value)
 {
     char buf[MSG_SIZ];
 
-    if (gameMode == EditPosition) EditPositionDone();
+    if (gameMode == EditPosition) EditPositionDone(TRUE);
     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
     SendToProgram(buf, &first);
     if (gameMode == TwoMachinesPlay) {
@@ -12650,7 +13186,7 @@ ShowThinkingEvent()
     
     if (oldState == newState) return;
     oldState = newState;
-    if (gameMode == EditPosition) EditPositionDone();
+    if (gameMode == EditPosition) EditPositionDone(TRUE);
     if (oldState) {
        SendToProgram("post\n", &first);
        if (gameMode == TwoMachinesPlay) {
@@ -12734,92 +13270,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;
      char *text;
@@ -12827,26 +13277,23 @@ DisplayComment(moveNumber, text)
     char title[MSG_SIZ];
     char buf[8000]; // comment can be long!
     int score, depth;
-
-    if( appData.autoDisplayComment ) {
-        if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
-           strcpy(title, "Comment");
-        } else {
-           sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
-                   WhiteOnMove(moveNumber) ? " " : ".. ",
-                   parseList[moveNumber]);
-        }
-       // [HGM] PV info: display PV info together with (or as) comment
-       if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
-           if(text == NULL) text = "";                                           
-           score = pvInfoList[moveNumber].score;
-           sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
-                              depth, (pvInfoList[moveNumber].time+50)/100, text);
-           text = buf;
-       }
-    } else title[0] = 0;
-
-    if (text != NULL)
+    
+    if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
+      strcpy(title, "Comment");
+    } else {
+      sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
+             WhiteOnMove(moveNumber) ? " " : ".. ",
+             parseList[moveNumber]);
+    }
+    // [HGM] PV info: display PV info together with (or as) comment
+    if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
+      if(text == NULL) text = "";                                           
+      score = pvInfoList[moveNumber].score;
+      sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
+             depth, (pvInfoList[moveNumber].time+50)/100, text);
+      text = buf;
+    }
+    if (text != NULL && (appData.autoDisplayComment || commentUp))
         CommentPopUp(title, text);
 }
 
@@ -12937,7 +13384,7 @@ CheckFlags()
 void
 CheckTimeControl()
 {
-    if (!appData.clockMode || appData.icsActive ||
+    if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
        gameMode == PlayFromGameFile || forwardMostMove == 0) return;
 
     /*
@@ -13063,6 +13510,9 @@ ResetClocks()
     (void) StopClockTimer();
     if (appData.icsActive) {
        whiteTimeRemaining = blackTimeRemaining = 0;
+    } else if (searchTime) {
+       whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
+       blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
     } else { /* [HGM] correct new time quote for time odds */
         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
@@ -13099,12 +13549,12 @@ DecrementClocks()
        if(whiteNPS >= 0) lastTickLength = 0;
        timeRemaining = whiteTimeRemaining -= lastTickLength;
        DisplayWhiteClock(whiteTimeRemaining - fudge,
-                         WhiteOnMove(currentMove));
+                         WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
     } else {
        if(blackNPS >= 0) lastTickLength = 0;
        timeRemaining = blackTimeRemaining -= lastTickLength;
        DisplayBlackClock(blackTimeRemaining - fudge,
-                         !WhiteOnMove(currentMove));
+                         !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
     }
 
     if (CheckFlags()) return;
@@ -13192,6 +13642,12 @@ SwitchClocks()
        break;
     }
 
+    if (searchTime) { // [HGM] st: set clock of player that has to move to max time
+       if(WhiteOnMove(forwardMostMove))
+            whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
+       else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
+    }
+
     tickStartTM = now;
     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
       whiteTimeRemaining : blackTimeRemaining);
@@ -13491,36 +13947,36 @@ PositionToFEN(move, overrideCastling)
     *p++ = ' ';
 
   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
-    while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
+    while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
   } else {
   if(nrCastlingRights) {
      q = p;
      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
        /* [HGM] write directly from rights */
-           if(castlingRights[move][2] >= 0 &&
-              castlingRights[move][0] >= 0   )
-                *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
-           if(castlingRights[move][2] >= 0 &&
-              castlingRights[move][1] >= 0   )
-                *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
-           if(castlingRights[move][5] >= 0 &&
-              castlingRights[move][3] >= 0   )
-                *p++ = castlingRights[move][3] + AAA;
-           if(castlingRights[move][5] >= 0 &&
-              castlingRights[move][4] >= 0   )
-                *p++ = castlingRights[move][4] + AAA;
+           if(boards[move][CASTLING][2] != NoRights &&
+              boards[move][CASTLING][0] != NoRights   )
+                *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
+           if(boards[move][CASTLING][2] != NoRights &&
+              boards[move][CASTLING][1] != NoRights   )
+                *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
+           if(boards[move][CASTLING][5] != NoRights &&
+              boards[move][CASTLING][3] != NoRights   )
+                *p++ = boards[move][CASTLING][3] + AAA;
+           if(boards[move][CASTLING][5] != NoRights &&
+              boards[move][CASTLING][4] != NoRights   )
+                *p++ = boards[move][CASTLING][4] + AAA;
      } else {
 
         /* [HGM] write true castling rights */
         if( nrCastlingRights == 6 ) {
-            if(castlingRights[move][0] == BOARD_RGHT-1 &&
-               castlingRights[move][2] >= 0  ) *p++ = 'K';
-            if(castlingRights[move][1] == BOARD_LEFT &&
-               castlingRights[move][2] >= 0  ) *p++ = 'Q';
-            if(castlingRights[move][3] == BOARD_RGHT-1 &&
-               castlingRights[move][5] >= 0  ) *p++ = 'k';
-            if(castlingRights[move][4] == BOARD_LEFT &&
-               castlingRights[move][5] >= 0  ) *p++ = 'q';
+            if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
+               boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
+            if(boards[move][CASTLING][1] == BOARD_LEFT &&
+               boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
+            if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
+               boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
+            if(boards[move][CASTLING][4] == BOARD_LEFT &&
+               boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
         }
      }
      if (q == p) *p++ = '-'; /* No castling rights */
@@ -13528,7 +13984,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;
@@ -13545,6 +14001,14 @@ PositionToFEN(move, overrideCastling)
        } else {
            *p++ = '-';
        }
+    } else if(move == backwardMostMove) {
+       // [HGM] perhaps we should always do it like this, and forget the above?
+       if((signed char)boards[move][EP_STATUS] >= 0) {
+           *p++ = boards[move][EP_STATUS] + AAA;
+           *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
+       } else {
+           *p++ = '-';
+       }
     } else {
        *p++ = '-';
     }
@@ -13558,11 +14022,11 @@ PositionToFEN(move, overrideCastling)
         if (appData.debugMode) { int k;
             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
             for(k=backwardMostMove; k<=forwardMostMove; k++)
-                fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
+                fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
 
         }
 
-        while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
+        while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
         if( j == backwardMostMove ) i += initialRulePlies;
         sprintf(p, "%d ", i);
         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
@@ -13606,7 +14070,7 @@ ParseFEN(board, blackPlaysFirst, fen)
                 while (emptycount--)
                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
                break;
-#if(BOARD_SIZE >= 10)
+#if(BOARD_FILES >= 10)
             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
                 p++; emptycount=10;
                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
@@ -13688,17 +14152,19 @@ ParseFEN(board, blackPlaysFirst, fen)
     /* return the extra info in global variiables             */
 
     /* set defaults in case FEN is incomplete */
-    FENepStatus = EP_UNKNOWN;
+    board[EP_STATUS] = EP_UNKNOWN;
     for(i=0; i<nrCastlingRights; i++ ) {
-        FENcastlingRights[i] =
-            gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
+        board[CASTLING][i] =
+            gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
     }   /* assume possible unless obviously impossible */
-    if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
-    if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
-    if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
-    if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
-    if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
-    if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
+    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]] != 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]] != BlackUnicorn
+                                 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
     FENrulePlies = 0;
 
     while(*p==' ') p++;
@@ -13706,39 +14172,45 @@ ParseFEN(board, blackPlaysFirst, fen)
       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
           /* castling indicator present, so default becomes no castlings */
           for(i=0; i<nrCastlingRights; i++ ) {
-                 FENcastlingRights[i] = -1;
+                 board[CASTLING][i] = NoRights;
           }
       }
       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
              (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--);
-              FENcastlingRights[0] = i != whiteKingFile ? i : -1;
-              FENcastlingRights[2] = whiteKingFile;
+              board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
+              board[CASTLING][2] = whiteKingFile;
               break;
           case'Q':
-              for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
-              FENcastlingRights[1] = i != whiteKingFile ? i : -1;
-              FENcastlingRights[2] = whiteKingFile;
+              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;
           case'k':
               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
-              FENcastlingRights[3] = i != blackKingFile ? i : -1;
-              FENcastlingRights[5] = blackKingFile;
+              board[CASTLING][3] = i != blackKingFile ? i : NoRights;
+              board[CASTLING][5] = blackKingFile;
               break;
           case'q':
-              for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
-              FENcastlingRights[4] = i != blackKingFile ? i : -1;
-              FENcastlingRights[5] = blackKingFile;
+              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 '-':
               break;
           default: /* FRC castlings */
@@ -13746,32 +14218,34 @@ ParseFEN(board, blackPlaysFirst, fen)
                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
                   if(i == BOARD_RGHT) break;
-                  FENcastlingRights[5] = i;
+                  board[CASTLING][5] = i;
                   c -= AAA;
                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
                   if(c > i)
-                      FENcastlingRights[3] = c;
+                      board[CASTLING][3] = c;
                   else
-                      FENcastlingRights[4] = c;
+                      board[CASTLING][4] = c;
               } else { /* white rights */
                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
                     if(board[0][i] == WhiteKing) break;
                   if(i == BOARD_RGHT) break;
-                  FENcastlingRights[2] = i;
+                  board[CASTLING][2] = i;
                   c -= AAA - 'a' + 'A';
                   if(board[0][c] >= WhiteKing) break;
                   if(c > i)
-                      FENcastlingRights[0] = c;
+                      board[CASTLING][0] = c;
                   else
-                      FENcastlingRights[1] = c;
+                      board[CASTLING][1] = c;
               }
         }
       }
+      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++)
-        fprintf(debugFP, " %d", FENcastlingRights[i]);
+        fprintf(debugFP, " %d", board[CASTLING][i]);
         fprintf(debugFP, "\n");
     }
 
@@ -13780,15 +14254,15 @@ 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++; FENepStatus = EP_NONE;
+        p++; board[EP_STATUS] = EP_NONE;
       } else {
          char c = *p++ - AAA;
 
          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
          if(*p >= '0' && *p <='9') *p++;
-         FENepStatus = c;
+         board[EP_STATUS] = c;
       }
     }
 
@@ -13815,16 +14289,224 @@ EditPositionPasteFEN(char *fen)
       EditPositionEvent();
       blackPlaysFirst = savedBlackPlaysFirst;
       CopyBoard(boards[0], initial_position);
-          /* [HGM] copy FEN attributes as well */
-          {   int i;
-              initialRulePlies = FENrulePlies;
-              epStatus[0] = FENepStatus;
-              for( i=0; i<nrCastlingRights; i++ )
-                  castlingRights[0][i] = FENcastlingRights[i];
-          }
-      EditPositionDone();
+      initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
+      EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
       DisplayBothClocks();
       DrawPosition(FALSE, boards[currentMove]);
     }
   }
 }
+
+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: %u)\n", new_seq, (unsigned) 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;
+}
+
+// [HGM] vari: routines for shelving variations
+
+void 
+PushTail(int firstMove, int lastMove)
+{
+       int i, j, nrMoves = lastMove - firstMove;
+
+       if(appData.icsActive) { // only in local mode
+               forwardMostMove = currentMove; // mimic old ICS behavior
+               return;
+       }
+       if(storedGames >= MAX_VARIATIONS-1) return;
+
+       // push current tail of game on stack
+       savedResult[storedGames] = gameInfo.result;
+       savedDetails[storedGames] = gameInfo.resultDetails;
+       gameInfo.resultDetails = NULL;
+       savedFirst[storedGames] = firstMove;
+       savedLast [storedGames] = lastMove;
+       savedFramePtr[storedGames] = framePtr;
+       framePtr -= nrMoves; // reserve space for the boards
+       for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
+           CopyBoard(boards[framePtr+i], boards[firstMove+i]);
+           for(j=0; j<MOVE_LEN; j++)
+               moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
+           for(j=0; j<2*MOVE_LEN; j++)
+               parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
+           timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
+           timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
+           pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
+           pvInfoList[firstMove+i-1].depth = 0;
+           commentList[framePtr+i] = commentList[firstMove+i];
+           commentList[firstMove+i] = NULL;
+       }
+
+       storedGames++;
+       forwardMostMove = currentMove; // truncte game so we can start variation
+       if(storedGames == 1) GreyRevert(FALSE);
+}
+
+Boolean
+PopTail(Boolean annotate)
+{
+       int i, j, nrMoves;
+       char buf[8000], moveBuf[20];
+
+       if(appData.icsActive) return FALSE; // only in local mode
+       if(!storedGames) return FALSE; // sanity
+
+       storedGames--;
+       ToNrEvent(savedFirst[storedGames]); // sets currentMove
+       nrMoves = savedLast[storedGames] - currentMove;
+       if(annotate) {
+               int cnt = 10;
+               if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
+               else strcpy(buf, "(");
+               for(i=currentMove; i<forwardMostMove; i++) {
+                       if(WhiteOnMove(i))
+                            sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
+                       else sprintf(moveBuf, " %s", SavePart(parseList[i]));
+                       strcat(buf, moveBuf);
+                       if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
+               }
+               strcat(buf, ")");
+       }
+       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];
+           for(j=0; j<2*MOVE_LEN; j++)
+               parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
+           timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
+           timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
+           pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
+           if(commentList[currentMove+i]) free(commentList[currentMove+i]);
+           commentList[currentMove+i] = commentList[framePtr+i];
+           commentList[framePtr+i] = NULL;
+       }
+       if(annotate) AppendComment(currentMove+1, buf, FALSE);
+       framePtr = savedFramePtr[storedGames];
+       gameInfo.result = savedResult[storedGames];
+       if(gameInfo.resultDetails != NULL) {
+           free(gameInfo.resultDetails);
+      }
+       gameInfo.resultDetails = savedDetails[storedGames];
+       forwardMostMove = currentMove + nrMoves;
+       if(storedGames == 0) GreyRevert(TRUE);
+       return TRUE;
+}
+
+void 
+CleanupTail()
+{      // remove all shelved variations
+       int i;
+       for(i=0; i<storedGames; i++) {
+           if(savedDetails[i])
+               free(savedDetails[i]);
+           savedDetails[i] = NULL;
+       }
+       for(i=framePtr; i<MAX_MOVES; i++) {
+               if(commentList[i]) free(commentList[i]);
+               commentList[i] = NULL;
+       }
+       framePtr = MAX_MOVES-1;
+       storedGames = 0;
+}