Allow promotion choice in variant asean
[xboard.git] / moves.c
diff --git a/moves.c b/moves.c
index 8c09e23..b276ded 100644 (file)
--- a/moves.c
+++ b/moves.c
@@ -236,10 +236,13 @@ char xStep[]    = "2110.130.102.10.00....0..2";
 char yStep[]    = "2132.133.313.20.11....1..3";
 char dirType[]  = "01000104000200000260050000";
 char upgrade[]  = "AFCD.BGH.JQL.NO.KW....R..Z";
+char rotate[]   = "DRCA.WHG.JKL.NO.QB....F..Z";
 
 //  alphabet   "a b    c d e f    g h    i j k l    m n o p q r    s    t u v    w x y z "
 int dirs1[] = { 0,0x3C,0,0,0,0xC3,0,0,   0,0,0,0xF0,0,0,0,0,0,0x0F,0   ,0,0,0   ,0,0,0,0 };
 int dirs2[] = { 0,0x18,0,0,0,0x81,0,0xFF,0,0,0,0x60,0,0,0,0,0,0x06,0x66,0,0,0x99,0,0,0,0 };
+int dirs3[] = { 0,0x38,0,0,0,0x83,0,0xFF,0,0,0,0xE0,0,0,0,0,0,0x0E,0xEE,0,0,0xBB,0,0,0,0 };
+int dirs4[] = { 0,0x10,0,0,0,0x01,0,0xFF,0,0,0,0x40,0,0,0,0,0,0x04,0x44,0,0,0x11,0,0,0,0 };
 
 int rot[][4] = { // rotation matrices for each direction
   { 1, 0, 0, 1 },
@@ -261,21 +264,26 @@ OK (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOID
 void
 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;
-    int mine, his, dir, bit, occup, i;
+    char buf[80], *p = desc, *atom = NULL;
+    int mine, his, dir, bit, occup, i, promoRank = -1;
+    ChessMove promo= NormalMove; ChessSquare pc = board[r][f];
     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;
     while(*p) {                  // more moves to go
-       int expo = 1, dx, dy, x, y, mode, dirSet, retry=0, initial=0, jump=1, skip = 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(islower(*p)) p++;  // skip prefixes
        if(!isupper(*p)) return; // syntax error: no atom
+       dx = xStep[*p-'A'] - '0';// step vector of atom
+       dy = yStep[*p-'A'] - '0';
        dirSet = 0;              // build direction set based on atom symmetry
        switch(symmetry[*p-'A']) {
          case 'B': expo = 0;    // bishop, slide
-         case 'F':              // diagonal atom (degenerate 4-fold)
-                   if(tx < 0) { // for continuation legs relative directions are orthogonal!
-                     while(islower(*desc) && (i = dirType[*desc-'a']) != '0') {
+         case 'F': all = 0xAA;  // diagonal atom (degenerate 4-fold)
+                   if(tx >= 0) goto king;        // continuation legs specified in K/Q system!
+                   while(islower(*desc) && (i = dirType[*desc-'a']) != '0') {
                        int b = dirs1[*desc-'a']; // use wide version
                        if( islower(desc[1]) &&
                                 ((i | dirType[desc[1]-'a']) & 3) == 3) {   // combinable (perpendicular dim)
@@ -283,17 +291,21 @@ MovesFromString (Board board, int flags, int f, int r, int tx, int ty, int angle
                            desc += 2;
                        } else desc++;
                        dirSet |= b;
-                     }
-                     dirSet &= 0x55; if(!dirSet) dirSet = 0x55;
-                     break;
                    }
+                   dirSet &= 0xAA; if(!dirSet) dirSet = 0xAA;
+                   break;
          case 'R': expo = 0;    // rook, slide
-         case 'W':              // orthogonal atom (non-deg 4-fold)
+         case 'W': all = 0x55;  // orthogonal atom (non-deg 4-fold)
+                   if(tx >= 0) goto king;        // continuation legs specified in K/Q system!
                    while(islower(*desc) && (dirType[*desc-'a'] & ~4) != '0') dirSet |= dirs2[*desc++-'a'];
                    dirSet &= 0x55; if(!dirSet) dirSet = 0x55;
                    dirSet = (dirSet << angle | dirSet >> 8-angle) & 255;   // re-orient direction system
                    break;
-         case 'N':              // oblique atom (degenerate 8-fold)
+         case 'N': all = 0xFF;  // oblique atom (degenerate 8-fold)
+                   if(tx >= 0) goto king;        // continuation legs specified in K/Q system!
+                   if(*desc == 'h') {            // chiral direction sets 'hr' and 'hl'
+                       dirSet = (desc[1] == 'r' ? 0x55 :  0xAA); desc += 2;
+                   } else
                    while(islower(*desc) && (i = dirType[*desc-'a']) != '0') {
                        int b = dirs2[*desc-'a']; // when alone, use narrow version
                        if(desc[1] == 'h') b = dirs1[*desc-'a'], desc += 2; // dirs1 is wide version
@@ -307,26 +319,42 @@ MovesFromString (Board board, int flags, int f, int r, int tx, int ty, int angle
                    if(!dirSet) dirSet = 0xFF;
                    break;
          case 'Q': expo = 0;    // queen, slide
-         case 'K':              // non-deg (pseudo) 8-fold
-                   dirSet=0x55; // start with orthogonal moves
-                   retry = 1;   // and schedule the diagonal moves for later
+         case 'K': all = 0xFF;  // non-deg (pseudo) 8-fold
+         king:
+                   while(islower(*desc) && (i = dirType[*desc-'a']) != '0') {
+                       int b = dirs4[*desc-'a'];    // when alone, use narrow version
+                       if(desc[1] == *desc) desc++; // doubling forces alone
+                       else if(islower(desc[1]) && i < '4'
+                               && ((i | dirType[desc[1]-'a']) & 3) == 3) { // combinable (perpendicular dim or same)
+                           b = dirs3[*desc-'a'] & dirs3[desc[1]-'a'];      // intersect wide & perp wide
+                           desc += 2;
+                       } else desc++;
+                       dirSet |= b;
+                   }
+                   if(!dirSet) dirSet = (tx < 0 ? 0xFF                     // default is all directions, but in continuation leg
+                                         : all == 0xFF ? 0xEF : 0x45);     // omits backward, and for 4-fold atoms also diags
+                   dirSet = (dirSet << angle | dirSet >> 8-angle) & 255;   // re-orient direction system
+                   ds2 = dirSet & 0xAA;          // extract diagonal directions
+                   if(dirSet &= 0x55)            // start with orthogonal moves, if present
+                        retry = 1, dx = 0;       // and schedule the diagonal moves for later
+                   else dx = dy, dirSet = ds2;   // if no orthogonal directions, do diagonal immediately
                    break;       // should not have direction indicators
          default:  return;      // syntax error: invalid atom
        }
        if(mine == 2 && tx < 0) dirSet = dirSet >> 4 | dirSet << 4 & 255;   // invert black moves
        mode = 0;                // build mode mask
-       if(*desc == 'm') mode |= 4, desc++;
-       if(*desc == 'c') mode |= his, desc++;
-       if(*desc == 'd') mode |= mine, desc++;
-       if(*desc == 'e') mode |= 8, desc++;
-       if(*desc == 'p') mode |= 32, desc++;
-       if(*desc == 'g') mode |= 64, desc++;
-       if(*desc == 'o') mode |= 128, desc++;
-       if(*desc == 'n') jump = 0, desc++;
-       while(*desc == 'j') jump++, desc++;
-       if(*desc == 'a') cont = ++desc;
-       dx = xStep[*p-'A'] - '0';                     // step vector of atom
-       dy = yStep[*p-'A'] - '0';
+       if(*desc == 'm') mode |= 4, desc++;           // move to empty
+       if(*desc == 'c') mode |= his, desc++;         // capture foe
+       if(*desc == 'd') mode |= mine, desc++;        // destroy (capture friend)
+       if(*desc == 'e') mode |= 8, desc++;           // e.p. capture last mover
+       if(*desc == 't') mode |= 16, desc++;          // exclude enemies as hop platform ('test')
+       if(*desc == 'p') mode |= 32, desc++;          // hop over occupied
+       if(*desc == 'g') mode |= 64, desc++;          // hop and toggle range
+       if(*desc == 'o') mode |= 128, desc++;         // wrap around cylinder board
+       if(*desc == 'y') mode |= 512, desc++;         // toggle range on empty square
+       if(*desc == 'n') jump = 0, desc++;            // non-jumping
+       while(*desc == 'j') jump++, desc++;           // must jump (on B,R,Q: skip first square)
+       if(*desc == 'a') cont = ++desc;               // move again after doing what preceded it
        if(isdigit(*++p)) expo = atoi(p++);           // read exponent
        if(expo > 9) p++;                             // allow double-digit
        desc = p;                                     // this is start of next move
@@ -334,27 +362,27 @@ MovesFromString (Board board, int flags, int f, int r, int tx, int ty, int angle
                       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
-           mode |= 16; dy = 1;
+           mode |= 1024; dy = 1;
        }
         if(!cont) {
-           if(!(mode & 15)) mode = his + 4;          // no mode spec, use default = mc
+           if(!(mode & 15)) mode |= his + 4;         // no mode spec, use default = mc
        } else {
+           strncpy(buf, cont, 80); cont = buf;       // copy next leg(s), so we can modify
+           atom = buf; while(islower(*atom)) atom++; // skip to atom
            if(mode & 32) mode ^= 256 + 32;           // in non-final legs 'p' means 'pass through'
-           if(mode & 64) {
+           if(mode & 64 + 512) {
                mode |= 256;                          // and 'g' too, but converts leaper <-> slider
-               strncpy(buf, cont, 80); cont = buf;   // copy next leg(s), so we can modify
-               while(islower(*cont)) cont++;         // skip to atom
-               *cont = upgrade[*cont-'A'];           // replace atom, BRQ <-> FWK
-               if(expo == 1) *++cont = '0';          // turn other leapers into riders 
-               *++cont = '\0';                       // make sure any old range is stripped off
-               cont = buf;                           // use modified string for continuation leg
+               if(mode & 512) mode ^= 0x304;         // and 'y' is m-like 'g'
+               *atom = upgrade[*atom-'A'];           // replace atom, BRQ <-> FWK
+               atom[1] = atom[2] = '\0';             // make sure any old range is stripped off
+               if(expo == 1) atom[1] = '0';          // turn other leapers into riders 
            }
-           if(!(mode & 0x10F)) mode = his + 0x104;   // and default = mcp
+           if(!(mode & 0x30F)) mode |= 4;            // and default of this leg = m
        }
        if(dy == 1) skip = jump - 1, jump = 1;        // on W & F atoms 'j' = skip first square
         do {
          for(dir=0, bit=1; dir<8; dir++, bit += bit) { // loop over directions
-           int i = expo, j = skip, hop = mode, vx, vy;
+           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;                        // 
            vx = dx*rot[dir][0] + dy*rot[dir][1];     // rotate step vector
@@ -364,17 +392,20 @@ MovesFromString (Board board, int flags, int f, int r, int tx, int ty, int angle
            do {                                      // traverse ray
                x += vx; y += vy;                     // step to next 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; else break; }
-               if(x >= BOARD_RGHT) { if(mode & 128) x -= BOARD_RGHT - BOARD_LEFT; else break; }
+               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(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
-               if(x == f && y == r)          occup = 4;     else                         // start square counts as empty
+               if(x == f && y == r && !loop) occup = 4;     else // start square counts as empty (if not around cylinder!)
                if(board[y][x] < BlackPawn)   occup = 0x101; else
                if(board[y][x] < EmptySquare) occup = 0x102; else
                                              occup = 4;
                if(cont) {                            // non-final leg
+                 if(mode&16 && his&occup) occup &= 3;// suppress hopping foe in t-mode
                  if(occup & mode) {                  // valid intermediate square, do continuation
+                   char origAtom = *atom;
+                   if(!(bit & all)) *atom = rotate[*atom - 'A']; // orth-diag interconversion to make direction valid
                    if(occup & mode & 0x104)          // no side effects, merge legs to one move
                        MovesFromString(board, flags, f, r, x, y, dir, cont, cb, cl);
                    if(occup & mode & 3 && (killX < 0 || killX == x && killY == y)) {     // destructive first leg
@@ -387,6 +418,7 @@ MovesFromString (Board board, int flags, int f, int r, int tx, int ty, int angle
                            legNr >>= 1;
                        }
                    }
+                   *atom = origAtom;        // undo any interconversion
                  }
                  if(occup != 4) break;      // occupied squares always terminate the leg
                  continue;
@@ -395,7 +427,7 @@ MovesFromString (Board board, int flags, int f, int r, int tx, int ty, int angle
                if(mode & 8 && y == board[EP_RANK] && occup == 4 && board[EP_FILE] == x) { // to e.p. square
                    cb(board, flags, mine == 1 ? WhiteCapturesEnPassant : BlackCapturesEnPassant, r, f, y, x, cl);
                }
-               if(mode & 16) {              // castling
+               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
@@ -404,12 +436,13 @@ MovesFromString (Board board, int flags, int f, int r, int tx, int ty, int angle
                        cb(board, flags, mine == 1 ? WhiteKingSideCastle : BlackKingSideCastle, r, f, y, f + expo, cl);
                    break;
                }
-               if(occup & mode) cb(board, flags, NormalMove, r, f, y, x, cl);    // allowed, generate
+               if(mode & 16 && (board[y][x] == WhiteKing || board[y][x] == BlackKing)) break; // tame piece, cannot capture royal
+               if(occup & mode) cb(board, flags, y == promoRank ? promo : NormalMove, r, f, y, x, cl); // allowed, generate
                if(occup != 4) break; // not valid transit square
            } while(--i);
          }
-         dx = dy = 1; dirSet = 0x99; // prepare for diagonal moves of K,Q
-       } while(retry--);             // and start doing them
+         dx = dy; dirSet = ds2;      // prepare for diagonal moves of K,Q
+       } while(retry-- && ds2);      // and start doing them
        if(tx >= 0) break;            // don't do other atoms in continuation legs
     }
 } // next atom
@@ -1580,14 +1613,16 @@ CheckTest (Board board, int flags, int rf, int ff, int rt, int ft, int enPassant
     CheckTestClosure cl;
     ChessSquare king = flags & F_WHITE_ON_MOVE ? WhiteKing : BlackKing;
     ChessSquare captured = EmptySquare, ep=0, trampled=0;
+    int saveKill = killX;
     /*  Suppress warnings on uninitialized variables    */
 
     if(gameInfo.variant == VariantXiangqi)
         king = flags & F_WHITE_ON_MOVE ? WhiteWazir : BlackWazir;
     if(gameInfo.variant == VariantKnightmate)
         king = flags & F_WHITE_ON_MOVE ? WhiteUnicorn : BlackUnicorn;
-    if(gameInfo.variant == VariantChu) { // strictly speaking this is not needed, as Chu officially has no check
+    if(gameInfo.variant == VariantChu || gameInfo.variant == VariantShogi) { // strictly speaking this is not needed, as Chu officially has no check
        int r, f, k = king, royals=0, prince = flags & F_WHITE_ON_MOVE ? WhiteMonarch : BlackMonarch;
+       if(gameInfo.variant == VariantShogi) prince -= 11;                   // White/BlackFalcon
        for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
            if(board[r][f] == k || board[r][f] == prince) {
                if(++royals > 1) return FALSE; // no check if we have two royals (ignores double captureby Lion!)
@@ -1602,7 +1637,7 @@ CheckTest (Board board, int flags, int rf, int ff, int rt, int ft, int enPassant
            board[rf][ft] = EmptySquare;
        } else {
            captured = board[rt][ft];
-           if(killX >= 0) { trampled = board[killY][killX]; board[killY][killX] = EmptySquare; }
+           if(killX >= 0) { trampled = board[killY][killX]; board[killY][killX] = EmptySquare; killX = -1; }
        }
        if(rf == DROP_RANK) board[rt][ft] = ff; else { // [HGM] drop
            board[rt][ft] = board[rf][ff];
@@ -1650,7 +1685,7 @@ CheckTest (Board board, int flags, int rf, int ff, int rt, int ft, int enPassant
            board[rf][ft] = captured;
            board[rt][ft] = EmptySquare;
        } else {
-           if(killX >= 0) board[killY][killX] = trampled;
+           if(saveKill >= 0) board[killY][killX = saveKill] = trampled;
            board[rt][ft] = captured;
        }
        board[EP_STATUS] = ep;
@@ -2059,8 +2094,10 @@ Disambiguate (Board board, int flags, DisambiguateClosure *closure)
     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)