Merge branch 'v4.8.x'
[xboard.git] / moves.c
diff --git a/moves.c b/moves.c
index e0ad2e1..867a940 100644 (file)
--- a/moves.c
+++ b/moves.c
@@ -72,7 +72,6 @@ int BlackPiece P((ChessSquare));
 int SameColor P((ChessSquare, ChessSquare));
 int PosFlags(int index);
 
-extern signed char initialRights[BOARD_FILES]; /* [HGM] all rights enabled, set in InitPosition */
 int quickFlag;
 char *pieceDesc[EmptySquare];
 char *defaultDesc[EmptySquare] = {
@@ -122,12 +121,13 @@ unsigned char pieceToChar[EmptySquare+1] = {
                         'o', 'h', 'i', 'j', 'g', 'd', 'v', 'l', 's', 'u', 'k',
                         'x' };
 unsigned char pieceNickName[EmptySquare];
+int promoPartner[EmptySquare];
 
 char
 PieceToChar (ChessSquare p)
 {
     int c;
-    if((int)p < 0 || (int)p >= (int)EmptySquare) return('x'); /* [HGM] for safety */
+    if((int)p < 0 || (int)p >= (int)EmptySquare) return('?'); /* [HGM] for safety */
     c = pieceToChar[(int) p];
     if(c & 128) c = c & 63 | 64;
     return c;
@@ -205,7 +205,7 @@ CollectPieceDescriptors ()
     // dump all engine defined pieces, and pieces with non-standard names,
     // but suppress black pieces that are the same as their white counterpart
     ChessSquare p;
-    static char buf[MSG_SIZ];
+    static char buf[MSG_SIZ], s[2];
     char *m, c, d, *pieceName = defaultName;
     int len;
     *buf = NULLCHAR;
@@ -215,22 +215,56 @@ CollectPieceDescriptors ()
     if(gameInfo.variant == VariantXiangqi) pieceName = xqName;
     for(p=WhitePawn; p<EmptySquare; p++) {
        if((c = pieceToChar[p]) == '.' || c == '~') continue;  // does not participate
-       m = pieceDesc[p]; d = (c == '+' ? pieceToChar[DEMOTED p] : c);
-       if(p >= BlackPawn && pieceToChar[BLACK_TO_WHITE p] == toupper(c)
-             && (c != '+' || pieceToChar[DEMOTED BLACK_TO_WHITE p] == d)) { // black member of normal pair
+       m = pieceDesc[p]; d = (c == '+' ? pieceToChar[DEMOTED(p)] : c);
+       if(p >= BlackPawn && pieceToChar[BLACK_TO_WHITE p] == (c & ~32)
+             && (c != '+' || pieceToChar[DEMOTED(BLACK_TO_WHITE p)] == d)) {// black member of normal pair
            char *wm = pieceDesc[BLACK_TO_WHITE p];
            if(!m && !wm || m && wm && !strcmp(wm, m)) continue;            // moves as a white piece
        } else                                                              // white or unpaired black
-       if((p < BlackPawn || CharToPiece(toupper(d)) != EmptySquare) &&     // white or lone black
+       if((p < BlackPawn || CharToPiece(d & ~32) != EmptySquare) &&        // white or lone black
           !pieceDesc[p] /*&& pieceName[p] == c*/) continue; // orthodox piece known by its usual name
 // TODO: listing pieces because of unusual name can only be done if we have accurate Betza of all defaults
        if(!m) m = defaultDesc[p];
+       if(!m) continue;
        len = strlen(buf);
-       snprintf(buf+len, MSG_SIZ-len, "%s%s%c:%s", len ? ";" : "", c == '+' ? "+" : "", d, m);
+       *s = (d > 128 ? SUFFIXES[d-128>>6] : 0); d = 64 + (d & 63);
+       snprintf(buf+len, MSG_SIZ-len, "%s%s%c%s:%s", len ? ";" : "", c == '+' ? "+" : "", d, s, m);
     }
     return buf;
 }
 
+int
+LoadPieceDesc (char *s)
+{
+    ChessSquare piece;
+    static char suf[] = SUFFIXES;
+    char *r, *p, *q = s;
+    int ok = TRUE, promoted, c;
+    while(q && *s) {
+       p = s;
+       q = strchr(s, ';');
+       if(q) *q = 0, s = q+1;
+       if(*p == '+') promoted = 1, p++; else promoted = 0;
+       c = *p++;
+       if(!c) { ok = FALSE; continue; } // bad syntax
+       if(*p && (r = strchr(suf, *p))) c += 64*(r - suf + 1), p++;
+       if(*p++ != ':') { ok = FALSE; continue; } // bad syntax
+       if(!strcmp(p, "(null)")) continue; // handle bug in writing of XBoard 4.8.0
+        piece = CharToPiece(c);
+       if(piece >= EmptySquare) { ok = FALSE; continue; } // non-existent piece
+       if(promoted) {
+           piece = promoPartner[piece];
+           if(pieceToChar[piece] != '+') { ok = FALSE; continue; } // promoted form does not exist
+       }
+       ASSIGN(pieceDesc[piece], p);
+       if(piece < BlackPawn && (pieceToChar[WHITE_TO_BLACK piece] == pieceToChar[piece] + 32 || promoted)) {
+           ASSIGN(pieceDesc[WHITE_TO_BLACK piece], p);
+       }
+       pieceDefs = TRUE;
+    }
+    return ok;
+}
+
 // [HGM] gen: configurable move generation from Betza notation sent by engine.
 // Some notes about two-leg moves: GenPseudoLegal() works in two modes, depending on whether a 'kill-
 // square has been set: without one is generates all moves, and a global int legNr flags in bits 0 and 1
@@ -286,9 +320,9 @@ MovesFromString (Board board, int flags, int f, int r, int tx, int ty, int angle
     if(pc == WhitePawn || pc == WhiteLance) promo = WhitePromotion, promoRank = BOARD_HEIGHT-1; else
     if(pc == BlackPawn || pc == BlackLance) promo = BlackPromotion, promoRank = 0;
     while(*p) {                  // more moves to go
-       int expo = 1, dx, dy, x, y, mode, dirSet, ds2=0, retry=0, initial=0, jump=1, skip = 0, all = 0;
+       int expo = -1, dx, dy, x, y, mode, dirSet, ds2=0, retry=0, initial=0, jump=1, skip = 0, all = 0;
        char *cont = NULL;
-       if(*p == 'i') initial = 1, desc = ++p;
+       while(*p == 'i') initial++, desc = ++p;
        while(islower(*p)) p++;  // skip prefixes
        if(!isupper(*p)) return; // syntax error: no atom
        dx = xStep[*p-'A'] - '0';// step vector of atom
@@ -373,12 +407,14 @@ MovesFromString (Board board, int flags, int f, int r, int tx, int ty, int angle
        if(isdigit(*++p)) expo = atoi(p++);           // read exponent
        if(expo > 9) p++;                             // allow double-digit
        desc = p;                                     // this is start of next move
+       if(initial == 2) { if(board[r][f] != initialPosition[r-2*his+3][f]) continue; } else
        if(initial && (board[r][f] != initialPosition[r][f] ||
                       r == 0              && board[TOUCHED_W] & 1<<f ||
                       r == BOARD_HEIGHT-1 && board[TOUCHED_B] & 1<<f   ) ) continue;
-       if(expo > 1 && dx == 0 && dy == 0) {          // castling indicated by O + number
+       if(expo > 0 && dx == 0 && dy == 0) {          // castling indicated by O + number
            mode |= 1024; dy = 1;
        }
+       if(expo < 0) expo = 1;                        // use 1 for default
         if(!cont) {
            if(!(mode & 15)) mode |= his + 4;         // no mode spec, use default = mc
        } else {
@@ -409,8 +445,8 @@ MovesFromString (Board board, int flags, int f, int r, int tx, int ty, int angle
                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(board[y][x] == DarkSquare) break;  // black squares are supposed to be off board
                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
                if(x == f && y == r && !loop) occup = 4;     else // start square counts as empty (if not around cylinder!)
@@ -449,7 +485,7 @@ MovesFromString (Board board, int flags, int f, int r, int tx, int ty, int angle
                    if(occup == 4) continue; // skip empty squares
                    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(pc != WhiteKing && pc != BlackKing || expo == 1) { // 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
@@ -457,7 +493,7 @@ MovesFromString (Board board, int flags, int f, int r, int tx, int ty, int angle
                    }
                    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(pc != WhiteKing && pc != BlackKing || expo == 1) {
                        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
@@ -709,7 +745,7 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure,
          if ((flags & F_WHITE_ON_MOVE) != (board[rf][ff] < BlackPawn)) continue; // [HGM] speed: wrong color
           m = 0; piece = board[rf][ff];
           if(PieceToChar(piece) == '~')
-                 piece = (ChessSquare) ( DEMOTED piece );
+                 piece = (ChessSquare) ( DEMOTED(piece) );
           if(filter != EmptySquare && piece != filter) continue;
           if(pieceDefs && pieceDesc[piece]) { // [HGM] gen: use engine-defined moves
               MovesFromString(board, flags, ff, rf, -1, -1, 0, pieceDesc[piece], callback, closure);
@@ -885,14 +921,14 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure,
 
             /* Gold General (and all its promoted versions) . First do the */
             /* diagonal forward steps, then proceed as normal Wazir        */
-            case SHOGI (PROMOTED WhitePawn):
+            case SHOGI (PROMO WhitePawn):
                if(gameInfo.variant == VariantShogi) goto WhiteGold;
-            case SHOGI (PROMOTED BlackPawn):
+            case SHOGI (PROMO BlackPawn):
                if(gameInfo.variant == VariantShogi) goto BlackGold;
                SlideVertical(board, flags, rf, ff, callback, closure);
                break;
 
-            case SHOGI (PROMOTED WhiteKnight):
+            case SHOGI (PROMO WhiteKnight):
                if(gameInfo.variant == VariantShogi) goto WhiteGold;
             case SHOGI BlackDrunk:
             case SHOGI BlackAlfil:
@@ -901,7 +937,7 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure,
                StepBackward(board, flags, rf, ff, callback, closure);
                break;
 
-            case SHOGI (PROMOTED BlackKnight):
+            case SHOGI (PROMO BlackKnight):
                if(gameInfo.variant == VariantShogi) goto BlackGold;
             case SHOGI WhiteDrunk:
             case SHOGI WhiteAlfil:
@@ -911,15 +947,15 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure,
                break;
 
 
-            case SHOGI WhiteStag:
-            case SHOGI BlackStag:
+            case SHOGI WhiteGnu:
+            case SHOGI BlackGnu:
                if(gameInfo.variant == VariantShogi) goto BlackGold;
                SlideVertical(board, flags, rf, ff, callback, closure);
                Ferz(board, flags, rf, ff, callback, closure);
                StepSideways(board, flags, rf, ff, callback, closure);
                break;
 
-            case SHOGI (PROMOTED WhiteQueen):
+            case SHOGI (PROMO WhiteQueen):
             case SHOGI WhiteTokin:
             case SHOGI WhiteWazir:
            WhiteGold:
@@ -927,7 +963,7 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure,
                Wazir(board, flags, rf, ff, callback, closure);
                break;
 
-            case SHOGI (PROMOTED BlackQueen):
+            case SHOGI (PROMO BlackQueen):
             case SHOGI BlackTokin:
             case SHOGI BlackWazir:
             BlackGold:
@@ -1238,9 +1274,9 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure,
                StepVertical(board, flags, rf, ff, callback, closure);
                break;
 
-            case SHOGI (PROMOTED WhiteFerz):
+            case SHOGI (PROMO WhiteFerz):
                if(gameInfo.variant == VariantShogi) goto WhiteGold;
-            case SHOGI (PROMOTED BlackFerz):
+            case SHOGI (PROMO BlackFerz):
                if(gameInfo.variant == VariantShogi) goto BlackGold;
             case SHOGI WhitePSword:
             case SHOGI BlackPSword:
@@ -1276,7 +1312,7 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure,
                SlideVertical(board, flags, rf, ff, callback, closure);
                break;
 
-            case SHOGI WhiteHorned:
+            case SHOGI WhiteCat:
                Sting(board, flags, rf, ff, 1, 0, callback, closure);
                callback(board, flags, NormalMove, rf, ff, rf, ff, closure);
                if(killX >= 0) break;
@@ -1285,7 +1321,7 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure,
                SlideBackward(board, flags, rf, ff, callback, closure);
                break;
 
-            case SHOGI BlackHorned:
+            case SHOGI BlackCat:
                Sting(board, flags, rf, ff, -1, 0, callback, closure);
                callback(board, flags, NormalMove, rf, ff, rf, ff, closure);
                if(killX >= 0) break;
@@ -1294,7 +1330,7 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure,
                SlideForward(board, flags, rf, ff, callback, closure);
                break;
 
-            case SHOGI WhiteEagle:
+            case SHOGI WhiteDagger:
                Sting(board, flags, rf, ff, 1,  1, callback, closure);
                Sting(board, flags, rf, ff, 1, -1, callback, closure);
                callback(board, flags, NormalMove, rf, ff, rf, ff, closure);
@@ -1303,7 +1339,7 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure,
                SlideDiagBackward(board, flags, rf, ff, callback, closure);
                break;
 
-            case SHOGI BlackEagle:
+            case SHOGI BlackDagger:
                Sting(board, flags, rf, ff, -1,  1, callback, closure);
                Sting(board, flags, rf, ff, -1, -1, callback, closure);
                callback(board, flags, NormalMove, rf, ff, rf, ff, closure);
@@ -1674,10 +1710,11 @@ CheckTest (Board board, int flags, int rf, int ff, int rt, int ft, int enPassant
        }
        ep = board[EP_STATUS];
        if( captured == WhiteLion || captured == BlackLion ) { // [HGM] lion: Chu Lion-capture rules
-           ChessSquare victim = killX < 0 ? EmptySquare : trampled;
+           ChessSquare victim = saveKill < 0 ? EmptySquare : trampled;
            if( (board[rt][ft] == WhiteLion || board[rt][ft] == BlackLion) &&           // capturer is Lion
                (ff - ft > 1 || ft - ff > 1 || rf - rt > 1 || rt - rf > 1) &&           // captures from a distance
-               (victim == EmptySquare || victim == WhitePawn || victim == BlackPawn) ) // no or worthless 'bridge'
+               (victim == EmptySquare || victim == WhitePawn || victim == BlackPawn    // no or worthless 'bridge'
+                                    || victim == WhiteCobra || victim == BlackCobra) ) // (Pawn or Go Between)
                     board[EP_STATUS] = EP_ROYAL_LION; // on distant Lion x Lion victim must not be pseudo-legally protected
        }
     }
@@ -1790,7 +1827,7 @@ LegalityTest (Board board, int flags, int rf, int ff, int rt, int ft, int promoC
     if(quickFlag) flags = flags & ~1 | quickFlag & 1; // [HGM] speed: in quick mode quickFlag specifies side-to-move.
     if(rf == DROP_RANK) return LegalDrop(board, flags, ff, rt, ft);
     piece = filterPiece = board[rf][ff];
-    if(PieceToChar(piece) == '~') filterPiece = DEMOTED piece;
+    if(PieceToChar(piece) == '~') filterPiece = DEMOTED(piece);
 
     /* [HGM] Cobra and Falcon are wildcard pieces; consider all their moves legal */
     /* (perhaps we should disallow moves that obviously leave us in check?)              */
@@ -1830,7 +1867,7 @@ LegalityTest (Board board, int flags, int rf, int ff, int rt, int ft, int promoC
         if(cl.kind != NormalMove || promoChar == NULLCHAR || promoChar == '=') return cl.kind;
         if(promoChar != '+')
             return CharToPiece(promoChar) == EmptySquare ? ImpossibleMove : IllegalMove;
-        if(PieceToChar(CHUPROMOTED board[rf][ff]) != '+') {
+        if(PieceToChar(CHUPROMOTED(board[rf][ff])) != '+') {
            if(PieceToChar(CHUPROMOTED (board[rf][ff] < BlackPawn ? WhitePawn : BlackPawn)) != '.')
            return ImpossibleMove;
        }
@@ -1877,7 +1914,7 @@ if(appData.debugMode)fprintf(debugFP,"SHOGI promoChar = %c\n", promoChar ? promo
             // should test if in zone, really
             if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight) && HasLion(board, flags))
                 return IllegalMove;
-            if(PieceToChar(PROMOTED piece) == '+') return flags & F_WHITE_ON_MOVE ? WhitePromotion : BlackPromotion;
+            if(PieceToChar(PROMOTED(piece)) == '+') return flags & F_WHITE_ON_MOVE ? WhitePromotion : BlackPromotion;
         } else
        if(promoChar == '=') cl.kind = IllegalMove; else // [HGM] shogi: no deferred promotion outside Shogi
        if (cl.kind == WhitePromotion || cl.kind == BlackPromotion) {
@@ -1999,7 +2036,7 @@ DisambiguateCallback (Board board, int flags, ChessMove kind, int rf, int ff, in
 
     if ((cl->pieceIn == EmptySquare || cl->pieceIn == board[rf][ff]
          || PieceToChar(board[rf][ff]) == '~'
-              && cl->pieceIn == (ChessSquare)(DEMOTED board[rf][ff])
+              && cl->pieceIn == (ChessSquare)(DEMOTED(board[rf][ff]))
                                                                       ) &&
        (cl->rfIn == -1 || cl->rfIn == rf) &&
        (cl->ffIn == -1 || cl->ffIn == ff) &&
@@ -2155,7 +2192,7 @@ Disambiguate (Board board, int flags, DisambiguateClosure *closure)
         else if(c == 'l' && gameInfo.variant == VariantChuChess && HasLion(board, flags)) closure->kind = IllegalMove;
     } else if (c == '+') { // '+' outside shogi, check if pieceToCharTable enabled it
         ChessSquare p = closure->piece;
-        if(p > WhiteMan && p < BlackPawn || p > BlackMan || PieceToChar(PROMOTED p) != '+')
+        if(p > WhiteMan && p < BlackPawn || p > BlackMan || PieceToChar(PROMOTED(p)) != '+')
             closure->kind = ImpossibleMove; // used on non-promotable piece
         else if(gameInfo.variant == VariantChuChess && HasLion(board, flags)) closure->kind = IllegalMove;
     } else if (c != NULLCHAR) closure->kind = IllegalMove;
@@ -2200,7 +2237,7 @@ CoordsToAlgebraicCallback (Board board, int flags, ChessMove kind, int rf, int f
     if ((rt == cl->rt && ft == cl->ft || rt == rf && ft == ff) && // [HGM] null move matches any toSquare
         (board[rf][ff] == cl->piece
          || PieceToChar(board[rf][ff]) == '~' &&
-            (ChessSquare) (DEMOTED board[rf][ff]) == cl->piece)
+            (ChessSquare) (DEMOTED(board[rf][ff])) == cl->piece)
                                      ) {
        if (rf == cl->rf) {
            if (ff == cl->ff) {
@@ -2244,7 +2281,7 @@ CoordsToAlgebraic (Board board, int flags, int rf, int ff, int rt, int ft, int p
 
     if (promoChar == 'x') promoChar = NULLCHAR;
     piece = board[rf][ff];
-    if(PieceToChar(piece)=='~') piece = (ChessSquare)(DEMOTED piece);
+    if(PieceToChar(piece)=='~') piece = (ChessSquare)(DEMOTED(piece));
 
     switch (piece) {
       case WhitePawn:
@@ -2336,13 +2373,13 @@ CoordsToAlgebraic (Board board, int flags, int rf, int ff, int rt, int ft, int p
        cl.kind = IllegalMove;
        cl.rank = cl.file = cl.either = 0;
         c = PieceToChar(piece) ;
-        GenLegal(board, flags, CoordsToAlgebraicCallback, (VOIDSTAR) &cl, c!='~' ? piece : (DEMOTED piece)); // [HGM] speed
+        GenLegal(board, flags, CoordsToAlgebraicCallback, (VOIDSTAR) &cl, c!='~' ? piece : (DEMOTED(piece))); // [HGM] speed
 
        if (cl.kind == IllegalMove && !(flags&F_IGNORE_CHECK)) {
            /* Generate pretty moves for moving into check, but
               still return IllegalMove.
            */
-            GenLegal(board, flags|F_IGNORE_CHECK, CoordsToAlgebraicCallback, (VOIDSTAR) &cl, c!='~' ? piece : (DEMOTED piece));
+            GenLegal(board, flags|F_IGNORE_CHECK, CoordsToAlgebraicCallback, (VOIDSTAR) &cl, c!='~' ? piece : (DEMOTED(piece)));
            if (cl.kind == IllegalMove) break;
            cl.kind = IllegalMove;
        }
@@ -2354,7 +2391,7 @@ CoordsToAlgebraic (Board board, int flags, int rf, int ff, int rt, int ft, int p
        */
         if( c == '~' || c == '+') {
            /* [HGM] print nonexistent piece as its demoted version */
-           piece = (ChessSquare) (DEMOTED piece - 11*(gameInfo.variant == VariantChu));
+           piece = (ChessSquare) (CHUDEMOTED(piece));
         }
         if(c=='+') *outp++ = c;
         *outp++ = ToUpper(PieceToChar(piece));
@@ -2412,7 +2449,10 @@ CoordsToAlgebraic (Board board, int flags, int rf, int ff, int rt, int ft, int p
        int r, f;
       for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<=BOARD_RGHT; f++)
                c += (board[r][f] == piece); // count on-board pieces of given type
-       *outp++ = ToUpper(PieceToChar(piece));
+        *outp = PieceToChar(piece);
+        if(*outp == '+') outp++, piece = CHUDEMOTED(piece);
+        *outp++ = ToUpper(PieceToChar(piece));
+        if(*outp = PieceSuffix(piece)) outp++;
     }
   if(c != 1) { // [HGM] but if there is only one piece of the mentioned type, no from-square, thank you!
     *outp++ = ff + AAA;