Implement Chu Shogi
[xboard.git] / backend.c
index 57155b3..f449d26 100644 (file)
--- a/backend.c
+++ b/backend.c
@@ -225,6 +225,7 @@ 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();
@@ -268,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;
@@ -328,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;
@@ -387,6 +389,7 @@ PosFlags (index)
   case VariantShatranj:
   case VariantCourier:
   case VariantMakruk:
+  case VariantASEAN:
   case VariantGrand:
     flags &= ~F_ALL_CASTLE_OK;
     break;
@@ -464,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;
@@ -556,6 +559,13 @@ 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 }
+};
+
 
 #if (BOARD_FILES>=10)
 ChessSquare ShogiArray[2][BOARD_FILES] = {
@@ -636,8 +646,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)
 
 
@@ -780,9 +805,10 @@ 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;
@@ -807,7 +833,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];
@@ -815,6 +841,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)
@@ -840,6 +867,8 @@ InitEngine (ChessProgramState *cps, int n)
 
 ChessProgramState *savCps;
 
+GameMode oldMode;
+
 void
 LoadEngine ()
 {
@@ -849,21 +878,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;
@@ -907,7 +940,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*
@@ -931,11 +964,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;
@@ -1130,6 +1165,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 */
@@ -1150,6 +1186,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 */
@@ -1338,7 +1375,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
 
@@ -1473,7 +1514,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;
@@ -1983,10 +2024,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];
 }
 
@@ -2188,7 +2231,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;
@@ -3002,8 +3045,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));
                        }
@@ -3547,7 +3592,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;
@@ -4180,9 +4225,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];
@@ -4194,7 +4243,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;
@@ -4202,7 +4250,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;
@@ -4311,7 +4359,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;
     }
@@ -4671,11 +4719,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);
@@ -5008,6 +5055,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);
@@ -5076,7 +5128,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));
@@ -5140,7 +5193,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);
@@ -5182,6 +5235,8 @@ UploadGameEvent ()
     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
 }
 
+static 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])
 {
@@ -5193,6 +5248,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);
@@ -5213,7 +5269,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)
@@ -5233,7 +5290,7 @@ Sweep (int step)
        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));
+           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;
@@ -5483,6 +5540,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)
 {
@@ -5514,6 +5573,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;
 }
 
@@ -5543,6 +5603,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;
 }
 
@@ -5553,7 +5614,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
@@ -5824,7 +5885,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;
@@ -5881,9 +5942,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;
@@ -5944,6 +6009,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;
@@ -6012,7 +6085,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)
@@ -6039,6 +6113,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;
@@ -6121,7 +6204,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);
@@ -6304,7 +6387,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
@@ -6335,9 +6419,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 = (int)WhiteFerz;
+        highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteKing;
+    } else if(gameInfo.variant == VariantShogi) {
+        promotionZoneSize = BOARD_HEIGHT/3;
+        highestPromotingPiece = (int)WhiteAlfil;
     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
         promotionZoneSize = 3;
     }
@@ -6389,7 +6477,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;
     }
@@ -6400,7 +6489,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;
@@ -6411,7 +6500,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;
     }
@@ -6980,6 +7069,39 @@ 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];
@@ -6994,13 +7116,14 @@ Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VO
 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++;
@@ -7032,10 +7155,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 ||
@@ -7043,6 +7166,40 @@ 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];
+       int r, f;
+       if(dragging == 2) DragPieceMove(xPix, yPix); // [HGM] lion: drag without button for second leg
+       if(!first.highlight) return;
+       if(hiX == -1 && hiY == -1 && x == fromX && y == fromY) // record markings 
+         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(hiX != x || hiY != 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);
+         }
+         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;
@@ -7057,6 +7214,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);
@@ -7141,6 +7299,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;
@@ -7161,7 +7320,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;
@@ -7189,7 +7348,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 {
@@ -7203,6 +7362,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)) {
@@ -7219,7 +7379,7 @@ LeftClick (ClickType clickType, int xPix, int yPix)
        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
@@ -7239,6 +7399,7 @@ LeftClick (ClickType clickType, int xPix, int yPix)
            second = sweepSelecting = 0;
            fromX = fromY = -1;
            gatingPiece = EmptySquare;
+           MarkTargetSquares(1);
            ClearHighlights();
            gotPremove = 0;
            ClearPremoveHighlights();
@@ -7251,6 +7412,13 @@ LeftClick (ClickType clickType, int xPix, int yPix)
 
     clearFlag = 0;
 
+    if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] && (x != killX || y != killY)) {
+       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) {
@@ -7266,11 +7434,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;
@@ -7295,6 +7471,16 @@ 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
+           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;
@@ -7330,6 +7516,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));
 
@@ -7754,13 +7941,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;
            }
 
@@ -7856,7 +8051,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) {
@@ -7874,6 +8069,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);
@@ -7981,6 +8182,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) {
@@ -8053,6 +8255,7 @@ 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;
@@ -8195,12 +8398,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) {
@@ -8257,6 +8478,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;
@@ -8397,13 +8629,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;
@@ -8416,7 +8661,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 {
@@ -8465,7 +8710,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)) {
@@ -8518,6 +8763,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
@@ -8858,7 +9125,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."));
          }
        }
     }
@@ -8937,7 +9204,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;
@@ -9359,6 +9629,10 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
   } else {
       int i;
 
+      if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
+           board[killY][killX] = EmptySquare,
+           board[EP_STATUS] = EP_CAPTURE;
+
       if( board[toY][toX] != EmptySquare )
            board[EP_STATUS] = EP_CAPTURE;
 
@@ -9457,6 +9731,7 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
             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
@@ -9517,6 +9792,7 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
             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
@@ -9540,8 +9816,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;
     }
   }
 
@@ -9624,7 +9901,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
@@ -9650,12 +9927,18 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
 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;
@@ -9751,6 +10034,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 */
@@ -9817,11 +10101,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;
@@ -9853,29 +10167,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 */
@@ -10032,7 +10324,6 @@ TwoMachinesEventIfReady P((void))
     return;
   }
   DisplayMessage("", ""); curMess = 0;
-  ThawUI();
   TwoMachinesEvent();
 }
 
@@ -10093,7 +10384,7 @@ WriteTourneyFile (char *results, FILE *f)
        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, "-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");
@@ -10329,6 +10620,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;
@@ -10403,7 +10701,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");
@@ -10455,18 +10753,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
@@ -10714,7 +11012,7 @@ GameEnds (ChessMove result, char *resultDetails, int whosays)
                        else
                        SaveGameToFile(appData.saveGameFile, TRUE);
                    } else if (appData.autoSaveGames) {
-                       AutoSaveGame();
+                       if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
                    }
                    if (*appData.savePositionFile != NULLCHAR) {
                        SavePositionToFile(appData.savePositionFile);
@@ -10787,6 +11085,7 @@ GameEnds (ChessMove result, char *resultDetails, int whosays)
                    PlayIcsUnfinishedSound();
                }
            }
+           if(appData.quitNext) { ExitEvent(0); return; }
        } else if (gameMode == EditGame ||
                   gameMode == PlayFromGameFile ||
                   gameMode == AnalyzeMode ||
@@ -10867,7 +11166,7 @@ GameEnds (ChessMove result, char *resultDetails, int whosays)
        second.pr = NoProc;
     }
 
-    if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
+    if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
        char resChar = '=';
         switch (result) {
        case WhiteWins:
@@ -10892,7 +11191,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
@@ -10991,8 +11290,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 {
@@ -11046,6 +11345,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;
@@ -11137,17 +11437,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);
@@ -11873,7 +12179,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;
@@ -11997,7 +12303,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);
@@ -12195,7 +12501,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;
@@ -12393,10 +12699,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
       AnalyzeFileEvent();
+    } 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;
@@ -12521,7 +12838,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;
        }
@@ -12763,7 +13080,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);
@@ -12900,8 +13217,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));
     }
@@ -13032,7 +13352,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);
     }
@@ -13614,7 +13934,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();
             }
@@ -13628,7 +13948,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; }
@@ -13902,9 +14222,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);
@@ -13933,7 +14254,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 */
@@ -13957,16 +14278,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;
     }
@@ -14000,7 +14324,7 @@ TwoMachinesEvent P((void))
     }
 
     gameMode = TwoMachinesPlay;
-    pausing = FALSE;
+    pausing = startingEngine = FALSE;
     ModeHighlight(); // [HGM] logo: this triggers display update of logos
     SetGameInfo();
     DisplayTwoMachinesTitle();
@@ -14367,11 +14691,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");
@@ -14379,6 +14707,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++) {
@@ -14389,9 +14718,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) {
@@ -14449,6 +14802,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;
@@ -15044,7 +15398,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;
@@ -15142,14 +15496,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;
@@ -15165,7 +15519,7 @@ void
 CreateBookEvent ()
 {
     ListGame * lg = (ListGame *) gameList.head;
-    FILE *f;
+    FILE *f, *g;
     int nItem;
     static int secondTime = FALSE;
 
@@ -15174,8 +15528,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;
@@ -15202,14 +15556,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;
@@ -15473,7 +15827,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);
@@ -15540,8 +15895,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;
 
@@ -15571,6 +15929,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;
@@ -15594,7 +15953,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
@@ -15868,6 +16227,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)
 {
@@ -15905,14 +16285,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);
@@ -16033,7 +16414,7 @@ void
 ParseFeatures (char *args, ChessProgramState *cps)
 {
   char *p = args;
-  char *q;
+  char *q = NULL;
   int val;
   char buf[MSG_SIZ];
 
@@ -16053,7 +16434,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 {
@@ -16061,7 +16442,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;
@@ -16081,17 +16462,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(cps->reload) continue; // we are reloading because of xreuse
+    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);
@@ -16235,7 +16616,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;
        }
@@ -16937,7 +17318,7 @@ PGNDate ()
 
 
 char *
-PositionToFEN (int move, char *overrideCastling)
+PositionToFEN (int move, char *overrideCastling, int moveCounts)
 {
     int i, j, fromX, fromY, toX, toY;
     int whiteToPlay;
@@ -16969,7 +17350,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));
@@ -17067,7 +17448,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;
@@ -17099,9 +17481,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++)
@@ -17113,42 +17496,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 */
@@ -17157,6 +17538,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 */
@@ -17168,7 +17551,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++);
 
@@ -17186,6 +17569,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 == '[') {
@@ -17351,7 +17746,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 {
@@ -17378,7 +17774,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 {