Allow entry of variations in PlayFromGameFile mode
[xboard.git] / backend.c
index e73b5c6..a3ce9ed 100644 (file)
--- a/backend.c
+++ b/backend.c
@@ -63,6 +63,7 @@ int flock(int f, int code);
 
 #else
 
+#include <sys/file.h>
 #define DoSleep( n ) if( (n) >= 0) sleep(n)
 #define SLASH '/'
 
@@ -4860,6 +4861,11 @@ SendMoveToProgram(moveNum, cps)
 {
     char buf[MSG_SIZ];
 
+    if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
+       // null move in variant where engine does not understand it (for analysis purposes)
+       SendBoard(cps, moveNum + 1); // send position after move in stead.
+       return;
+    }
     if (cps->useUsermove) {
       SendToProgram("usermove ", cps);
     }
@@ -4900,6 +4906,7 @@ SendMoveToProgram(moveNum, 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
          snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
                                              moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
        } else
@@ -5075,6 +5082,7 @@ CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
      char move[7];
 {
     if (rf == DROP_RANK) {
+      if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
       sprintf(move, "%c@%c%c\n",
                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
     } else {
@@ -6223,7 +6231,6 @@ OKToStartUserMove(x, y)
       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
 
     switch (gameMode) {
-      case PlayFromGameFile:
       case AnalyzeFile:
       case TwoMachinesPlay:
       case EndOfGame:
@@ -6251,6 +6258,8 @@ OKToStartUserMove(x, y)
        }
        break;
 
+      case PlayFromGameFile:
+           if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
       case EditGame:
        if (!white_piece && WhiteOnMove(currentMove)) {
            DisplayMoveError(_("It is White's turn"));
@@ -6294,6 +6303,7 @@ OKToStartUserMove(x, y)
     }
     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
        && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
+       && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
        && gameMode != AnalyzeFile && gameMode != Training) {
        DisplayMoveError(_("Displayed position is not current"));
        return FALSE;
@@ -6385,7 +6395,6 @@ UserMoveEvent(fromX, fromY, toX, toY, promoChar)
        */
 
     switch (gameMode) {
-      case PlayFromGameFile:
       case AnalyzeFile:
       case TwoMachinesPlay:
       case EndOfGame:
@@ -6411,6 +6420,8 @@ UserMoveEvent(fromX, fromY, toX, toY, promoChar)
        }
        break;
 
+      case PlayFromGameFile:
+           if(!shiftKey ||!appData.variations) return; // [HGM] only variations
       case EditGame:
       case IcsExamining:
       case BeginningOfGame:
@@ -6526,6 +6537,9 @@ UserMoveEvent(fromX, fromY, toX, toY, promoChar)
     /* [HGM] always test for legality, to get promotion info */
     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
                                          fromY, fromX, toY, toX, promoChar);
+
+    if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
+
     /* [HGM] but possibly ignore an IllegalMove result */
     if (appData.testLegality) {
        if (moveType == IllegalMove || moveType == ImpossibleMove) {
@@ -6602,7 +6616,7 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
 
   /* Ok, now we know that the move is good, so we can kill
      the previous line in Analysis Mode */
-  if ((gameMode == AnalyzeMode || gameMode == EditGame)
+  if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
                                && currentMove < forwardMostMove) {
     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
     else forwardMostMove = currentMove;
@@ -6667,6 +6681,9 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
         // [HGM] book: if program might be playing, let it use book
        bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
        first.maybeThinking = TRUE;
+    } else if(fromY == DROP_RANK && fromX == EmptySquare) {
+       if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
+       SendBoard(&first, currentMove+1);
     } else SendMoveToProgram(forwardMostMove-1, &first);
     if (currentMove == cmailOldMove + 1) {
       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
@@ -6744,7 +6761,7 @@ void
 MarkTargetSquares(int clear)
 {
   int x, y;
-  if(!appData.markers || !appData.highlightDragging ||
+  if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
      !appData.testLegality || gameMode == EditPosition) return;
   if(clear) {
     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
@@ -9005,15 +9022,19 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board)
       oldEP = (signed char)board[EP_STATUS];
       board[EP_STATUS] = EP_NONE;
 
-      if( board[toY][toX] != EmptySquare )
-           board[EP_STATUS] = EP_CAPTURE;
-
   if (fromY == DROP_RANK) {
        /* must be first */
+        if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
+           board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
+           return;
+       }
         piece = board[toY][toX] = (ChessSquare) fromX;
   } else {
       int i;
 
+      if( board[toY][toX] != EmptySquare )
+           board[EP_STATUS] = EP_CAPTURE;
+
       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
@@ -10444,6 +10465,7 @@ Reset(redraw, init)
                redraw, init, gameMode);
     }
     CleanupTail(); // [HGM] vari: delete any stored variations
+    CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
     pausing = pauseExamInvalid = FALSE;
     startedFromSetupPosition = blackPlaysFirst = FALSE;
     firstMove = TRUE;
@@ -10551,8 +10573,8 @@ AutoPlayOneMove()
 
     if (currentMove >= forwardMostMove) {
       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
-      gameMode = EditGame;
-      ModeHighlight();
+//      gameMode = EndOfGame;
+//      ModeHighlight();
 
       /* [AS] Clear current move marker at the end of a game */
       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
@@ -11441,8 +11463,6 @@ LoadGame(f, gameNumber, title, useList)
 
     if (matchMode || appData.timeDelay == 0) {
       ToEndEvent();
-      gameMode = EditGame;
-      ModeHighlight();
     } else if (appData.timeDelay > 0) {
       AutoPlayGameLoop();
     }
@@ -13558,7 +13578,9 @@ ClockClick(int which)
          if (gameMode == EditPosition || gameMode == IcsExamining) {
            if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
            SetBlackToPlayEvent();
-         } else if (gameMode == EditGame || shiftKey) {
+         } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
+          UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
+         } else if (shiftKey) {
            AdjustClock(which, -1);
          } else if (gameMode == IcsPlayingWhite ||
                     gameMode == MachinePlaysBlack) {
@@ -13568,7 +13590,9 @@ ClockClick(int which)
          if (gameMode == EditPosition || gameMode == IcsExamining) {
            if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
            SetWhiteToPlayEvent();
-         } else if (gameMode == EditGame || shiftKey) {
+         } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
+          UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
+         } else if (shiftKey) {
            AdjustClock(which, -1);
          } else if (gameMode == IcsPlayingBlack ||
                   gameMode == MachinePlaysWhite) {
@@ -13845,6 +13869,16 @@ BackwardInner(target)
     if (gameMode == EditGame || gameMode==AnalyzeMode ||
        gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
        while (currentMove > target) {
+           if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
+               // null move cannot be undone. Reload program with move history before it.
+               int i;
+               for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
+                   if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
+               }
+               SendBoard(&first, i); 
+               for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
+               break;
+           }
            SendToProgram("undo\n", &first);
            currentMove--;
        }
@@ -16359,6 +16393,7 @@ int wrap(char *dest, char *src, int count, int width, int *lp)
 }
 
 // [HGM] vari: routines for shelving variations
+Boolean modeRestore = FALSE;
 
 void
 PushInner(int firstMove, int lastMove)
@@ -16402,6 +16437,7 @@ PushTail(int firstMove, int lastMove)
 
        PushInner(firstMove, lastMove);
        if(storedGames == 1) GreyRevert(FALSE);
+       if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
 }
 
 void
@@ -16410,8 +16446,8 @@ PopInner(Boolean annotate)
        int i, j, nrMoves;
        char buf[8000], moveBuf[20];
 
-       storedGames--;
-       ToNrEvent(savedFirst[storedGames]); // sets currentMove
+       ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
+       storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
        nrMoves = savedLast[storedGames] - currentMove;
        if(annotate) {
                int cnt = 10;
@@ -16459,8 +16495,10 @@ PopTail(Boolean annotate)
        CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
 
        PopInner(annotate);
+       if(currentMove < forwardMostMove) ForwardEvent(); else
+       HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
 
-       if(storedGames == 0) GreyRevert(TRUE);
+       if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
        return TRUE;
 }
 
@@ -16487,7 +16525,7 @@ LoadVariation(int index, char *text)
        char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
        int level = 0, move;
 
-       if(gameMode != EditGame && gameMode != AnalyzeMode) return;
+       if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
        // first find outermost bracketing variation
        while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
            if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}