Lock game an position file during writing
[xboard.git] / backend.c
index 67a1c1a..9ecc707 100644 (file)
--- a/backend.c
+++ b/backend.c
@@ -5,7 +5,7 @@
  * Massachusetts.
  *
  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
- * 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
+ * 2007, 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
  *
  * Enhancements Copyright 2005 Alessandro Scotti
  *
@@ -57,6 +57,9 @@
 
 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
 
+int flock(int f, int code);
+#define LOCK_EX 2
+
 #else
 
 #define DoSleep( n ) if( (n) >= 0) sleep(n)
@@ -267,6 +270,10 @@ 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 */
+char lastMsg[MSG_SIZ];
+ChessSquare pieceSweep = EmptySquare;
+ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
+int promoDefaultAltered;
 
 /* States for ics_getting_history */
 #define H_FALSE 0
@@ -311,23 +318,19 @@ char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
 
 char*
 safeStrCpy( char *dst, const char *src, size_t count )
-{
-  /* see for example: https://buildsecurityin.us-cert.gov/bsi-rules/home/g1/854-BSI.html
-   *
-   * usage:   safeStrCpy( stringA, stringB, sizeof(stringA)/sizeof(stringA[0]);
-   */
-
+{ // [HGM] made safe
+  int i;
   assert( dst != NULL );
   assert( src != NULL );
   assert( count > 0 );
 
-  strncpy( dst, src, count );
-  if(  dst[ count-1 ] != '\0' )
+  for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
+  if(  i == count && dst[count-1] != NULLCHAR)
     {
+      dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
       if(appData.debugMode)
-      printf("safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst,count);
+      fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
     }
-  dst[ count-1 ] = '\0';
 
   return dst;
 }
@@ -449,6 +452,8 @@ int adjudicateLossPlies = 6;
 char white_holding[64], black_holding[64];
 TimeMark lastNodeCountTime;
 long lastNodeCount=0;
+int shiftKey; // [HGM] set by mouse handler
+
 int have_sent_ICS_logon = 0;
 int movesPerSession;
 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
@@ -518,11 +523,18 @@ ChessSquare  KnightmateArray[2][BOARD_FILES] = {
         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
 };
 
+ChessSquare SpartanArray[2][BOARD_FILES] = {
+    { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
+        WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
+    { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
+        BlackDragon, BlackKing, BlackAngel, BlackAlfil }
+};
+
 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
-    { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
-       BlackKing, BlackMarshall, BlackAlfil, BlackLance }
+    { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
+       BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
 };
 
 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
@@ -651,6 +663,194 @@ ClearProgramStats()
 }
 
 void
+CommonEngineInit()
+{   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
+    if (appData.firstPlaysBlack) {
+       first.twoMachinesColor = "black\n";
+       second.twoMachinesColor = "white\n";
+    } else {
+       first.twoMachinesColor = "white\n";
+       second.twoMachinesColor = "black\n";
+    }
+
+    first.other = &second;
+    second.other = &first;
+
+    { float norm = 1;
+        if(appData.timeOddsMode) {
+            norm = appData.timeOdds[0];
+            if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
+        }
+        first.timeOdds  = appData.timeOdds[0]/norm;
+        second.timeOdds = appData.timeOdds[1]/norm;
+    }
+
+    if(programVersion) free(programVersion);
+    if (appData.noChessProgram) {
+       programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
+       sprintf(programVersion, "%s", PACKAGE_STRING);
+    } 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);
+    }
+}
+
+void
+UnloadEngine(ChessProgramState *cps)
+{
+       /* Kill off first chess program */
+       if (cps->isr != NULL)
+         RemoveInputSource(cps->isr);
+       cps->isr = NULL;
+
+       if (cps->pr != NoProc) {
+           ExitAnalyzeMode();
+            DoSleep( appData.delayBeforeQuit );
+           SendToProgram("quit\n", cps);
+            DoSleep( appData.delayAfterQuit );
+           DestroyChildProcess(cps->pr, cps->useSigterm);
+       }
+       cps->pr = NoProc;
+}
+
+void
+ClearOptions(ChessProgramState *cps)
+{
+    int i;
+    cps->nrOptions = cps->comboCnt = 0;
+    for(i=0; i<MAX_OPTIONS; i++) {
+       cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
+       cps->option[i].textValue = 0;
+    }
+}
+
+char *engineNames[] = {
+"first",
+"second"
+};
+
+InitEngine(ChessProgramState *cps, int n)
+{   // [HGM] all engine initialiation put in a function that does one engine
+
+    ClearOptions(cps);
+
+    cps->which = engineNames[n];
+    cps->maybeThinking = FALSE;
+    cps->pr = NoProc;
+    cps->isr = NULL;
+    cps->sendTime = 2;
+    cps->sendDrawOffers = 1;
+
+    cps->program = appData.chessProgram[n];
+    cps->host = appData.host[n];
+    cps->dir = appData.directory[n];
+    cps->initString = appData.engInitString[n];
+    cps->computerString = appData.computerString[n];
+    cps->useSigint  = TRUE;
+    cps->useSigterm = TRUE;
+    cps->reuse = appData.reuse[n];
+    cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
+    cps->useSetboard = FALSE;
+    cps->useSAN = FALSE;
+    cps->usePing = FALSE;
+    cps->lastPing = 0;
+    cps->lastPong = 0;
+    cps->usePlayother = FALSE;
+    cps->useColors = TRUE;
+    cps->useUsermove = FALSE;
+    cps->sendICS = FALSE;
+    cps->sendName = appData.icsActive;
+    cps->sdKludge = FALSE;
+    cps->stKludge = FALSE;
+    TidyProgramName(cps->program, cps->host, cps->tidy);
+    cps->matchWins = 0;
+    safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
+    cps->analysisSupport = 2; /* detect */
+    cps->analyzing = FALSE;
+    cps->initDone = FALSE;
+
+    /* New features added by Tord: */
+    cps->useFEN960 = FALSE;
+    cps->useOOCastle = TRUE;
+    /* End of new features added by Tord. */
+    cps->fenOverride  = appData.fenOverride[n];
+
+    /* [HGM] time odds: set factor for each machine */
+    cps->timeOdds  = appData.timeOdds[n];
+
+    /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
+    cps->accumulateTC = appData.accumulateTC[n];
+    cps->maxNrOfSessions = 1;
+
+    /* [HGM] debug */
+    cps->debug = FALSE;
+    cps->supportsNPS = UNKNOWN;
+
+    /* [HGM] options */
+    cps->optionSettings  = appData.engOptions[n];
+
+    cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
+    cps->isUCI = appData.isUCI[n]; /* [AS] */
+    cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
+
+    if (appData.protocolVersion[n] > PROTOVER
+       || appData.protocolVersion[n] < 1)
+      {
+       char buf[MSG_SIZ];
+       int len;
+
+       len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
+                      appData.protocolVersion[n]);
+       if( (len > MSG_SIZ) && appData.debugMode )
+         fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
+
+       DisplayFatalError(buf, 0, 2);
+      }
+    else
+      {
+       cps->protocolVersion = appData.protocolVersion[n];
+      }
+
+    InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
+}
+
+ChessProgramState *savCps;
+
+void
+LoadEngine()
+{
+    int i;
+    if(WaitForEngine(savCps, LoadEngine)) return;
+    CommonEngineInit(); // recalculate time odds
+    if(gameInfo.variant != StringToVariant(appData.variant)) {
+       // we changed variant when loading the engine; this forces us to reset
+       Reset(TRUE, savCps != &first);
+       EditGameEvent(); // for consistency with other path, as Reset changes mode
+    }
+    InitChessProgram(savCps, FALSE);
+    SendToProgram("force\n", savCps);
+    DisplayMessage("", "");
+    if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
+    for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
+    ThawUI();
+    SetGNUMode();
+}
+
+void
+ReplaceEngine(ChessProgramState *cps, int n)
+{
+    EditGameEvent();
+    UnloadEngine(cps);
+    appData.noChessProgram = False;
+    appData.clockMode = True;
+    InitEngine(cps, n);
+    if(n) return; // only startup first engine immediately; second can wait
+    savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
+    LoadEngine();
+}
+
+void
 InitBackEnd1()
 {
     int matched, min, sec;
@@ -732,137 +932,12 @@ InitBackEnd1()
     /* [AS] Adjudication threshold */
     adjudicateLossThreshold = appData.adjudicateLossThreshold;
 
-    first.which = _("first");
-    second.which = _("second");
-    first.maybeThinking = second.maybeThinking = FALSE;
-    first.pr = second.pr = NoProc;
-    first.isr = second.isr = NULL;
-    first.sendTime = second.sendTime = 2;
-    first.sendDrawOffers = 1;
-    if (appData.firstPlaysBlack) {
-       first.twoMachinesColor = "black\n";
-       second.twoMachinesColor = "white\n";
-    } else {
-       first.twoMachinesColor = "white\n";
-       second.twoMachinesColor = "black\n";
-    }
-    first.program = appData.firstChessProgram;
-    second.program = appData.secondChessProgram;
-    first.host = appData.firstHost;
-    second.host = appData.secondHost;
-    first.dir = appData.firstDirectory;
-    second.dir = appData.secondDirectory;
-    first.other = &second;
-    second.other = &first;
-    first.initString = appData.initString;
-    second.initString = appData.secondInitString;
-    first.computerString = appData.firstComputerString;
-    second.computerString = appData.secondComputerString;
-    first.useSigint = second.useSigint = TRUE;
-    first.useSigterm = second.useSigterm = TRUE;
-    first.reuse = appData.reuseFirst;
-    second.reuse = appData.reuseSecond;
-    first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
-    second.nps = appData.secondNPS;
-    first.useSetboard = second.useSetboard = FALSE;
-    first.useSAN = second.useSAN = FALSE;
-    first.usePing = second.usePing = FALSE;
-    first.lastPing = second.lastPing = 0;
-    first.lastPong = second.lastPong = 0;
-    first.usePlayother = second.usePlayother = FALSE;
-    first.useColors = second.useColors = TRUE;
-    first.useUsermove = second.useUsermove = FALSE;
-    first.sendICS = second.sendICS = FALSE;
-    first.sendName = second.sendName = appData.icsActive;
-    first.sdKludge = second.sdKludge = FALSE;
-    first.stKludge = second.stKludge = FALSE;
-    TidyProgramName(first.program, first.host, first.tidy);
-    TidyProgramName(second.program, second.host, second.tidy);
-    first.matchWins = second.matchWins = 0;
-    safeStrCpy(first.variants, appData.variant, sizeof(first.variants)/sizeof(first.variants[0]));
-    safeStrCpy(second.variants, appData.variant,sizeof(second.variants)/sizeof(second.variants[0]));
-    first.analysisSupport = second.analysisSupport = 2; /* detect */
-    first.analyzing = second.analyzing = FALSE;
-    first.initDone = second.initDone = FALSE;
-
-    /* New features added by Tord: */
-    first.useFEN960 = FALSE; second.useFEN960 = FALSE;
-    first.useOOCastle = TRUE; second.useOOCastle = TRUE;
-    /* End of new features added by Tord. */
-    first.fenOverride  = appData.fenOverride1;
-    second.fenOverride = appData.fenOverride2;
-
-    /* [HGM] time odds: set factor for each machine */
-    first.timeOdds  = appData.firstTimeOdds;
-    second.timeOdds = appData.secondTimeOdds;
-    { float norm = 1;
-        if(appData.timeOddsMode) {
-            norm = first.timeOdds;
-            if(norm > second.timeOdds) norm = second.timeOdds;
-        }
-        first.timeOdds /= norm;
-        second.timeOdds /= norm;
-    }
-
-    /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
-    first.accumulateTC = appData.firstAccumulateTC;
-    second.accumulateTC = appData.secondAccumulateTC;
-    first.maxNrOfSessions = second.maxNrOfSessions = 1;
-
-    /* [HGM] debug */
-    first.debug = second.debug = FALSE;
-    first.supportsNPS = second.supportsNPS = UNKNOWN;
-
-    /* [HGM] options */
-    first.optionSettings  = appData.firstOptions;
-    second.optionSettings = appData.secondOptions;
-
-    first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
-    second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
-    first.isUCI = appData.firstIsUCI; /* [AS] */
-    second.isUCI = appData.secondIsUCI; /* [AS] */
-    first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
-    second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
-
-    if (appData.firstProtocolVersion > PROTOVER
-       || appData.firstProtocolVersion < 1)
-      {
-       char buf[MSG_SIZ];
-       int len;
-
-       len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
-                      appData.firstProtocolVersion);
-       if( (len > MSG_SIZ) && appData.debugMode )
-         fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
-
-       DisplayFatalError(buf, 0, 2);
-      }
-    else
-      {
-       first.protocolVersion = appData.firstProtocolVersion;
-      }
-
-    if (appData.secondProtocolVersion > PROTOVER
-       || appData.secondProtocolVersion < 1)
-      {
-       char buf[MSG_SIZ];
-       int len;
-
-       len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
-                      appData.secondProtocolVersion);
-       if( (len > MSG_SIZ) && appData.debugMode )
-         fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
-
-       DisplayFatalError(buf, 0, 2);
-      }
-    else
-      {
-       second.protocolVersion = appData.secondProtocolVersion;
-      }
+    InitEngine(&first, 0);
+    InitEngine(&second, 1);
+    CommonEngineInit();
 
     if (appData.icsActive) {
         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
-//    } 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;
@@ -878,15 +953,6 @@ InitBackEnd1()
     }
 #endif
 
-    if (appData.noChessProgram) {
-       programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
-       sprintf(programVersion, "%s", PACKAGE_STRING);
-    } 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);
-    }
-
     if (!appData.icsActive) {
       char buf[MSG_SIZ];
       int len;
@@ -956,12 +1022,12 @@ InitBackEnd1()
       case VariantJanus:      /* should work */
       case VariantSuper:      /* experimental */
       case VariantGreat:      /* experimental, requires legality testing to be off */
+      case VariantSChess:     /* S-Chess, should work */
+      case VariantSpartan:    /* should work */
        break;
       }
     }
 
-    InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
-    InitEngineUCI( installDir, &second );
 }
 
 int NextIntegerFromString( char ** str, long * value )
@@ -1023,6 +1089,13 @@ int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *i
             if(**str == '!') type = *(*str)++; // Bronstein TC
             if(result = NextIntegerFromString( str, &temp2)) return -1;
             *inc = temp2 * 1000;
+            if(**str == '.') { // read fraction of increment
+                char *start = ++(*str);
+                if(result = NextIntegerFromString( str, &temp2)) return -1;
+                temp2 *= 1000;
+                while(start++ < *str) temp2 /= 10;
+                *inc += temp2;
+            }
         } else *inc = 0;
         *moves = 0; *tc = temp * 1000; *incType = type;
         return 0;
@@ -1065,14 +1138,13 @@ int GetTimeQuota(int movenr, int lastUsed, char *tcString)
 int
 ParseTimeControl(tc, ti, mps)
      char *tc;
-     int ti;
+     float ti;
      int mps;
 {
   long tc1;
   long tc2;
   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
   int min, sec=0;
-  int len;
 
   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
@@ -1080,9 +1152,9 @@ ParseTimeControl(tc, ti, mps)
   if(ti > 0) {
 
     if(mps)
-      snprintf(buf, MSG_SIZ, ":%d/%s+%d", mps, mytc, ti);
+      snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
     else 
-      snprintf(buf, MSG_SIZ, ":%s+%d", mytc, ti);
+      snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
   } else {
     if(mps)
       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
@@ -1160,6 +1232,40 @@ InitBackEnd2()
 }
 
 void
+MatchEvent(int mode)
+{      // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
+       /* Set up machine vs. machine match */
+       if (appData.noChessProgram) {
+           DisplayFatalError(_("Can't have a match with no chess programs"),
+                             0, 2);
+           return;
+       }
+       matchMode = mode;
+       matchGame = 1;
+       if (*appData.loadGameFile != NULLCHAR) {
+           int index = appData.loadGameIndex; // [HGM] autoinc
+           if(index<0) lastIndex = index = 1;
+           if (!LoadGameFromFile(appData.loadGameFile,
+                                 index,
+                                 appData.loadGameFile, FALSE)) {
+               DisplayFatalError(_("Bad game file"), 0, 1);
+               return;
+           }
+       } else if (*appData.loadPositionFile != NULLCHAR) {
+           int index = appData.loadPositionIndex; // [HGM] autoinc
+           if(index<0) lastIndex = index = 1;
+           if (!LoadPositionFromFile(appData.loadPositionFile,
+                                     index,
+                                     appData.loadPositionFile)) {
+               DisplayFatalError(_("Bad position file"), 0, 1);
+               return;
+           }
+       }
+       first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches\r
+       TwoMachinesEvent();
+}
+
+void
 InitBackEnd3 P((void))
 {
     GameMode initialMode;
@@ -1219,6 +1325,12 @@ InitBackEnd3 P((void))
     DisplayMessage("", "");
     if (StrCaseCmp(appData.initialMode, "") == 0) {
       initialMode = BeginningOfGame;
+      if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
+        gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
+        ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
+        gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
+        ModeHighlight();
+      }
     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
       initialMode = TwoMachinesPlay;
     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
@@ -1245,34 +1357,7 @@ InitBackEnd3 P((void))
     }
 
     if (appData.matchMode) {
-       /* Set up machine vs. machine match */
-       if (appData.noChessProgram) {
-           DisplayFatalError(_("Can't have a match with no chess programs"),
-                             0, 2);
-           return;
-       }
-       matchMode = TRUE;
-       matchGame = 1;
-       if (*appData.loadGameFile != NULLCHAR) {
-           int index = appData.loadGameIndex; // [HGM] autoinc
-           if(index<0) lastIndex = index = 1;
-           if (!LoadGameFromFile(appData.loadGameFile,
-                                 index,
-                                 appData.loadGameFile, FALSE)) {
-               DisplayFatalError(_("Bad game file"), 0, 1);
-               return;
-           }
-       } else if (*appData.loadPositionFile != NULLCHAR) {
-           int index = appData.loadPositionIndex; // [HGM] autoinc
-           if(index<0) lastIndex = index = 1;
-           if (!LoadPositionFromFile(appData.loadPositionFile,
-                                     index,
-                                     appData.loadPositionFile)) {
-               DisplayFatalError(_("Bad position file"), 0, 1);
-               return;
-           }
-       }
-       TwoMachinesEvent();
+       MatchEvent(TRUE);
     } else if (*appData.cmailGameName != NULLCHAR) {
        /* Set up cmail mode */
        ReloadCmailMsgEvent(TRUE);
@@ -2848,7 +2933,7 @@ read_from_ics(isr, closure, data, count, error)
 
                if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
                for(p=0; p<MAX_CHAT; p++) {
-                   if(channel == atoi(chatPartner[p])) {
+                   if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
                    talker[0] = '['; strcat(talker, "] ");
                    Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
                    chattingPartner = p; break;
@@ -2903,9 +2988,9 @@ read_from_ics(isr, closure, data, count, error)
                }
            } // [HGM] chat: end of patch
 
+          backup = i;
            if (appData.zippyTalk || appData.zippyPlay) {
                 /* [DM] Backup address for color zippy lines */
-                backup = i;
 #if ZIPPY
                if (loggedOn == TRUE)
                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
@@ -3070,6 +3155,8 @@ read_from_ics(isr, closure, data, count, error)
                    continue;
            }
 
+          if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
+
            if (looking_at(buf, &i, "\\   ")) {
                if (prevColor != ColorNormal) {
                    if (oldi > next_out) {
@@ -3538,7 +3625,7 @@ read_from_ics(isr, closure, data, count, error)
                /*           [4] is " *" or empty (don't care). */
                int gamenum = atoi(star_match[0]);
                char *whitename, *blackname, *why, *endtoken;
-               ChessMove endtype = (ChessMove) 0;
+               ChessMove endtype = EndOfFile;
 
                if (tkind == 0) {
                  whitename = star_match[1];
@@ -3832,7 +3919,7 @@ ParseBoard12(string)
     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
     char to_play, board_chars[200];
-    char move_str[500], str[500], elapsed_time[500];
+    char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
     char black[32], white[32];
     Board board;
     int prevMove = currentMove;
@@ -3873,7 +3960,7 @@ ParseBoard12(string)
               &ticking);
 
     if (n < 21) {
-        snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
+        snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
        DisplayError(str, 0);
        return;
     }
@@ -3982,7 +4069,7 @@ ParseBoard12(string)
     }
 
    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
-                                       weird && (int)gameInfo.variant <= (int)VariantShogi) {
+                                       weird && (int)gameInfo.variant < (int)VariantShogi) {
      /* [HGM] We seem to have switched variant unexpectedly
       * Try to guess new variant from board size
       */
@@ -4311,6 +4398,8 @@ ParseBoard12(string)
          //                 So we parse the long-algebraic move string in stead of the SAN move
          int valid; char buf[MSG_SIZ], *prom;
 
+         if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
+               strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
          // str looks something like "Q/a1-a2"; kill the slash
          if(str[1] == '/')
            snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
@@ -4320,7 +4409,7 @@ ParseBoard12(string)
          if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
                if(appData.debugMode)
                        fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
-               safeStrCpy(move_str, buf, sizeof(move_str)/sizeof(move_str[0]));
+               safeStrCpy(move_str, buf, MSG_SIZ);
           }
          valid = ParseOneMove(move_str, moveNum - 1, &moveType,
                                &fromX, &fromY, &toX, &toY, &promoChar)
@@ -4349,8 +4438,25 @@ ParseBoard12(string)
            strcat(parseList[moveNum - 1], " ");
            strcat(parseList[moveNum - 1], elapsed_time);
            /* currentMoveString is set as a side-effect of ParseOneMove */
+           if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
            safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
            strcat(moveList[moveNum - 1], "\n");
+
+            if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
+                                 && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
+              for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
+                ChessSquare old, new = boards[moveNum][k][j];
+                  if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
+                  old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
+                  if(old == new) continue;
+                  if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
+                  else if(new == WhiteWazir || new == BlackWazir) {
+                      if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
+                           boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
+                      else boards[moveNum][k][j] = old; // preserve type of Gold
+                  } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
+                      boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
+              }
          } else {
            /* Move from ICS was illegal!?  Punt. */
            if (appData.debugMode) {
@@ -4640,7 +4746,7 @@ SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
        break;
       case WhiteNonPromotion:
       case BlackNonPromotion:
-        sprintf(user_move, "%c%c%c%c=\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
+        sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
         break;
       case WhitePromotion:
       case BlackPromotion:
@@ -4659,14 +4765,16 @@ SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
        break;
       case WhiteDrop:
       case BlackDrop:
+      drop:
        snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
-               ToUpper(PieceToChar((ChessSquare) fromX)),
-                AAA + toX, ONE + toY);
+                ToUpper(PieceToChar((ChessSquare) fromX)),
+                AAA + toX, ONE + toY);
        break;
+      case IllegalMove:  /* could be a variant we don't quite understand */
+        if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
       case NormalMove:
       case WhiteCapturesEnPassant:
       case BlackCapturesEnPassant:
-      case IllegalMove:  /* could be a variant we don't quite understand */
        snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
        break;
@@ -4777,6 +4885,59 @@ ProcessICSInitScript(f)
 }
 
 
+static int lastX, lastY, selectFlag, dragging;
+
+void
+Sweep(int step)
+{
+    ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
+    if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
+    if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
+    if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
+    if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
+    if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
+    do {
+       promoSweep -= step;
+       if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
+       else if((int)promoSweep == -1) promoSweep = WhiteKing;
+       else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
+       else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
+       if(!step) step = 1;
+    } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
+           appData.testLegality && (promoSweep == king ||
+           gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
+    ChangeDragPiece(promoSweep);
+}
+
+int PromoScroll(int x, int y)
+{
+  int step = 0;
+
+  if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
+  if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
+  if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
+  if(!step) return FALSE;
+  lastX = x; lastY = y;
+  if((promoSweep < BlackPawn) == flipView) step = -step;
+  if(step > 0) selectFlag = 1;
+  if(!selectFlag) Sweep(step);
+  return FALSE;
+}
+
+void
+NextPiece(int step)
+{
+    ChessSquare piece = boards[currentMove][toY][toX];
+    do {
+       pieceSweep -= step;
+       if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
+       if((int)pieceSweep == -1) pieceSweep = BlackKing;
+       if(!step) step = -1;
+    } while(PieceToChar(pieceSweep) == '.');
+    boards[currentMove][toY][toX] = pieceSweep;
+    DrawPosition(FALSE, boards[currentMove]);
+    boards[currentMove][toY][toX] = piece;
+}
 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
 void
 AlphaRank(char *move, int n)
@@ -4838,9 +4999,6 @@ ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
      int *fromX, *fromY, *toX, *toY;
      char *promoChar;
 {
-    if (appData.debugMode) {
-        fprintf(debugFP, "move to parse: %s\n", move);
-    }
     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
 
     switch (*moveType) {
@@ -4899,7 +5057,7 @@ ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
 
       case AmbiguousMove:
       case ImpossibleMove:
-      case (ChessMove) 0:      /* end of file */
+      case EndOfFile:
       case ElapsedTime:
       case Comment:
       case PGNTag:
@@ -4969,6 +5127,9 @@ fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, f
     moveList[endPV-1][1] = fromY + ONE;
     moveList[endPV-1][2] = toX + AAA;
     moveList[endPV-1][3] = toY + ONE;
+    moveList[endPV-1][4] = promoChar;
+    moveList[endPV-1][5] = NULLCHAR;
+    strncat(moveList[endPV-1], "\n", MOVE_LEN);
     if(storeComments)
        CoordsToAlgebraic(boards[endPV - 1],
                             PosFlags(endPV - 1),
@@ -4984,8 +5145,6 @@ fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, f
   DrawPosition(TRUE, boards[currentMove]);
 }
 
-static int lastX, lastY;
-
 Boolean
 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
 {
@@ -5031,13 +5190,17 @@ 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(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
+  if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
+  if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
   if(!step) return;
   lastX = x; lastY = y;
+
+  if(pieceSweep != EmptySquare) { NextPiece(step); return; }
+  if(endPV < 0) return;
+  if(y < margin) step = 1; else
+  if(y > h - margin) step = -1;
   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
   currentMove += step;
   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
@@ -5270,8 +5433,8 @@ InitPosition(redraw)
     int i, j, pawnRow, overrule,
     oldx = gameInfo.boardWidth,
     oldy = gameInfo.boardHeight,
-    oldh = gameInfo.holdingsWidth,
-    oldv = gameInfo.variant;
+    oldh = gameInfo.holdingsWidth;
+    static int oldv;
 
     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
 
@@ -5309,12 +5472,12 @@ InitPosition(redraw)
     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
          SetCharTable(pieceNickName, appData.pieceNickNames);
     else SetCharTable(pieceNickName, "............");
+    pieces = FIDEArray;
 
     switch (gameInfo.variant) {
     case VariantFischeRandom:
       shuffleOpenings = TRUE;
     default:
-      pieces = FIDEArray;
       break;
     case VariantShatranj:
       pieces = ShatranjArray;
@@ -5342,6 +5505,10 @@ InitPosition(redraw)
       gameInfo.boardWidth = 10;
       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
       break;
+    case VariantSChess:
+      SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
+      gameInfo.holdingsSize = 7;
+      break;
     case VariantJanus:
       pieces = JanusArray;
       gameInfo.boardWidth = 10;
@@ -5384,6 +5551,10 @@ InitPosition(redraw)
       pieces = KnightmateArray;
       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
       break;
+    case VariantSpartan:
+      pieces = SpartanArray;
+      SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
+      break;
     case VariantFairy:
       pieces = fairyArray;
       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
@@ -5454,7 +5625,7 @@ InitPosition(redraw)
         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
         initialPosition[pawnRow][j] = WhitePawn;
-        initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
+        initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
         if(gameInfo.variant == VariantXiangqi) {
             if(j&1) {
                 initialPosition[pawnRow][j] =
@@ -5498,6 +5669,14 @@ InitPosition(redraw)
        initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
        initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
      }
+     if( gameInfo.variant == VariantSChess ) {
+      initialPosition[1][0] = BlackMarshall;
+      initialPosition[2][0] = BlackAngel;
+      initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
+      initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
+      initialPosition[1][1] = initialPosition[2][1] = 
+      initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
+     }
   if (appData.debugMode) {
     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
   }
@@ -5517,18 +5696,12 @@ InitPosition(redraw)
 
     if(oldx != gameInfo.boardWidth ||
        oldy != gameInfo.boardHeight ||
+       oldv != gameInfo.variant ||
        oldh != gameInfo.holdingsWidth
-#ifdef GOTHIC
-       || oldv == VariantGothic ||        // For licensing popups
-       gameInfo.variant == VariantGothic
-#endif
-#ifdef FALCON
-       || oldv == VariantFalcon ||
-       gameInfo.variant == VariantFalcon
-#endif
                                          )
             InitDrawingSizes(-2 ,0);
 
+    oldv = gameInfo.variant;
     if (redraw)
       DrawPosition(TRUE, boards[currentMove]);
 }
@@ -5552,7 +5725,8 @@ SendBoard(cps, moveNum)
       /* Kludge to set black to move, avoiding the troublesome and now
        * deprecated "black" command.
        */
-      if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
+      if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
+        SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
 
       SendToProgram("edit\n", cps);
       SendToProgram("#\n", cps);
@@ -5603,6 +5777,21 @@ SendBoard(cps, moveNum)
     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
 }
 
+ChessSquare
+DefaultPromoChoice(int white)
+{
+    ChessSquare result;
+    if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
+       result = WhiteFerz; // no choice
+    else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
+       result= WhiteKing; // in Suicide Q is the last thing we want
+    else if(gameInfo.variant == VariantSpartan)
+       result = white ? WhiteQueen : WhiteAngel;
+    else result = WhiteQueen;
+    if(!white) result = WHITE_TO_BLACK result;
+    return result;
+}
+
 static int autoQueen; // [HGM] oneclick
 
 int
@@ -5630,6 +5819,12 @@ HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
         promotionZoneSize = 3;
     }
 
+    // Treat Lance as Pawn when it is not representing Amazon
+    if(gameInfo.variant != VariantSuper) {
+        if(piece == WhiteLance) piece = WhitePawn; else
+        if(piece == BlackLance) piece = BlackPawn;
+    }
+
     // next weed out all moves that do not touch the promotion zone at all
     if((int)piece >= BlackPawn) {
         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
@@ -5675,19 +5870,25 @@ HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
        *promoChoice = PieceToChar(BlackFerz);  // no choice
        return FALSE;
     }
-    if(autoQueen) { // predetermined
-       if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
-            *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
-       else *promoChoice = PieceToChar(BlackQueen);
+    // no sense asking what we must promote to if it is going to explode...
+    if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
+       *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
        return FALSE;
     }
+    // give caller the default choice even if we will not make it
+    *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
+    if(gameInfo.variant == VariantShogi) *promoChoice = '+';
+    if(appData.sweepSelect && gameInfo.variant != VariantGreat
+                          && gameInfo.variant != VariantShogi
+                          && gameInfo.variant != VariantSuper) return FALSE;
+    if(autoQueen) return FALSE; // predetermined
 
     // 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);
+                       fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
        if(moveType != WhitePromotion && moveType  != BlackPromotion)
            return FALSE;
     }
@@ -5829,6 +6030,8 @@ OnlyMove(int *x, int *y, Boolean captures) {
       case IcsPlayingBlack:
        if(WhiteOnMove(currentMove)) return FALSE;
        break;
+      case EditGame:
+        break;
       default:
        return FALSE;
     }
@@ -5875,7 +6078,7 @@ FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
 int lastLoadGameUseList = FALSE;
 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
-ChessMove lastLoadGameStart = (ChessMove) 0;
+ChessMove lastLoadGameStart = EndOfFile;
 
 void
 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
@@ -5928,6 +6131,7 @@ UserMoveEvent(fromX, fromY, toX, toY, promoChar)
       case BeginningOfGame:
       case AnalyzeMode:
       case Training:
+       if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
        if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
            /* User is moving for Black */
@@ -6022,7 +6226,7 @@ UserMoveEvent(fromX, fromY, toX, toY, promoChar)
     pup = boards[currentMove][toY][toX];
 
     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
-    if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
+    if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
          if( pup != EmptySquare ) return;
          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
           if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
@@ -6115,7 +6319,8 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
      the previous line in Analysis Mode */
   if ((gameMode == AnalyzeMode || gameMode == EditGame)
                                && currentMove < forwardMostMove) {
-    PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
+    if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
+    else forwardMostMove = currentMove;
   }
 
   /* If we need the chess program but it's dead, restart it */
@@ -6129,7 +6334,10 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
 
   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
 
-  if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
+  if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
+    ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
+    return 1;
+  }
 
   if (gameMode == BeginningOfGame) {
     if (appData.noChessProgram) {
@@ -6184,6 +6392,7 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
 
   switch (gameMode) {
   case EditGame:
+    if(appData.testLegality)
     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
     case MT_NONE:
     case MT_CHECK:
@@ -6213,6 +6422,7 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
   }
 
   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
+  promoDefaultAltered = FALSE; // [HGM] fall back on default choice
 
   if(bookHit) { // [HGM] book: simulate book reply
        static char bookMove[MSG_SIZ]; // a bit generous?
@@ -6265,12 +6475,43 @@ MarkTargetSquares(int clear)
   DrawPosition(TRUE, NULL);
 }
 
+int
+Explode(Board board, int fromX, int fromY, int toX, int toY)
+{
+    if(gameInfo.variant == VariantAtomic &&
+       (board[toY][toX] != EmptySquare ||                     // capture?
+        toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
+                         board[fromY][fromX] == BlackPawn   )
+      )) {
+        AnimateAtomicCapture(board, fromX, fromY, toX, toY);
+        return TRUE;
+    }
+    return FALSE;
+}
+
+ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
+
+int CanPromote(ChessSquare piece, int y)
+{
+       if(gameMode == EditPosition) return FALSE; // no promotions when editing position
+       // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
+       if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
+          gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
+          gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
+                                                 gameInfo.variant == VariantMakruk) return FALSE;
+       return (piece == BlackPawn && y == 1 ||
+               piece == WhitePawn && y == BOARD_HEIGHT-2 ||
+               piece == BlackLance && y == 1 ||
+               piece == WhiteLance && y == BOARD_HEIGHT-2 );
+}
+
 void LeftClick(ClickType clickType, int xPix, int yPix)
 {
     int x, y;
     Boolean saveAnimate;
-    static int second = 0, promotionChoice = 0, dragging = 0;
+    static int second = 0, promotionChoice = 0, clearFlag = 0;
     char promoChoice = NULLCHAR;
+    ChessSquare piece;
 
     if(appData.seekGraph && appData.icsActive && loggedOn &&
        (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
@@ -6290,6 +6531,13 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
        x = BOARD_WIDTH - 1 - x;
     }
 
+    if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
+       defaultPromoChoice = promoSweep;
+       promoSweep = EmptySquare;   // terminate sweep
+       promoDefaultAltered = TRUE;
+       if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
+    }
+
     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
@@ -6318,28 +6566,48 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
        return;
 
+    if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
+       fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
+
+    if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
+       int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
+                   gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
+       defaultPromoChoice = DefaultPromoChoice(side);
+    }
+
     autoQueen = appData.alwaysPromoteToQueen;
 
     if (fromX == -1) {
-      if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
-       if (clickType == Press) {
+      int originalY = y;
+      gatingPiece = EmptySquare;
+      if (clickType != Press) {
+       if(dragging) { // [HGM] from-square must have been reset due to game end since last press
+           DragPieceEnd(xPix, yPix); dragging = 0;
+           DrawPosition(FALSE, NULL);
+       }
+       return;
+      }
+      fromX = x; fromY = y;
+      if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
+        // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
+        appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
            /* First square */
-           if (OKToStartUserMove(x, y)) {
-               fromX = x;
-               fromY = y;
+           if (OKToStartUserMove(fromX, fromY)) {
                second = 0;
                MarkTargetSquares(0);
                DragPieceBegin(xPix, yPix); dragging = 1;
+               if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
+                   promoSweep = defaultPromoChoice;
+                   selectFlag = 0; lastX = xPix; lastY = yPix;
+                   Sweep(0); // Pawn that is going to promote: preview promotion piece
+                   DisplayMessage("", _("Pull pawn backwards to under-promote"));
+               }
                if (appData.highlightDragging) {
-                   SetHighlights(x, y, -1, -1);
+                   SetHighlights(fromX, fromY, -1, -1);
                }
-           }
-       } else if(dragging) { // [HGM] from-square must have been reset due to game end since last press
-           DragPieceEnd(xPix, yPix); dragging = 0;
-           DrawPosition(FALSE, NULL);
+           } else fromX = fromY = -1;
+           return;
        }
-       return;
-      }
     }
 
     /* fromX != -1 */
@@ -6354,7 +6622,7 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
        /* 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;
+       frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
        if ((WhitePawn <= fromP && fromP <= WhiteKing &&
             WhitePawn <= toP && toP <= WhiteKing &&
             !(fromP == WhiteKing && toP == WhiteRook && frc) &&
@@ -6365,6 +6633,7 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
             !(fromP == BlackKing && toP == BlackRook && frc))) {
            /* Clicked again on same color piece -- changed his mind */
            second = (x == fromX && y == fromY);
+           promoDefaultAltered = FALSE;
           if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
            if (appData.highlightDragging) {
                SetHighlights(x, y, -1, -1);
@@ -6372,13 +6641,24 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
                ClearHighlights();
            }
            if (OKToStartUserMove(x, y)) {
+               if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
+                 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
+               y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
+                 gatingPiece = boards[currentMove][fromY][fromX];
+               else gatingPiece = EmptySquare;
                fromX = x;
                fromY = y; dragging = 1;
                MarkTargetSquares(0);
                DragPieceBegin(xPix, yPix);
+               if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
+                   promoSweep = defaultPromoChoice;
+                   selectFlag = 0; lastX = xPix; lastY = yPix;
+                   Sweep(0); // Pawn that is going to promote: preview promotion piece
+               }
            }
-           return;
           }
+          if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
+          second = FALSE; 
        }
        // ignore clicks on holdings
        if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
@@ -6386,6 +6666,14 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
 
     if (clickType == Release && x == fromX && y == fromY) {
        DragPieceEnd(xPix, yPix); dragging = 0;
+       if(clearFlag) {
+           // a deferred attempt to click-click move an empty square on top of a piece
+           boards[currentMove][y][x] = EmptySquare;
+           ClearHighlights();
+           DrawPosition(FALSE, boards[currentMove]);
+           fromX = fromY = -1; clearFlag = 0;
+           return;
+       }
        if (appData.animateDragging) {
            /* Undo animation damage if any */
            DrawPosition(FALSE, NULL);
@@ -6394,6 +6682,7 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
            /* Second up/down in same square; just abort move */
            second = 0;
            fromX = fromY = -1;
+           gatingPiece = EmptySquare;
            ClearHighlights();
            gotPremove = 0;
            ClearPremoveHighlights();
@@ -6404,12 +6693,20 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
        return;
     }
 
+    clearFlag = 0;
+
     /* we now have a different from- and (possibly off-board) to-square */
     /* Completed move */
     toX = x;
     toY = y;
     saveAnimate = appData.animate;
     if (clickType == Press) {
+       if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
+           // must be Edit Position mode with empty-square selected
+           fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag
+           if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
+           return;
+       }
        /* Finish clickclick move */
        if (appData.animate || appData.highlightLastMove) {
            SetHighlights(fromX, fromY, toX, toY);
@@ -6456,7 +6753,9 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
     }
 
     // off-board moves should not be highlighted
-    if(x < 0 || x < 0) ClearHighlights();
+    if(x < 0 || y < 0) ClearHighlights();
+
+    if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
 
     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
        SetHighlights(fromX, fromY, toX, toY);
@@ -6475,9 +6774,13 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
        }
        PromotionPopUp();
     } else {
+       int oldMove = currentMove;
        UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
        if (!appData.highlightLastMove || gotPremove) ClearHighlights();
        if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
+       if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
+          Explode(boards[currentMove-1], fromX, fromY, toX, toY))
+           DrawPosition(TRUE, boards[currentMove]);
        fromX = fromY = -1;
     }
     appData.animate = saveAnimate;
@@ -6517,16 +6820,25 @@ int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
 
     xSqr = EventToSquare(x, BOARD_WIDTH);
     ySqr = EventToSquare(y, BOARD_HEIGHT);
-    if (action == Release) UnLoadPV(); // [HGM] pv
+    if (action == Release) {
+       if(pieceSweep != EmptySquare) {
+           EditPositionMenuEvent(pieceSweep, toX, toY);
+           pieceSweep = EmptySquare;
+       } else UnLoadPV(); // [HGM] pv
+    }
     if (action != Press) return -2; // return code to be ignored
     switch (gameMode) {
       case IcsExamining:
        if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
       case EditPosition:
        if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
-       if (xSqr < 0 || ySqr < 0) return -1;\r
-       whichMenu = 0; // edit-position menu
-       break;
+       if (xSqr < 0 || ySqr < 0) return -1;
+       if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
+       pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
+       toX = xSqr; toY = ySqr; lastX = x, lastY = y;
+       if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
+       NextPiece(0);
+       return -2;\r
       case IcsObserving:
        if(!appData.icsEngineAnalyze) return -1;
       case IcsPlayingWhite:
@@ -6720,7 +7032,6 @@ Adjudicate(ChessProgramState *cps)
                     if(canAdjudicate && appData.checkMates) {
                         if(engineOpponent)
                           SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
-                         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
                                                        "Xboard adjudication: King destroyed", GE_XBOARD );
                          return 1;
@@ -6734,7 +7045,6 @@ Adjudicate(ChessProgramState *cps)
                     if(canAdjudicate && appData.checkMates) {
                         if(engineOpponent)
                           SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
-                         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
                                                        "Xboard adjudication: Bare king", GE_XBOARD );
                          return 1;
@@ -6747,7 +7057,6 @@ Adjudicate(ChessProgramState *cps)
                            /* but only adjudicate if adjudication enabled */
                            if(engineOpponent)
                              SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
-                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
                            GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
                                                        "Xboard adjudication: Bare king", GE_XBOARD );
                            return 1;
@@ -6803,12 +7112,11 @@ Adjudicate(ChessProgramState *cps)
                    case EP_WINS:
                        result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
                    default:
-                       result = (ChessMove) 0;
+                       result = EndOfFile;
                }
                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
                    if(engineOpponent)
                      SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
-                   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
                    GameEnds( result, reason, GE_XBOARD );
                    return 1;
                }
@@ -6827,7 +7135,6 @@ Adjudicate(ChessProgramState *cps)
                           SendToProgram("force\n", engineOpponent); // suppress reply
                           SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
                         }
-                         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
                          return 1;
                      }
@@ -6848,7 +7155,6 @@ Adjudicate(ChessProgramState *cps)
                            SendToProgram("force\n", engineOpponent); // suppress reply
                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
                          }
-                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
                           return 1;
                      }
@@ -6931,7 +7237,6 @@ Adjudicate(ChessProgramState *cps)
                               SendToProgram("force\n", engineOpponent); // suppress reply
                               SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
                             }
-                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
                              GameEnds( result, details, GE_XBOARD );
                              return 1;
                         }
@@ -6968,7 +7273,6 @@ Adjudicate(ChessProgramState *cps)
                           SendToProgram("force\n", engineOpponent); // suppress reply
                           SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
                         }
-                         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
                          return 1;
                 }
@@ -6991,7 +7295,6 @@ Adjudicate(ChessProgramState *cps)
                               SendToProgram("force\n", engineOpponent); // suppress reply
                               SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
                             }
-                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
                              GameEnds( GameIsDrawn, p, GE_XBOARD );
                              return 1;
                          }
@@ -7002,7 +7305,6 @@ Adjudicate(ChessProgramState *cps)
                      SendToProgram("force\n", engineOpponent); // suppress reply
                      SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
                    }
-                   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
                    GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
                    return 1;
                }
@@ -7181,7 +7483,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
                               &fromX, &fromY, &toX, &toY, &promoChar)) {
            /* Machine move could not be parsed; ignore it. */
          snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
-                   machineMove, cps->which);
+                   machineMove, _(cps->which));
            DisplayError(buf1, 0);
             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
@@ -7238,11 +7540,6 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
        if (cps->sendTime == 2) cps->sendTime = 1;
        if (cps->offeredDraw) cps->offeredDraw--;
 
-       /* currentMoveString is set as a side-effect of ParseOneMove */
-       safeStrCpy(machineMove, currentMoveString, sizeof(machineMove)/sizeof(machineMove[0]));
-       strcat(machineMove, "\n");
-       safeStrCpy(moveList[forwardMostMove], machineMove, sizeof(moveList[forwardMostMove])/sizeof(moveList[forwardMostMove][0]));
-
         /* [AS] Save move info*/
         pvInfoList[ forwardMostMove ].score = programStats.score;
         pvInfoList[ forwardMostMove ].depth = programStats.depth;
@@ -7279,7 +7576,10 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
             }
         }
 
-       if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
+       if(Adjudicate(cps)) {
+           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
+           return; // [HGM] adjudicate: for all automatic game ends
+       }
 
 #if ZIPPY
        if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
@@ -7387,6 +7687,15 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
     }
 
+    if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
+      int dummy, s=6; char buf[MSG_SIZ];
+      if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
+      if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
+      ParseFEN(boards[0], &dummy, message+s);
+      DrawPosition(TRUE, boards[0]);
+      startedFromSetupPosition = TRUE;
+      return;
+    }
     /* [HGM] Allow engine to set up a position. Don't ask me why one would
      * want this, I was asked to put it in, and obliged.
      */
@@ -7413,13 +7722,15 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
      * Look for communication commands
      */
     if (!strncmp(message, "telluser ", 9)) {
-       EscapeExpand(message+9, message+9); // [HGM] esc: allow escape sequences in popup box
+       if(message[9] == '\\' && message[10] == '\\')
+           EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
        DisplayNote(message + 9);
        return;
     }
     if (!strncmp(message, "tellusererror ", 14)) {
        cps->userError = 1;
-       EscapeExpand(message+14, message+14); // [HGM] esc: allow escape sequences in popup box
+       if(message[14] == '\\' && message[15] == '\\')
+           EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
        DisplayError(message + 14, 0);
        return;
     }
@@ -7590,21 +7901,21 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
            gameMode = EditGame;
            ModeHighlight();
        }
+        /* [HGM] illegal-move claim should forfeit game when Xboard */
+        /* only passes fully legal moves                            */
+        if( appData.testLegality && gameMode == TwoMachinesPlay ) {
+            GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
+                                "False illegal-move claim", GE_XBOARD );
+            return; // do not take back move we tested as valid
+        }
        currentMove = forwardMostMove-1;
        DisplayMove(currentMove-1); /* before DisplayMoveError */
        SwitchClocks(forwardMostMove-1); // [HGM] race
        DisplayBothClocks();
        snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
-               parseList[currentMove], cps->which);
+               parseList[currentMove], _(cps->which));
        DisplayMoveError(buf1);
        DrawPosition(FALSE, boards[currentMove]);
-
-        /* [HGM] illegal-move claim should forfeit game when Xboard */
-        /* only passes fully legal moves                            */
-        if( appData.testLegality && gameMode == TwoMachinesPlay ) {
-            GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
-                                "False illegal-move claim", GE_XBOARD );
-        }
        return;
     }
     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
@@ -7627,9 +7938,12 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
 
        cps->maybeThinking = FALSE;
        snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
-               cps->which, cps->program, cps->host, message);
+               _(cps->which), cps->program, cps->host, message);
        RemoveInputSource(cps->isr);
-       DisplayFatalError(buf1, 0, 1);
+       if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
+           if(cps == &first) appData.noChessProgram = TRUE;
+           DisplayError(buf1, 0);
+       }
        return;
     }
 
@@ -7650,7 +7964,7 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
                /* Hint move could not be parsed!? */
              snprintf(buf2, sizeof(buf2),
                        _("Illegal hint move \"%s\"\nfrom %s chess program"),
-                       buf1, cps->which);
+                       buf1, _(cps->which));
                DisplayError(buf2, 0);
            }
        } else {
@@ -8143,7 +8457,7 @@ ParseGameHistory(game)
     yynewstr(game);
     for (;;) {
        yyboardindex = boardIndex;
-       moveType = (ChessMove) yylex();
+       moveType = (ChessMove) Myylex();
        switch (moveType) {
          case IllegalMove:             /* maybe suicide chess, etc. */
   if (appData.debugMode) {
@@ -8208,7 +8522,7 @@ ParseGameHistory(game)
   }
            DisplayError(buf, 0);
            return;
-         case (ChessMove) 0:   /* end of file */
+         case EndOfFile:
            if (boardIndex < backwardMostMove) {
                /* Oops, gap.  How did that happen? */
                DisplayError(_("Gap in move list"), 0);
@@ -8254,6 +8568,7 @@ ParseGameHistory(game)
                if (q != NULL) *q = NULLCHAR;
                p++;
            }
+           while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
            gameInfo.resultDetails = StrSave(p);
            continue;
        }
@@ -8309,16 +8624,16 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board)
       if( board[toY][toX] != EmptySquare )
            board[EP_STATUS] = EP_CAPTURE;
 
-  /* [HGM] In Shatranj and Courier all promotions are to Ferz */
-  if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
-       && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
-         
   if (fromY == DROP_RANK) {
        /* must be first */
         piece = board[toY][toX] = (ChessSquare) fromX;
   } else {
       int i;
 
+      if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
+           if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
+               board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
+      } else
       if( board[fromY][fromX] == WhitePawn ) {
            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
               board[EP_STATUS] = EP_PAWN_MOVE;
@@ -8358,21 +8673,21 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board)
          king += (int) WhiteUnicorn - (int) WhiteKing;
 
     /* Code added by Tord: */
-    /* FRC castling assumed when king captures friendly rook. */
-    if (board[fromY][fromX] == WhiteKing &&
-            board[toY][toX] == WhiteRook) {
+    /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
+    if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
+        board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
       board[fromY][fromX] = EmptySquare;
       board[toY][toX] = EmptySquare;
-      if(toX > fromX) {
+      if((toX > fromX) != (piece == WhiteRook)) {
         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
       } else {
         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
       }
-    } else if (board[fromY][fromX] == BlackKing &&
-              board[toY][toX] == BlackRook) {
+    } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
+               board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
       board[fromY][fromX] = EmptySquare;
       board[toY][toX] = EmptySquare;
-      if(toX > fromX) {
+      if((toX > fromX) != (piece == BlackRook)) {
         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
       } else {
         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
@@ -8393,9 +8708,9 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board)
         board[toY][toX] = king;
         board[toY][toX+1] = board[fromY][BOARD_LEFT];
         board[fromY][BOARD_LEFT] = EmptySquare;
-    } else if (board[fromY][fromX] == WhitePawn
+    } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
+                board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
                && toY >= BOARD_HEIGHT-promoRank
-               && gameInfo.variant != VariantXiangqi
                ) {
        /* white pawn promotion */
         board[toY][toX] = CharToPiece(ToUpper(promoChar));
@@ -8457,9 +8772,9 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board)
        board[toY][toX] = BlackKing;
        board[fromY][0] = EmptySquare;
        board[toY][2] = BlackRook;
-    } else if (board[fromY][fromX] == BlackPawn
+    } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
+                board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
               && toY < promoRank
-               && gameInfo.variant != VariantXiangqi
                ) {
        /* black pawn promotion */
        board[toY][toX] = CharToPiece(ToLower(promoChar));
@@ -8505,10 +8820,11 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board)
       /* [HGM] OK, so I have written it. Holdings are stored in the */
       /* penultimate board files, so they are automaticlly stored   */
       /* in the game history.                                       */
-      if (fromY == DROP_RANK) {
+      if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
+                                && promoChar && piece != WhitePawn && piece != BlackPawn) {
         /* Delete from holdings, by decreasing count */
         /* and erasing image if necessary            */
-        p = (int) fromX;
+        p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
         if(p < (int) BlackPawn) { /* white drop */
              p -= (int)WhitePawn;
                 p = PieceToNumber((ChessSquare)p);
@@ -8528,7 +8844,7 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board)
         }
       }
       if (captured != EmptySquare && gameInfo.holdingsSize > 0
-          && gameInfo.variant != VariantBughouse        ) {
+          && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
         /* [HGM] holdings: Add to holdings, if holdings exist */
        if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
                // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
@@ -8572,11 +8888,14 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board)
        board[toY][toX] = EmptySquare;
       }
     }
+    if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
+        board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
+    } else
     if(promoChar == '+') {
         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
         board[toY][toX] = (ChessSquare) (PROMOTED piece);
-    } else if(!appData.testLegality) { // without legality testing, unconditionally believe promoChar
-        board[toY][toX] = CharToPiece(promoChar);
+    } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
+        board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
     }
     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
                && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
@@ -8796,7 +9115,7 @@ InitChessProgram(cps, setup)
       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
-                               gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
+          gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
       if( gameInfo.variant == VariantCourier )
            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
@@ -8804,6 +9123,8 @@ InitChessProgram(cps, setup)
            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
       if( gameInfo.variant == VariantGreat )
            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
+      if( gameInfo.variant == VariantSChess )
+           overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
 
       if(overruled) {
        snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
@@ -9346,6 +9667,13 @@ GameEnds(result, resultDetails, whosays)
                     first.matchWins, second.matchWins,
                     appData.matchGames - (first.matchWins + second.matchWins));
            popupRequested++; // [HGM] crash: postpone to after resetting endingGame
+           if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
+               first.twoMachinesColor = "black\n";
+               second.twoMachinesColor = "white\n";
+           } else {
+               first.twoMachinesColor = "white\n";
+               second.twoMachinesColor = "black\n";
+           }
        }
     }
     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
@@ -9466,7 +9794,7 @@ Reset(redraw, init)
     gotPremove = FALSE;
     alarmSounded = FALSE;
 
-    GameEnds((ChessMove) 0, NULL, GE_PLAYER);
+    GameEnds(EndOfFile, NULL, GE_PLAYER);
     if(appData.serverMovesName != NULL) {
         /* [HGM] prepare to make moves file for broadcasting */
         clock_t t = clock();
@@ -9515,7 +9843,7 @@ AutoPlayGameLoop()
          return;
        if (matchMode || appData.timeDelay == 0)
          continue;
-       if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
+       if (appData.timeDelay < 0)
          return;
        StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
        break;
@@ -9532,10 +9860,18 @@ AutoPlayOneMove()
       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
     }
 
-    if (gameMode != PlayFromGameFile)
+    if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
       return FALSE;
 
+    if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
+      pvInfoList[currentMove].depth = programStats.depth;
+      pvInfoList[currentMove].score = programStats.score;
+      pvInfoList[currentMove].time  = 0;
+      if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
+    }
+
     if (currentMove >= forwardMostMove) {
+      if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
       gameMode = EditGame;
       ModeHighlight();
 
@@ -9591,12 +9927,12 @@ LoadGameOneMove(readAhead)
     }
 
     yyboardindex = forwardMostMove;
-    if (readAhead != (ChessMove)0) {
+    if (readAhead != EndOfFile) {
       moveType = readAhead;
     } else {
       if (gameFileFP == NULL)
          return FALSE;
-      moveType = (ChessMove) yylex();
+      moveType = (ChessMove) Myylex();
     }
 
     done = FALSE;
@@ -9668,6 +10004,7 @@ LoadGameOneMove(readAhead)
            if (q != NULL) *q = NULLCHAR;
            p++;
        }
+       while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
        GameEnds(moveType, p, GE_FILE);
        done = TRUE;
        if (cmailMsgLoaded) {
@@ -9679,7 +10016,7 @@ LoadGameOneMove(readAhead)
        }
        break;
 
-      case (ChessMove) 0:      /* end of file */
+      case EndOfFile:
        if (appData.debugMode)
          fprintf(debugFP, "Parser hit end of file\n");
        switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
@@ -9707,7 +10044,7 @@ LoadGameOneMove(readAhead)
            if (appData.debugMode)
              fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
                      yy_text, (int) moveType);
-           return LoadGameOneMove((ChessMove)0); /* tail recursion */
+           return LoadGameOneMove(EndOfFile); /* tail recursion */
        }
        /* else fall thru */
 
@@ -9742,7 +10079,7 @@ LoadGameOneMove(readAhead)
        if (appData.debugMode)
          fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
                  yy_text, (int) moveType);
-       return LoadGameOneMove((ChessMove)0); /* tail recursion */
+       return LoadGameOneMove(EndOfFile); /* tail recursion */
 
       case IllegalMove:
        if (appData.testLegality) {
@@ -9800,8 +10137,6 @@ LoadGameOneMove(readAhead)
        return FALSE;
     } else {
        /* currentMoveString is set as a side-effect of yylex */
-       strcat(currentMoveString, "\n");
-       safeStrCpy(moveList[forwardMostMove], currentMoveString, sizeof(moveList[forwardMostMove])/sizeof(moveList[forwardMostMove][0]));
 
        thinkOutput[0] = NULLCHAR;
        MakeMove(fromX, fromY, toX, toY, promoChar);
@@ -10088,12 +10423,12 @@ LoadGame(f, gameNumber, title, useList)
      * 5-4-02: Let's try being more lenient and allowing a game to
      * start with an unnumbered move.  Does that break anything?
      */
-    cm = lastLoadGameStart = (ChessMove) 0;
+    cm = lastLoadGameStart = EndOfFile;
     while (gn > 0) {
        yyboardindex = forwardMostMove;
-       cm = (ChessMove) yylex();
+       cm = (ChessMove) Myylex();
        switch (cm) {
-         case (ChessMove) 0:
+         case EndOfFile:
            if (cmailMsgLoaded) {
                nCmailGames = CMAIL_MAX_GAMES - gn;
            } else {
@@ -10115,7 +10450,7 @@ LoadGame(f, gameNumber, title, useList)
              case PGNTag:
                break;
              case MoveNumberOne:
-             case (ChessMove) 0:
+             case EndOfFile:
                gn--;           /* count this game */
                lastLoadGameStart = cm;
                break;
@@ -10130,7 +10465,7 @@ LoadGame(f, gameNumber, title, useList)
              case GNUChessGame:
              case PGNTag:
              case MoveNumberOne:
-             case (ChessMove) 0:
+             case EndOfFile:
                gn--;           /* count this game */
                lastLoadGameStart = cm;
                break;
@@ -10144,7 +10479,7 @@ LoadGame(f, gameNumber, title, useList)
            if (gn > 0) {
                do {
                    yyboardindex = forwardMostMove;
-                   cm = (ChessMove) yylex();
+                   cm = (ChessMove) Myylex();
                } while (cm == PGNTag || cm == Comment);
            }
            break;
@@ -10165,7 +10500,7 @@ LoadGame(f, gameNumber, title, useList)
          case NormalMove:
            /* Only a NormalMove can be at the start of a game
             * without a position diagram. */
-           if (lastLoadGameStart == (ChessMove) 0) {
+           if (lastLoadGameStart == EndOfFile ) {
              gn--;
              lastLoadGameStart = MoveNumberOne;
            }
@@ -10183,12 +10518,12 @@ LoadGame(f, gameNumber, title, useList)
        /* Skip any header junk before position diagram and/or move 1 */
        for (;;) {
            yyboardindex = forwardMostMove;
-           cm = (ChessMove) yylex();
+           cm = (ChessMove) Myylex();
 
-           if (cm == (ChessMove) 0 ||
+           if (cm == EndOfFile ||
                cm == GNUChessGame || cm == XBoardGame) {
                /* Empty game; pretend end-of-file and handle later */
-               cm = (ChessMove) 0;
+               cm = EndOfFile;
                break;
            }
 
@@ -10256,7 +10591,7 @@ LoadGame(f, gameNumber, title, useList)
        }
 
        yyboardindex = forwardMostMove;
-       cm = (ChessMove) yylex();
+       cm = (ChessMove) Myylex();
 
        /* Handle comments interspersed among the tags */
        while (cm == Comment) {
@@ -10266,7 +10601,7 @@ LoadGame(f, gameNumber, title, useList)
            p = yy_text;
            AppendComment(currentMove, p, FALSE);
            yyboardindex = forwardMostMove;
-           cm = (ChessMove) yylex();
+           cm = (ChessMove) Myylex();
        }
     }
 
@@ -10306,6 +10641,7 @@ LoadGame(f, gameNumber, title, useList)
             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
                switch (*p) {
+                 case '{':
                  case '[':
                  case '-':
                  case ' ':
@@ -10343,7 +10679,7 @@ LoadGame(f, gameNumber, title, useList)
            }
        }
        yyboardindex = forwardMostMove;
-       cm = (ChessMove) yylex();
+       cm = (ChessMove) Myylex();
     }
 
     if (first.pr == NoProc) {
@@ -10369,10 +10705,10 @@ LoadGame(f, gameNumber, title, useList)
        p = yy_text;
        AppendComment(currentMove, p, FALSE);
        yyboardindex = forwardMostMove;
-       cm = (ChessMove) yylex();
+       cm = (ChessMove) Myylex();
     }
 
-    if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
+    if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
        cm == WhiteWins || cm == BlackWins ||
        cm == GameIsDrawn || cm == GameUnfinished) {
        DisplayMessage("", _("No moves in game"));
@@ -10409,7 +10745,7 @@ LoadGame(f, gameNumber, title, useList)
        LoadGameOneMove(cm);
 
     /* load the remaining moves from the file */
-    while (LoadGameOneMove((ChessMove)0)) {
+    while (LoadGameOneMove(EndOfFile)) {
       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
     }
@@ -10656,6 +10992,7 @@ SaveGameToFile(filename, append)
 {
     FILE *f;
     char buf[MSG_SIZ];
+    int result;
 
     if (strcmp(filename, "-") == 0) {
        return SaveGame(stdout, 0, NULL);
@@ -10666,7 +11003,14 @@ SaveGameToFile(filename, append)
            DisplayError(buf, errno);
            return FALSE;
        } else {
-           return SaveGame(f, 0, NULL);
+           safeStrCpy(buf, lastMsg, MSG_SIZ);
+           DisplayMessage(_("Waiting for access to save file"), "");
+           flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
+           DisplayMessage(_("Saving game"), "");
+           if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
+           result = SaveGame(f, 0, NULL);
+           DisplayMessage(buf, "");
+           return result;
        }
     }
 }
@@ -11028,7 +11372,13 @@ SavePositionToFile(filename)
            DisplayError(buf, errno);
            return FALSE;
        } else {
+           safeStrCpy(buf, lastMsg, MSG_SIZ);
+           DisplayMessage(_("Waiting for access to save file"), "");
+           flock(fileno(f), LOCK_EX); // [HGM] lock
+           DisplayMessage(_("Saving position"), "");
+           lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
            SavePosition(f, 0, NULL);
+           DisplayMessage(buf, "");
            return TRUE;
        }
     }
@@ -11540,7 +11890,7 @@ void
 EditTagsEvent()
 {
     char *tags = PGNTags(&gameInfo);
-    EditTagsPopUp(tags);
+    EditTagsPopUp(tags, NULL);
     free(tags);
 }
 
@@ -11778,12 +12128,46 @@ DisplayTwoMachinesTitle()
 }
 
 void
+SettingsMenuIfReady()
+{
+  if (second.lastPing != second.lastPong) {
+    DisplayMessage("", _("Waiting for second chess program"));
+    ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
+    return;
+  }
+  ThawUI();
+  DisplayMessage("", "");
+  SettingsPopUp(&second);
+}
+
+int
+WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
+{
+    char buf[MSG_SIZ];
+    if (cps->pr == NULL) {
+       StartChessProgram(cps);
+       if (cps->protocolVersion == 1) {
+         retry();
+       } else {
+         /* kludge: allow timeout for initial "feature" command */
+         FreezeUI();
+         snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
+         DisplayMessage("", buf);
+         ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
+       }
+       return 1;
+    }
+    return 0;
+}
+
+void
 TwoMachinesEvent P((void))
 {
     int i;
     char buf[MSG_SIZ];
     ChessProgramState *onmove;
     char *bookHit = NULL;
+    static int stalling = 0;
 
     if (appData.noChessProgram) return;
 
@@ -11819,21 +12203,24 @@ TwoMachinesEvent P((void))
     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
     ResurrectChessProgram();   /* in case first program isn't running */
 
-    if (second.pr == NULL) {
-       StartChessProgram(&second);
-       if (second.protocolVersion == 1) {
-         TwoMachinesEventIfReady();
-       } else {
-         /* kludge: allow timeout for initial "feature" command */
-         FreezeUI();
-         DisplayMessage("", _("Starting second chess program"));
-         ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
-       }
-       return;
+    if(WaitForEngine(&second, TwoMachinesEventIfReady)) return;
+    if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
+      DisplayMessage("", _("Waiting for first chess program"));
+      ScheduleDelayedEvent(TwoMachinesEvent, 10);
+      return;
+    }
+    if(!stalling) {
+      InitChessProgram(&second, FALSE);
+      SendToProgram("force\n", &second);
+    }
+    if(second.lastPing != second.lastPong) { // [HGM] second engine might have to reallocate hash
+      if(!stalling) DisplayMessage("", _("Waiting for second chess program"));
+      stalling = 1;
+      ScheduleDelayedEvent(TwoMachinesEvent, 10);
+      return;
     }
+    stalling = 0;
     DisplayMessage("", "");
-    InitChessProgram(&second, FALSE);
-    SendToProgram("force\n", &second);
     if (startedFromSetupPosition) {
        SendBoard(&second, backwardMostMove);
     if (appData.debugMode) {
@@ -11992,7 +12379,7 @@ EditGameEvent()
        SendToProgram("force\n", &first);
        break;
       case TwoMachinesPlay:
-       GameEnds((ChessMove) 0, NULL, GE_PLAYER);
+       GameEnds(EndOfFile, NULL, GE_PLAYER);
        ResurrectChessProgram();
        SetUserThinkingEnables();
        break;
@@ -12469,6 +12856,32 @@ CallFlagEvent()
 }
 
 void
+ClockClick(int which)
+{      // [HGM] code moved to back-end from winboard.c
+       if(which) { // black clock
+         if (gameMode == EditPosition || gameMode == IcsExamining) {
+           if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
+           SetBlackToPlayEvent();
+         } else if (gameMode == EditGame || shiftKey) {
+           AdjustClock(which, -1);
+         } else if (gameMode == IcsPlayingWhite ||
+                    gameMode == MachinePlaysBlack) {
+           CallFlagEvent();
+         }
+       } else { // white clock
+         if (gameMode == EditPosition || gameMode == IcsExamining) {
+           if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
+           SetWhiteToPlayEvent();
+         } else if (gameMode == EditGame || shiftKey) {
+           AdjustClock(which, -1);
+         } else if (gameMode == IcsPlayingBlack ||
+                  gameMode == MachinePlaysWhite) {
+           CallFlagEvent();
+         }
+       }
+}
+
+void
 DrawEvent()
 {
     /* Offer draw or accept pending draw offer from opponent */
@@ -13172,7 +13585,13 @@ ReplaceComment(index, text)
      char *text;
 {
     int len;
+    char *p;
+    float score;
 
+    if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
+       pvInfoList[index-1].depth == len &&
+       fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
+       (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
     while (*text == '\n') text++;
     len = strlen(text);
     while (len > 0 && text[len - 1] == '\n') len--;
@@ -13243,27 +13662,27 @@ if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
        while(commentList[index][oldlen-1] ==  '\n')
          commentList[index][--oldlen] = NULLCHAR;
        commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
-       safeStrCpy(commentList[index], old, oldlen);
+       safeStrCpy(commentList[index], old, oldlen + len + 6);
        free(old);
        // [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--; }
+       if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
+         if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
          while (*text == '\n') { text++; len--; }
          commentList[index][--oldlen] = NULLCHAR;
       }
-       if(addBraces) strcat(commentList[index], "\n{\n");
+       if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
        else          strcat(commentList[index], "\n");
        strcat(commentList[index], text);
-       if(addBraces) strcat(commentList[index], "\n}\n");
+       if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
        else          strcat(commentList[index], "\n");
     } else {
        commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
        if(addBraces)
-         safeStrCpy(commentList[index], "{\n", sizeof(commentList[index])/sizeof(commentList[index][0]));
+         safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
        else commentList[index][0] = NULLCHAR;
        strcat(commentList[index], text);
-       strcat(commentList[index], "\n");
-       if(addBraces) strcat(commentList[index], "}\n");
+       strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
+       if(addBraces == TRUE) strcat(commentList[index], "}\n");
     }
 }
 
@@ -13282,7 +13701,7 @@ static char * FindStr( char * text, char * sub_text )
 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
 char *GetInfoFromComment( int index, char * text )
 {
-    char * sep = text;
+    char * sep = text, *p;
 
     if( text != NULL && index > 0 ) {
         int score = 0;
@@ -13320,11 +13739,20 @@ char *GetInfoFromComment( int index, char * text )
                 return text;
             }
 
+            p = text;
+            if(p[1] == '(') { // comment starts with PV
+               p = strchr(p, ')'); // locate end of PV
+               if(p == NULL || sep < p+5) return text;
+               // at this point we have something like "{(.*) +0.23/6 ..."
+               p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
+               *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
+               // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
+            }
             time = -1; sec = -1; deci = -1;
-            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   ) {
+            if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
+               sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
+                sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
+                sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
                 return text;
             }
 
@@ -13340,7 +13768,7 @@ char *GetInfoFromComment( int index, char * text )
             /* [HGM] PV time: now locate end of PV info */
             while( *++sep >= '0' && *sep <= '9'); // strip depth
             if(time >= 0)
-            while( *++sep >= '0' && *sep <= '9'); // strip time
+            while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
             if(sec >= 0)
             while( *++sep >= '0' && *sep <= '9'); // strip seconds
             if(deci >= 0)
@@ -13360,6 +13788,7 @@ char *GetInfoFromComment( int index, char * text )
         pvInfoList[index-1].score = score;
         pvInfoList[index-1].time  = 10*time; // centi-sec
         if(*sep == '}') *sep = 0; else *--sep = '{';
+        if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
     }
     return sep;
 }
@@ -13387,11 +13816,12 @@ SendToProgram(message, cps)
     outCount = OutputToProcess(cps->pr, message, count, &error);
     if (outCount < count && !exiting
                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
-      snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), cps->which);
+      if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
+      snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
-                snprintf(buf, MSG_SIZ, "%s program exits in draw position (%s)", cps->which, cps->program);
+                snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
             } else {
                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
             }
@@ -13417,11 +13847,11 @@ ReceiveFromProgram(isr, closure, message, count, error)
     if (count <= 0) {
        if (count == 0) {
            snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
-                   cps->which, cps->program);
+                   _(cps->which), cps->program);
         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
-                    snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), cps->which, cps->program);
+                    snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
                 } else {
                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
                 }
@@ -13431,7 +13861,7 @@ ReceiveFromProgram(isr, closure, message, count, error)
            if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
        } else {
            snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
-                   cps->which, cps->program);
+                   _(cps->which), cps->program);
            RemoveInputSource(cps->isr);
 
             /* [AS] Program is misbehaving badly... kill it */
@@ -13463,6 +13893,8 @@ ReceiveFromProgram(isr, closure, message, count, error)
                   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 &&
+                  sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
+                  sscanf(message, "hint: %c", &c)!=1 && 
                   sscanf(message, "pong %c", &c)!=1   && start != '#') {
                    quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
                    print = (appData.engineComments >= 2);
@@ -13528,10 +13960,10 @@ SendTimeControl(cps, mps, tc, inc, sd, st)
        /* Note old gnuchess bug -- minutes:seconds used to not work.
           Fixed in later versions, but still avoid :seconds
           when seconds is 0. */
-       snprintf(buf, MSG_SIZ, "level %d %ld %d\n", mps, tc/60000, inc/1000);
+       snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
       } else {
-       snprintf(buf, MSG_SIZ, "level %d %ld:%02d %d\n", mps, tc/60000,
-                seconds, inc/1000);
+       snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
+                seconds, inc/1000.);
       }
     }
     SendToProgram(buf, cps);
@@ -13547,7 +13979,7 @@ SendTimeControl(cps, mps, tc, inc, sd, st)
       SendToProgram(buf, cps);
     }
 
-    if(cps->nps > 0) { /* [HGM] nps */
+    if(cps->nps >= 0) { /* [HGM] nps */
        if(cps->supportsNPS == FALSE)
          cps->nps = -1; // don't use if engine explicitly says not supported!
        else {
@@ -13700,11 +14132,11 @@ ParseOption(Option *opt, ChessProgramState *cps)
        } else if((p = strstr(opt->name, " -file "))) {
            // for now -file is a synonym for -string, to already provide compatibility with future polyglots
            opt->textValue = p+7;
-           opt->type = TextBox; // FileName;
+           opt->type = FileName; // FileName;
        } else if((p = strstr(opt->name, " -path "))) {
            // for now -file is a synonym for -string, to already provide compatibility with future polyglots
            opt->textValue = p+7;
-           opt->type = TextBox; // PathName;
+           opt->type = PathName; // PathName;
        } else if(p = strstr(opt->name, " -check ")) {
            if(sscanf(p, " -check %d", &def) < 1) return FALSE;
            opt->value = (def != 0);
@@ -13738,6 +14170,20 @@ ParseOption(Option *opt, ChessProgramState *cps)
        if(p && (p == cps->optionSettings || p[-1] == ',')) {
          snprintf(buf, MSG_SIZ, "option %s", p);
                if(p = strstr(buf, ",")) *p = 0;
+               if(q = strchr(buf, '=')) switch(opt->type) {
+                   case ComboBox:
+                       for(n=0; n<opt->max; n++)
+                           if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
+                       break;
+                   case TextBox:
+                       safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
+                       break;
+                   case Spin:
+                   case CheckBox:
+                       opt->value = atoi(q+1);
+                   default:
+                       break;
+               }
                strcat(buf, "\n");
                SendToProgram(buf, cps);
        }
@@ -13751,6 +14197,8 @@ FeatureDone(cps, val)
 {
   DelayedEventCallback cb = GetDelayedEvent();
   if ((cb == InitBackEnd3 && cps == &first) ||
+      (cb == SettingsMenuIfReady && cps == &second) ||
+      (cb == LoadEngine) ||
       (cb == TwoMachinesEventIfReady && cps == &second)) {
     CancelDelayedEvent();
     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
@@ -13825,7 +14273,7 @@ ParseFeatures(args, cps)
        }
        if(cps->nrOptions >= MAX_OPTIONS) {
            cps->nrOptions--;
-           snprintf(buf, MSG_SIZ, "%s engine has too many options\n", cps->which);
+           snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
            DisplayError(buf, 0);
        }
        continue;
@@ -13944,6 +14392,55 @@ AskQuestionEvent(title, question, replyPrefix, which)
 }
 
 void
+TypeInEvent(char firstChar)
+{
+    if ((gameMode == BeginningOfGame && !appData.icsActive) || \r
+        gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||\r
+       gameMode == AnalyzeMode || gameMode == EditGame || \r
+       gameMode == EditPosition || gameMode == IcsExamining ||\r
+       gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
+       isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes\r
+               ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||\r
+                 gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||\r
+       gameMode == Training) PopUpMoveDialog(firstChar);
+}
+
+void
+TypeInDoneEvent(char *move)
+{
+       Board board;
+       int n, fromX, fromY, toX, toY;
+       char promoChar;
+       ChessMove moveType;\r
+
+       // [HGM] FENedit\r
+       if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {\r
+               EditPositionPasteFEN(move);\r
+               return;\r
+       }\r
+       // [HGM] movenum: allow move number to be typed in any mode\r
+       if(sscanf(move, "%d", &n) == 1 && n != 0 ) {\r
+         ToNrEvent(2*n-1);\r
+         return;\r
+       }\r
+
+      if (gameMode != EditGame && currentMove != forwardMostMove && \r
+       gameMode != Training) {\r
+       DisplayMoveError(_("Displayed move is not current"));\r
+      } else {\r
+       int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
+         &moveType, &fromX, &fromY, &toX, &toY, &promoChar);\r
+       if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized\r
+       if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
+         &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {\r
+         UserMoveEvent(fromX, fromY, toX, toY, promoChar);     \r
+       } else {\r
+         DisplayMoveError(_("Could not parse move"));\r
+       }
+      }\r
+}\r
+
+void
 DisplayMove(moveNumber)
      int moveNumber;
 {
@@ -14287,7 +14784,7 @@ DecrementClocks()
     if (WhiteOnMove(forwardMostMove)) {
        if(whiteNPS >= 0) lastTickLength = 0;
        timeRemaining = whiteTimeRemaining -= lastTickLength;
-        if(timeRemaining < 0) {
+        if(timeRemaining < 0 && !appData.icsActive) {
             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
@@ -14299,7 +14796,7 @@ DecrementClocks()
     } else {
        if(blackNPS >= 0) lastTickLength = 0;
        timeRemaining = blackTimeRemaining -= lastTickLength;
-        if(timeRemaining < 0) { // [HGM] if we run out of a non-last incremental session, go to the next
+        if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
             if(suddenDeath) {
                 blackStartMove = forwardMostMove;
@@ -14361,15 +14858,15 @@ SwitchClocks(int newMoveNr)
            if(blackNPS >= 0) lastTickLength = 0;
            blackTimeRemaining -= lastTickLength;
            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
-//         if(pvInfoList[forwardMostMove-1].time == -1)
-                 pvInfoList[forwardMostMove-1].time =               // use GUI time
+//         if(pvInfoList[forwardMostMove].time == -1)
+                 pvInfoList[forwardMostMove].time =               // use GUI time
                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
        } else {
           if(whiteNPS >= 0) lastTickLength = 0;
           whiteTimeRemaining -= lastTickLength;
            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
-//         if(pvInfoList[forwardMostMove-1].time == -1)
-                 pvInfoList[forwardMostMove-1].time =
+//         if(pvInfoList[forwardMostMove].time == -1)
+                 pvInfoList[forwardMostMove].time =
                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
        }
        flagged = CheckFlags();
@@ -14864,12 +15361,12 @@ ParseFEN(board, blackPlaysFirst, fen)
     while(*p==' ') p++;
     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
         if(*p == '[') p++;
-        if(*p == '-' ) *p++; /* empty holdings */ else {
+        if(*p == '-' ) p++; /* empty holdings */ else {
             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
             /* if we would allow FEN reading to set board size, we would   */
             /* have to add holdings and shift the board read so far here   */
             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
-                *p++;
+                p++;
                 if((int) piece >= (int) BlackPawn ) {
                     i = (int)piece - (int)BlackPawn;
                    i = PieceToNumber((ChessSquare)i);
@@ -14885,7 +15382,7 @@ ParseFEN(board, blackPlaysFirst, fen)
                 }
             }
         }
-        if(*p == ']') *p++;
+        if(*p == ']') p++;
     }
 
     while(*p == ' ') p++;
@@ -15020,7 +15517,7 @@ ParseFEN(board, blackPlaysFirst, fen)
          char c = *p++ - AAA;
 
          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
-         if(*p >= '0' && *p <='9') *p++;
+         if(*p >= '0' && *p <='9') p++;
          board[EP_STATUS] = c;
       }
     }
@@ -15219,11 +15716,11 @@ PopTail(Boolean annotate)
        if(annotate) {
                int cnt = 10;
                if(!WhiteOnMove(currentMove))
-                 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", currentMove+2>>1);
+                 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
                else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
                for(i=currentMove; i<forwardMostMove; i++) {
                        if(WhiteOnMove(i))
-                         snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", i+2>>1, SavePart(parseList[i]));
+                         snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
                        else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
                        strcat(buf, moveBuf);
                        if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }