Merge branch 'v4.8.x' into master
authorArun Persaud <arun@nubati.net>
Fri, 8 May 2015 15:00:11 +0000 (08:00 -0700)
committerArun Persaud <arun@nubati.net>
Fri, 8 May 2015 15:00:11 +0000 (08:00 -0700)
1  2 
backend.c
gtk/xboard.c
menus.c
moves.c
po/LINGUAS

diff --combined backend.c
+++ b/backend.c
@@@ -5141,17 -5141,10 +5141,17 @@@ SendMoveToProgram (int moveNum, ChessPr
        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');
 +      char *m = moveList[moveNum];
 +      if((boards[moveNum][m[6]-ONE][m[5]-AAA] < BlackPawn) == (boards[moveNum][m[1]-ONE][m[0]-AAA] < BlackPawn)) // move is kludge to indicate castling
 +        snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
 +                                             m[2], m[3] - '0',
 +                                             m[5], m[6] - '0',
 +                                             m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
 +      else
 +        snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
 +                                             m[5], m[6] - '0',
 +                                             m[5], m[6] - '0',
 +                                             m[2], m[3] - '0');
          SendToProgram(buf, cps);
        } else
        if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
@@@ -5228,7 -5221,7 +5228,7 @@@ SendMoveToICS (ChessMove moveType, int 
        case WhitePromotion:
        case BlackPromotion:
          if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
-            gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
+            gameInfo.variant == VariantMakruk)
          snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
                  AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
                PieceToChar(WhiteFerz));
@@@ -5334,11 -5327,11 +5334,11 @@@ 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
 +int killX = -1, killY = -1, kill2X, kill2Y; // [HGM] lion: used for passing e.p. capture square to MakeMove
  int legNr = 1;
  
  void
 -CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
 +CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[9])
  {
      if (rf == DROP_RANK) {
        if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
        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);
 +        if(killX >= 0 && killY >= 0) {
 +          sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
 +          if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%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);
@@@ -5402,8 -5392,8 +5402,8 @@@ Sweep (int 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;
 +      else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
 +      else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
        if(!step) step = -1;
      } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
            !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
@@@ -5530,12 -5520,6 +5530,12 @@@ ParseOneMove (char *move, int moveNum, 
        case BlackASideCastleFR:
        /* End of code added by Tord */
        case IllegalMove:               /* bug or odd chess variant */
 +      if(currentMoveString[1] == '@') { // illegal drop
 +        *fromX = WhiteOnMove(moveNum) ?
 +          (int) CharToPiece(ToUpper(currentMoveString[0])) :
 +          (int) CharToPiece(ToLower(currentMoveString[0]));
 +        goto drop;
 +      }
          *fromX = currentMoveString[0] - AAA;
          *fromY = currentMoveString[1] - ONE;
          *toX = currentMoveString[2] - AAA;
        *fromX = *moveType == WhiteDrop ?
          (int) CharToPiece(ToUpper(currentMoveString[0])) :
          (int) CharToPiece(ToLower(currentMoveString[0]));
 +      drop:
        *fromY = DROP_RANK;
          *toX = currentMoveString[2] - AAA;
          *toY = currentMoveString[3] - ONE;
@@@ -5950,40 -5933,23 +5950,40 @@@ SetUpShuffle (Board board, int number
  }
  
  int
 -SetCharTable (char *table, const char * map)
 +ptclen (const char *s, char *escapes)
 +{
 +    int n = 0;
 +    if(!*escapes) return strlen(s);
 +    while(*s) n += (*s != ':' && !strchr(escapes, *s)), s++;
 +    return n;
 +}
 +
 +int
 +SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
  /* [HGM] moved here from winboard.c because of its general usefulness */
  /*       Basically a safe strcpy that uses the last character as King */
  {
      int result = FALSE; int NrPieces;
  
 -    if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
 +    if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
                      && NrPieces >= 12 && !(NrPieces&1)) {
 -        int i; /* [HGM] Accept even length from 12 to 34 */
 +        int i, j = 0; /* [HGM] Accept even length from 12 to 88 */
  
          for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
          for( i=0; i<NrPieces/2-1; i++ ) {
 -            table[i] = map[i];
 -            table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
 +            char *p;
 +            if(map[j] == ':' && *escapes) i = CHUPROMOTED WhitePawn, j++;
 +            table[i] = map[j++];
 +            if(p = strchr(escapes, map[j])) j++, table[i] += 64*(p - escapes + 1);
 +        }
 +        table[(int) WhiteKing]  = map[j++];
 +        for( i=0; i<NrPieces/2-1; i++ ) {
 +            char *p;
 +            if(map[j] == ':' && *escapes) i = CHUPROMOTED WhitePawn, j++;
 +            table[WHITE_TO_BLACK i] = map[j++];
 +            if(p = strchr(escapes, map[j])) j++, table[WHITE_TO_BLACK i] += 64*(p - escapes + 1);
          }
 -        table[(int) WhiteKing]  = map[NrPieces/2-1];
 -        table[(int) BlackKing]  = map[NrPieces-1];
 +        table[(int) BlackKing]  = map[j++];
  
          result = TRUE;
      }
      return result;
  }
  
 +int
 +SetCharTable (unsigned char *table, const char * map)
 +{
 +    return SetCharTableEsc(table, map, "");
 +}
 +
  void
  Prelude (Board board)
  {     // [HGM] superchess: random selection of exo-pieces
@@@ -6170,8 -6130,8 +6170,8 @@@ InitPosition (int redraw
        gameInfo.boardWidth  = 12;
        gameInfo.boardHeight = 12;
        nrCastlingRights = 0;
 -      SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
 -                                "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
 +      SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN:+.++.++++++++++.+++++K"
 +                                   "p.brqsexogcathd.vmlifn:+.++.++++++++++.+++++k", SUFFIXES);
        break;
      case VariantCourier:
        pieces = CourierArray;
  
      /* User pieceToChar list overrules defaults */
      if(appData.pieceToCharTable != NULL)
 -        SetCharTable(pieceToChar, appData.pieceToCharTable);
 +        SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
  
      for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
  
@@@ -6556,8 -6516,10 +6556,10 @@@ DefaultPromoChoice (int white
  {
      ChessSquare result;
      if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
-        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
+        gameInfo.variant == VariantMakruk)
        result = WhiteFerz; // no choice
+     else if(gameInfo.variant == VariantASEAN)
+       result = WhiteRook; // no choice
      else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
        result= WhiteKing; // in Suicide Q is the last thing we want
      else if(gameInfo.variant == VariantSpartan)
@@@ -6591,8 -6553,8 +6593,8 @@@ HasPromotionChoice (int fromX, int from
          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;
 +    } else if(gameInfo.variant == VariantShogi) {
 +        promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
          highestPromotingPiece = (int)WhiteAlfil;
      } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
          promotionZoneSize = 3;
  
      // 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 || gameInfo.variant == VariantASEAN) {
+        gameInfo.variant == VariantMakruk) {
        ChessSquare p=BlackFerz;  // no choice
        while(p < EmptySquare) {  //but make sure we use piece that exists
            *promoChoice = PieceToChar(p++);
@@@ -6988,7 -6950,7 +6990,7 @@@ UserMoveEvent(int fromX, int fromY, in
        /* EditPosition, empty square, or different color piece;
           click-click move is possible */
        if (toX == -2 || toY == -2) {
 -          boards[0][fromY][fromX] = EmptySquare;
 +          boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
            DrawPosition(FALSE, boards[currentMove]);
            return;
        } else if (toX >= 0 && toY >= 0) {
      if(addToBookFlag) { // adding moves to book
        char buf[MSG_SIZ], move[MSG_SIZ];
          CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
 +      if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d", fromX + AAA, fromY + ONE - '0', killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0');
        snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
        AddBookMove(buf);
        addToBookFlag = FALSE;
@@@ -7361,11 -7322,11 +7363,11 @@@ CanPromote (ChessSquare piece, int y
        if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
           gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
           gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
-          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
+          gameInfo.variant == VariantMakruk) return FALSE;
        return (piece == BlackPawn && y <= zone ||
                piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
 -              piece == BlackLance && y == 1 ||
 -              piece == WhiteLance && y == BOARD_HEIGHT-2 );
 +              piece == BlackLance && y <= zone ||
 +              piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
  }
  
  void
@@@ -7408,8 -7369,6 +7410,8 @@@ void ReportClick(char *action, int x, i
        SendToProgram(buf, &first);
  }
  
 +Boolean right; // instructs front-end to use button-1 events as if they were button 3
 +
  void
  LeftClick (ClickType clickType, int xPix, int yPix)
  {
      ChessSquare piece;
      static TimeMark lastClickTime, prevClickTime;
  
 -    if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
 -
 -    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);
      if (!flipView && y >= 0) {
        x = BOARD_WIDTH - 1 - x;
      }
  
 +    if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
 +      static int dummy;
 +      RightClick(clickType, xPix, yPix, &dummy, &dummy);
 +      right = TRUE;
 +      return;
 +    }
 +
 +    if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
 +
 +    prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
 +
 +    if (clickType == Press) ErrorPopDown();
 +    lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
 +
      if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
        defaultPromoChoice = promoSweep;
        promoSweep = EmptySquare;   // terminate sweep
            return;
        }
      }
 -
 +printf("to click %d,%d\n",x,y);
      /* fromX != -1 */
      if (clickType == Press && gameMode != EditPosition) {
        ChessSquare fromP;
        toP = boards[currentMove][y][x];
        frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
        if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
 +          marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
           ((WhitePawn <= fromP && fromP <= WhiteKing &&
             WhitePawn <= toP && toP <= WhiteKing &&
             !(fromP == WhiteKing && toP == WhiteRook && frc) &&
                else gatingPiece = doubleClick ? fromP : EmptySquare;
                fromX = x;
                fromY = y; dragging = 1;
 -              ReportClick("lift", x, y);
 +              if(!second) ReportClick("lift", x, y);
                MarkTargetSquares(0);
                DragPieceBegin(xPix, yPix, FALSE);
                if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
        // ignore clicks on holdings
        if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
      }
 +printf("A type=%d\n",clickType);
  
 -    if (clickType == Release && x == fromX && y == fromY && killX < 0) {
 +    if(x == fromX && y == fromY && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
 +      gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
 +      return;
 +    }
 +
 +    if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
        DragPieceEnd(xPix, yPix); dragging = 0;
        if(clearFlag) {
            // a deferred attempt to click-click move an empty square on top of a piece
            /* Undo animation damage if any */
            DrawPosition(FALSE, NULL);
        }
 -      if (second || sweepSelecting) {
 +      if (second) {
            /* Second up/down in same square; just abort move */
 -          if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
 -          second = sweepSelecting = 0;
 +          second = 0;
            fromX = fromY = -1;
            gatingPiece = EmptySquare;
            MarkTargetSquares(1);
      }
  
      clearFlag = 0;
 -
 +printf("B\n");
      if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
         fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
        if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
        DrawPosition(TRUE, NULL);
        return; // ignore to-click
      }
 -
 +printf("(%d,%d)-(%d,%d) %d %d\n",fromX,fromY,toX,toY,x,y);
      /* we now have a different from- and (possibly off-board) to-square */
      /* Completed move */
      if(!sweepSelecting) {
        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 {
 +        if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
 +        else {
 +          kill2X = killX; kill2Y = killY;
            killX = x; killY = y;     //remeber this square as intermediate
            ReportClick("put", x, y); // and inform engine
            ReportClick("lift", x, y);
  
      if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
  
 -    if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
 +    if(legal[toY][toX] == 2) promoChoice = ToLower(PieceToChar(defaultPromoChoice)); // highlight-induced promotion
 +
 +    if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
        SetHighlights(fromX, fromY, toX, toY);
          MarkTargetSquares(1);
        if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
@@@ -8697,23 -8639,17 +8699,23 @@@ FakeBookMove: // [HGM] book: we jump he
          if(cps->alphaRank) AlphaRank(machineMove, 4);
  
        // [HGM] lion: (some very limited) support for Alien protocol
 -      killX = killY = -1;
 +      killX = killY = kill2X = kill2Y = -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!)
 +      }
 +      if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
 +          safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
 +          safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
 +      }
 +      if(firstLeg[0]) { // there was a previous leg;
 +          // only support case where same piece makes two step
            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++;
 +          while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
 +          if(*p == *buf)          // if first-leg to not equal to second-leg from first leg says unmodified (assume it ia King move of castling)
            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;
          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 via %c%c) res=%d",
 -                    machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
 +            snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
 +                    machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
            if (gameMode == TwoMachinesPlay) {
              GameEnds(machineWhite ? BlackWins : WhiteWins,
                         buf1, GE_XBOARD);
        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);
 +        s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
          ASSIGN(appData.pieceToCharTable, buf);
        }
        dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
            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
 +          if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
            startedFromSetupPosition = FALSE;
          }
        }
      }
      if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
        ChessSquare piece = WhitePawn;
 -      char *p=buf2;
 -      if(*p == '+') piece = CHUPROMOTED WhitePawn, p++;
 -      piece += CharToPiece(*p) - WhitePawn;
 +      char *p=buf2, *q, *s = SUFFIXES, ID = *p;
 +      if(*p == '+') piece = CHUPROMOTED WhitePawn, ID = *++p;
 +      if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
 +      piece += CharToPiece(ID) - WhitePawn;
        if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
        /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
        /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
        return;
      }
      if(!strncmp(message, "highlight ", 10)) {
 -      if(appData.testLegality && appData.markers) return;
 +      if(appData.testLegality && !*engineVariant && appData.markers) return;
        MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
        return;
      }
@@@ -9956,14 -9891,14 +9958,14 @@@ ParseGameHistory (char *game
  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;
 +  ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, berolina = 0;
    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 */
  
        if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
 -      oldEP = (signed char)board[EP_STATUS];
 +      oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
        board[EP_STATUS] = EP_NONE;
        board[EP_FILE] = board[EP_RANK] = 100;
  
  //      ChessSquare victim;
        int i;
  
 -      if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
 +      if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
  //           victim = board[killY][killX],
 +           killed = board[killY][killX],
             board[killY][killX] = EmptySquare,
             board[EP_STATUS] = EP_CAPTURE;
 +           if( kill2X >= 0 && kill2Y >= 0)
 +             killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
 +      }
  
        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
 -      } else
 -      if( board[fromY][fromX] == WhitePawn ) {
 +      pawn = board[fromY][fromX];
 +      if( pawn == WhiteLance || pawn == BlackLance ) {
 +           if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
 +               if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
 +               else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
 +           }
 +      }
 +      if( pawn == WhitePawn ) {
             if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
               board[EP_STATUS] = EP_PAWN_MOVE;
 -           if( toY-fromY==2) {
 -               board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = (fromY + toY)/2;
 +           if( toY-fromY>=2) {
 +               board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
                 if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
                        gameInfo.variant != VariantBerolina || toX < fromX)
                      board[EP_STATUS] = toX | berolina;
                      board[EP_STATUS] = toX;
           }
        } else
 -      if( board[fromY][fromX] == BlackPawn ) {
 +      if( pawn == BlackPawn ) {
             if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
               board[EP_STATUS] = EP_PAWN_MOVE;
 -           if( toY-fromY== -2) {
 -               board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = (fromY + toY)/2;
 +           if( toY-fromY<= -2) {
 +               board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
                 if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
                        gameInfo.variant != VariantBerolina || toX < fromX)
                      board[EP_STATUS] = toX | berolina;
       if(gameInfo.variant == VariantKnightmate)
           king += (int) WhiteUnicorn - (int) WhiteKing;
  
 +    if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
 +       && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
 +      board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
 +      board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
 +        board[EP_STATUS] = EP_NONE; // capture was fake!
 +    } else
      /* Code added by Tord: */
      /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
      if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
      } else if (board[fromY][fromX] == king
          && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
          && toY == fromY && toX > fromX+1) {
 +      for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++); // castle with nearest piece
 +        board[fromY][toX-1] = board[fromY][rookX];
 +        board[fromY][rookX] = EmptySquare;
        board[fromY][fromX] = EmptySquare;
          board[toY][toX] = king;
 -        board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
 -        board[fromY][BOARD_RGHT-1] = EmptySquare;
      } else if (board[fromY][fromX] == king
          && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
                 && toY == fromY && toX < fromX-1) {
 +      for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--); // castle with nearest piece
 +        board[fromY][toX+1] = board[fromY][rookX];
 +        board[fromY][rookX] = EmptySquare;
        board[fromY][fromX] = EmptySquare;
          board[toY][toX] = king;
 -        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 != VariantChu)
                 && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
               && (toX != fromX)
                 && gameInfo.variant != VariantXiangqi
                 && gameInfo.variant != VariantBerolina
 -             && (board[fromY][fromX] == WhitePawn)
 +             && (pawn == WhitePawn)
               && (board[toY][toX] == EmptySquare)) {
        board[fromY][fromX] = EmptySquare;
 -      board[toY][toX] = WhitePawn;
 -      captured = board[toY - 1][toX];
 -      board[toY - 1][toX] = EmptySquare;
 +      board[toY][toX] = piece;
 +      if(toY == epRank - 128 + 1)
 +          captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
 +      else
 +          captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
      } else if ((fromY == BOARD_HEIGHT-4)
               && (toX == fromX)
                 && gameInfo.variant == VariantBerolina
      } else if (board[fromY][fromX] == king
          && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
                 && toY == fromY && toX > fromX+1) {
 +      for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++);
 +        board[fromY][toX-1] = board[fromY][rookX];
 +        board[fromY][rookX] = EmptySquare;
        board[fromY][fromX] = EmptySquare;
          board[toY][toX] = king;
 -        board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
 -        board[fromY][BOARD_RGHT-1] = EmptySquare;
      } else if (board[fromY][fromX] == king
          && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
                 && toY == fromY && toX < fromX-1) {
 +      for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--);
 +        board[fromY][toX+1] = board[fromY][rookX];
 +        board[fromY][rookX] = EmptySquare;
        board[fromY][fromX] = EmptySquare;
          board[toY][toX] = king;
 -        board[toY][toX+1] = board[fromY][BOARD_LEFT];
 -        board[fromY][BOARD_LEFT] = EmptySquare;
      } else if (fromY == 7 && fromX == 3
               && board[fromY][fromX] == BlackKing
               && toY == 7 && toX == 5) {
               && (toX != fromX)
                 && gameInfo.variant != VariantXiangqi
                 && gameInfo.variant != VariantBerolina
 -             && (board[fromY][fromX] == BlackPawn)
 +             && (pawn == BlackPawn)
               && (board[toY][toX] == EmptySquare)) {
        board[fromY][fromX] = EmptySquare;
 -      board[toY][toX] = BlackPawn;
 -      captured = board[toY + 1][toX];
 -      board[toY + 1][toX] = EmptySquare;
 +      board[toY][toX] = piece;
 +      if(toY == epRank - 128 - 1)
 +          captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
 +      else
 +          captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
      } else if ((fromY == 3)
               && (toX == fromX)
                 && gameInfo.variant == VariantBerolina
@@@ -12113,13 -12027,8 +12115,13 @@@ LoadGameOneMove (ChessMove readAhead
            if (appData.debugMode)
              fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
                      yy_text, currentMoveString);
 -            fromX = currentMoveString[0] - AAA;
 -            fromY = currentMoveString[1] - ONE;
 +            if(currentMoveString[1] == '@') {
 +                fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
 +                fromY = DROP_RANK;
 +            } else {
 +                fromX = currentMoveString[0] - AAA;
 +                fromY = currentMoveString[1] - ONE;
 +            }
              toX = currentMoveString[2] - AAA;
              toY = currentMoveString[3] - ONE;
            promoChar = currentMoveString[4];
@@@ -12736,10 -12645,7 +12738,10 @@@ LoadGame (FILE *f, int gameNumber, cha
      int numPGNTags = 0;
      int err, pos = -1;
      GameMode oldGameMode;
 -    VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
 +    VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
 +    char oldName[MSG_SIZ];
 +
 +    safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
  
      if (appData.debugMode)
        fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
        StartChessProgram(&first);
      }
      InitChessProgram(&first, FALSE);
 +    if(gameInfo.variant == VariantUnknown && *oldName) {
 +      safeStrCpy(engineVariant, oldName, MSG_SIZ);
 +      gameInfo.variant = v;
 +    }
      SendToProgram("force\n", &first);
      if (startedFromSetupPosition) {
        SendBoard(&first, forwardMostMove);
@@@ -13293,8 -13195,8 +13295,8 @@@ LoadPosition (FILE *f, int positionNumb
        DisplayError(_("Position not found in file"), 0);
        return FALSE;
      }
 -    // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
 -    fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
 +    // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
 +    fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
  
      if (pn >= 2) {
        if (fenMode || line[0] == '#') pn--;
@@@ -15202,7 -15104,7 +15204,7 @@@ EditPositionMenuEvent (ChessSquare sele
                                      AAA + x, ONE + y);
                            SendToICS(buf);
                        }
 -                  } else {
 +                  } else if(boards[0][y][x] != DarkSquare) {
                        if(boards[0][y][x] != p) nonEmpty++;
                        boards[0][y][x] = p;
                    }
@@@ -17886,7 -17788,6 +17888,7 @@@ PositionToFEN (int move, char *override
                      piece = (ChessSquare)(CHUDEMOTED piece);
                  }
                  *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
 +                if(*p = PieceSuffix(piece)) p++;
                  if(p[-1] == '~') {
                      /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
                      p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED piece));
  Boolean
  ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
  {
 -    int i, j, k, w=0, subst=0, shuffle=0;
 +    int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
      char *p, c;
      int emptycount, virgin[BOARD_FILES];
      ChessSquare piece;
                  if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
                board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
              } else if (*p == '+' || isalpha(*p)) {
 +              char *q, *s = SUFFIXES;
                  if (j >= gameInfo.boardWidth) return FALSE;
                  if(*p=='+') {
 -                    piece = CharToPiece(*++p);
 +                    char c = *++p;
 +                    if(q = strchr(s, p[1])) p++;
 +                    piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
                      if(piece == EmptySquare) return FALSE; /* unknown piece */
                      piece = (ChessSquare) (CHUPROMOTED piece ); p++;
                      if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
 -                } else piece = CharToPiece(*p++);
 +                } else {
 +                    char c = *p++;
 +                  if(q = strchr(s, *p)) p++;
 +                  piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
 +              }
  
                  if(piece==EmptySquare) return FALSE; /* unknown piece */
                  if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
                      p++;
                  }
                  board[i][(j++)+gameInfo.holdingsWidth] = piece;
 +                if(piece == WhiteKing) wKingRank = i;
 +                if(piece == BlackKing) bKingRank = i;
            } else {
                return FALSE;
            }
      /* [HGM] We NO LONGER ignore the rest of the FEN notation */
      /* return the extra info in global variiables             */
  
 +    while(*p==' ') p++;
 +
 +    if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
 +        if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
 +        if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
 +    }
 +
      /* set defaults in case FEN is incomplete */
      board[EP_STATUS] = EP_UNKNOWN;
      for(i=0; i<nrCastlingRights; i++ ) {
                                  && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
      FENrulePlies = 0;
  
 -    while(*p==' ') p++;
      if(nrCastlingRights) {
        int fischer = 0;
        if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
          int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
  
          for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
 -            if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
 -            if(board[0             ][i] == WhiteKing) whiteKingFile = i;
 +            if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
 +            if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
          }
          if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
              whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
                                       && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
          switch(c) {
            case'K':
 -              for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
 +              for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
                board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
                board[CASTLING][2] = whiteKingFile;
              if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
                if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
                break;
            case'Q':
 -              for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
 +              for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
                board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
                board[CASTLING][2] = whiteKingFile;
              if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
                if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
                break;
            case'k':
 -              for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
 +              for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
                board[CASTLING][3] = i != blackKingFile ? i : NoRights;
                board[CASTLING][5] = blackKingFile;
              if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
                if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
                break;
            case'q':
 -              for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
 +              for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
                board[CASTLING][4] = i != blackKingFile ? i : NoRights;
                board[CASTLING][5] = blackKingFile;
              if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
diff --combined gtk/xboard.c
@@@ -1930,7 -1930,7 +1930,7 @@@ void MoveTypeInProc(eventkey
  
      buf[0]=eventkey->keyval;
      buf[1]='\0';
 -    if (eventkey->keyval > 32 && eventkey->keyval < 256)
 +    if (eventkey->keyval > 32 && eventkey->keyval < 256 || *buf == 27)
        ConsoleAutoPopUp (buf);
  }
  
@@@ -2319,8 -2319,11 +2319,14 @@@ void FileNamePopUpWrapper(label, def, f
    char *cp;
    char curDir[MSG_SIZ];
  
 +  StartDir(filter, NULL); // change to start directory for this file type
 +
+   if(def && *def && def[strlen(def)-1] == '/') {
+     getcwd(curDir, MSG_SIZ);
+     chdir(def);
+   }
++
    /* make a copy of the filter string, so that strtok can work with it*/
    cp = strdup(filter);
  
            ASSIGN(*name, filename);
            ScheduleDelayedEvent(DelayedLoad, 50);
          }
 +      StartDir(filter, filename);
        g_free (filename);
 -    };
 +    }
 +  else StartDir(filter, "");
  
    gtk_widget_destroy (dialog);
    ModeHighlight();
diff --combined menus.c
+++ b/menus.c
@@@ -105,7 -105,6 +105,7 @@@ extern char *getenv()
  char  *gameCopyFilename, *gamePasteFilename;
  Boolean saveSettingsOnExit;
  char *settingsFileName;
 +char gamesDir[MSG_SIZ], positionsDir[MSG_SIZ], textureDir[MSG_SIZ], bookDir[MSG_SIZ], piecesDir[MSG_SIZ];
  
  static int
  LoadGamePopUp (FILE *f, int gameNumber, char *title)
@@@ -174,10 -173,12 +174,12 @@@ ReloadPositionProc (
  void
  LoadPositionProc()
  {
+     static char buf[MSG_SIZ];
      if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
        Reset(FALSE, TRUE);
      }
-     FileNamePopUp(_("Load position file name?"), "", ".fen .epd .pos", LoadPosition, "rb");
+     snprintf(buf, MSG_SIZ, "%s/", appData.positionDir);
+     FileNamePopUp(_("Load position file name?"), buf, ".fen .epd .pos", LoadPosition, "rb");
  }
  
  void
diff --combined moves.c
+++ b/moves.c
@@@ -114,32 -114,19 +114,32 @@@ SameColor (ChessSquare piece1, ChessSqu
  #define SameColor(piece1, piece2) (piece1 < EmptySquare && piece2 < EmptySquare && (piece1 < BlackPawn) == (piece2 < BlackPawn) || piece1 == DarkSquare || piece2 == DarkSquare)
  #endif
  
 -char pieceToChar[] = {
 +unsigned char pieceToChar[EmptySquare+1] = {
                          'P', 'N', 'B', 'R', 'Q', 'F', 'E', 'A', 'C', 'W', 'M',
                          'O', 'H', 'I', 'J', 'G', 'D', 'V', 'L', 'S', 'U', 'K',
                          'p', 'n', 'b', 'r', 'q', 'f', 'e', 'a', 'c', 'w', 'm',
                          'o', 'h', 'i', 'j', 'g', 'd', 'v', 'l', 's', 'u', 'k',
                          'x' };
 -char pieceNickName[EmptySquare];
 +unsigned char pieceNickName[EmptySquare];
  
  char
  PieceToChar (ChessSquare p)
  {
 +    int c;
      if((int)p < 0 || (int)p >= (int)EmptySquare) return('x'); /* [HGM] for safety */
 -    return pieceToChar[(int) p];
 +    c = pieceToChar[(int) p];
 +    if(c & 128) c = c & 63 | 64;
 +    return c;
 +}
 +
 +char
 +PieceSuffix (ChessSquare p)
 +{
 +    int c;
 +    if((int)p < 0 || (int)p >= (int)EmptySquare) return 0; /* [HGM] for safety */
 +    c = pieceToChar[(int) p];
 +    if(c < 128) return 0;
 +    return SUFFIXES[c - 128 >> 6];
  }
  
  int
@@@ -278,9 -265,8 +278,9 @@@ voi
  MovesFromString (Board board, int flags, int f, int r, int tx, int ty, int angle, char *desc, MoveCallback cb, VOIDSTAR cl)
  {
      char buf[80], *p = desc, *atom = NULL;
 -    int mine, his, dir, bit, occup, i, promoRank = -1;
 +    int mine, his, dir, bit, occup, i, ep, promoRank = -1;
      ChessMove promo= NormalMove; ChessSquare pc = board[r][f];
 +    if(pc == DarkSquare) return; // this is not a piece, but a 'hole' in the board
      if(flags & F_WHITE_ON_MOVE) his = 2, mine = 1; else his = 1, mine = 2;
      if(pc == WhitePawn || pc == WhiteLance) promo = WhitePromotion, promoRank = BOARD_HEIGHT-1; else
      if(pc == BlackPawn || pc == BlackLance) promo = BlackPromotion, promoRank = 0;
          for(dir=0, bit=1; dir<8; dir++, bit += bit) { // loop over directions
            int i = expo, j = skip, hop = mode, vx, vy, loop = 0;
            if(!(bit & dirSet)) continue;             // does not move in this direction
 -          if(dy != 1) j = 0;                        // 
 +          if(dy != 1 || mode & 1024) j = 0;         // 
            vx = dx*rot[dir][0] + dy*rot[dir][1];     // rotate step vector
            vy = dx*rot[dir][2] + dy*rot[dir][3];
            if(tx < 0) x = f, y = r;                  // start square
                if(y < 0 || y >= BOARD_HEIGHT) break; // vertically off-board: always done
                if(x <  BOARD_LEFT) { if(mode & 128) x += BOARD_RGHT - BOARD_LEFT, loop++; else break; }
                if(x >= BOARD_RGHT) { if(mode & 128) x -= BOARD_RGHT - BOARD_LEFT, loop++; else break; }
 +              if(board[y][x] == DarkSquare) break;  // black squares are supposed to be off board
                if(j) { j--; continue; }              // skip irrespective of occupation
                if(!jump    && board[y - vy + vy/2][x - vx + vx/2] != EmptySquare) break; // blocked
                if(jump > 1 && board[y - vy + vy/2][x - vx + vx/2] == EmptySquare) break; // no hop
                  continue;
                }
                if(hop & 32+64) { if(occup != 4) { if(hop & 64 && i != 1) i = 2; hop &= 31; } continue; } // hopper
 -              if(mode & 8 && y == board[EP_RANK] && occup == 4 && board[EP_FILE] == x) { // to e.p. square
 +              ep = board[EP_RANK];
 +              if(mode & 8 && occup == 4 && board[EP_FILE] == x && (y == (ep & 127) || y - vy == ep - 128)) { // to e.p. square (or 2nd e.p. square)
                    cb(board, flags, mine == 1 ? WhiteCapturesEnPassant : BlackCapturesEnPassant, r, f, y, x, cl);
                }
                if(mode & 1024) {            // castling
                    i = 2;                   // kludge to elongate move indefinitely
                    if(occup == 4) continue; // skip empty squares
 -                  if(x == BOARD_LEFT   && board[y][x] == initialPosition[y][x]) // reached initial corner piece
 +                  if((x == BOARD_LEFT + skip || x > BOARD_LEFT + skip && vx < 0 && board[y][x-1-skip] == DarkSquare)
 +                                                                  && board[y][x] == initialPosition[y][x]) { // reached initial corner piece
 +                    if(pc != WhiteKing && pc != BlackKing) { // non-royal castling (to be entered as two-leg move via 'Rook')
 +                      if(killX < 0) cb(board, flags, FirstLeg,   r, f, y, x, cl); if(killX < f)
 +                      legNr <<= 1,  cb(board, flags, NormalMove, r, f, y, f - expo, cl), legNr >>= 1;
 +                    } else
                        cb(board, flags, mine == 1 ? WhiteQueenSideCastle : BlackQueenSideCastle, r, f, y, f - expo, cl);
 -                  if(x == BOARD_RGHT-1 && board[y][x] == initialPosition[y][x])
 +                  }
 +                  if((x == BOARD_RGHT-1-skip || x < BOARD_RGHT-1-skip && vx > 0 && board[y][x+1+skip] == DarkSquare)
 +                                                                  && board[y][x] == initialPosition[y][x]) {
 +                    if(pc != WhiteKing && pc != BlackKing) {
 +                      if(killX < 0) cb(board, flags, FirstLeg,   r, f, y, x, cl); if(killX > f)
 +                      legNr <<= 1,  cb(board, flags, NormalMove, r, f, y, f + expo, cl), legNr >>= 1;
 +                    } else
                        cb(board, flags, mine == 1 ? WhiteKingSideCastle : BlackKingSideCastle, r, f, y, f + expo, cl);
 +                  }
                    break;
                }
                if(mode & 16 && (board[y][x] == WhiteKing || board[y][x] == BlackKing)) break; // tame piece, cannot capture royal
@@@ -1829,17 -1801,13 +1829,17 @@@ LegalityTest (Board board, int flags, i
          if(cl.kind != NormalMove || promoChar == NULLCHAR || promoChar == '=') return cl.kind;
          if(promoChar != '+')
              return CharToPiece(promoChar) == EmptySquare ? ImpossibleMove : IllegalMove;
 -        if(PieceToChar(CHUPROMOTED board[rf][ff]) != '+') return ImpossibleMove;
 +        if(PieceToChar(CHUPROMOTED board[rf][ff]) != '+') {
 +          if(PieceToChar(CHUPROMOTED (board[rf][ff] < BlackPawn ? WhitePawn : BlackPawn)) != '.')
 +          return ImpossibleMove;
 +      }
          return flags & F_WHITE_ON_MOVE ? WhitePromotion : BlackPromotion;
      } else
      if(gameInfo.variant == VariantShogi) {
          /* [HGM] Shogi promotions. '=' means defer */
          if(rf != DROP_RANK && cl.kind == NormalMove) {
              ChessSquare piece = board[rf][ff];
 +            int zone = BOARD_HEIGHT/3 + (BOARD_HEIGHT == 8);
  
              if(promoChar == PieceToChar(BlackQueen)) promoChar = NULLCHAR; /* [HGM] Kludge */
              if(promoChar == 'd' && (piece == WhiteRook   || piece == BlackRook)   ||
@@@ -1851,7 -1819,7 +1851,7 @@@ if(appData.debugMode)fprintf(debugFP,"S
                  return CharToPiece(promoChar) == EmptySquare ? ImpossibleMove : IllegalMove;
              else if(flags & F_WHITE_ON_MOVE) {
                  if( (int) piece < (int) WhiteWazir &&
 -                     (rf >= BOARD_HEIGHT - BOARD_HEIGHT/3 || rt >= BOARD_HEIGHT - BOARD_HEIGHT/3) ) {
 +                     (rf >= BOARD_HEIGHT - zone || rt >= BOARD_HEIGHT - zone) ) {
                      if( (piece == WhitePawn || piece == WhiteQueen) && rt > BOARD_HEIGHT-2 ||
                           piece == WhiteKnight && rt > BOARD_HEIGHT-3) /* promotion mandatory */
                         cl.kind = promoChar == '=' ? IllegalMove : WhitePromotion;
                         cl.kind = promoChar == '+' ? WhitePromotion : WhiteNonPromotion;
                  } else cl.kind = promoChar == '+' ? IllegalMove : NormalMove;
              } else {
 -                if( (int) piece < (int) BlackWazir && (rf < BOARD_HEIGHT/3 || rt < BOARD_HEIGHT/3) ) {
 +                if( (int) piece < (int) BlackWazir && (rf < zone || rt < zone) ) {
                      if( (piece == BlackPawn || piece == BlackQueen) && rt < 1 ||
                           piece == BlackKnight && rt < 2 ) /* promotion obligatory */
                         cl.kind = promoChar == '=' ? IllegalMove : BlackPromotion;
@@@ -1989,7 -1957,6 +1989,7 @@@ DisambiguateCallback (Board board, int 
  {
      register DisambiguateClosure *cl = (DisambiguateClosure *) closure;
      int wildCard = FALSE; ChessSquare piece = board[rf][ff];
 +    extern int kifu; // in parser.c
  
      // [HGM] wild: for wild-card pieces rt and rf are dummies
      if(piece == WhiteFalcon || piece == BlackFalcon ||
  
        if(cl->count && rf == cl->rf && ff == cl->ff) return; // duplicate move
  
 +      if(cl->count == 1 && kifu & 0x7E && cl->rfIn == -1 && cl->ffIn == -1) { // traditional Shogi disambiguation required
 +          int this = 1, other = 1;
 +          if(kifu & 2) this &= (flags & 1 ? rt > rf : rt < rf), other &= (flags & 1 ? cl->rt > cl->rf : cl->rt < cl->rf);
 +          if(kifu & 4) this &= (flags & 1 ? rt < rf : rt > rf), other &= (flags & 1 ? cl->rt < cl->rf : cl->rt > cl->rf);
 +          if(kifu & 8) this &= (rf == rt), other &= (cl->rt == cl->rf);
 +          if(kifu & 0x10) this &= (flags & 1 ? ft <= ff : ft >= ff), other &= (flags & 1 ? cl->ft <= cl->ff : cl->ft >= cl->ff);
 +          if(kifu & 0x20) this &= (flags & 1 ? ft >= ff : ft <= ff), other &= (flags & 1 ? cl->ft >= cl->ff : cl->ft <= cl->ff);
 +          if(kifu & 0x40) this &= (ft == ff), other &= (cl->ft == cl->ff); // should never be used
 +          if(!other) cl->count--; // the old move did not satisfy the requested relative position, erase it
 +          if(!this) return;       // the current move does not satisfy the requested relative position, ignore it
 +      }
 +
        cl->count++;
        if(cl->count == 1 || board[rt][ft] != EmptySquare) {
          // [HGM] oneclick: if multiple moves, be sure we remember capture
@@@ -2060,7 -2015,7 +2060,7 @@@ Disambiguate (Board board, int flags, D
          GenLegal(board, flags|F_IGNORE_CHECK, DisambiguateCallback, (VOIDSTAR) closure, closure->pieceIn);
        if (closure->count == 0) {
            /* No, it's not even that */
 -        if(!appData.testLegality && closure->pieceIn != EmptySquare) {
 +        if(!appData.testLegality && !pieceDefs && closure->pieceIn != EmptySquare) {
            int f, r; // if there is only a single piece of the requested type on the board, use that
            closure->rt = closure->rtIn, closure->ft = closure->ftIn;
            for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
          /* [HGM] Shogi promotions. On input, '=' means defer, '+' promote. Afterwards, c is set to '+' for promotions, NULL other */
          if(closure->rfIn != DROP_RANK && closure->kind == NormalMove) {
              ChessSquare piece = closure->piece;
 +            int zone = BOARD_HEIGHT/3 + (BOARD_HEIGHT == 8);
              if (c == 'd' && (piece == WhiteRook   || piece == BlackRook)   ||
                  c == 'h' && (piece == WhiteBishop || piece == BlackBishop) ||
                  c == 'g' && (piece <= WhiteFerz || piece <= BlackFerz && piece >= BlackPawn) )
              if(c != NULLCHAR && c != '+' && c != '=') closure->kind = IllegalMove; // otherwise specifying a piece is illegal
              else if(flags & F_WHITE_ON_MOVE) {
                  if( (int) piece < (int) WhiteWazir &&
 -                     (closure->rf >= BOARD_HEIGHT-(BOARD_HEIGHT/3) || closure->rt >= BOARD_HEIGHT-(BOARD_HEIGHT/3)) ) {
 +                     (closure->rf >= BOARD_HEIGHT-zone || closure->rt >= BOARD_HEIGHT-zone) ) {
                      if( (piece == WhitePawn || piece == WhiteQueen) && closure->rt > BOARD_HEIGHT-2 ||
                           piece == WhiteKnight && closure->rt > BOARD_HEIGHT-3) /* promotion mandatory */
                         closure->kind = c == '=' ? IllegalMove : WhitePromotion;
                         closure->kind = c == '+' ? WhitePromotion : WhiteNonPromotion;
                  } else closure->kind = c == '+' ? IllegalMove : NormalMove;
              } else {
 -                if( (int) piece < (int) BlackWazir && (closure->rf < BOARD_HEIGHT/3 || closure->rt < BOARD_HEIGHT/3) ) {
 +                if( (int) piece < (int) BlackWazir && (closure->rf < zone || closure->rt < zone) ) {
                      if( (piece == BlackPawn || piece == BlackQueen) && closure->rt < 1 ||
                           piece == BlackKnight && closure->rt < 2 ) /* promotion obligatory */
                         closure->kind = c == '=' ? IllegalMove : BlackPromotion;
      if (closure->kind == WhitePromotion || closure->kind == BlackPromotion) {
          if(c == NULLCHAR) { // missing promoChar on mandatory promotion; use default for variant
              if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
-                gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
+                gameInfo.variant == VariantMakruk)
                  c = PieceToChar(BlackFerz);
+             else if(gameInfo.variant == VariantASEAN)
+                 c = PieceToChar(BlackRook);
              else if(gameInfo.variant == VariantGreat)
                  c = PieceToChar(BlackMan);
              else if(gameInfo.variant == VariantGrand)
@@@ -2355,7 -2311,6 +2357,7 @@@ CoordsToAlgebraic (Board board, int fla
          }
          if(c=='+') *outp++ = c;
          *outp++ = ToUpper(PieceToChar(piece));
 +        if(*outp = PieceSuffix(piece)) outp++;
  
        if (cl.file || (cl.either && !cl.rank)) {
              *outp++ = ff + AAA;
diff --combined po/LINGUAS
@@@ -1,3 -1,3 +1,3 @@@
  # whitespace separated list of translated languages goes below
  # note: zh translations are untested; xboard fails to create a fontset for them
- da de es fr it nl pl ru tr uk vi zh_CN zh_HK zh_TW
 -da de es it nl pl ru sr tr uk vi zh_CN zh_HK zh_TW
++da de es fr it nl pl ru sr tr uk vi zh_CN zh_HK zh_TW