Implement (inaccessible) dark squares
[xboard.git] / backend.c
index a196a9f..f5ee841 100644 (file)
--- a/backend.c
+++ b/backend.c
@@ -225,6 +225,7 @@ void DisplayTwoMachinesTitle P(());
 static void ExcludeClick P((int index));
 void ToggleSecond P((void));
 void PauseEngine P((ChessProgramState *cps));
+static int NonStandardBoardSize P((void));
 
 #ifdef WIN32
        extern void ConsoleCreate();
@@ -268,6 +269,7 @@ char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
 extern int chatCount;
 int chattingPartner;
 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
+char legal[BOARD_RANKS][BOARD_FILES];  /* [HGM] legal target squares */
 char lastMsg[MSG_SIZ];
 ChessSquare pieceSweep = EmptySquare;
 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
@@ -387,6 +389,7 @@ PosFlags (index)
   case VariantShatranj:
   case VariantCourier:
   case VariantMakruk:
+  case VariantASEAN:
   case VariantGrand:
     flags &= ~F_ALL_CASTLE_OK;
     break;
@@ -556,6 +559,13 @@ ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatran
         BlackKing, BlackMan, BlackKnight, BlackRook }
 };
 
+ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
+    { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
+        WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
+    { BlackRook, BlackKnight, BlackMan, BlackFerz,
+        BlackKing, BlackMan, BlackKnight, BlackRook }
+};
+
 
 #if (BOARD_FILES>=10)
 ChessSquare ShogiArray[2][BOARD_FILES] = {
@@ -780,9 +790,10 @@ InitEngine (ChessProgramState *cps, int n)
     cps->sendName = appData.icsActive;
     cps->sdKludge = FALSE;
     cps->stKludge = FALSE;
+    if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
     TidyProgramName(cps->program, cps->host, cps->tidy);
     cps->matchWins = 0;
-    safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
+    ASSIGN(cps->variants, appData.variant);
     cps->analysisSupport = 2; /* detect */
     cps->analyzing = FALSE;
     cps->initDone = FALSE;
@@ -807,7 +818,7 @@ InitEngine (ChessProgramState *cps, int n)
     cps->supportsNPS = UNKNOWN;
     cps->memSize = FALSE;
     cps->maxCores = FALSE;
-    cps->egtFormats[0] = NULLCHAR;
+    ASSIGN(cps->egtFormats, "");
 
     /* [HGM] options */
     cps->optionSettings  = appData.engOptions[n];
@@ -815,6 +826,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)
@@ -1156,6 +1168,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 */
@@ -1344,7 +1357,11 @@ void
 InitBackEnd2 ()
 {
     if (appData.debugMode) {
+#    ifdef __GIT_VERSION
       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
+#    else
+      fprintf(debugFP, "Version: %s\n", programVersion);
+#    endif
     }
     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
 
@@ -1989,10 +2006,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];
 }
 
@@ -5084,7 +5103,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));
@@ -5148,7 +5168,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);
@@ -5221,7 +5241,8 @@ ProcessICSInitScript (FILE *f)
 }
 
 
-static int lastX, lastY, selectFlag, dragging;
+static int lastX, lastY, lastLeftX, lastLeftY, selectFlag, dragging;
+static ClickType lastClickType;
 
 void
 Sweep (int step)
@@ -5893,9 +5914,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;
@@ -6024,7 +6049,7 @@ InitPosition (int redraw)
 
     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
     if(pawnRow < 1) pawnRow = 1;
-    if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
+    if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN || gameInfo.variant == VariantGrand) pawnRow = 2;
 
     /* User pieceToChar list overrules defaults */
     if(appData.pieceToCharTable != NULL)
@@ -6133,7 +6158,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);
@@ -6316,7 +6341,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
@@ -6401,7 +6427,8 @@ HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, i
     }
 
     // we either have a choice what to promote to, or (in Shogi) whether to promote
-    if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
+    if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
+       gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
        *promoChoice = PieceToChar(BlackFerz);  // no choice
        return FALSE;
     }
@@ -6992,6 +7019,39 @@ FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int prom
 }
 
 void
+MarkByFEN(char *fen)
+{
+       int r, f;
+       if(!appData.markers || !appData.highlightDragging) return;
+       for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
+       r=BOARD_HEIGHT-1; f=BOARD_LEFT;
+       while(*fen) {
+           int s = 0;
+           marker[r][f] = 0;
+           if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
+           if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
+           if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
+           if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
+           if(*fen == 'T') marker[r][f++] = 0; else
+           if(*fen == 'Y') marker[r][f++] = 1; else
+           if(*fen == 'G') marker[r][f++] = 3; else
+           if(*fen == 'B') marker[r][f++] = 4; else
+           if(*fen == 'C') marker[r][f++] = 5; else
+           if(*fen == 'M') marker[r][f++] = 6; else
+           if(*fen == 'W') marker[r][f++] = 7; else
+           if(*fen == 'D') marker[r][f++] = 8; else
+           if(*fen == 'R') marker[r][f++] = 2; else {
+               while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
+             f += s; fen -= s>0;
+           }
+           while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
+           if(r < 0) break;
+           fen++;
+       }
+       DrawPosition(TRUE, NULL);
+}
+
+void
 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
 {
     typedef char Markers[BOARD_RANKS][BOARD_FILES];
@@ -7006,13 +7066,14 @@ Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VO
 void
 MarkTargetSquares (int clear)
 {
-  int x, y;
-  if(clear) // no reason to ever suppress clearing
-    for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
-  if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
-     !appData.testLegality || gameMode == EditPosition) return;
-  if(!clear) {
+  int x, y, sum=0;
+  if(clear) { // no reason to ever suppress clearing
+    for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
+    if(!sum) return; // nothing was cleared,no redraw needed
+  } else {
     int capt = 0;
+    if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
+       !appData.testLegality || gameMode == EditPosition) return;
     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
@@ -7047,7 +7108,7 @@ CanPromote (ChessSquare piece, int y)
        if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
           gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
           gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
-                                                 gameInfo.variant == VariantMakruk) return FALSE;
+         gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
        return (piece == BlackPawn && y == 1 ||
                piece == WhitePawn && y == BOARD_HEIGHT-2 ||
                piece == BlackLance && y == 1 ||
@@ -7055,6 +7116,39 @@ CanPromote (ChessSquare piece, int y)
 }
 
 void
+HoverEvent (int hiX, int hiY, int x, int y)
+{
+       static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
+       int r, f;
+       if(!first.highlight) return;
+       if(hiX == -1 && hiY == -1 && x == fromX && y == fromY) // record markings 
+         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
+           baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
+       else if(hiX != x || hiY != y) {
+         // [HGM] lift: entered new to-square; redraw arrow, and inform engine
+         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
+           marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
+         if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
+           char buf[MSG_SIZ];
+           snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
+           SendToProgram(buf, &first);
+         }
+         SetHighlights(fromX, fromY, x, y);
+       }
+}
+
+void ReportClick(char *action, int x, int y)
+{
+       char buf[MSG_SIZ]; // Inform engine of what user does
+       int r, f;
+       if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
+         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
+       if(!first.highlight || gameMode == EditPosition) return;
+       snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
+       SendToProgram(buf, &first);
+}
+
+void
 LeftClick (ClickType clickType, int xPix, int yPix)
 {
     int x, y;
@@ -7069,6 +7163,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);
@@ -7153,6 +7248,7 @@ LeftClick (ClickType clickType, int xPix, int yPix)
            /* First square */
            if (OKToStartUserMove(fromX, fromY)) {
                second = 0;
+               ReportClick("lift", x, y);
                MarkTargetSquares(0);
                if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
                DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
@@ -7201,7 +7297,7 @@ LeftClick (ClickType clickType, int xPix, int yPix)
            }
            promoDefaultAltered = FALSE;
            MarkTargetSquares(1);
-          if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
+          if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
            if (appData.highlightDragging) {
                SetHighlights(x, y, -1, -1);
            } else {
@@ -7215,6 +7311,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)) {
@@ -7251,6 +7348,7 @@ LeftClick (ClickType clickType, int xPix, int yPix)
            second = sweepSelecting = 0;
            fromX = fromY = -1;
            gatingPiece = EmptySquare;
+           MarkTargetSquares(1);
            ClearHighlights();
            gotPremove = 0;
            ClearPremoveHighlights();
@@ -7263,6 +7361,13 @@ LeftClick (ClickType clickType, int xPix, int yPix)
 
     clearFlag = 0;
 
+    if(gameMode != EditPosition && !appData.testLegality && !legal[y][x]) {
+       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) {
@@ -7278,7 +7383,7 @@ LeftClick (ClickType clickType, int xPix, int yPix)
            if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
            return;
        }
-       if(HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
+       if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
          if(appData.sweepSelect) {
            ChessSquare piece = boards[currentMove][fromY][fromX];
            promoSweep = defaultPromoChoice;
@@ -7342,6 +7447,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));
 
@@ -8007,6 +8113,7 @@ SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
        SendToProgram("force\n", cps);
        cps->bookSuspend = TRUE; // flag indicating it has to be restarted
     }
+    if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
     // now arrange restart after book miss
     if(bookHit) {
@@ -8283,6 +8390,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;
@@ -8423,12 +8541,25 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
     }
 
-    if ((!appData.testLegality || gameInfo.variant == VariantFairy) &&
-                                       !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
-      int dummy, s=6; char buf[MSG_SIZ];
+    if (!strncmp(message, "setup ", 6) && 
+       (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown || NonStandardBoardSize())
+                                       ) { // [HGM] allow first engine to define opening position
+      int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
+      *buf = NULLCHAR;
       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
       if(startedFromSetupPosition) return;
+      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);
       DrawPosition(TRUE, boards[0]);
       startedFromSetupPosition = TRUE;
@@ -8544,6 +8675,28 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
        return;
     }
+    if(!strncmp(message, "highlight ", 10)) {
+       if(appData.testLegality && appData.markers) return;
+       MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
+       return;
+    }
+    if(!strncmp(message, "click ", 6)) {
+       char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
+       if(appData.testLegality || !appData.oneClick) return;
+       sscanf(message+6, "%c%d%c", &f, &y, &c);
+       x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
+       if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
+       x = x*squareSize + (x+1)*lineGap + squareSize/2;
+       y = y*squareSize + (y+1)*lineGap + squareSize/2;
+       f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
+       if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
+           LeftClick(Release, lastLeftX, lastLeftY);
+       controlKey  = (c == ',');
+       LeftClick(Press, x, y);
+       LeftClick(Release, x, y);
+       first.highlight = f;
+       return;
+    }
     /*
      * If the move is illegal, cancel it and redraw the board.
      * Also deal with other error cases.  Matching is rather loose
@@ -9845,11 +9998,39 @@ SendEgtPath (ChessProgramState *cps)
        }
 }
 
+static int
+NonStandardBoardSize ()
+{
+      /* [HGM] Awkward testing. Should really be a table */
+      int overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
+      if( gameInfo.variant == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
+      if( gameInfo.variant == VariantXiangqi )
+           overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
+      if( gameInfo.variant == VariantShogi )
+           overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
+      if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
+           overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
+      if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
+          gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
+           overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
+      if( gameInfo.variant == VariantCourier )
+           overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
+      if( gameInfo.variant == VariantSuper )
+           overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
+      if( gameInfo.variant == VariantGreat )
+           overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
+      if( gameInfo.variant == VariantSChess )
+           overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
+      if( gameInfo.variant == VariantGrand )
+           overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
+      return overruled;
+}
+
 void
 InitChessProgram (ChessProgramState *cps, int setup)
 /* setup needed to setup FRC opening position */
 {
-    char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
+    char buf[MSG_SIZ], b[MSG_SIZ];
     if (appData.noChessProgram) return;
     hintRequested = FALSE;
     bookRequested = FALSE;
@@ -9881,29 +10062,7 @@ InitChessProgram (ChessProgramState *cps, int setup)
        return;
       }
 
-      /* [HGM] make prefix for non-standard board size. Awkward testing... */
-      overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
-      if( gameInfo.variant == VariantXiangqi )
-           overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
-      if( gameInfo.variant == VariantShogi )
-           overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
-      if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
-           overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
-      if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
-          gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
-           overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
-      if( gameInfo.variant == VariantCourier )
-           overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
-      if( gameInfo.variant == VariantSuper )
-           overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
-      if( gameInfo.variant == VariantGreat )
-           overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
-      if( gameInfo.variant == VariantSChess )
-           overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
-      if( gameInfo.variant == VariantGrand )
-           overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
-
-      if(overruled) {
+      if(NonStandardBoardSize()) { /* [HGM] make prefix for non-standard board size. */
        snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
                 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
            /* [HGM] varsize: try first if this defiant size variant is specifically known */
@@ -10120,7 +10279,7 @@ WriteTourneyFile (char *results, FILE *f)
        fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
        fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
        fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
-       fprintf(f, "-polyglotBook %s\n", appData.polyglotBook);
+       fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
        fprintf(f, "-bookDepth %d\n", appData.bookDepth);
        fprintf(f, "-bookVariation %d\n", appData.bookStrength);
        fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
@@ -10821,6 +10980,7 @@ GameEnds (ChessMove result, char *resultDetails, int whosays)
                    PlayIcsUnfinishedSound();
                }
            }
+           if(appData.quitNext) { ExitEvent(0); return; }
        } else if (gameMode == EditGame ||
                   gameMode == PlayFromGameFile ||
                   gameMode == AnalyzeMode ||
@@ -11080,6 +11240,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;
@@ -11171,17 +11332,23 @@ AutoPlayOneMove ()
     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
       return FALSE;
 
-    if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
+    if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
       pvInfoList[currentMove].depth = programStats.depth;
       pvInfoList[currentMove].score = programStats.score;
       pvInfoList[currentMove].time  = 0;
       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
+      else { // append analysis of final position as comment
+       char buf[MSG_SIZ];
+       snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
+       AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
+      }
+      programStats.depth = 0;
     }
 
     if (currentMove >= forwardMostMove) {
       if(gameMode == AnalyzeFile) {
          if(appData.loadGameIndex == -1) {
-           GameEnds(EndOfFile, NULL, GE_FILE);
+           GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
           ScheduleDelayedEvent(AnalyzeNextGame, 10);
          } else {
           ExitAnalyzeMode(); SendToProgram("force\n", &first);
@@ -12031,7 +12198,7 @@ LoadGame (FILE *f, int gameNumber, char *title, int useList)
            gn = 1;
        }
        else {
-           if(gameMode == AnalyzeFile && appData.loadGameIndex == -1)
+           if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
              appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
            else
            DisplayError(_("Game number out of range"), 0);
@@ -12427,10 +12594,12 @@ LoadGame (FILE *f, int gameNumber, char *title, int useList)
 
     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
 
-    if (oldGameMode == AnalyzeFile ||
-       oldGameMode == AnalyzeMode) {
+    if (oldGameMode == AnalyzeFile) {
       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
       AnalyzeFileEvent();
+    } else
+    if (oldGameMode == AnalyzeMode) {
+      AnalyzeFileEvent();
     }
 
     if(creatingBook) return TRUE;
@@ -12797,7 +12966,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);
@@ -13066,7 +13235,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);
     }
@@ -14487,6 +14656,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;
@@ -15203,7 +15373,7 @@ void
 CreateBookEvent ()
 {
     ListGame * lg = (ListGame *) gameList.head;
-    FILE *f;
+    FILE *f, *g;
     int nItem;
     static int secondTime = FALSE;
 
@@ -15212,8 +15382,8 @@ CreateBookEvent ()
         return;
     }
 
-    if(!secondTime && (f = fopen(appData.polyglotBook, "r"))) {
-        fclose(f);
+    if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
+        fclose(g);
        secondTime++;
        DisplayNote(_("Book file exists! Try again for overwrite."));
        return;
@@ -15511,7 +15681,8 @@ AppendComment (int index, char *text, Boolean addBraces)
     int oldlen, len;
     char *old;
 
-if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
+if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
+    if(addBraces == 3) addBraces = 0; else // force appending literally
     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
 
     CrushCRs(text);
@@ -15578,8 +15749,11 @@ GetInfoFromComment (int index, char * text)
         int time = -1, sec = 0, deci;
         char * s_eval = FindStr( text, "[%eval " );
         char * s_emt = FindStr( text, "[%emt " );
-
+#if 0
         if( s_eval != NULL || s_emt != NULL ) {
+#else
+        if(0) { // [HGM] this code is not finished, and could actually be detrimental
+#endif
             /* New style */
             char delim;
 
@@ -15609,6 +15783,7 @@ GetInfoFromComment (int index, char * text)
             }
 
             p = text;
+            if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
             if(p[1] == '(') { // comment starts with PV
                p = strchr(p, ')'); // locate end of PV
                if(p == NULL || sep < p+5) return text;
@@ -15632,7 +15807,7 @@ GetInfoFromComment (int index, char * text)
             if(sec >= 0) time = 600*time + 10*sec; else
             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
 
-            score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
+            score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
 
             /* [HGM] PV time: now locate end of PV info */
             while( *++sep >= '0' && *sep <= '9'); // strip depth
@@ -15906,6 +16081,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)
 {
@@ -15943,14 +16139,15 @@ IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
 }
 
 int
-StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
+StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
 {
   char buf[MSG_SIZ];
   int len = strlen(name);
   if (strncmp((*p), name, len) == 0
       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
     (*p) += len + 2;
-    sscanf(*p, "%[^\"]", loc);
+    ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
+    sscanf(*p, "%[^\"]", *loc);
     while (**p && **p != '\"') (*p)++;
     if (**p == '\"') (*p)++;
     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
@@ -16071,7 +16268,7 @@ void
 ParseFeatures (char *args, ChessProgramState *cps)
 {
   char *p = args;
-  char *q;
+  char *q = NULL;
   int val;
   char buf[MSG_SIZ];
 
@@ -16091,7 +16288,7 @@ ParseFeatures (char *args, ChessProgramState *cps)
       continue;
     }
     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
-    if (StringFeature(&p, "myname", cps->tidy, cps)) {
+    if (StringFeature(&p, "myname", &cps->tidy, cps)) {
       if (gameMode == TwoMachinesPlay) {
        DisplayTwoMachinesTitle();
       } else {
@@ -16099,7 +16296,7 @@ ParseFeatures (char *args, ChessProgramState *cps)
       }
       continue;
     }
-    if (StringFeature(&p, "variants", cps->variants, cps)) continue;
+    if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
@@ -16119,17 +16316,17 @@ ParseFeatures (char *args, ChessProgramState *cps)
     /* End of additions by Tord */
 
     /* [HGM] added features: */
+    if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
-    if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
-    if (StringFeature(&p, "option", buf, cps)) {
-       if(cps->reload) continue; // we are reloading because of xreuse
+    if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
+    if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
+       if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
        FREE(cps->option[cps->nrOptions].name);
-       cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
-       safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
+       cps->option[cps->nrOptions].name = q; q = NULL;
        if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
          snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
            SendToProgram(buf, cps);
@@ -16975,7 +17172,7 @@ PGNDate ()
 
 
 char *
-PositionToFEN (int move, char *overrideCastling)
+PositionToFEN (int move, char *overrideCastling, int moveCounts)
 {
     int i, j, fromX, fromY, toX, toY;
     int whiteToPlay;
@@ -17007,7 +17204,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));
@@ -17105,7 +17302,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;
@@ -17137,9 +17335,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++)
@@ -17151,9 +17350,10 @@ 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);
 }
@@ -17195,6 +17395,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 */
@@ -17389,7 +17591,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 {