Implement LionChess
[xboard.git] / backend.c
index 849259e..001f970 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, 2011, 2012 Free Software Foundation, Inc.
+ * 2007, 2008, 2009, 2010, 2011, 2012, 2013 Free Software Foundation, Inc.
  *
  * Enhancements Copyright 2005 Alessandro Scotti
  *
@@ -224,6 +224,8 @@ FILE *WriteTourneyFile P((char *results, FILE *f));
 void DisplayTwoMachinesTitle P(());
 static void ExcludeClick P((int index));
 void ToggleSecond P((void));
+void PauseEngine P((ChessProgramState *cps));
+static int NonStandardBoardSize P((void));
 
 #ifdef WIN32
        extern void ConsoleCreate();
@@ -267,6 +269,7 @@ 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 legal[BOARD_RANKS][BOARD_FILES];  /* [HGM] legal target squares */
 char lastMsg[MSG_SIZ];
 ChessSquare pieceSweep = EmptySquare;
 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
@@ -327,7 +330,7 @@ safeStrCpy (char *dst, const char *src, size_t count)
     {
       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
       if(appData.debugMode)
-      fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
+       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
     }
 
   return dst;
@@ -386,6 +389,7 @@ PosFlags (index)
   case VariantShatranj:
   case VariantCourier:
   case VariantMakruk:
+  case VariantASEAN:
   case VariantGrand:
     flags &= ~F_ALL_CASTLE_OK;
     break;
@@ -463,7 +467,7 @@ long timeControl_2; /* [AS] Allow separate time controls */
 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
 long timeRemaining[2][MAX_MOVES];
 int matchGame = 0, nextGame = 0, roundNr = 0;
-Boolean waitingForGame = FALSE;
+Boolean waitingForGame = FALSE, startingEngine = FALSE;
 TimeMark programStartTime, pauseStart;
 char ics_handle[MSG_SIZ];
 int have_set_title = 0;
@@ -555,6 +559,20 @@ ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatran
         BlackKing, BlackMan, BlackKnight, BlackRook }
 };
 
+ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
+    { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
+        WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
+    { BlackRook, BlackKnight, BlackMan, BlackFerz,
+        BlackKing, BlackMan, BlackKnight, BlackRook }
+};
+
+ChessSquare  lionArray[2][BOARD_FILES] = {
+    { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
+       WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
+    { BlackRook, BlackLion, BlackBishop, BlackQueen,
+       BlackKing, BlackBishop, BlackKnight, BlackRook }
+};
+
 
 #if (BOARD_FILES>=10)
 ChessSquare ShogiArray[2][BOARD_FILES] = {
@@ -635,8 +653,23 @@ ChessSquare CourierArray[2][BOARD_FILES] = {
     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
 };
+ChessSquare ChuArray[6][BOARD_FILES] = {
+    { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
+      WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
+    { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
+      BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
+    { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
+      WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
+    { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
+      BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
+    { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
+      WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
+    { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
+      BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
+};
 #else // !(BOARD_FILES>=12)
 #define CourierArray CapablancaArray
+#define ChuArray CapablancaArray
 #endif // !(BOARD_FILES>=12)
 
 
@@ -779,12 +812,14 @@ InitEngine (ChessProgramState *cps, int n)
     cps->sendName = appData.icsActive;
     cps->sdKludge = FALSE;
     cps->stKludge = FALSE;
+    if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
     TidyProgramName(cps->program, cps->host, cps->tidy);
     cps->matchWins = 0;
-    safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
+    ASSIGN(cps->variants, appData.variant);
     cps->analysisSupport = 2; /* detect */
     cps->analyzing = FALSE;
     cps->initDone = FALSE;
+    cps->reload = FALSE;
 
     /* New features added by Tord: */
     cps->useFEN960 = FALSE;
@@ -805,7 +840,7 @@ InitEngine (ChessProgramState *cps, int n)
     cps->supportsNPS = UNKNOWN;
     cps->memSize = FALSE;
     cps->maxCores = FALSE;
-    cps->egtFormats[0] = NULLCHAR;
+    ASSIGN(cps->egtFormats, "");
 
     /* [HGM] options */
     cps->optionSettings  = appData.engOptions[n];
@@ -813,6 +848,7 @@ InitEngine (ChessProgramState *cps, int n)
     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
     cps->isUCI = appData.isUCI[n]; /* [AS] */
     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
+    cps->highlight = 0;
 
     if (appData.protocolVersion[n] > PROTOVER
        || appData.protocolVersion[n] < 1)
@@ -838,6 +874,8 @@ InitEngine (ChessProgramState *cps, int n)
 
 ChessProgramState *savCps;
 
+GameMode oldMode;
+
 void
 LoadEngine ()
 {
@@ -847,21 +885,25 @@ LoadEngine ()
     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
+       oldMode = BeginningOfGame; // to prevent restoring old mode
     }
     InitChessProgram(savCps, FALSE);
-    SendToProgram("force\n", savCps);
+    if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
     DisplayMessage("", "");
     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
     ThawUI();
     SetGNUMode();
+    if(oldMode == AnalyzeMode) AnalyzeModeEvent();
 }
 
 void
 ReplaceEngine (ChessProgramState *cps, int n)
 {
-    EditGameEvent();
+    oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
+    keepInfo = 1;
+    if(oldMode != BeginningOfGame) EditGameEvent();
+    keepInfo = 0;
     UnloadEngine(cps);
     appData.noChessProgram = FALSE;
     appData.clockMode = TRUE;
@@ -875,7 +917,7 @@ ReplaceEngine (ChessProgramState *cps, int n)
 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
 
-static char resetOptions[] = 
+static char resetOptions[] =
        "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
        "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
        "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
@@ -905,7 +947,7 @@ char *insert, *wbOptions; // point in ChessProgramNames were we should insert ne
 void
 Load (ChessProgramState *cps, int i)
 {
-    char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
+    char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
        snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
        SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
@@ -929,11 +971,13 @@ Load (ChessProgramState *cps, int i)
        p[-1] = SLASH;
        if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
     } else { ASSIGN(appData.directory[i], "."); }
+    jar = (strstr(p, ".jar") == p + strlen(p) - 4);
     if(params[0]) {
        if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
        snprintf(command, MSG_SIZ, "%s %s", p, params);
        p = command;
     }
+    if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
     ASSIGN(appData.chessProgram[i], p);
     appData.isUCI[i] = isUCI;
     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
@@ -947,7 +991,7 @@ Load (ChessProgramState *cps, int i)
        if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
        quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
        snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
-                       quote, p, quote, appData.directory[i], 
+                       quote, p, quote, appData.directory[i],
                        useNick ? " -fn \"" : "",
                        useNick ? nickName : "",
                        useNick ? "\"" : "",
@@ -1128,6 +1172,7 @@ InitBackEnd1 ()
       case VariantCapablanca: /* [HGM] should work */
       case VariantCourier:    /* [HGM] initial forced moves not implemented */
       case VariantShogi:      /* [HGM] could still mate with pawn drop */
+      case VariantChu:        /* [HGM] experimental */
       case VariantKnightmate: /* [HGM] should work */
       case VariantCylinder:   /* [HGM] untested */
       case VariantFalcon:     /* [HGM] untested */
@@ -1148,6 +1193,7 @@ InitBackEnd1 ()
       case Variant3Check:     /* should work except for win condition */
       case VariantShatranj:   /* should work except for all win conditions */
       case VariantMakruk:     /* should work except for draw countdown */
+      case VariantASEAN :     /* should work except for draw countdown */
       case VariantBerolina:   /* might work if TestLegality is off */
       case VariantCapaRandom: /* should work */
       case VariantJanus:      /* should work */
@@ -1156,6 +1202,7 @@ InitBackEnd1 ()
       case VariantSChess:     /* S-Chess, should work */
       case VariantGrand:      /* should work */
       case VariantSpartan:    /* should work */
+      case VariantLion:       /* should work */
        break;
       }
     }
@@ -1284,16 +1331,16 @@ ParseTimeControl (char *tc, float ti, int mps)
 
     if(mps)
       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
-    else 
+    else
       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
   } else {
     if(mps)
       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
-    else 
+    else
       snprintf(buf, MSG_SIZ, ":%s", mytc);
   }
   fullTimeControlString = StrSave(buf); // this should now be in PGN format
-  
+
   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
     return FALSE;
   }
@@ -1336,7 +1383,11 @@ void
 InitBackEnd2 ()
 {
     if (appData.debugMode) {
-       fprintf(debugFP, "%s\n", programVersion);
+#    ifdef __GIT_VERSION
+      fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
+#    else
+      fprintf(debugFP, "Version: %s\n", programVersion);
+#    endif
     }
     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
 
@@ -1471,7 +1522,7 @@ MatchEvent (int mode)
                        NextTourneyGame(-1, &dummy);
                        ReserveGame(-1, 0);
                        if(nextGame <= appData.matchGames) {
-                           DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
+                           DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
                            matchMode = mode;
                            ScheduleDelayedEvent(NextMatchGame, 10000);
                            return;
@@ -1981,10 +2032,12 @@ StripHighlight (char *s)
     return retbuf;
 }
 
+char engineVariant[MSG_SIZ];
 char *variantNames[] = VARIANT_NAMES;
 char *
 VariantName (VariantClass v)
 {
+    if(v == VariantUnknown || *engineVariant) return engineVariant;
     return variantNames[v];
 }
 
@@ -2186,7 +2239,7 @@ StringToVariant (char *e)
       }
     }
     if (appData.debugMode) {
-      fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
+      fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
              e, wnum, VariantName(v));
     }
     return v;
@@ -3000,8 +3053,10 @@ read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int
                        } else {
                            char tmp[MSG_SIZ];
                            if(gameMode == IcsObserving) // restore original ICS messages
+                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
                              snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
                            else
+                           /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
                            snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
                            SendToPlayer(tmp, strlen(tmp));
                        }
@@ -3545,7 +3600,7 @@ read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int
                    gameInfo.whiteRating = string_to_rating(star_match[1]);
                    gameInfo.blackRating = string_to_rating(star_match[3]);
                    if (appData.debugMode)
-                     fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
+                     fprintf(debugFP, "Ratings from header: W %d, B %d\n",
                              gameInfo.whiteRating, gameInfo.blackRating);
                }
                continue;
@@ -4178,9 +4233,13 @@ read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int
 void
 ParseBoard12 (char *string)
 {
+#if ZIPPY
+    int i, takeback;
+    char *bookHit = NULL; // [HGM] book
+#endif
     GameMode newGameMode;
-    int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
-    int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
+    int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
+    int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
     char to_play, board_chars[200];
     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
@@ -4192,7 +4251,6 @@ ParseBoard12 (char *string)
     int fromX, fromY, toX, toY;
     char promoChar;
     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
-    char *bookHit = NULL; // [HGM] book
     Boolean weird = FALSE, reqFlag = FALSE;
 
     fromX = fromY = toX = toY = -1;
@@ -4200,7 +4258,7 @@ ParseBoard12 (char *string)
     newGame = FALSE;
 
     if (appData.debugMode)
-      fprintf(debugFP, _("Parsing board: %s\n"), string);
+      fprintf(debugFP, "Parsing board: %s\n", string);
 
     move_str[0] = NULLCHAR;
     elapsed_time[0] = NULLCHAR;
@@ -4309,7 +4367,7 @@ ParseBoard12 (char *string)
                      partnerUp = 0; flipView = !flipView; } // [HGM] dual
       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
                 (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
-      DisplayMessage(partnerStatus, "");
+      if(!twoBoards) DisplayMessage(partnerStatus, "");
        partnerBoardValid = TRUE;
       return;
     }
@@ -4669,11 +4727,10 @@ ParseBoard12 (char *string)
        to canonical algebraic form. */
     if (moveNum > 0) {
   if (appData.debugMode) {
-    if (appData.debugMode) { int f = forwardMostMove;
-        fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
-                boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
-                boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
-    }
+    int f = forwardMostMove;
+    fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
+           boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
+           boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
     fprintf(debugFP, "moveNum = %d\n", moveNum);
     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
@@ -5006,6 +5063,11 @@ SendMoveToProgram (int moveNum, ChessProgramState *cps)
          if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
          snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
                                              moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
+       } else if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
+         snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", moveList[moveNum][0], moveList[moveNum][1] - '0', // convert to two moves
+                                              moveList[moveNum][5], moveList[moveNum][6] - '0',
+                                              moveList[moveNum][5], moveList[moveNum][6] - '0',
+                                              moveList[moveNum][2], moveList[moveNum][3] - '0');
        } else
          snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
@@ -5074,7 +5136,8 @@ SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char
         break;
       case WhitePromotion:
       case BlackPromotion:
-        if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
+        if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
+           gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
          snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
                PieceToChar(WhiteFerz));
@@ -5138,7 +5201,7 @@ UploadGameEvent ()
        SendToICS(ics_prefix);
        SendToICS(buf);
        if(startedFromSetupPosition || backwardMostMove != 0) {
-         fen = PositionToFEN(backwardMostMove, NULL);
+         fen = PositionToFEN(backwardMostMove, NULL, 1);
          if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
            snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
            SendToICS(buf);
@@ -5180,6 +5243,8 @@ UploadGameEvent ()
     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
 }
 
+int killX = -1, killY = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
+
 void
 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
 {
@@ -5191,6 +5256,7 @@ CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char
        if (promoChar == 'x' || promoChar == NULLCHAR) {
          sprintf(move, "%c%c%c%c\n",
                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
+         if(killX >= 0 && killY >= 0) sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
        } else {
            sprintf(move, "%c%c%c%c%c\n",
                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
@@ -5211,7 +5277,8 @@ ProcessICSInitScript (FILE *f)
 }
 
 
-static int lastX, lastY, selectFlag, dragging;
+static int lastX, lastY, lastLeftX, lastLeftY, selectFlag, dragging;
+static ClickType lastClickType;
 
 void
 Sweep (int step)
@@ -5230,8 +5297,8 @@ Sweep (int step)
        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));
+           appData.testLegality && (promoSweep == king || promoSweep == WhiteLion || promoSweep == BlackLion) ||
+           IS_SHOGI(gameInfo.variant) && promoSweep != CHUPROMOTED last && last != CHUPROMOTED promoSweep && last != promoSweep);
     if(toX >= 0) {
        int victim = boards[currentMove][toY][toX];
        boards[currentMove][toY][toX] = promoSweep;
@@ -5415,7 +5482,8 @@ ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
   Boolean valid;
   int nr = 0;
 
-  if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
+  lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
+  if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
     pushed = TRUE;
   }
@@ -5480,6 +5548,8 @@ MultiPV (ChessProgramState *cps)
        return -1;
 }
 
+Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
+
 Boolean
 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
 {
@@ -5511,6 +5581,7 @@ LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
        }
        ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
        *start = startPV; *end = index-1;
+       extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
        return TRUE;
 }
 
@@ -5520,7 +5591,7 @@ PvToSAN (char *pv)
        static char buf[10*MSG_SIZ];
        int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
        *buf = NULLCHAR;
-       if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
+       if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
        ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
        for(i = forwardMostMove; i<endPV; i++){
            if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
@@ -5528,6 +5599,7 @@ PvToSAN (char *pv)
            k += strlen(buf+k);
        }
        snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
+       if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
        if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
        endPV = savedEnd;
        return buf;
@@ -5539,6 +5611,7 @@ LoadPV (int x, int y)
   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
   lastX = x; lastY = y;
   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
+  extendGame = FALSE;
   return TRUE;
 }
 
@@ -5549,7 +5622,7 @@ UnLoadPV ()
   if(endPV < 0) return;
   if(appData.autoCopyPV) CopyFENToClipboard();
   endPV = -1;
-  if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
+  if(extendGame && currentMove > forwardMostMove) {
        Boolean saveAnimate = appData.animate;
        if(pushed) {
            if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
@@ -5820,7 +5893,7 @@ void
 InitPosition (int redraw)
 {
     ChessSquare (* pieces)[BOARD_FILES];
-    int i, j, pawnRow, overrule,
+    int i, j, pawnRow=1, pieceRows=1, overrule,
     oldx = gameInfo.boardWidth,
     oldy = gameInfo.boardHeight,
     oldh = gameInfo.holdingsWidth;
@@ -5877,9 +5950,13 @@ InitPosition (int redraw)
     case VariantMakruk:
       pieces = makrukArray;
       nrCastlingRights = 0;
-      startedFromSetupPosition = TRUE;
       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
       break;
+    case VariantASEAN:
+      pieces = aseanArray;
+      nrCastlingRights = 0;
+      SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
+      break;
     case VariantTwoKings:
       pieces = twoKingsArray;
       break;
@@ -5940,6 +6017,14 @@ InitPosition (int redraw)
       nrCastlingRights = 0;
       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
       break;
+    case VariantChu:
+      pieces = ChuArray; pieceRows = 3;
+      gameInfo.boardWidth  = 12;
+      gameInfo.boardHeight = 12;
+      nrCastlingRights = 0;
+      SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
+                                "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
+      break;
     case VariantCourier:
       pieces = CourierArray;
       gameInfo.boardWidth  = 12;
@@ -5954,6 +6039,10 @@ InitPosition (int redraw)
       pieces = SpartanArray;
       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
       break;
+    case VariantLion:
+      pieces = lionArray;
+      SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
+      break;
     case VariantFairy:
       pieces = fairyArray;
       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
@@ -6008,7 +6097,8 @@ InitPosition (int redraw)
 
     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
     if(pawnRow < 1) pawnRow = 1;
-    if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
+    if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN || gameInfo.variant == VariantGrand) pawnRow = 2;
+    if(gameInfo.variant == VariantChu) pawnRow = 3;
 
     /* User pieceToChar list overrules defaults */
     if(appData.pieceToCharTable != NULL)
@@ -6035,6 +6125,15 @@ InitPosition (int redraw)
                 }
             }
         }
+        if(gameInfo.variant == VariantChu) {
+             if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
+               initialPosition[pawnRow+1][j] = WhiteCobra,
+               initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
+             for(i=1; i<pieceRows; i++) {
+               initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
+               initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
+             }
+        }
         if(gameInfo.variant == VariantGrand) {
             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
                initialPosition[0][j] = WhiteRook;
@@ -6079,7 +6178,7 @@ InitPosition (int redraw)
       initialPosition[2][0] = BlackAngel;
       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
-      initialPosition[1][1] = initialPosition[2][1] = 
+      initialPosition[1][1] = initialPosition[2][1] =
       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
      }
   if (appData.debugMode) {
@@ -6117,7 +6216,7 @@ SendBoard (ChessProgramState *cps, int moveNum)
     char message[MSG_SIZ];
 
     if (cps->useSetboard) {
-      char* fen = PositionToFEN(moveNum, cps->fenOverride);
+      char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
       SendToProgram(message, cps);
       free(fen);
@@ -6300,7 +6399,8 @@ ChessSquare
 DefaultPromoChoice (int white)
 {
     ChessSquare result;
-    if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
+    if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
+       gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
        result = WhiteFerz; // no choice
     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
        result= WhiteKing; // in Suicide Q is the last thing we want
@@ -6331,9 +6431,13 @@ HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, i
        return FALSE;
 
     piece = boards[currentMove][fromY][fromX];
-    if(gameInfo.variant == VariantShogi) {
+    if(gameInfo.variant == VariantChu) {
+        int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
+        promotionZoneSize = BOARD_HEIGHT/3;
+        highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteKing;
+    } else if(gameInfo.variant == VariantShogi) {
         promotionZoneSize = BOARD_HEIGHT/3;
-        highestPromotingPiece = (int)WhiteFerz;
+        highestPromotingPiece = (int)WhiteAlfil;
     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
         promotionZoneSize = 3;
     }
@@ -6385,7 +6489,8 @@ HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, i
     }
 
     // we either have a choice what to promote to, or (in Shogi) whether to promote
-    if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
+    if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
+       gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
        *promoChoice = PieceToChar(BlackFerz);  // no choice
        return FALSE;
     }
@@ -6396,7 +6501,7 @@ HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, i
     }
     // give caller the default choice even if we will not make it
     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
-    if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
+    if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
     if(        sweepSelect && gameInfo.variant != VariantGreat
                           && gameInfo.variant != VariantGrand
                           && gameInfo.variant != VariantSuper) return FALSE;
@@ -6407,7 +6512,7 @@ HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, i
              gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
     if(appData.testLegality && !premove) {
        moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
-                       fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
+                       fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) ? '+' : NULLCHAR);
        if(moveType != WhitePromotion && moveType  != BlackPromotion)
            return FALSE;
     }
@@ -6534,7 +6639,7 @@ OKToStartUserMove (int x, int y)
 }
 
 Boolean
-OnlyMove (int *x, int *y, Boolean captures) 
+OnlyMove (int *x, int *y, Boolean captures)
 {
     DisambiguateClosure cl;
     if (appData.zippyPlay || !appData.testLegality) return FALSE;
@@ -6747,12 +6852,12 @@ UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
     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", 
+          if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
                moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
           // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
           if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
           fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
-          while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
+          while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
          fromY = DROP_RANK;
     }
 
@@ -6976,27 +7081,61 @@ FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int prom
 }
 
 void
+MarkByFEN(char *fen)
+{
+       int r, f;
+       if(!appData.markers || !appData.highlightDragging) return;
+       for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
+       r=BOARD_HEIGHT-1; f=BOARD_LEFT;
+       while(*fen) {
+           int s = 0;
+           marker[r][f] = 0;
+           if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
+           if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
+           if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
+           if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
+           if(*fen == 'T') marker[r][f++] = 0; else
+           if(*fen == 'Y') marker[r][f++] = 1; else
+           if(*fen == 'G') marker[r][f++] = 3; else
+           if(*fen == 'B') marker[r][f++] = 4; else
+           if(*fen == 'C') marker[r][f++] = 5; else
+           if(*fen == 'M') marker[r][f++] = 6; else
+           if(*fen == 'W') marker[r][f++] = 7; else
+           if(*fen == 'D') marker[r][f++] = 8; else
+           if(*fen == 'R') marker[r][f++] = 2; else {
+               while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
+             f += s; fen -= s>0;
+           }
+           while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
+           if(r < 0) break;
+           fen++;
+       }
+       DrawPosition(TRUE, NULL);
+}
+
+void
 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
 {
     typedef char Markers[BOARD_RANKS][BOARD_FILES];
     Markers *m = (Markers *) closure;
-    if(rf == fromY && ff == fromX)
+    if(rf == fromY && ff == fromX && (killX < 0 && !(rt == rf && ft == ff) || abs(ft-killX) < 2 && abs(rt-killY) < 2))
        (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
                         || kind == WhiteCapturesEnPassant
-                        || kind == BlackCapturesEnPassant);
+                        || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0);
     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
 }
 
 void
 MarkTargetSquares (int clear)
 {
-  int x, y;
-  if(clear) // no reason to ever suppress clearing
-    for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
-  if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
-     !appData.testLegality || gameMode == EditPosition) return;
-  if(!clear) {
+  int x, y, sum=0;
+  if(clear) { // no reason to ever suppress clearing
+    for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
+    if(!sum) return; // nothing was cleared,no redraw needed
+  } else {
     int capt = 0;
+    if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
+       !appData.testLegality || gameMode == EditPosition) return;
     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
@@ -7028,10 +7167,10 @@ 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 ||
+       if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
           gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
           gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
-                                                 gameInfo.variant == VariantMakruk) return FALSE;
+         gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
        return (piece == BlackPawn && y == 1 ||
                piece == WhitePawn && y == BOARD_HEIGHT-2 ||
                piece == BlackLance && y == 1 ||
@@ -7039,6 +7178,45 @@ CanPromote (ChessSquare piece, int y)
 }
 
 void
+HoverEvent (int xPix, int yPix, int x, int y)
+{
+       static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
+       static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
+       int r, f;
+       if(dragging == 2) DragPieceMove(xPix, yPix); // [HGM] lion: drag without button for second leg
+       if(!first.highlight) return;
+       if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
+       if(x == oldX && y == oldY) return; // only do something if we enter new square
+       oldFromX = fromX; oldFromY = fromY;
+       if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) // record markings after from-change
+         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
+           baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
+       else if(oldX != x || oldY != y) {
+         // [HGM] lift: entered new to-square; redraw arrow, and inform engine
+         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
+           marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
+         if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
+           char buf[MSG_SIZ];
+           snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
+           SendToProgram(buf, &first);
+         }
+         oldX = x; oldY = y;
+//       SetHighlights(fromX, fromY, x, y);
+       }
+}
+
+void ReportClick(char *action, int x, int y)
+{
+       char buf[MSG_SIZ]; // Inform engine of what user does
+       int r, f;
+       if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
+         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
+       if(!first.highlight || gameMode == EditPosition) return;
+       snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
+       SendToProgram(buf, &first);
+}
+
+void
 LeftClick (ClickType clickType, int xPix, int yPix)
 {
     int x, y;
@@ -7053,6 +7231,7 @@ LeftClick (ClickType clickType, int xPix, int yPix)
     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
 
     if (clickType == Press) ErrorPopDown();
+    lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
 
     x = EventToSquare(xPix, BOARD_WIDTH);
     y = EventToSquare(yPix, BOARD_HEIGHT);
@@ -7127,7 +7306,7 @@ LeftClick (ClickType clickType, int xPix, int yPix)
        return;
       }
       doubleClick = FALSE;
-      if(gameMode == AnalyzeMode && pausing && first.excludeMoves) { // use pause state to exclude moves
+      if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
        doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
       }
       fromX = x; fromY = y; toX = toY = -1;
@@ -7137,6 +7316,7 @@ LeftClick (ClickType clickType, int xPix, int yPix)
            /* First square */
            if (OKToStartUserMove(fromX, fromY)) {
                second = 0;
+               ReportClick("lift", x, y);
                MarkTargetSquares(0);
                if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
                DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
@@ -7157,7 +7337,7 @@ LeftClick (ClickType clickType, int xPix, int yPix)
     }
 
     /* fromX != -1 */
-    if (clickType == Press && gameMode != EditPosition) {
+    if (clickType == Press && gameMode != EditPosition && killX < 0) {
        ChessSquare fromP;
        ChessSquare toP;
        int frc;
@@ -7185,7 +7365,7 @@ LeftClick (ClickType clickType, int xPix, int yPix)
            }
            promoDefaultAltered = FALSE;
            MarkTargetSquares(1);
-          if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
+          if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
            if (appData.highlightDragging) {
                SetHighlights(x, y, -1, -1);
            } else {
@@ -7199,6 +7379,7 @@ LeftClick (ClickType clickType, int xPix, int yPix)
                else gatingPiece = doubleClick ? fromP : EmptySquare;
                fromX = x;
                fromY = y; dragging = 1;
+               ReportClick("lift", x, y);
                MarkTargetSquares(0);
                DragPieceBegin(xPix, yPix, FALSE);
                if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
@@ -7209,13 +7390,13 @@ LeftClick (ClickType clickType, int xPix, int yPix)
            }
           }
           if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
-          second = FALSE; 
+          second = FALSE;
        }
        // ignore clicks on holdings
        if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
     }
 
-    if (clickType == Release && x == fromX && y == fromY) {
+    if (clickType == Release && x == fromX && y == fromY && killX < 0) {
        DragPieceEnd(xPix, yPix); dragging = 0;
        if(clearFlag) {
            // a deferred attempt to click-click move an empty square on top of a piece
@@ -7235,6 +7416,7 @@ LeftClick (ClickType clickType, int xPix, int yPix)
            second = sweepSelecting = 0;
            fromX = fromY = -1;
            gatingPiece = EmptySquare;
+           MarkTargetSquares(1);
            ClearHighlights();
            gotPremove = 0;
            ClearPremoveHighlights();
@@ -7247,12 +7429,19 @@ LeftClick (ClickType clickType, int xPix, int yPix)
 
     clearFlag = 0;
 
+    if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] && (x != killX || y != killY) && !sweepSelecting) {
+       if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
+       DisplayMessage(_("only marked squares are legal"),"");
+       DrawPosition(TRUE, NULL);
+       return; // ignore to-click
+    }
+
     /* we now have a different from- and (possibly off-board) to-square */
     /* Completed move */
     if(!sweepSelecting) {
        toX = x;
        toY = y;
-    } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
+    }
 
     saveAnimate = appData.animate;
     if (clickType == Press) {
@@ -7262,11 +7451,19 @@ LeftClick (ClickType clickType, int xPix, int yPix)
            if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
            return;
        }
-       if(HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
+       if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
+           dragging = 1;
+           return;
+       }
+       if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
+           killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
+       } else
+       if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
+       if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
          if(appData.sweepSelect) {
            ChessSquare piece = boards[currentMove][fromY][fromX];
            promoSweep = defaultPromoChoice;
-           if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
+           if(PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
            selectFlag = 0; lastX = xPix; lastY = yPix;
            Sweep(0); // Pawn that is going to promote: preview promotion piece
            sweepSelecting = 1;
@@ -7281,6 +7478,13 @@ LeftClick (ClickType clickType, int xPix, int yPix)
        } else {
            ClearHighlights();
        }
+    } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
+       sweepSelecting = 0;
+       if (appData.animate || appData.highlightLastMove) {
+           SetHighlights(fromX, fromY, toX, toY);
+       } else {
+           ClearHighlights();
+       }
     } else {
 #if 0
 // [HGM] this must be done after the move is made, as with arrow it could lead to a board redraw with piece still on from square
@@ -7291,6 +7495,17 @@ LeftClick (ClickType clickType, int xPix, int yPix)
            ClearHighlights();
        }
 #endif
+       if(!dragging || marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
+         dragging *= 2;            // flag button-less dragging if we are dragging
+         MarkTargetSquares(1);
+         if(x == killX && y == killY) killX = killY = -1; else {
+           killX = x; killY = y;     //remeber this square as intermediate
+           MarkTargetSquares(0);
+           ReportClick("put", x, y); // and inform engine
+           ReportClick("lift", x, y);
+           return;
+         }
+       }
        DragPieceEnd(xPix, yPix); dragging = 0;
        /* Don't animate move and drag both */
        appData.animate = FALSE;
@@ -7326,6 +7541,7 @@ LeftClick (ClickType clickType, int xPix, int yPix)
 
     // off-board moves should not be highlighted
     if(x < 0 || y < 0) ClearHighlights();
+    else ReportClick("put", x, y);
 
     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
 
@@ -7611,6 +7827,9 @@ MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisCo
                        || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
                // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
 
+       } else if(v == VariantKnightmate) {
+               if(nMine == 1) return FALSE;
+               if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
        } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
                int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
 
@@ -7663,7 +7882,7 @@ Adjudicate (ChessProgramState *cps)
        //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
        //       Actually ending the game is now based on the additional internal condition canAdjudicate.
        //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
-       int k, count = 0; static int bare = 1;
+       int k, drop, count = 0; static int bare = 1;
        ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
        Boolean canAdjudicate = !appData.icsActive;
 
@@ -7747,13 +7966,21 @@ Adjudicate (ChessProgramState *cps)
                        boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
                                                   ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
                                                                        EP_CHECKMATE : EP_WINS);
-                   else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
+                   else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
                        boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
                }
                break;
              case MT_CHECKMATE:
                reason = "Xboard adjudication: Checkmate";
                boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
+               if(gameInfo.variant == VariantShogi) {
+                   if(forwardMostMove > backwardMostMove
+                      && moveList[forwardMostMove-1][1] == '@'
+                      && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
+                       reason = "XBoard adjudication: pawn-drop mate";
+                       boards[forwardMostMove][EP_STATUS] = EP_WINS;
+                   }
+               }
                break;
            }
 
@@ -7818,10 +8045,12 @@ Adjudicate (ChessProgramState *cps)
 
                 /* Check for rep-draws */
                 count = 0;
+                drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
+                                              && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
                 for(k = forwardMostMove-2;
-                    k>=backwardMostMove && k>=forwardMostMove-100 &&
+                    k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
-                        (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
+                        (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
                     k-=2)
                 {   int rights=0;
                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
@@ -7847,7 +8076,7 @@ Adjudicate (ChessProgramState *cps)
                              /* adjudicate after user-specified nr of repeats */
                             int result = GameIsDrawn;
                             char *details = "XBoard adjudication: repetition draw";
-                            if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
+                            if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
                                // [HGM] xiangqi: check for forbidden perpetuals
                                int m, ourPerpetual = 1, hisPerpetual = 1;
                                for(m=forwardMostMove; m>k; m-=2) {
@@ -7865,6 +8094,12 @@ Adjudicate (ChessProgramState *cps)
                                if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
                                    break; // (or we would have caught him before). Abort repetition-checking loop.
                                } else
+                               if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
+                                   if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
+                                       result = BlackWins;
+                                       details = "Xboard adjudication: repetition";
+                                   }
+                               } else // it must be XQ
                                // Now check for perpetual chases
                                if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
                                    hisPerpetual = PerpetualChase(k, forwardMostMove);
@@ -7972,6 +8207,7 @@ SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
        SendToProgram("force\n", cps);
        cps->bookSuspend = TRUE; // flag indicating it has to be restarted
     }
+    if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
     // now arrange restart after book miss
     if(bookHit) {
@@ -8013,7 +8249,7 @@ LoadError (char *errmess, ChessProgramState *cps)
     if(cps->initDone) return FALSE;
     cps->isr = NULL; // this should suppress further error popups from breaking pipes
     DestroyChildProcess(cps->pr, 9 ); // just to be sure
-    cps->pr = NoProc; 
+    cps->pr = NoProc;
     if(cps == &first) {
        appData.noChessProgram = TRUE;
        gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
@@ -8038,10 +8274,13 @@ DeferredBookMove (void)
 }
 
 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
+static ChessProgramState *stalledEngine;
+static char stashedInputMove[MSG_SIZ];
 
 void
 HandleMachineMove (char *message, ChessProgramState *cps)
 {
+    static char firstLeg[20];
     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
     char realname[MSG_SIZ];
     int fromX, fromY, toX, toY;
@@ -8103,6 +8342,22 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
        (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
     {
+        if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
+           if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
+           safeStrCpy(stashedInputMove, message, MSG_SIZ);
+           stalledEngine = cps;
+           if(appData.ponderNextMove) { // bring opponent out of ponder
+               if(gameMode == TwoMachinesPlay) {
+                   if(cps->other->pause)
+                       PauseEngine(cps->other);
+                   else
+                       SendToProgram("easy\n", cps->other);
+               }
+           }
+           StopClocks();
+           return;
+       }
+
         /* This method is only useful on engines that support ping */
         if (cps->lastPing != cps->lastPong) {
          if (gameMode == BeginningOfGame) {
@@ -8168,12 +8423,30 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
        }
 
         if(cps->alphaRank) AlphaRank(machineMove, 4);
+
+       // [HGM] lion: (some very limited) support for Alien protocol
+       killX = killY = -1;
+       if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
+           safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
+           return;
+       } else if(firstLeg[0]) { // there was a previous leg;
+           // only support case where same piece makes two step (and don't even test that!)
+           char buf[20], *p = machineMove+1, *q = buf+1, f;
+           safeStrCpy(buf, machineMove, 20);
+           while(isdigit(*q)) q++; // find start of to-square
+           safeStrCpy(machineMove, firstLeg, 20);
+           while(isdigit(*p)) p++;
+           safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
+           sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
+           firstLeg[0] = NULLCHAR;
+       }
+
         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
                               &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));
-           DisplayError(buf1, 0);
+           DisplayMoveError(buf1);
             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);
            if (gameMode == TwoMachinesPlay) {
@@ -8230,6 +8503,17 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
 
        MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
 
+        /* Test suites abort the 'game' after one move */
+        if(*appData.finger) {
+           static FILE *f;
+           char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
+           if(!f) f = fopen(appData.finger, "w");
+           if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
+           else { DisplayFatalError("Bad output file", errno, 0); return; }
+           free(fen);
+           GameEnds(GameUnfinished, NULL, GE_XBOARD);
+        }
+
         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
             int count = 0;
@@ -8370,13 +8654,26 @@ 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 || gameInfo.variant == VariantFairy) && 
-                                       !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
-      int dummy, s=6; char buf[MSG_SIZ];
+    if (!strncmp(message, "setup ", 6) && 
+       (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown || NonStandardBoardSize())
+                                       ) { // [HGM] allow first engine to define opening position
+      int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
+      *buf = NULLCHAR;
       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
       if(startedFromSetupPosition) return;
-      ParseFEN(boards[0], &dummy, message+s);
+      dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
+      if(dummy >= 3) {
+        while(message[s] && message[s++] != ' ');
+        if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
+           dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
+           appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
+           if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
+          InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
+          if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
+        }
+      }
+      ParseFEN(boards[0], &dummy, message+s, FALSE);
       DrawPosition(TRUE, boards[0]);
       startedFromSetupPosition = TRUE;
       return;
@@ -8389,7 +8686,7 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
 
         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
 
-        if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
+        if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
             DisplayError(_("Bad FEN received from engine"), 0);
             return ;
         } else {
@@ -8438,7 +8735,7 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
          snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
          SendToICS(buf1);
        }
-      }
+      } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
       return;
     }
     if (!strncmp(message, "tellall ", 8)) {
@@ -8491,6 +8788,28 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
        return;
     }
+    if(!strncmp(message, "highlight ", 10)) {
+       if(appData.testLegality && appData.markers) return;
+       MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
+       return;
+    }
+    if(!strncmp(message, "click ", 6)) {
+       char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
+       if(appData.testLegality || !appData.oneClick) return;
+       sscanf(message+6, "%c%d%c", &f, &y, &c);
+       x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
+       if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
+       x = x*squareSize + (x+1)*lineGap + squareSize/2;
+       y = y*squareSize + (y+1)*lineGap + squareSize/2;
+       f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
+       if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
+           LeftClick(Release, lastLeftX, lastLeftY);
+       controlKey  = (c == ',');
+       LeftClick(Press, x, y);
+       LeftClick(Release, x, y);
+       first.highlight = f;
+       return;
+    }
     /*
      * If the move is illegal, cancel it and redraw the board.
      * Also deal with other error cases.  Matching is rather loose
@@ -8831,7 +9150,7 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
            DisplayInformation(_("Machine accepts your draw offer"));
            GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
          } else {
-            DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
+            DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
          }
        }
     }
@@ -8910,7 +9229,10 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
                        if(f = fopen(buf, "w")) { // export PV to applicable PV file
                                fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
                                fclose(f);
-                       } else DisplayError(_("failed writing PV"), 0);
+                       }
+                       else
+                         /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
+                         DisplayError(_("failed writing PV"), 0);
                }
 
                tempStats.depth = plylev;
@@ -9330,10 +9652,22 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
        }
         piece = board[toY][toX] = (ChessSquare) fromX;
   } else {
+      ChessSquare victim;
       int i;
 
-      if( board[toY][toX] != EmptySquare )
+      if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
+           victim = board[killY][killX],
+           board[killY][killX] = EmptySquare,
+           board[EP_STATUS] = EP_CAPTURE;
+
+      if( board[toY][toX] != EmptySquare ) {
            board[EP_STATUS] = EP_CAPTURE;
+           if( (fromX != toX || fromY != toY) && // not igui!
+               (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
+                captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
+               board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
+           }
+      }
 
       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
@@ -9426,11 +9760,11 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
                ) {
        /* white pawn promotion */
         board[toY][toX] = CharToPiece(ToUpper(promoChar));
-        if(gameInfo.variant==VariantBughouse ||
-           gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
+        if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
        board[fromY][fromX] = EmptySquare;
     } else if ((fromY >= BOARD_HEIGHT>>1)
+              && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
               && (toX != fromX)
                && gameInfo.variant != VariantXiangqi
                && gameInfo.variant != VariantBerolina
@@ -9487,11 +9821,11 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
                ) {
        /* black pawn promotion */
        board[toY][toX] = CharToPiece(ToLower(promoChar));
-        if(gameInfo.variant==VariantBughouse ||
-           gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
+        if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
        board[fromY][fromX] = EmptySquare;
     } else if ((fromY < BOARD_HEIGHT>>1)
+              && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
               && (toX != fromX)
                && gameInfo.variant != VariantXiangqi
                && gameInfo.variant != VariantBerolina
@@ -9515,8 +9849,9 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
                board[fromY][fromX+1] = EmptySquare;
        }
     } else {
-       board[toY][toX] = board[fromY][fromX];
+       ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
        board[fromY][fromX] = EmptySquare;
+       board[toY][toX] = piece;
     }
   }
 
@@ -9599,7 +9934,7 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
     } else
     if(promoChar == '+') {
         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
-        board[toY][toX] = (ChessSquare) (PROMOTED piece);
+        board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
        if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
@@ -9618,19 +9953,24 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
                board[BOARD_HEIGHT-1-k][0] = EmptySquare;
        }
     }
-
 }
 
 /* Updates forwardMostMove */
 void
 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
 {
+    int x = toX, y = toY;
+    char *s = parseList[forwardMostMove];
+    ChessSquare p = boards[forwardMostMove][toY][toX];
 //    forwardMostMove++; // [HGM] bare: moved downstream
 
+    if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
     (void) CoordsToAlgebraic(boards[forwardMostMove],
                             PosFlags(forwardMostMove),
-                            fromY, fromX, toY, toX, promoChar,
-                            parseList[forwardMostMove]);
+                            fromY, fromX, y, x, promoChar,
+                            s);
+    if(killX >= 0 && killY >= 0)
+        sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
 
     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
         int timeLeft; static int lastLoadFlag=0; int king, piece;
@@ -9726,6 +10066,7 @@ MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
        break;
     }
 
+    killX = killY = -1; // [HGM] lion: used up
 }
 
 /* Updates currentMove if not pausing */
@@ -9792,11 +10133,41 @@ SendEgtPath (ChessProgramState *cps)
        }
 }
 
+static int
+NonStandardBoardSize ()
+{
+      /* [HGM] Awkward testing. Should really be a table */
+      int overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
+      if( gameInfo.variant == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
+      if( gameInfo.variant == VariantXiangqi )
+           overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
+      if( gameInfo.variant == VariantShogi )
+           overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
+      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 == 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;
+      if( gameInfo.variant == VariantSuper )
+           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( gameInfo.variant == VariantGrand )
+           overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
+      if( gameInfo.variant == VariantChu )
+           overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 12 || gameInfo.holdingsSize != 0;
+      return overruled;
+}
+
 void
 InitChessProgram (ChessProgramState *cps, int setup)
 /* setup needed to setup FRC opening position */
 {
-    char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
+    char buf[MSG_SIZ], b[MSG_SIZ];
     if (appData.noChessProgram) return;
     hintRequested = FALSE;
     bookRequested = FALSE;
@@ -9828,29 +10199,7 @@ InitChessProgram (ChessProgramState *cps, int setup)
        return;
       }
 
-      /* [HGM] make prefix for non-standard board size. Awkward testing... */
-      overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
-      if( gameInfo.variant == VariantXiangqi )
-           overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
-      if( gameInfo.variant == VariantShogi )
-           overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
-      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 == 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;
-      if( gameInfo.variant == VariantSuper )
-           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( gameInfo.variant == VariantGrand )
-           overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
-
-      if(overruled) {
+      if(NonStandardBoardSize()) { /* [HGM] make prefix for non-standard board size. */
        snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
                 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
            /* [HGM] varsize: try first if this defiant size variant is specifically known */
@@ -9915,6 +10264,33 @@ InitChessProgram (ChessProgramState *cps, int setup)
 
 
 void
+ResendOptions (ChessProgramState *cps)
+{ // send the stored value of the options
+  int i;
+  char buf[MSG_SIZ];
+  Option *opt = cps->option;
+  for(i=0; i<cps->nrOptions; i++, opt++) {
+      switch(opt->type) {
+        case Spin:
+        case Slider:
+        case CheckBox:
+           snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
+          break;
+        case ComboBox:
+          snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
+          break;
+        default:
+           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
+          break;
+        case Button:
+        case SaveButton:
+          continue;
+      }
+      SendToProgram(buf, cps);
+  }
+}
+
+void
 StartChessProgram (ChessProgramState *cps)
 {
     char buf[MSG_SIZ];
@@ -9954,9 +10330,12 @@ StartChessProgram (ChessProgramState *cps)
     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
     if (cps->protocolVersion > 1) {
       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
-      cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
-      cps->comboCnt = 0;  //                and values of combo boxes
+      if(!cps->reload) { // do not clear options when reloading because of -xreuse
+        cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
+        cps->comboCnt = 0;  //                and values of combo boxes
+      }
       SendToProgram(buf, cps);
+      if(cps->reload) ResendOptions(cps);
     } else {
       SendToProgram("xboard\n", cps);
     }
@@ -9977,7 +10356,6 @@ TwoMachinesEventIfReady P((void))
     return;
   }
   DisplayMessage("", ""); curMess = 0;
-  ThawUI();
   TwoMachinesEvent();
 }
 
@@ -10037,7 +10415,15 @@ WriteTourneyFile (char *results, FILE *f)
        fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
        fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
        fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
+       fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
+       fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
+       fprintf(f, "-bookDepth %d\n", appData.bookDepth);
+       fprintf(f, "-bookVariation %d\n", appData.bookStrength);
        fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
+       fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
+       fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
+       fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
+       fprintf(f, "-smpCores %d\n", appData.smpCores);
        if(searchTime > 0)
                fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
        else {
@@ -10266,6 +10652,13 @@ SetPlayer (int player, char *p)
        ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
        appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
        ParseArgsFromString(buf);
+    } else { // no engine with this nickname is installed!
+       snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
+       ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
+       matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
+       ModeHighlight();
+       DisplayError(buf, 0);
+       return 0;
     }
     free(engineName);
     return i;
@@ -10331,7 +10724,7 @@ Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInte
        *blackPlayer = curRound + appData.tourneyType;
     }
 
-    // take care of white/black alternation per round. 
+    // take care of white/black alternation per round.
     // For cycles and games this is already taken care of by default, derived from matchGame!
     return curRound & 1;
 }
@@ -10340,7 +10733,7 @@ int
 NextTourneyGame (int nr, int *swapColors)
 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
     char *p, *q;
-    int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
+    int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
     FILE *tf;
     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
     tf = fopen(appData.tourneyFile, "r");
@@ -10380,7 +10773,7 @@ NextTourneyGame (int nr, int *swapColors)
            SendToProgram(buf, &pairing);
            return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
        }
-       pairingReceived = 0;                              // ... so we continue here 
+       pairingReceived = 0;                              // ... so we continue here
        *swapColors = 0;
        appData.matchGames = appData.tourneyCycles * syncInterval - 1;
        whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
@@ -10392,18 +10785,18 @@ NextTourneyGame (int nr, int *swapColors)
     // redefine engines, engine dir, etc.
     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
     if(first.pr == NoProc) {
-      SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
+      if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
     }
     if(second.pr == NoProc) {
       SwapEngines(1);
-      SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
+      if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
       SwapEngines(1);         // and make that valid for second engine by swapping
       InitEngine(&second, 1);
     }
     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
     UpdateLogos(FALSE);     // leave display to ModeHiglight()
-    return 1;
+    return OK;
 }
 
 void
@@ -10504,6 +10897,8 @@ GameEnds (ChessMove result, char *resultDetails, int whosays)
 
     fromX = fromY = -1; // [HGM] abort any move the user is entering.
 
+    if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
+
     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
        /* If we are playing on ICS, the server decides when the
           game is over, but the engine can offer to draw, claim
@@ -10596,6 +10991,10 @@ GameEnds (ChessMove result, char *resultDetails, int whosays)
                       resultDetails = buf;
                 }
                 /* (Claiming a loss is accepted no questions asked!) */
+           } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
+               forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
+               result = GameUnfinished;
+               if(!*appData.tourneyFile) matchGame--; // replay even in plain match
            }
            /* [HGM] bare: don't allow bare King to win */
            if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
@@ -10640,9 +11039,12 @@ GameEnds (ChessMove result, char *resultDetails, int whosays)
                    && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
                                                                ) {
                    if (*appData.saveGameFile != NULLCHAR) {
+                       if(result == GameUnfinished && matchMode && *appData.tourneyFile)
+                           AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
+                       else
                        SaveGameToFile(appData.saveGameFile, TRUE);
                    } else if (appData.autoSaveGames) {
-                       AutoSaveGame();
+                       if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
                    }
                    if (*appData.savePositionFile != NULLCHAR) {
                        SavePositionToFile(appData.savePositionFile);
@@ -10715,6 +11117,7 @@ GameEnds (ChessMove result, char *resultDetails, int whosays)
                    PlayIcsUnfinishedSound();
                }
            }
+           if(appData.quitNext) { ExitEvent(0); return; }
        } else if (gameMode == EditGame ||
                   gameMode == PlayFromGameFile ||
                   gameMode == AnalyzeMode ||
@@ -10764,6 +11167,7 @@ GameEnds (ChessMove result, char *resultDetails, int whosays)
            SendToProgram("quit\n", &first);
             DoSleep( appData.delayAfterQuit );
            DestroyChildProcess(first.pr, first.useSigterm);
+           first.reload = TRUE;
        }
        first.pr = NoProc;
     }
@@ -10789,11 +11193,12 @@ GameEnds (ChessMove result, char *resultDetails, int whosays)
            SendToProgram("quit\n", &second);
             DoSleep( appData.delayAfterQuit );
            DestroyChildProcess(second.pr, second.useSigterm);
+           second.reload = TRUE;
        }
        second.pr = NoProc;
     }
 
-    if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
+    if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
        char resChar = '=';
         switch (result) {
        case WhiteWins:
@@ -10818,7 +11223,7 @@ GameEnds (ChessMove result, char *resultDetails, int whosays)
          break;
        }
 
-       if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
+       if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
        if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
            if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
            ReserveGame(nextGame, resChar); // sets nextGame
@@ -10917,8 +11322,8 @@ ResurrectChessProgram ()
 
     if (appData.noChessProgram) return 1;
 
-    if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
-       if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
+    if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
+       if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
        if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
        doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
     } else {
@@ -10972,6 +11377,7 @@ Reset (int redraw, int init)
     lastHint[0] = NULLCHAR;
     ClearGameInfo(&gameInfo);
     gameInfo.variant = StringToVariant(appData.variant);
+    if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
     ics_user_moved = ics_clock_paused = FALSE;
     ics_getting_history = H_FALSE;
     ics_gamenum = -1;
@@ -11063,17 +11469,23 @@ AutoPlayOneMove ()
     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
       return FALSE;
 
-    if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
+    if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
       pvInfoList[currentMove].depth = programStats.depth;
       pvInfoList[currentMove].score = programStats.score;
       pvInfoList[currentMove].time  = 0;
       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
+      else { // append analysis of final position as comment
+       char buf[MSG_SIZ];
+       snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
+       AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
+      }
+      programStats.depth = 0;
     }
 
     if (currentMove >= forwardMostMove) {
       if(gameMode == AnalyzeFile) {
          if(appData.loadGameIndex == -1) {
-           GameEnds(EndOfFile, NULL, GE_FILE);
+           GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
           ScheduleDelayedEvent(AnalyzeNextGame, 10);
          } else {
           ExitAnalyzeMode(); SendToProgram("force\n", &first);
@@ -11753,12 +12165,12 @@ InitSearch ()
        if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
        reverseBoard[r][f] = piece;
     }
-    reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
+    reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
-                || (boards[currentMove][CASTLING][2] == NoRights || 
+                || (boards[currentMove][CASTLING][2] == NoRights ||
                     boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
-                && (boards[currentMove][CASTLING][5] == NoRights || 
+                && (boards[currentMove][CASTLING][5] == NoRights ||
                     boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
       ) {
        flipSearch = TRUE;
@@ -11799,7 +12211,7 @@ GameContainsPosition (FILE *f, ListGame *lg)
        for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
        initDone = TRUE;
     }
-    if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
+    if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
     else CopyBoard(boards[scratch], initialPosition); // default start position
     if(lg->moves) {
        turn = btm + 1;
@@ -11923,7 +12335,7 @@ LoadGame (FILE *f, int gameNumber, char *title, int useList)
            gn = 1;
        }
        else {
-           if(gameMode == AnalyzeFile && appData.loadGameIndex == -1)
+           if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
              appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
            else
            DisplayError(_("Game number out of range"), 0);
@@ -12121,7 +12533,7 @@ LoadGame (FILE *f, int gameNumber, char *title, int useList)
        if (gameInfo.fen != NULL) {
          Board initial_position;
          startedFromSetupPosition = TRUE;
-         if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
+         if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
            Reset(TRUE, TRUE);
            DisplayError(_("Bad FEN position in file"), 0);
            return FALSE;
@@ -12319,12 +12731,21 @@ LoadGame (FILE *f, int gameNumber, char *title, int useList)
 
     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
 
-    if (oldGameMode == AnalyzeFile ||
-       oldGameMode == AnalyzeMode) {
+    if (oldGameMode == AnalyzeFile) {
       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
-      keepInfo = 1;
       AnalyzeFileEvent();
-      keepInfo = 0;
+    } else
+    if (oldGameMode == AnalyzeMode) {
+      AnalyzeFileEvent();
+    }
+
+    if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
+       long int w, b; // [HGM] adjourn: restore saved clock times
+       char *p = strstr(gameInfo.resultDetails, "(Clocks:");
+       if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
+           timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
+           timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
+       }
     }
 
     if(creatingBook) return TRUE;
@@ -12449,7 +12870,7 @@ LoadPosition (FILE *f, int positionNumber, char *title)
     }
 
     if (fenMode) {
-       if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
+       if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
            DisplayError(_("Bad FEN position in file"), 0);
            return FALSE;
        }
@@ -12691,7 +13112,7 @@ SaveGamePGN (FILE *f)
     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
 
     if (backwardMostMove > 0 || startedFromSetupPosition) {
-        char *fen = PositionToFEN(backwardMostMove, NULL);
+        char *fen = PositionToFEN(backwardMostMove, NULL, 1);
         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
        fprintf(f, "\n{--------------\n");
        PrintPosition(f, backwardMostMove);
@@ -12828,8 +13249,11 @@ SaveGamePGN (FILE *f)
     /* Print result */
     if (gameInfo.resultDetails != NULL &&
        gameInfo.resultDetails[0] != NULLCHAR) {
-       fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
-               PGNResult(gameInfo.result));
+       char buf[MSG_SIZ], *p = gameInfo.resultDetails;
+       if(gameInfo.result == GameUnfinished && appData.clockMode &&
+          (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
+           snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
+       fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
     } else {
        fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
     }
@@ -12960,7 +13384,7 @@ SavePosition (FILE *f, int dummy, char *dummy2)
        PrintPosition(f, currentMove);
        fprintf(f, "--------------]\n");
     } else {
-       fen = PositionToFEN(currentMove, NULL);
+       fen = PositionToFEN(currentMove, NULL, 1);
        fprintf(f, "%s\n", fen);
        free(fen);
     }
@@ -13362,6 +13786,20 @@ ExitEvent (int status)
 }
 
 void
+PauseEngine (ChessProgramState *cps)
+{
+    SendToProgram("pause\n", cps);
+    cps->pause = 2;
+}
+
+void
+UnPauseEngine (ChessProgramState *cps)
+{
+    SendToProgram("resume\n", cps);
+    cps->pause = 1;
+}
+
+void
 PauseEvent ()
 {
     if (appData.debugMode)
@@ -13369,8 +13807,24 @@ PauseEvent ()
     if (pausing) {
        pausing = FALSE;
        ModeHighlight();
+       if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
+           StartClocks();
+           if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
+               if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
+               else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
+           }
+           if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
+           HandleMachineMove(stashedInputMove, stalledEngine);
+           stalledEngine = NULL;
+           return;
+       }
        if (gameMode == MachinePlaysWhite ||
-           gameMode == MachinePlaysBlack) {
+           gameMode == TwoMachinesPlay   ||
+           gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
+           if(first.pause)  UnPauseEngine(&first);
+           else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
+           if(second.pause) UnPauseEngine(&second);
+           else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
            StartClocks();
        } else {
            DisplayBothClocks();
@@ -13382,7 +13836,7 @@ PauseEvent ()
            Reset(FALSE, TRUE);
            SendToICS(ics_prefix);
            SendToICS("refresh\n");
-       } else if (currentMove < forwardMostMove) {
+       } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
            ForwardInner(forwardMostMove);
        }
        pauseExamInvalid = FALSE;
@@ -13413,12 +13867,29 @@ PauseEvent ()
          case TwoMachinesPlay:
            if (forwardMostMove == 0)
              return;           /* don't pause if no one has moved */
-           if ((gameMode == MachinePlaysWhite &&
-                !WhiteOnMove(forwardMostMove)) ||
-               (gameMode == MachinePlaysBlack &&
-                WhiteOnMove(forwardMostMove))) {
+           if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
+               ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
+               if(onMove->pause) {           // thinking engine can be paused
+                   PauseEngine(onMove);      // do it
+                   if(onMove->other->pause)  // pondering opponent can always be paused immediately
+                       PauseEngine(onMove->other);
+                   else
+                       SendToProgram("easy\n", onMove->other);
+                   StopClocks();
+               } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
+           } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
+               if(first.pause) {
+                   PauseEngine(&first);
+                   StopClocks();
+               } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
+           } else { // human on move, pause pondering by either method
+               if(first.pause)
+                   PauseEngine(&first);
+               else if(appData.ponderNextMove)
+                   SendToProgram("easy\n", &first);
                StopClocks();
            }
+           // if no immediate pausing is possible, wait for engine to move, and stop clocks then
          case AnalyzeMode:
            pausing = TRUE;
            ModeHighlight();
@@ -13495,7 +13966,7 @@ AnalyzeModeEvent ()
             /* secure check */
             if (appData.icsEngineAnalyze) {
                 if (appData.debugMode)
-                    fprintf(debugFP, _("Found unexpected active ICS engine analyze \n"));
+                    fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
                 ExitAnalyzeMode();
                 ModeHighlight();
             }
@@ -13509,7 +13980,7 @@ AnalyzeModeEvent ()
         }
         appData.icsEngineAnalyze = TRUE;
         if (appData.debugMode)
-            fprintf(debugFP, _("ICS engine analyze starting... \n"));
+            fprintf(debugFP, "ICS engine analyze starting... \n");
     }
 
     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
@@ -13554,7 +14025,9 @@ AnalyzeFileEvent ()
     }
 
     if (gameMode != AnalyzeMode) {
+       keepInfo = 1; // mere annotating should not alter PGN tags
        EditGameEvent();
+       keepInfo = 0;
        if (gameMode != EditGame) return;
        if (!appData.showThinking) ToggleShowThinking();
        ResurrectChessProgram();
@@ -13567,7 +14040,6 @@ AnalyzeFileEvent ()
     gameMode = AnalyzeFile;
     pausing = FALSE;
     ModeHighlight();
-    SetGameInfo();
 
     StartAnalysisClock();
     GetTimeMark(&lastNodeCountTime);
@@ -13743,7 +14215,7 @@ DisplayTwoMachinesTitle ()
                   gameInfo.white, _("vs."), gameInfo.black,
                   nextGame+1, appData.matchGames+1,
                   appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
-        } else 
+        } else
         if (first.twoMachinesColor[0] == 'w') {
          snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
                   gameInfo.white, _("vs."),  gameInfo.black,
@@ -13782,9 +14254,10 @@ WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
        StartChessProgram(cps);
        if (cps->protocolVersion == 1) {
          retry();
+         ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
        } else {
          /* kludge: allow timeout for initial "feature" command */
-         FreezeUI();
+         if(retry != TwoMachinesEventIfReady) FreezeUI();
          snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
          DisplayMessage("", buf);
          ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
@@ -13813,7 +14286,7 @@ TwoMachinesEvent P((void))
       case MachinePlaysWhite:
       case MachinePlaysBlack:
        if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
-           DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
+           DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
            return;
        }
        /* fall through */
@@ -13837,16 +14310,19 @@ TwoMachinesEvent P((void))
 
 //    forwardMostMove = currentMove;
     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
+    startingEngine = TRUE;
 
     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
 
-    if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
+    if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
       return;
     }
+    if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
 
     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
+       startingEngine = FALSE;
        DisplayError("second engine does not play this", 0);
        return;
     }
@@ -13880,7 +14356,7 @@ TwoMachinesEvent P((void))
     }
 
     gameMode = TwoMachinesPlay;
-    pausing = FALSE;
+    pausing = startingEngine = FALSE;
     ModeHighlight(); // [HGM] logo: this triggers display update of logos
     SetGameInfo();
     DisplayTwoMachinesTitle();
@@ -14247,11 +14723,15 @@ EditPositionMenuEvent (ChessSquare selection, int x, int y)
 {
     char buf[MSG_SIZ];
     ChessSquare piece = boards[0][y][x];
+    static Board erasedBoard, currentBoard, menuBoard, nullBoard;
+    static int lastVariant;
 
     if (gameMode != EditPosition && gameMode != IcsExamining) return;
 
     switch (selection) {
       case ClearBoard:
+       CopyBoard(currentBoard, boards[0]);
+       CopyBoard(menuBoard, initialPosition);
        if (gameMode == IcsExamining && ics_type == ICS_FICS) {
            SendToICS(ics_prefix);
            SendToICS("bsetup clear\n");
@@ -14259,6 +14739,7 @@ EditPositionMenuEvent (ChessSquare selection, int x, int y)
            SendToICS(ics_prefix);
            SendToICS("clearboard\n");
        } else {
+            int nonEmpty = 0;
             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
                if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
                 for (y = 0; y < BOARD_HEIGHT; y++) {
@@ -14269,9 +14750,33 @@ EditPositionMenuEvent (ChessSquare selection, int x, int y)
                            SendToICS(buf);
                        }
                    } else {
+                       if(boards[0][y][x] != p) nonEmpty++;
                        boards[0][y][x] = p;
                    }
                }
+               menuBoard[1][x] = menuBoard[BOARD_HEIGHT-2][x] = p;
+           }
+           if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
+               for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
+                   ChessSquare p = menuBoard[0][x];
+                   for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[0][y] == p) menuBoard[0][y] = EmptySquare;
+                   p = menuBoard[BOARD_HEIGHT-1][x];
+                   for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[BOARD_HEIGHT-1][y] == p) menuBoard[BOARD_HEIGHT-1][y] = EmptySquare;
+               }
+               DisplayMessage("Clicking clock again restores position", "");
+               if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
+               if(!nonEmpty) { // asked to clear an empty board
+                   CopyBoard(boards[0], menuBoard);
+               } else
+               if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
+                   CopyBoard(boards[0], initialPosition);
+               } else
+               if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
+                                                                && !CompareBoards(nullBoard, erasedBoard)) {
+                   CopyBoard(boards[0], erasedBoard);
+               } else
+                   CopyBoard(erasedBoard, currentBoard);
+
            }
        }
        if (gameMode == EditPosition) {
@@ -14329,6 +14834,7 @@ EditPositionMenuEvent (ChessSquare selection, int x, int y)
         if(gameInfo.variant == VariantShatranj ||
            gameInfo.variant == VariantXiangqi  ||
            gameInfo.variant == VariantCourier  ||
+           gameInfo.variant == VariantASEAN    ||
            gameInfo.variant == VariantMakruk     )
             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
         goto defaultlabel;
@@ -14818,7 +15324,7 @@ BackwardInner (int target)
                for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
                    if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
                }
-               SendBoard(&first, i); 
+               SendBoard(&first, i);
              if(second.analyzing) SendBoard(&second, i);
                for(currentMove=i; currentMove<target; currentMove++) {
                    SendMoveToProgram(currentMove, &first);
@@ -14924,7 +15430,7 @@ RetractMoveEvent ()
       case MachinePlaysWhite:
       case MachinePlaysBlack:
        if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
-           DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
+           DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
            return;
        }
        if (forwardMostMove < 2) return;
@@ -15022,14 +15528,14 @@ HintEvent ()
     switch (gameMode) {
       case MachinePlaysWhite:
        if (WhiteOnMove(forwardMostMove)) {
-           DisplayError(_("Wait until your turn"), 0);
+           DisplayError(_("Wait until your turn."), 0);
            return;
        }
        break;
       case BeginningOfGame:
       case MachinePlaysBlack:
        if (!WhiteOnMove(forwardMostMove)) {
-           DisplayError(_("Wait until your turn"), 0);
+           DisplayError(_("Wait until your turn."), 0);
            return;
        }
        break;
@@ -15045,7 +15551,7 @@ void
 CreateBookEvent ()
 {
     ListGame * lg = (ListGame *) gameList.head;
-    FILE *f;
+    FILE *f, *g;
     int nItem;
     static int secondTime = FALSE;
 
@@ -15054,8 +15560,8 @@ CreateBookEvent ()
         return;
     }
 
-    if(!secondTime && (f = fopen(appData.polyglotBook, "r"))) {
-        fclose(f);
+    if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
+        fclose(g);
        secondTime++;
        DisplayNote(_("Book file exists! Try again for overwrite."));
        return;
@@ -15082,14 +15588,14 @@ BookEvent ()
     switch (gameMode) {
       case MachinePlaysWhite:
        if (WhiteOnMove(forwardMostMove)) {
-           DisplayError(_("Wait until your turn"), 0);
+           DisplayError(_("Wait until your turn."), 0);
            return;
        }
        break;
       case BeginningOfGame:
       case MachinePlaysBlack:
        if (!WhiteOnMove(forwardMostMove)) {
-           DisplayError(_("Wait until your turn"), 0);
+           DisplayError(_("Wait until your turn."), 0);
            return;
        }
        break;
@@ -15298,7 +15804,7 @@ ReplaceComment (int index, char *text)
     char *p;
     float score;
 
-    if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
+    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
@@ -15353,7 +15859,8 @@ AppendComment (int index, char *text, Boolean addBraces)
     int oldlen, len;
     char *old;
 
-if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
+if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
+    if(addBraces == 3) addBraces = 0; else // force appending literally
     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
 
     CrushCRs(text);
@@ -15420,8 +15927,11 @@ GetInfoFromComment (int index, char * text)
         int time = -1, sec = 0, deci;
         char * s_eval = FindStr( text, "[%eval " );
         char * s_emt = FindStr( text, "[%emt " );
-
+#if 0
         if( s_eval != NULL || s_emt != NULL ) {
+#else
+        if(0) { // [HGM] this code is not finished, and could actually be detrimental
+#endif
             /* New style */
             char delim;
 
@@ -15451,6 +15961,7 @@ GetInfoFromComment (int index, char * text)
             }
 
             p = text;
+            if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
             if(p[1] == '(') { // comment starts with PV
                p = strchr(p, ')'); // locate end of PV
                if(p == NULL || sep < p+5) return text;
@@ -15474,7 +15985,7 @@ GetInfoFromComment (int index, char * text)
             if(sec >= 0) time = 600*time + 10*sec; else
             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
 
-            score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
+            score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
 
             /* [HGM] PV time: now locate end of PV info */
             while( *++sep >= '0' && *sep <= '9'); // strip depth
@@ -15611,7 +16122,7 @@ ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int cou
                   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, "hint: %c", &c)!=1 &&
                   sscanf(message, "pong %c", &c)!=1   && start != '#') {
                    quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
                    print = (appData.engineComments >= 2);
@@ -15748,6 +16259,27 @@ SendTimeRemaining (ChessProgramState *cps, int machineWhite)
     SendToProgram(message, cps);
 }
 
+char *
+EngineDefinedVariant (ChessProgramState *cps, int n)
+{   // return name of n-th unknown variant that engine supports
+    static char buf[MSG_SIZ];
+    char *p, *s = cps->variants;
+    if(!s) return NULL;
+    do { // parse string from variants feature
+      VariantClass v;
+       p = strchr(s, ',');
+       if(p) *p = NULLCHAR;
+      v = StringToVariant(s);
+      if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
+       if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
+           if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
+       }
+       if(p) *p++ = ',';
+       if(n < 0) return buf;
+    } while(s = p);
+    return NULL;
+}
+
 int
 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
 {
@@ -15785,14 +16317,15 @@ IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
 }
 
 int
-StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
+StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
 {
   char buf[MSG_SIZ];
   int len = strlen(name);
   if (strncmp((*p), name, len) == 0
       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
     (*p) += len + 2;
-    sscanf(*p, "%[^\"]", loc);
+    ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
+    sscanf(*p, "%[^\"]", *loc);
     while (**p && **p != '\"') (*p)++;
     if (**p == '\"') (*p)++;
     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
@@ -15905,6 +16438,7 @@ FeatureDone (ChessProgramState *cps, int val)
     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
   }
   cps->initDone = val;
+  if(val) cps->reload = FALSE;
 }
 
 /* Parse feature command from engine */
@@ -15912,7 +16446,7 @@ void
 ParseFeatures (char *args, ChessProgramState *cps)
 {
   char *p = args;
-  char *q;
+  char *q = NULL;
   int val;
   char buf[MSG_SIZ];
 
@@ -15932,7 +16466,7 @@ ParseFeatures (char *args, ChessProgramState *cps)
       continue;
     }
     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
-    if (StringFeature(&p, "myname", cps->tidy, cps)) {
+    if (StringFeature(&p, "myname", &cps->tidy, cps)) {
       if (gameMode == TwoMachinesPlay) {
        DisplayTwoMachinesTitle();
       } else {
@@ -15940,7 +16474,7 @@ ParseFeatures (char *args, ChessProgramState *cps)
       }
       continue;
     }
-    if (StringFeature(&p, "variants", cps->variants, cps)) continue;
+    if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
@@ -15949,7 +16483,7 @@ ParseFeatures (char *args, ChessProgramState *cps)
     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
-    if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
+    if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
     if (IntFeature(&p, "done", &val, cps)) {
       FeatureDone(cps, val);
       continue;
@@ -15960,16 +16494,17 @@ ParseFeatures (char *args, ChessProgramState *cps)
     /* End of additions by Tord */
 
     /* [HGM] added features: */
+    if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
-    if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
-    if (StringFeature(&p, "option", buf, cps)) {
+    if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
+    if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
+       if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
        FREE(cps->option[cps->nrOptions].name);
-       cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
-       safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
+       cps->option[cps->nrOptions].name = q; q = NULL;
        if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
          snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
            SendToProgram(buf, cps);
@@ -16093,9 +16628,9 @@ AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
 void
 TypeInEvent (char firstChar)
 {
-    if ((gameMode == BeginningOfGame && !appData.icsActive) || 
+    if ((gameMode == BeginningOfGame && !appData.icsActive) ||
         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
-       gameMode == AnalyzeMode || gameMode == EditGame || 
+       gameMode == AnalyzeMode || gameMode == EditGame ||
        gameMode == EditPosition || gameMode == IcsExamining ||
        gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
        isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
@@ -16113,7 +16648,7 @@ TypeInDoneEvent (char *move)
        ChessMove moveType;
 
        // [HGM] FENedit
-       if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
+       if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
                EditPositionPasteFEN(move);
                return;
        }
@@ -16131,16 +16666,16 @@ TypeInDoneEvent (char *move)
            return;
         }
 
-      if (gameMode != EditGame && currentMove != forwardMostMove && 
+      if (gameMode != EditGame && currentMove != forwardMostMove &&
        gameMode != Training) {
        DisplayMoveError(_("Displayed move is not current"));
       } else {
-       int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
+       int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
          &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
        if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
-       if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
+       if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
          &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
-         UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
+         UserMoveEvent(fromX, fromY, toX, toY, promoChar);
        } else {
          DisplayMoveError(_("Could not parse move"));
        }
@@ -16815,7 +17350,7 @@ PGNDate ()
 
 
 char *
-PositionToFEN (int move, char *overrideCastling)
+PositionToFEN (int move, char *overrideCastling, int moveCounts)
 {
     int i, j, fromX, fromY, toX, toY;
     int whiteToPlay;
@@ -16847,7 +17382,7 @@ PositionToFEN (int move, char *overrideCastling)
                     *p++ = '+';
                     piece = (ChessSquare)(DEMOTED piece);
                 }
-                *p++ = PieceToChar(piece);
+                *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
                 if(p[-1] == '~') {
                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
@@ -16945,7 +17480,8 @@ PositionToFEN (int move, char *overrideCastling)
   }
 
   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
-     gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
+     gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
+     gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
     /* En passant target square */
     if (move > backwardMostMove) {
         fromX = moveList[move - 1][0] - AAA;
@@ -16977,9 +17513,10 @@ PositionToFEN (int move, char *overrideCastling)
   }
   }
 
-    /* [HGM] find reversible plies */
+    if(moveCounts)
     {   int i = 0, j=move;
 
+        /* [HGM] find reversible plies */
         if (appData.debugMode) { int k;
             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
             for(k=backwardMostMove; k<=forwardMostMove; k++)
@@ -16991,42 +17528,40 @@ PositionToFEN (int move, char *overrideCastling)
         if( j == backwardMostMove ) i += initialRulePlies;
         sprintf(p, "%d ", i);
         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
-    }
-    /* Fullmove number */
-    sprintf(p, "%d", (move / 2) + 1);
+
+        /* Fullmove number */
+        sprintf(p, "%d", (move / 2) + 1);
+    } else *--p = NULLCHAR;
 
     return StrSave(buf);
 }
 
 Boolean
-ParseFEN (Board board, int *blackPlaysFirst, char *fen)
+ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
 {
-    int i, j;
+    int i, j, k, w=0;
     char *p, c;
     int emptycount, virgin[BOARD_FILES];
     ChessSquare piece;
 
     p = fen;
 
-    /* [HGM] by default clear Crazyhouse holdings, if present */
-    if(gameInfo.holdingsWidth) {
-       for(i=0; i<BOARD_HEIGHT; i++) {
-           board[i][0]             = EmptySquare; /* black holdings */
-           board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
-           board[i][1]             = (ChessSquare) 0; /* black counts */
-           board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
-       }
-    }
-
     /* Piece placement data */
     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
        j = 0;
        for (;;) {
-            if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
-                if (*p == '/') p++;
+            if (*p == '/' || *p == ' ' || *p == '[' ) {
+               if(j > w) w = j;
                 emptycount = gameInfo.boardWidth - j;
                 while (emptycount--)
                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
+                if (*p == '/') p++;
+               else if(autoSize) { // we stumbled unexpectedly into end of board
+                    for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
+                       for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
+                    }
+                   appData.NrRanks = gameInfo.boardHeight - i; i=0;
+                }
                break;
 #if(BOARD_FILES >= 10)
             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
@@ -17035,6 +17570,8 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen)
                 while (emptycount--)
                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
 #endif
+            } else if (*p == '*') {
+               board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
             } else if (isdigit(*p)) {
                emptycount = *p++ - '0';
                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
@@ -17046,7 +17583,7 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen)
                 if(*p=='+') {
                     piece = CharToPiece(*++p);
                     if(piece == EmptySquare) return FALSE; /* unknown piece */
-                    piece = (ChessSquare) (PROMOTED piece ); p++;
+                    piece = (ChessSquare) (CHUPROMOTED piece ); p++;
                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
                 } else piece = CharToPiece(*p++);
 
@@ -17064,6 +17601,18 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen)
     }
     while (*p == '/' || *p == ' ') p++;
 
+    if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
+
+    /* [HGM] by default clear Crazyhouse holdings, if present */
+    if(gameInfo.holdingsWidth) {
+       for(i=0; i<BOARD_HEIGHT; i++) {
+           board[i][0]             = EmptySquare; /* black holdings */
+           board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
+           board[i][1]             = (ChessSquare) 0; /* black counts */
+           board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
+       }
+    }
+
     /* [HGM] look for Crazyhouse holdings here */
     while(*p==' ') p++;
     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
@@ -17229,7 +17778,8 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen)
 
     /* read e.p. field in games that know e.p. capture */
     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
-       gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
+       gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
+       gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
       if(*p=='-') {
         p++; board[EP_STATUS] = EP_NONE;
       } else {
@@ -17256,7 +17806,7 @@ EditPositionPasteFEN (char *fen)
   if (fen != NULL) {
     Board initial_position;
 
-    if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
+    if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
       DisplayError(_("Bad FEN position in clipboard"), 0);
       return ;
     } else {
@@ -17558,7 +18108,7 @@ LoadTheme ()
        snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
       if(appData.useBitmaps) {
        snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
-               appData.liteBackTextureFile, appData.darkBackTextureFile, 
+               appData.liteBackTextureFile, appData.darkBackTextureFile,
                appData.liteBackTextureMode,
                appData.darkBackTextureMode );
       } else {