Merge branch 'v4.7.x' into master
[xboard.git] / backend.c
index c14f438..084c42c 100644 (file)
--- a/backend.c
+++ b/backend.c
 #ifdef WIN32
 #include <windows.h>
 
-int flock(int f, int code);
-#define LOCK_EX 2
-#define SLASH '\\'
+    int flock(int f, int code);
+#   define LOCK_EX 2
+#   define SLASH '\\'
+
+#   ifdef ARC_64BIT
+#       define EGBB_NAME "egbbdll64.dll"
+#   else
+#       define EGBB_NAME "egbbdll.dll"
+#   endif
 
 #else
 
-#include <sys/file.h>
-#define SLASH '/'
+#   include <sys/file.h>
+#   define SLASH '/'
+
+#   include <dlfcn.h>
+#   ifdef ARC_64BIT
+#       define EGBB_NAME "egbbso64.so"
+#   else
+#       define EGBB_NAME "egbbso.so"
+#   endif
+    // kludge to allow Windows code in back-end by converting it to corresponding Linux code 
+#   define CDECL
+#   define HMODULE void *
+#   define LoadLibrary(x) dlopen(x, RTLD_LAZY)
+#   define GetProcAddress dlsym
 
 #endif
 
@@ -225,7 +243,7 @@ void DisplayTwoMachinesTitle P(());
 static void ExcludeClick P((int index));
 void ToggleSecond P((void));
 void PauseEngine P((ChessProgramState *cps));
-static int NonStandardBoardSize P((void));
+static int NonStandardBoardSize P((VariantClass v, int w, int h, int s));
 
 #ifdef WIN32
        extern void ConsoleCreate();
@@ -269,11 +287,13 @@ 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;
 int promoDefaultAltered;
 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
+static int initPing = -1;
 
 /* States for ics_getting_history */
 #define H_FALSE 0
@@ -388,6 +408,7 @@ PosFlags (index)
   case VariantShatranj:
   case VariantCourier:
   case VariantMakruk:
+  case VariantASEAN:
   case VariantGrand:
     flags &= ~F_ALL_CASTLE_OK;
     break;
@@ -557,6 +578,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] = {
@@ -601,6 +636,13 @@ ChessSquare GrandArray[2][BOARD_FILES] = {
         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
 };
 
+ChessSquare ChuChessArray[2][BOARD_FILES] = {
+    { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
+        WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
+    { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
+        BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
+};
+
 #ifdef GOTHIC
 ChessSquare GothicArray[2][BOARD_FILES] = {
     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
@@ -637,8 +679,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)
 
 
@@ -806,6 +863,7 @@ InitEngine (ChessProgramState *cps, int n)
     /* [HGM] debug */
     cps->debug = FALSE;
 
+    cps->drawDepth = appData.drawDepth[n];
     cps->supportsNPS = UNKNOWN;
     cps->memSize = FALSE;
     cps->maxCores = FALSE;
@@ -817,6 +875,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)
@@ -915,7 +974,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*
@@ -939,11 +998,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;
@@ -1138,6 +1199,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 */
@@ -1158,6 +1220,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 */
@@ -1166,6 +1229,8 @@ InitBackEnd1 ()
       case VariantSChess:     /* S-Chess, should work */
       case VariantGrand:      /* should work */
       case VariantSpartan:    /* should work */
+      case VariantLion:       /* should work */
+      case VariantChuChess:   /* should work */
        break;
       }
     }
@@ -1485,7 +1550,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;
@@ -1518,6 +1583,23 @@ InitBackEnd3 P((void))
     char buf[MSG_SIZ];
     int err, len;
 
+    if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
+       !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
+        appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
+       !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
+       !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
+       char c, *q = first.variants, *p = strchr(q, ',');
+       if(p) *p = NULLCHAR;
+       if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
+           int w, h, s;
+           if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
+               appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
+           ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
+           Reset(TRUE, FALSE);         // and re-initialize
+       }
+       if(p) *p = ',';
+    }
+
     InitChessProgram(&first, startedFromSetupPosition);
 
     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
@@ -1995,10 +2077,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];
 }
 
@@ -2028,7 +2112,8 @@ StringToVariant (char *e)
        found = TRUE;
     } else
     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
-      if (StrCaseStr(e, variantNames[i])) {
+      if (p = StrCaseStr(e, variantNames[i])) {
+       if(p && i >= VariantShogi && isalpha(p[strlen(variantNames[i])])) continue;
        v = (VariantClass) i;
        found = TRUE;
        break;
@@ -3014,8 +3099,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));
                        }
@@ -4745,7 +4832,7 @@ ParseBoard12 (char *string)
              default:
                break;
              case MT_CHECK:
-                if(gameInfo.variant != VariantShogi)
+                if(!IS_SHOGI(gameInfo.variant))
                     strcat(parseList[moveNum - 1], "+");
                break;
              case MT_CHECKMATE:
@@ -4975,6 +5062,11 @@ SendMoveToProgram (int moveNum, ChessProgramState *cps)
     char buf[MSG_SIZ];
 
     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
+       if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
+           sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
+           SendToProgram(buf, cps);
+           return;
+       }
        // null move in variant where engine does not understand it (for analysis purposes)
        SendBoard(cps, moveNum + 1); // send position after move in stead.
        return;
@@ -5017,6 +5109,13 @@ SendMoveToProgram (int moveNum, ChessProgramState *cps)
        }
        else SendToProgram(moveList[moveNum], cps);
       } 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');
+         SendToProgram(buf, cps);
+      } else
       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
        if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
          if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
@@ -5090,7 +5189,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));
@@ -5154,7 +5254,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);
@@ -5196,6 +5296,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])
 {
@@ -5207,6 +5309,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);
@@ -5227,27 +5330,43 @@ ProcessICSInitScript (FILE *f)
 }
 
 
-static int lastX, lastY, selectFlag, dragging;
+static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
+int dragging;
+static ClickType lastClickType;
+
+int
+Partner (ChessSquare *p)
+{ // change piece into promotion partner if one shogi-promotes to the other
+  int stride = gameInfo.variant == VariantChu ? 22 : 11;
+  ChessSquare partner;
+  partner = (*p/stride & 1 ? *p - stride : *p + stride);
+  if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
+  *p = partner;
+  return 1;
+}
 
 void
 Sweep (int step)
 {
     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
+    static int toggleFlag;
     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
-    if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
+    if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
+    if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
     do {
-       promoSweep -= step;
+       if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
        if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
        else if((int)promoSweep == -1) promoSweep = WhiteKing;
        else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
        else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
        if(!step) step = -1;
     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
-           appData.testLegality && (promoSweep == king ||
-           gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
+           !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
+           appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
+            (promoSweep == WhiteLion || promoSweep == BlackLion)));
     if(toX >= 0) {
        int victim = boards[currentMove][toY][toX];
        boards[currentMove][toY][toX] = promoSweep;
@@ -5351,6 +5470,7 @@ ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fro
       case WhiteNonPromotion:
       case BlackNonPromotion:
       case NormalMove:
+      case FirstLeg:
       case WhiteCapturesEnPassant:
       case BlackCapturesEnPassant:
       case WhiteKingSideCastle:
@@ -5384,7 +5504,8 @@ ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fro
        if (appData.testLegality) {
          return (*moveType != IllegalMove);
        } else {
-         return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
+         return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
+                        // [HGM] lion: if this is a double move we are less critical
                        WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
        }
 
@@ -5842,7 +5963,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;
@@ -5899,9 +6020,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;
@@ -5962,6 +6087,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;
@@ -5976,6 +6109,16 @@ 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 VariantChuChess:
+      pieces = ChuChessArray;
+      gameInfo.boardWidth = 10;
+      gameInfo.boardHeight = 10;
+      SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
+      break;
     case VariantFairy:
       pieces = fairyArray;
       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
@@ -6030,7 +6173,9 @@ 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 || gameInfo.variant == VariantChuChess) pawnRow = 2;
+    if(gameInfo.variant == VariantChu) pawnRow = 3;
 
     /* User pieceToChar list overrules defaults */
     if(appData.pieceToCharTable != NULL)
@@ -6044,7 +6189,7 @@ InitPosition (int redraw)
             initialPosition[i][j] = s;
 
         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
-        initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
+        initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
         initialPosition[pawnRow][j] = WhitePawn;
         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
         if(gameInfo.variant == VariantXiangqi) {
@@ -6057,14 +6202,24 @@ InitPosition (int redraw)
                 }
             }
         }
-        if(gameInfo.variant == VariantGrand) {
+        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 || gameInfo.variant == VariantChuChess) {
             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
                initialPosition[0][j] = WhiteRook;
                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
             }
         }
-        initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
+        initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
     }
+    if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
     if( (gameInfo.variant == VariantShogi) && !overrule ) {
 
             j=BOARD_LEFT+1;
@@ -6139,7 +6294,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);
@@ -6322,7 +6477,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
@@ -6341,7 +6497,7 @@ HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, i
     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
     /* [HGM] add Shogi promotions */
     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
-    ChessSquare piece;
+    ChessSquare piece, partner;
     ChessMove moveType;
     Boolean premove;
 
@@ -6353,10 +6509,14 @@ 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 : WhiteLion;
+    } else if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantChuChess) {
         promotionZoneSize = BOARD_HEIGHT/3;
-        highestPromotingPiece = (int)WhiteFerz;
-    } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
+        highestPromotingPiece = (int)WhiteAlfil;
+    } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
         promotionZoneSize = 3;
     }
 
@@ -6370,10 +6530,13 @@ HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, i
     if((int)piece >= BlackPawn) {
         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
              return FALSE;
+        if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
     } else {
         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
+        if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
+             return FALSE;
     }
 
     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
@@ -6407,8 +6570,13 @@ 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) {
-       *promoChoice = PieceToChar(BlackFerz);  // no choice
+    if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
+       gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
+       ChessSquare p=BlackFerz;  // no choice
+       while(p < EmptySquare) {  //but make sure we use piece that exists
+           *promoChoice = PieceToChar(p++);
+           if(*promoChoice != '.') break;
+       }
        return FALSE;
     }
     // no sense asking what we must promote to if it is going to explode...
@@ -6418,7 +6586,9 @@ 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 ? '=' : '+');
+    partner = piece; // pieces can promote if the pieceToCharTable says so
+    if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
+    else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
     if(        sweepSelect && gameInfo.variant != VariantGreat
                           && gameInfo.variant != VariantGrand
                           && gameInfo.variant != VariantSuper) return FALSE;
@@ -6429,7 +6599,8 @@ 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) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
+        if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
        if(moveType != WhitePromotion && moveType  != BlackPromotion)
            return FALSE;
     }
@@ -6998,27 +7169,66 @@ 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);
+}
+
+static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
+
+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;
 }
 
+static int hoverSavedValid;
+
 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;
+    hoverSavedValid = 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++;
@@ -7048,19 +7258,59 @@ ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
 int
 CanPromote (ChessSquare piece, int y)
 {
+        int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
        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;
-       return (piece == BlackPawn && y == 1 ||
-               piece == WhitePawn && y == BOARD_HEIGHT-2 ||
+         gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
+       return (piece == BlackPawn && y <= zone ||
+               piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
                piece == BlackLance && y == 1 ||
                piece == WhiteLance && y == BOARD_HEIGHT-2 );
 }
 
 void
+HoverEvent (int xPix, int yPix, int x, int y)
+{
+       static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
+       int r, f;
+       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];
+         hoverSavedValid = 1;
+       } else if(oldX != x || oldY != y) {
+         // [HGM] lift: entered new to-square; redraw arrow, and inform engine
+         if(hoverSavedValid) // don't restore markers that are supposed to be cleared
+         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;
@@ -7075,6 +7325,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);
@@ -7152,13 +7403,14 @@ LeftClick (ClickType clickType, int xPix, int yPix)
       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;
+      fromX = x; fromY = y; toX = toY = killX = killY = -1;
       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
         // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
         appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
            /* First square */
            if (OKToStartUserMove(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;
@@ -7191,16 +7443,18 @@ LeftClick (ClickType clickType, int xPix, int yPix)
        fromP = boards[currentMove][fromY][fromX];
        toP = boards[currentMove][y][x];
        frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
-       if ((WhitePawn <= fromP && fromP <= WhiteKing &&
+       if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
+          ((WhitePawn <= fromP && fromP <= WhiteKing &&
             WhitePawn <= toP && toP <= WhiteKing &&
             !(fromP == WhiteKing && toP == WhiteRook && frc) &&
             !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
            (BlackPawn <= fromP && fromP <= BlackKing &&
             BlackPawn <= toP && toP <= BlackKing &&
             !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
-            !(fromP == BlackKing && toP == BlackRook && frc))) {
+            !(fromP == BlackKing && toP == BlackRook && frc)))) {
            /* Clicked again on same color piece -- changed his mind */
            second = (x == fromX && y == fromY);
+           killX = killY = -1;
            if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
                second = FALSE; // first double-click rather than scond click
                doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
@@ -7221,6 +7475,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)) {
@@ -7237,7 +7492,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
@@ -7270,26 +7525,42 @@ 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
+    }
+
+    piece = boards[currentMove][fromY][fromX];
 
     saveAnimate = appData.animate;
     if (clickType == Press) {
+       if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
        if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
            // must be Edit Position mode with empty-square selected
            fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
            if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
            return;
        }
-       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
+           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(gameInfo.variant != VariantChuChess && 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;
@@ -7304,6 +7575,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; appData.animate = FALSE; // do not animate, a selected piece already on to-square
+       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
@@ -7314,6 +7592,18 @@ LeftClick (ClickType clickType, int xPix, int yPix)
            ClearHighlights();
        }
 #endif
+       if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
+       if(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);
+           MarkTargetSquares(0);
+           return;
+         }
+       }
        DragPieceEnd(xPix, yPix); dragging = 0;
        /* Don't animate move and drag both */
        appData.animate = FALSE;
@@ -7349,6 +7639,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));
 
@@ -7368,7 +7659,7 @@ LeftClick (ClickType clickType, int xPix, int yPix)
            DisplayMessage("Click in holdings to choose piece", "");
            return;
        }
-       PromotionPopUp();
+       PromotionPopUp(promoChoice);
     } else {
        int oldMove = currentMove;
        UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
@@ -8001,11 +8292,66 @@ Adjudicate (ChessProgramState *cps)
        return 0;
 }
 
+typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
+typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
+static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
+
+static int
+BitbaseProbe ()
+{
+    int pieces[10], squares[10], cnt=0, r, f, res;
+    static int loaded;
+    static PPROBE_EGBB probeBB;
+    if(!appData.testLegality) return 10;
+    if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
+    if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
+    if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
+    for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
+       ChessSquare piece = boards[forwardMostMove][r][f];
+       int black = (piece >= BlackPawn);
+       int type = piece - black*BlackPawn;
+       if(piece == EmptySquare) continue;
+       if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
+       if(type == WhiteKing) type = WhiteQueen + 1;
+       type = egbbCode[type];
+       squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
+        pieces[cnt] = type + black*6;
+       if(++cnt > 5) return 11;
+    }
+    pieces[cnt] = squares[cnt] = 0;
+    // probe EGBB
+    if(loaded == 2) return 13; // loading failed before
+    if(loaded == 0) {
+       loaded = 2; // prepare for failure
+       char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
+       HMODULE lib;
+       PLOAD_EGBB loadBB;
+       if(!path) return 13; // no egbb installed
+       strncpy(buf, path + 8, MSG_SIZ);
+       if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
+       snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
+       lib = LoadLibrary(buf);
+       if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
+       loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
+       probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
+       if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
+       p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
+       loaded = 1; // success!
+    }
+    res = probeBB(forwardMostMove & 1, pieces, squares);
+    return res > 0 ? 1 : res < 0 ? -1 : 0;
+}
+
 char *
 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
 {   // [HGM] book: this routine intercepts moves to simulate book replies
     char *bookHit = NULL;
 
+    if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
+       char buf[MSG_SIZ];
+       snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
+       SendToProgram(buf, cps);
+    }
     //first determine if the incoming move brings opponent into his book
     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
        bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
@@ -8088,11 +8434,12 @@ 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;
     ChessMove moveType;
-    char promoChar;
+    char promoChar, roar;
     char *p, *pv=buf1;
     int machineWhite, oldError;
     char *bookHit;
@@ -8230,14 +8577,32 @@ 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));
            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);
+            snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
+                    machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
            if (gameMode == TwoMachinesPlay) {
              GameEnds(machineWhite ? BlackWins : WhiteWins,
                        buf1, GE_XBOARD);
@@ -8292,6 +8657,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;
@@ -8379,10 +8755,12 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
            cps->other->maybeThinking = TRUE;
        }
 
+       roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
+
        ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
 
         if (!pausing && appData.ringBellAfterMoves) {
-           RingBell();
+           if(!roar) RingBell();
        }
 
        /*
@@ -8433,14 +8811,26 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
     }
 
     if (!strncmp(message, "setup ", 6) && 
-       (!appData.testLegality || gameInfo.variant == VariantFairy || NonStandardBoardSize())
+       (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
+          NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
                                        ) { // [HGM] allow first engine to define opening position
-      int dummy, s=6; char buf[MSG_SIZ];
+      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;
-      if(sscanf(message+s, "%dx%d+%d", &dummy, &dummy, &dummy) == 3) while(message[s] && message[s++] != ' '); // for compatibility with Alien Edition
-      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;
@@ -8453,7 +8843,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 {
@@ -8553,6 +8943,36 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
        }
     }
     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
+       if(initPing == cps->lastPong) {
+           if(gameInfo.variant == VariantUnknown) {
+               DisplayError(_("Engine did not send setup for non-standard variant"), 0);
+               *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
+               GameEnds(GameUnfinished, NULL, GE_XBOARD);
+           }
+           initPing = -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;
     }
     /*
@@ -8895,7 +9315,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."));
          }
        }
     }
@@ -8974,7 +9394,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;
@@ -9237,6 +9660,7 @@ ParseGameHistory (char *game)
          case WhiteNonPromotion:
          case BlackNonPromotion:
          case NormalMove:
+         case FirstLeg:
          case WhiteCapturesEnPassant:
          case BlackCapturesEnPassant:
          case WhiteKingSideCastle:
@@ -9360,7 +9784,7 @@ ParseGameHistory (char *game)
          default:
            break;
          case MT_CHECK:
-            if(gameInfo.variant != VariantShogi)
+            if(!IS_SHOGI(gameInfo.variant))
                 strcat(parseList[boardIndex - 1], "+");
            break;
          case MT_CHECKMATE:
@@ -9377,7 +9801,7 @@ void
 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
 {
   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
-  int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
+  int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
 
     /* [HGM] compute & store e.p. status and castling rights for new position */
     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
@@ -9394,11 +9818,23 @@ 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 )
                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
@@ -9485,7 +9921,7 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
         board[toY][toX+1] = board[fromY][BOARD_LEFT];
         board[fromY][BOARD_LEFT] = EmptySquare;
     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
-                board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
+                board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
                ) {
        /* white pawn promotion */
@@ -9546,7 +9982,7 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
        board[fromY][0] = EmptySquare;
        board[toY][2] = BlackRook;
     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
-                board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
+                board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
               && toY < promoRank && promoChar
                ) {
        /* black pawn promotion */
@@ -9579,8 +10015,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;
     }
   }
 
@@ -9658,12 +10095,15 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
        board[toY][toX] = EmptySquare;
       }
     }
+
     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
     } else
     if(promoChar == '+') {
         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
-        board[toY][toX] = (ChessSquare) (PROMOTED piece);
+        board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
+        if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
+          board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
     } 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
@@ -9682,19 +10122,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;
@@ -9781,7 +10226,7 @@ MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
       default:
        break;
       case MT_CHECK:
-        if(gameInfo.variant != VariantShogi)
+        if(!IS_SHOGI(gameInfo.variant))
             strcat(parseList[forwardMostMove - 1], "+");
        break;
       case MT_CHECKMATE:
@@ -9789,7 +10234,6 @@ MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
        strcat(parseList[forwardMostMove - 1], "#");
        break;
     }
-
 }
 
 /* Updates currentMove if not pausing */
@@ -9809,6 +10253,8 @@ ShowMove (int fromX, int fromY, int toX, int toY)
        currentMove = forwardMostMove;
     }
 
+    killX = killY = -1; // [HGM] lion: used up
+
     if (instant) return;
 
     DisplayMove(currentMove - 1);
@@ -9857,37 +10303,75 @@ 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 == 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;
-      return overruled;
+NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
+{
+      int width = 8, height = 8, holdings = 0;             // most common sizes
+      if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
+      // correct the deviations default for each variant
+      if( v == VariantXiangqi ) width = 9,  height = 10;
+      if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
+      if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
+      if( v == VariantCapablanca || v == VariantCapaRandom ||
+          v == VariantGothic || v == VariantFalcon || v == VariantJanus )
+                                width = 10;
+      if( v == VariantCourier ) width = 12;
+      if( v == VariantSuper )                            holdings = 8;
+      if( v == VariantGreat )   width = 10,              holdings = 8;
+      if( v == VariantSChess )                           holdings = 7;
+      if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
+      if( v == VariantChuChess) width = 10, height = 10;
+      if( v == VariantChu )     width = 12, height = 12;
+      return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
+             boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
+             holdingsSize >= 0 && holdingsSize != holdings;
+}
+
+char variantError[MSG_SIZ];
+
+char *
+SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
+{     // returns error message (recognizable by upper-case) if engine does not support the variant
+      char *p, *variant = VariantName(v);
+      static char b[MSG_SIZ];
+      if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
+          snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
+                                               holdingsSize, variant); // cook up sized variant name
+           /* [HGM] varsize: try first if this deviant size variant is specifically known */
+           if(StrStr(list, b) == NULL) {
+               // specific sized variant not known, check if general sizing allowed
+               if(proto != 1 && StrStr(list, "boardsize") == NULL) {
+                   snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
+                            boardWidth, boardHeight, holdingsSize, engine);
+                   return NULL;
+               }
+               /* [HGM] here we really should compare with the maximum supported board size */
+           }
+      } else snprintf(b, MSG_SIZ,"%s", variant);
+      if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
+      p = StrStr(list, b);
+      while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
+      if(p == NULL) {
+          // occurs not at all in list, or only as sub-string
+          snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
+          if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
+              int l = strlen(variantError);
+              char *q;
+              while(p != list && p[-1] != ',') p--;
+              q = strchr(p, ',');
+              if(q) *q = NULLCHAR;
+              snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
+              if(q) *q= ',';
+          }
+          return NULL;
+      }
+      return b;
 }
 
 void
 InitChessProgram (ChessProgramState *cps, int setup)
 /* setup needed to setup FRC opening position */
 {
-    char buf[MSG_SIZ], b[MSG_SIZ];
+    char buf[MSG_SIZ], *b;
     if (appData.noChessProgram) return;
     hintRequested = FALSE;
     bookRequested = FALSE;
@@ -9909,33 +10393,15 @@ InitChessProgram (ChessProgramState *cps, int setup)
     if (gameInfo.variant != VariantNormal &&
        gameInfo.variant != VariantLoadable
         /* [HGM] also send variant if board size non-standard */
-        || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
-                                            ) {
-      char *v = VariantName(gameInfo.variant);
-      if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
-        /* [HGM] in protocol 1 we have to assume all variants valid */
-       snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
-       DisplayFatalError(buf, 0, 1);
+        || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
+
+      b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
+                           gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
+      if (b == NULL) {
+       DisplayFatalError(variantError, 0, 1);
        return;
       }
 
-      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 */
-           if(StrStr(cps->variants, b) == NULL) {
-               // specific sized variant not known, check if general sizing allowed
-               if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
-                   if(StrStr(cps->variants, "boardsize") == NULL) {
-                    snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
-                            gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
-                       DisplayFatalError(buf, 0, 1);
-                       return;
-                   }
-                   /* [HGM] here we really should compare with the maximum supported board size */
-               }
-           }
-      } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
       snprintf(buf, MSG_SIZ, "variant %s\n", b);
       SendToProgram(buf, cps);
     }
@@ -9975,7 +10441,7 @@ InitChessProgram (ChessProgramState *cps, int setup)
        SendToProgram("easy\n", cps);
     }
     if (cps->usePing) {
-      snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
+      snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
       SendToProgram(buf, cps);
     }
     cps->initDone = TRUE;
@@ -10334,6 +10800,7 @@ SwapEngines (int n)
     SWAP(fenOverride, p)
     SWAP(NPS, h)
     SWAP(accumulateTC, h)
+    SWAP(drawDepth, h)
     SWAP(host, p)
 }
 
@@ -10615,7 +11082,7 @@ GameEnds (ChessMove result, char *resultDetails, int whosays)
              result, resultDetails ? resultDetails : "(null)", whosays);
     }
 
-    fromX = fromY = -1; // [HGM] abort any move the user is entering.
+    fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
 
     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
 
@@ -10837,6 +11304,7 @@ GameEnds (ChessMove result, char *resultDetails, int whosays)
                    PlayIcsUnfinishedSound();
                }
            }
+           if(appData.quitNext) { ExitEvent(0); return; }
        } else if (gameMode == EditGame ||
                   gameMode == PlayFromGameFile ||
                   gameMode == AnalyzeMode ||
@@ -11013,7 +11481,8 @@ FeedMovesToProgram (ChessProgramState *cps, int upto)
     if(currentlyInitializedVariant != gameInfo.variant) {
       char buf[MSG_SIZ];
         // [HGM] variantswitch: make engine aware of new variant
-       if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
+       if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
+                             gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
                return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
        snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
        SendToProgram(buf, cps);
@@ -11096,6 +11565,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;
@@ -11109,6 +11579,7 @@ Reset (int redraw, int init)
     ClearPremoveHighlights();
     gotPremove = FALSE;
     alarmSounded = FALSE;
+    killX = killY = -1; // [HGM] lion
 
     GameEnds(EndOfFile, NULL, GE_PLAYER);
     if(appData.serverMovesName != NULL) {
@@ -11289,6 +11760,7 @@ LoadGameOneMove (ChessMove readAhead)
       case WhiteNonPromotion:
       case BlackNonPromotion:
       case NormalMove:
+      case FirstLeg:
       case WhiteKingSideCastle:
       case WhiteQueenSideCastle:
       case BlackKingSideCastle:
@@ -11310,6 +11782,7 @@ LoadGameOneMove (ChessMove readAhead)
         toX = currentMoveString[2] - AAA;
         toY = currentMoveString[3] - ONE;
        promoChar = currentMoveString[4];
+       if(promoChar == ';') promoChar = NULLCHAR;
        break;
 
       case WhiteDrop:
@@ -11476,6 +11949,7 @@ LoadGameOneMove (ChessMove readAhead)
 
        thinkOutput[0] = NULLCHAR;
        MakeMove(fromX, fromY, toX, toY, promoChar);
+       killX = killY = -1; // [HGM] lion: used up
        currentMove = forwardMostMove;
        return TRUE;
     }
@@ -11947,7 +12421,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;
@@ -11991,6 +12465,7 @@ GameContainsPosition (FILE *f, ListGame *lg)
            case WhiteNonPromotion:
            case BlackNonPromotion:
            case NormalMove:
+           case FirstLeg:
            case WhiteKingSideCastle:
            case WhiteQueenSideCastle:
            case BlackKingSideCastle:
@@ -12055,6 +12530,7 @@ LoadGame (FILE *f, int gameNumber, char *title, int useList)
     if (gameMode != BeginningOfGame) {
       Reset(FALSE, TRUE);
     }
+    killX = killY = -1; // [HGM] lion: in case we did not Reset
 
     gameFileFP = f;
     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
@@ -12208,6 +12684,7 @@ LoadGame (FILE *f, int gameNumber, char *title, int useList)
            break;
 
          case NormalMove:
+         case FirstLeg:
            /* Only a NormalMove can be at the start of a game
             * without a position diagram. */
            if (lastLoadGameStart == EndOfFile ) {
@@ -12269,7 +12746,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;
@@ -12475,6 +12952,15 @@ LoadGame (FILE *f, int gameNumber, char *title, int useList)
       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;
     if (!matchMode && pos > 0) {
        ToNrEvent(pos); // [HGM] no autoplay if selected on position
@@ -12597,7 +13083,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;
        }
@@ -12839,7 +13325,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);
@@ -12976,8 +13462,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));
     }
@@ -13108,7 +13597,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);
     }
@@ -14010,7 +14499,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 */
@@ -14045,7 +14534,8 @@ TwoMachinesEvent P((void))
     }
     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))) {
+    if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
+                         gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
        startingEngine = FALSE;
        DisplayError("second engine does not play this", 0);
        return;
@@ -14447,11 +14937,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");
@@ -14459,6 +14953,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++) {
@@ -14469,9 +14964,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) {
@@ -14529,6 +15048,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;
@@ -15124,7 +15644,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;
@@ -15222,14 +15742,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;
@@ -15282,14 +15802,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;
@@ -15953,6 +16473,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)
 {
@@ -16167,6 +16708,7 @@ 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;
@@ -16320,7 +16862,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;
        }
@@ -17022,7 +17564,7 @@ PGNDate ()
 
 
 char *
-PositionToFEN (int move, char *overrideCastling)
+PositionToFEN (int move, char *overrideCastling, int moveCounts)
 {
     int i, j, fromX, fromY, toX, toY;
     int whiteToPlay;
@@ -17054,7 +17596,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));
@@ -17152,7 +17694,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;
@@ -17184,9 +17727,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++)
@@ -17198,42 +17742,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 */
@@ -17242,6 +17784,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 */
@@ -17253,7 +17797,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++);
 
@@ -17271,6 +17815,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 == '[') {
@@ -17436,7 +17992,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 {
@@ -17463,7 +18020,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 {