allowe parsing / disambiguation of SAN moves like Xe4 in certain situations
[xboard.git] / moves.c
diff --git a/moves.c b/moves.c
index bed2064..2a017c3 100644 (file)
--- a/moves.c
+++ b/moves.c
@@ -1,9 +1,13 @@
 /*
  * moves.c - Move generation and checking
- * $Id$
  *
- * Copyright 1991 by Digital Equipment Corporation, Maynard, Massachusetts.
- * Enhancements Copyright 1992-95 Free Software Foundation, Inc.
+ * Copyright 1991 by Digital Equipment Corporation, Maynard,
+ * Massachusetts.
+ *
+ * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
+ * 2007, 2008, 2009 Free Software Foundation, Inc.
+ *
+ * Enhancements Copyright 2005 Alessandro Scotti
  *
  * The following terms apply to Digital Equipment Corporation's copyright
  * interest in XBoard:
  * SOFTWARE.
  * ------------------------------------------------------------------------
  *
- * The following terms apply to the enhanced version of XBoard distributed
- * by the Free Software Foundation:
+ * The following terms apply to the enhanced version of XBoard
+ * distributed by the Free Software Foundation:
  * ------------------------------------------------------------------------
- * This program is free software; you can redistribute it and/or modify
+ *
+ * GNU XBoard is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
  *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
+ * GNU XBoard is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- * ------------------------------------------------------------------------
- */
+ * along with this program. If not, see http://www.gnu.org/licenses/.  *
+ *
+ *------------------------------------------------------------------------
+ ** See the file ChangeLog for a revision history.  */
 
 #include "config.h"
 
 int WhitePiece P((ChessSquare));
 int BlackPiece P((ChessSquare));
 int SameColor P((ChessSquare, ChessSquare));
+int PosFlags(int index);
+
+extern signed char initialRights[BOARD_SIZE]; /* [HGM] all rights enabled, set in InitPosition */
 
 
 int WhitePiece(piece)
      ChessSquare piece;
 {
-    return (int) piece >= (int) WhitePawn && (int) piece <= (int) WhiteKing;
+    return (int) piece >= (int) WhitePawn && (int) piece < (int) BlackPawn;
 }
 
 int BlackPiece(piece)
      ChessSquare piece;
 {
-    return (int) piece >= (int) BlackPawn && (int) piece <= (int) BlackKing;
+    return (int) piece >= (int) BlackPawn && (int) piece < (int) EmptySquare;
 }
 
 int SameColor(piece1, piece2)
      ChessSquare piece1, piece2;
 {
-    return ((int) piece1 >= (int) WhitePawn &&
-           (int) piece1 <= (int) WhiteKing &&
+    return ((int) piece1 >= (int) WhitePawn &&   /* [HGM] can be > King ! */
+            (int) piece1 <  (int) BlackPawn &&
            (int) piece2 >= (int) WhitePawn &&
-           (int) piece2 <= (int) WhiteKing)
+            (int) piece2 <  (int) BlackPawn)
       ||   ((int) piece1 >= (int) BlackPawn &&
-           (int) piece1 <= (int) BlackKing &&
+            (int) piece1 <  (int) EmptySquare &&
            (int) piece2 >= (int) BlackPawn &&
-           (int) piece2 <= (int) BlackKing);
+            (int) piece2 <  (int) EmptySquare);
 }
 
 ChessSquare PromoPiece(moveType)
@@ -115,88 +123,100 @@ ChessSquare PromoPiece(moveType)
        return WhiteKing;
       case BlackPromotionKing:
        return BlackKing;
-    }
-}
-
-ChessMove PromoCharToMoveType(whiteOnMove, promoChar)
-     int whiteOnMove;
-     int promoChar;
-{
-    if (whiteOnMove) {
-       switch (promoChar) {
-         case 'n':
-         case 'N':
-           return WhitePromotionKnight;
-         case 'b':
-         case 'B':
-           return WhitePromotionBishop;
-         case 'r':
-         case 'R':
-           return WhitePromotionRook;
-         case 'q':
-         case 'Q':
-           return WhitePromotionQueen;
-         case 'k':
-         case 'K':
-           return WhitePromotionKing;
-         case NULLCHAR:
-         default:
-           return NormalMove;
-       }
-    } else {
-       switch (promoChar) {
-         case 'n':
-         case 'N':
-           return BlackPromotionKnight;
-         case 'b':
-         case 'B':
-           return BlackPromotionBishop;
-         case 'r':
-         case 'R':
-           return BlackPromotionRook;
-         case 'q':
-         case 'Q':
-           return BlackPromotionQueen;
-         case 'k':
-         case 'K':
-           return BlackPromotionKing;
-         case NULLCHAR:
-         default:
-           return NormalMove;
-       }
+      case WhitePromotionChancellor:
+        return WhiteMarshall;
+      case BlackPromotionChancellor:
+        return BlackMarshall;
+      case WhitePromotionArchbishop:
+        return WhiteAngel;
+      case BlackPromotionArchbishop:
+        return BlackAngel;
+      case WhitePromotionCentaur:
+        return WhiteSilver;
+      case BlackPromotionCentaur:
+        return BlackSilver;
     }
 }
 
 char pieceToChar[] = {
-    'P', 'N', 'B', 'R', 'Q', 'K',
-    'p', 'n', 'b', 'r', 'q', 'k', 'x'
-  };
+                        '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 PieceToChar(p)
      ChessSquare p;
 {
+    if((int)p < 0 || (int)p >= (int)EmptySquare) return('x'); /* [HGM] for safety */
     return pieceToChar[(int) p];
 }
 
+int PieceToNumber(p)  /* [HGM] holdings: count piece type, ignoring non-participating piece types */
+     ChessSquare p;
+{
+    int i=0;
+    ChessSquare start = (int)p >= (int)BlackPawn ? BlackPawn : WhitePawn;
+
+    while(start++ != p) if(pieceToChar[(int)start-1] != '.') i++;
+    return i;
+}
+
 ChessSquare CharToPiece(c)
      int c;
 {
-    switch (c) {
-      default:
-      case 'x':        return EmptySquare;
-      case 'P':        return WhitePawn;
-      case 'R':        return WhiteRook;
-      case 'N':        return WhiteKnight;
-      case 'B':        return WhiteBishop;
-      case 'Q':        return WhiteQueen;
-      case 'K':        return WhiteKing;
-      case 'p':        return BlackPawn;
-      case 'r':        return BlackRook;
-      case 'n':        return BlackKnight;
-      case 'b':        return BlackBishop;
-      case 'q':        return BlackQueen;
-      case 'k':        return BlackKing;
-    }
+     int i;
+     for(i=0; i< (int) EmptySquare; i++)
+          if(pieceToChar[i] == c) return (ChessSquare) i;
+     return EmptySquare;
+}
+
+ChessMove PromoCharToMoveType(whiteOnMove, promoChar)
+     int whiteOnMove;
+     int promoChar;
+{      /* [HGM] made dependent on CharToPiece to alow alternate piece letters */
+       ChessSquare piece = CharToPiece(whiteOnMove ? ToUpper(promoChar) : ToLower(promoChar) );
+
+
+       if(promoChar == NULLCHAR) return NormalMove;
+
+       switch(piece) {
+               case WhiteQueen:
+                       return WhitePromotionQueen;
+               case WhiteRook:
+                       return WhitePromotionRook;
+               case WhiteBishop:
+                       return WhitePromotionBishop;
+               case WhiteKnight:
+                       return WhitePromotionKnight;
+               case WhiteKing:
+                       return WhitePromotionKing;
+               case WhiteAngel:
+                       return WhitePromotionArchbishop;
+               case WhiteMarshall:
+                       return WhitePromotionChancellor;
+               case WhiteSilver:
+                       return WhitePromotionCentaur;
+               case BlackQueen:
+                       return BlackPromotionQueen;
+               case BlackRook:
+                       return BlackPromotionRook;
+               case BlackBishop:
+                       return BlackPromotionBishop;
+               case BlackKnight:
+                       return BlackPromotionKnight;
+               case BlackKing:
+                       return BlackPromotionKing;
+               case BlackAngel:
+                       return BlackPromotionArchbishop;
+               case BlackMarshall:
+                       return BlackPromotionChancellor;
+               case BlackSilver:
+                       return BlackPromotionCentaur;
+               default:
+                       // not all promotion implemented yet! Take Queen for those we don't know.
+                       return (whiteOnMove ? WhitePromotionQueen : BlackPromotionQueen);
+       }
 }
 
 void CopyBoard(to, from)
@@ -204,8 +224,8 @@ void CopyBoard(to, from)
 {
     int i, j;
     
-    for (i = 0; i < BOARD_SIZE; i++)
-      for (j = 0; j < BOARD_SIZE; j++)
+    for (i = 0; i < BOARD_HEIGHT; i++)
+      for (j = 0; j < BOARD_WIDTH; j++)
        to[i][j] = from[i][j];
 }
 
@@ -214,8 +234,8 @@ int CompareBoards(board1, board2)
 {
     int i, j;
     
-    for (i = 0; i < BOARD_SIZE; i++)
-      for (j = 0; j < BOARD_SIZE; j++) {
+    for (i = 0; i < BOARD_HEIGHT; i++)
+      for (j = 0; j < BOARD_WIDTH; j++) {
          if (board1[i][j] != board2[i][j])
            return FALSE;
     }
@@ -239,45 +259,73 @@ void GenPseudoLegal(board, flags, epfile, callback, closure)
      VOIDSTAR closure;
 {
     int rf, ff;
-    int i, j, d, s, fs, rs, rt, ft;
+    int i, j, d, s, fs, rs, rt, ft, m;
+
+    for (rf = 0; rf < BOARD_HEIGHT; rf++) 
+      for (ff = BOARD_LEFT; ff < BOARD_RGHT; ff++) {
+          ChessSquare piece;
 
-    for (rf = 0; rf <= 7; rf++) 
-      for (ff = 0; ff <= 7; ff++) {
          if (flags & F_WHITE_ON_MOVE) {
              if (!WhitePiece(board[rf][ff])) continue;
          } else {
              if (!BlackPiece(board[rf][ff])) continue;
          }
-         switch (board[rf][ff]) {
-           case EmptySquare:
+          m = 0; piece = board[rf][ff];
+          if(PieceToChar(piece) == '~') 
+                 piece = (ChessSquare) ( DEMOTED piece );
+          if(gameInfo.variant == VariantShogi)
+                 piece = (ChessSquare) ( SHOGI piece );
+
+          switch (piece) {
+            /* case EmptySquare: [HGM] this is nonsense, and conflicts with Shogi cases */
            default:
-             /* can't happen */
+             /* can't happen ([HGM] except for faries...) */
              break;
 
-           case WhitePawn:
-             if (rf < 7 && board[rf + 1][ff] == EmptySquare) {
+             case WhitePawn:
+              if(gameInfo.variant == VariantXiangqi) {
+                  /* [HGM] capture and move straight ahead in Xiangqi */
+                  if (rf < BOARD_HEIGHT-1 &&
+                           !SameColor(board[rf][ff], board[rf + 1][ff]) ) {
+                           callback(board, flags, NormalMove,
+                                    rf, ff, rf + 1, ff, closure);
+                  }
+                  /* and move sideways when across the river */
+                  for (s = -1; s <= 1; s += 2) {
+                      if (rf >= BOARD_HEIGHT>>1 &&
+                          ff + s >= BOARD_LEFT && ff + s < BOARD_RGHT &&
+                          !WhitePiece(board[rf][ff+s]) ) {
+                           callback(board, flags, NormalMove,
+                                    rf, ff, rf, ff+s, closure);
+                      }
+                  }
+                  break;
+              }
+              if (rf < BOARD_HEIGHT-1 && board[rf + 1][ff] == EmptySquare) {
                  callback(board, flags,
-                          rf == 6 ? WhitePromotionQueen : NormalMove,
+                          rf == BOARD_HEIGHT-2 ? WhitePromotionQueen : NormalMove,
                           rf, ff, rf + 1, ff, closure);
              }
              if (rf == 1 && board[2][ff] == EmptySquare &&
-                 board[3][ff] == EmptySquare) {
-                 callback(board, flags, NormalMove,
-                          rf, ff, 3, ff, closure);
+                  gameInfo.variant != VariantShatranj && /* [HGM] */
+                  gameInfo.variant != VariantCourier  && /* [HGM] */
+                  board[3][ff] == EmptySquare ) {
+                      callback(board, flags, NormalMove,
+                               rf, ff, 3, ff, closure);
              }
              for (s = -1; s <= 1; s += 2) {
-                 if (rf < 7 && ff + s >= 0 && ff + s <= 7 &&
+                  if (rf < BOARD_HEIGHT-1 && ff + s >= BOARD_LEFT && ff + s < BOARD_RGHT &&
                      ((flags & F_KRIEGSPIEL_CAPTURE) ||
                       BlackPiece(board[rf + 1][ff + s]))) {
                      callback(board, flags, 
-                              rf == 6 ? WhitePromotionQueen : NormalMove,
+                              rf == BOARD_HEIGHT-2 ? WhitePromotionQueen : NormalMove,
                               rf, ff, rf + 1, ff + s, closure);
                  }
-                 if (rf == 4) {
-                     if (ff + s >= 0 && ff + s <= 7 &&
+                 if (rf == BOARD_HEIGHT-4) {
+                      if (ff + s >= BOARD_LEFT && ff + s < BOARD_RGHT &&
                          (epfile == ff + s || epfile == EP_UNKNOWN) &&
-                         board[4][ff + s] == BlackPawn &&
-                         board[5][ff + s] == EmptySquare) {
+                          board[BOARD_HEIGHT-4][ff + s] == BlackPawn &&
+                          board[BOARD_HEIGHT-3][ff + s] == EmptySquare) {
                          callback(board, flags, WhiteCapturesEnPassant,
                                   rf, ff, 5, ff + s, closure);
                      }
@@ -286,18 +334,37 @@ void GenPseudoLegal(board, flags, epfile, callback, closure)
              break;
 
            case BlackPawn:
+              if(gameInfo.variant == VariantXiangqi) {
+                  /* [HGM] capture straight ahead in Xiangqi */
+                  if (rf > 0 && !SameColor(board[rf][ff], board[rf - 1][ff]) ) {
+                           callback(board, flags, NormalMove,
+                                    rf, ff, rf - 1, ff, closure);
+                  }
+                  /* and move sideways when across the river */
+                  for (s = -1; s <= 1; s += 2) {
+                      if (rf < BOARD_HEIGHT>>1 &&
+                          ff + s >= BOARD_LEFT && ff + s < BOARD_RGHT &&
+                          !BlackPiece(board[rf][ff+s]) ) {
+                           callback(board, flags, NormalMove,
+                                    rf, ff, rf, ff+s, closure);
+                      }
+                  }
+                  break;
+              }
              if (rf > 0 && board[rf - 1][ff] == EmptySquare) {
                  callback(board, flags, 
                           rf == 1 ? BlackPromotionQueen : NormalMove,
                           rf, ff, rf - 1, ff, closure);
              }
-             if (rf == 6 && board[5][ff] == EmptySquare &&
-                 board[4][ff] == EmptySquare) {
+             if (rf == BOARD_HEIGHT-2 && board[BOARD_HEIGHT-3][ff] == EmptySquare &&
+                  gameInfo.variant != VariantShatranj && /* [HGM] */
+                  gameInfo.variant != VariantCourier  && /* [HGM] */
+                 board[BOARD_HEIGHT-4][ff] == EmptySquare) {
                  callback(board, flags, NormalMove,
-                          rf, ff, 4, ff, closure);
+                          rf, ff, BOARD_HEIGHT-4, ff, closure);
              }
              for (s = -1; s <= 1; s += 2) {
-                 if (rf > 0 && ff + s >= 0 && ff + s <= 7 &&
+                  if (rf > 0 && ff + s >= BOARD_LEFT && ff + s < BOARD_RGHT &&
                      ((flags & F_KRIEGSPIEL_CAPTURE) ||
                       WhitePiece(board[rf - 1][ff + s]))) {
                      callback(board, flags, 
@@ -305,7 +372,7 @@ void GenPseudoLegal(board, flags, epfile, callback, closure)
                               rf, ff, rf - 1, ff + s, closure);
                  }
                  if (rf == 3) {
-                     if (ff + s >= 0 && ff + s <= 7 &&
+                      if (ff + s >= BOARD_LEFT && ff + s < BOARD_RGHT &&
                          (epfile == ff + s || epfile == EP_UNKNOWN) &&
                          board[3][ff + s] == WhitePawn &&
                          board[2][ff + s] == EmptySquare) {
@@ -316,48 +383,210 @@ void GenPseudoLegal(board, flags, epfile, callback, closure)
              }             
              break;
 
+            case WhiteUnicorn:
+            case BlackUnicorn:
            case WhiteKnight:
            case BlackKnight:
+            mounted:
              for (i = -1; i <= 1; i += 2)
                for (j = -1; j <= 1; j += 2)
                  for (s = 1; s <= 2; s++) {
                      rt = rf + i*s;
                      ft = ff + j*(3-s);
-                     if (rt < 0 || rt > 7 || ft < 0 || ft > 7) continue;
-                     if (SameColor(board[rf][ff], board[rt][ft])) continue;
+                      if (!(rt < 0 || rt >= BOARD_HEIGHT || ft < BOARD_LEFT || ft >= BOARD_RGHT)
+                          && ( gameInfo.variant != VariantXiangqi || board[rf+i*(s-1)][ff+j*(2-s)] == EmptySquare)
+                          && !SameColor(board[rf][ff], board[rt][ft]))
                      callback(board, flags, NormalMove,
                               rf, ff, rt, ft, closure);
                  }
              break;
 
+            case SHOGI WhiteKnight:
+             for (s = -1; s <= 1; s += 2) {
+                  if (rf < BOARD_HEIGHT-2 && ff + s >= BOARD_LEFT && ff + s < BOARD_RGHT &&
+                      !SameColor(board[rf][ff], board[rf + 2][ff + s])) {
+                      callback(board, flags, NormalMove,
+                               rf, ff, rf + 2, ff + s, closure);
+                 }
+              }
+             break;
+
+            case SHOGI BlackKnight:
+             for (s = -1; s <= 1; s += 2) {
+                  if (rf > 1 && ff + s >= BOARD_LEFT && ff + s < BOARD_RGHT &&
+                      !SameColor(board[rf][ff], board[rf - 2][ff + s])) {
+                      callback(board, flags, NormalMove,
+                               rf, ff, rf - 2, ff + s, closure);
+                 }
+             }             
+             break;
+
+            case WhiteCannon:
+            case BlackCannon:
+              for (d = 0; d <= 1; d++)
+                for (s = -1; s <= 1; s += 2) {
+                  m = 0;
+                 for (i = 1;; i++) {
+                     rt = rf + (i * s) * d;
+                     ft = ff + (i * s) * (1 - d);
+                      if (rt < 0 || rt >= BOARD_HEIGHT || ft < BOARD_LEFT || ft >= BOARD_RGHT) break;
+                      if (m == 0 && board[rt][ft] == EmptySquare)
+                                 callback(board, flags, NormalMove,
+                                          rf, ff, rt, ft, closure);
+                      if (m == 1 && board[rt][ft] != EmptySquare &&
+                          !SameColor(board[rf][ff], board[rt][ft]) )
+                                 callback(board, flags, NormalMove,
+                                          rf, ff, rt, ft, closure);
+                      if (board[rt][ft] != EmptySquare && m++) break;
+                  }
+                }
+             break;
+
+            /* Gold General (and all its promoted versions) . First do the */
+            /* diagonal forward steps, then proceed as normal Wazir        */
+            case SHOGI WhiteWazir:
+            case SHOGI (PROMOTED WhitePawn):
+            case SHOGI (PROMOTED WhiteKnight):
+            case SHOGI (PROMOTED WhiteQueen):
+            case SHOGI (PROMOTED WhiteFerz):
+             for (s = -1; s <= 1; s += 2) {
+                  if (rf < BOARD_HEIGHT-1 && ff + s >= BOARD_LEFT && ff + s < BOARD_RGHT &&
+                      !SameColor(board[rf][ff], board[rf + 1][ff + s])) {
+                      callback(board, flags, NormalMove,
+                              rf, ff, rf + 1, ff + s, closure);
+                 }
+              }
+              goto finishGold;
+
+            case SHOGI BlackWazir:
+            case SHOGI (PROMOTED BlackPawn):
+            case SHOGI (PROMOTED BlackKnight):
+            case SHOGI (PROMOTED BlackQueen):
+            case SHOGI (PROMOTED BlackFerz):
+             for (s = -1; s <= 1; s += 2) {
+                  if (rf > 0 && ff + s >= BOARD_LEFT && ff + s < BOARD_RGHT &&
+                      !SameColor(board[rf][ff], board[rf - 1][ff + s])) {
+                      callback(board, flags, NormalMove,
+                              rf, ff, rf - 1, ff + s, closure);
+                 }
+             }             
+
+            case WhiteWazir:
+            case BlackWazir:
+            finishGold:
+              for (d = 0; d <= 1; d++)
+                for (s = -1; s <= 1; s += 2) {
+                      rt = rf + s * d;
+                      ft = ff + s * (1 - d);
+                      if (!(rt < 0 || rt >= BOARD_HEIGHT || ft < BOARD_LEFT || ft >= BOARD_RGHT)
+                          && !SameColor(board[rf][ff], board[rt][ft]) &&
+                          (gameInfo.variant != VariantXiangqi || InPalace(rt, ft) ) )
+                               callback(board, flags, NormalMove,
+                                        rf, ff, rt, ft, closure);
+                      }
+             break;
+
+            case WhiteAlfil:
+            case BlackAlfil:
+                /* [HGM] support Shatranj pieces */
+                for (rs = -1; rs <= 1; rs += 2) 
+                  for (fs = -1; fs <= 1; fs += 2) {
+                      rt = rf + 2 * rs;
+                      ft = ff + 2 * fs;
+                      if (!(rt < 0 || rt >= BOARD_HEIGHT || ft < BOARD_LEFT || ft >= BOARD_RGHT)
+                          && ( gameInfo.variant != VariantXiangqi ||
+                               board[rf+rs][ff+fs] == EmptySquare && (2*rf < BOARD_HEIGHT) == (2*rt < BOARD_HEIGHT) )
+                         
+                          && !SameColor(board[rf][ff], board[rt][ft]))
+                               callback(board, flags, NormalMove,
+                                        rf, ff, rt, ft, closure);
+                 }
+                break;
+
+            /* Shogi Dragon Horse has to continue with Wazir after Bishop */
+            case SHOGI WhiteCardinal:
+            case SHOGI BlackCardinal:
+              m++;
+
+            /* Capablanca Archbishop continues as Knight                  */
+            case WhiteAngel:
+            case BlackAngel:
+              m++;
+
+            /* Shogi Bishops are ordinary Bishops */
+            case SHOGI WhiteBishop:
+            case SHOGI BlackBishop:
            case WhiteBishop:
            case BlackBishop:
              for (rs = -1; rs <= 1; rs += 2) 
-               for (fs = -1; fs <= 1; fs += 2)
+                for (fs = -1; fs <= 1; fs += 2) 
                  for (i = 1;; i++) {
                      rt = rf + (i * rs);
                      ft = ff + (i * fs);
-                     if (rt < 0 || rt > 7 || ft < 0 || ft > 7) break;
+                      if (rt < 0 || rt >= BOARD_HEIGHT || ft < BOARD_LEFT || ft >= BOARD_RGHT) break;
                      if (SameColor(board[rf][ff], board[rt][ft])) break;
                      callback(board, flags, NormalMove,
                               rf, ff, rt, ft, closure);
                      if (board[rt][ft] != EmptySquare) break;
                  }
+                if(m==1) goto mounted;
+                if(m==2) goto finishGold;
+                /* Bishop falls through */
              break;
 
+            /* Shogi Lance is unlike anything, and asymmetric at that */
+            case SHOGI WhiteQueen:
+              for(i = 1;; i++) {
+                      rt = rf + i;
+                      ft = ff;
+                      if (rt >= BOARD_HEIGHT) break;
+                     if (SameColor(board[rf][ff], board[rt][ft])) break;
+                     callback(board, flags, NormalMove,
+                              rf, ff, rt, ft, closure);
+                      if (board[rt][ft] != EmptySquare) break;
+              }
+              break;
+
+            case SHOGI BlackQueen:
+              for(i = 1;; i++) {
+                      rt = rf - i;
+                      ft = ff;
+                      if (rt < 0) break;
+                     if (SameColor(board[rf][ff], board[rt][ft])) break;
+                     callback(board, flags, NormalMove,
+                              rf, ff, rt, ft, closure);
+                      if (board[rt][ft] != EmptySquare) break;
+              }
+              break;
+
+            /* Shogi Dragon King has to continue as Ferz after Rook moves */
+            case SHOGI WhiteDragon:
+            case SHOGI BlackDragon:
+              m++;
+
+            /* Capablanca Chancellor sets flag to continue as Knight      */
+            case WhiteMarshall:
+            case BlackMarshall:
+              m++;
+
+            /* Shogi Rooks are ordinary Rooks */
+            case SHOGI WhiteRook:
+            case SHOGI BlackRook:
            case WhiteRook:
            case BlackRook:
-             for (d = 0; d <= 1; d++)
-               for (s = -1; s <= 1; s += 2)
+              for (d = 0; d <= 1; d++)
+                for (s = -1; s <= 1; s += 2)
                  for (i = 1;; i++) {
                      rt = rf + (i * s) * d;
                      ft = ff + (i * s) * (1 - d);
-                     if (rt < 0 || rt > 7 || ft < 0 || ft > 7) break;
+                      if (rt < 0 || rt >= BOARD_HEIGHT || ft < BOARD_LEFT || ft >= BOARD_RGHT) break;
                      if (SameColor(board[rf][ff], board[rt][ft])) break;
                      callback(board, flags, NormalMove,
                               rf, ff, rt, ft, closure);
                      if (board[rt][ft] != EmptySquare) break;
                  }
+                if(m==1) goto mounted;
+                if(m==2) goto finishSilver;
              break;
 
            case WhiteQueen:
@@ -368,7 +597,7 @@ void GenPseudoLegal(board, flags, epfile, callback, closure)
                    for (i = 1;; i++) {
                        rt = rf + (i * rs);
                        ft = ff + (i * fs);
-                       if (rt < 0 || rt > 7 || ft < 0 || ft > 7) break;
+                        if (rt < 0 || rt >= BOARD_HEIGHT || ft < BOARD_LEFT || ft >= BOARD_RGHT) break;
                        if (SameColor(board[rf][ff], board[rt][ft])) break;
                        callback(board, flags, NormalMove,
                                 rf, ff, rt, ft, closure);
@@ -377,19 +606,89 @@ void GenPseudoLegal(board, flags, epfile, callback, closure)
                }
              break;
 
+            /* Shogi Pawn and Silver General: first the Pawn move,    */
+            /* then the General continues like a Ferz                 */
+            case SHOGI WhitePawn:
+            case SHOGI WhiteFerz:
+                  if (rf < BOARD_HEIGHT-1 &&
+                           !SameColor(board[rf][ff], board[rf + 1][ff]) ) 
+                           callback(board, flags, NormalMove,
+                                    rf, ff, rf + 1, ff, closure);
+              if(piece != SHOGI WhitePawn) goto finishSilver;
+              break;
+
+            case SHOGI BlackPawn:
+            case SHOGI BlackFerz:
+                  if (rf > 0 &&
+                           !SameColor(board[rf][ff], board[rf - 1][ff]) ) 
+                           callback(board, flags, NormalMove,
+                                    rf, ff, rf - 1, ff, closure);
+              if(piece == SHOGI BlackPawn) break;
+
+            case WhiteFerz:
+            case BlackFerz:
+            finishSilver:
+                /* [HGM] support Shatranj pieces */
+                for (rs = -1; rs <= 1; rs += 2) 
+                  for (fs = -1; fs <= 1; fs += 2) {
+                      rt = rf + rs;
+                      ft = ff + fs;
+                      if (rt < 0 || rt >= BOARD_HEIGHT || ft < BOARD_LEFT || ft >= BOARD_RGHT) continue;
+                      if (!SameColor(board[rf][ff], board[rt][ft]) &&
+                          (gameInfo.variant != VariantXiangqi || InPalace(rt, ft) ) )
+                               callback(board, flags, NormalMove,
+                                        rf, ff, rt, ft, closure);
+                 }
+                break;
+
+           case WhiteSilver:
+           case BlackSilver:
+               m++; // [HGM] superchess: use for Centaur
+            case WhiteMan:
+            case BlackMan:
+            case SHOGI WhiteKing:
+            case SHOGI BlackKing:
            case WhiteKing:
            case BlackKing:
+//            walking:
              for (i = -1; i <= 1; i++)
                for (j = -1; j <= 1; j++) {
                    if (i == 0 && j == 0) continue;
                    rt = rf + i;
                    ft = ff + j;
-                   if (rt < 0 || rt > 7 || ft < 0 || ft > 7) continue;
+                    if (rt < 0 || rt >= BOARD_HEIGHT || ft < BOARD_LEFT || ft >= BOARD_RGHT) continue;
                    if (SameColor(board[rf][ff], board[rt][ft])) continue;
                    callback(board, flags, NormalMove,
                             rf, ff, rt, ft, closure);
                }
+               if(m==1) goto mounted;
+             break;
+
+           case WhiteNightrider:
+           case BlackNightrider:
+             for (i = -1; i <= 1; i += 2)
+               for (j = -1; j <= 1; j += 2)
+                 for (s = 1; s <= 2; s++) {  int k;
+                    for(k=1;; k++) {
+                     rt = rf + k*i*s;
+                     ft = ff + k*j*(3-s);
+                      if (rt < 0 || rt >= BOARD_HEIGHT || ft < BOARD_LEFT || ft >= BOARD_RGHT) break;
+                     if (SameColor(board[rf][ff], board[rt][ft])) break;
+                     callback(board, flags, NormalMove,
+                              rf, ff, rt, ft, closure);
+                     if (board[rt][ft] != EmptySquare) break;
+                    }
+                 }
              break;
+           case WhiteFalcon: // [HGM] wild: for wildcards, self-capture symbolizes move to anywhere
+           case BlackFalcon:
+           case WhiteCobra:
+           case BlackCobra:
+           case WhiteLance:
+           case BlackLance:
+             callback(board, flags, NormalMove, rf, ff, rf, ff, closure);
+             break;
+
          }
       }
 }
@@ -425,7 +724,7 @@ void GenLegalCallback(board, flags, kind, rf, ff, rt, ft, closure)
        if (board[rf][ff] == king) return;
        for (r = rt-1; r <= rt+1; r++) {
          for (f = ft-1; f <= ft+1; f++) {
-           if (r >= 0 && r <= 7 && f >= 0 && f <= 7 &&
+            if (r >= 0 && r < BOARD_HEIGHT && f >= BOARD_LEFT && f < BOARD_RGHT &&
                board[r][f] == king) return;
          }
        }
@@ -435,23 +734,32 @@ void GenLegalCallback(board, flags, kind, rf, ff, rt, ft, closure)
 }
 
 
+typedef struct {
+    int rf, ff, rt, ft;
+    ChessMove kind;
+    int captures; // [HGM] losers
+} LegalityTestClosure;
+
+
 /* Like GenPseudoLegal, but (1) include castling moves, (2) unless
    F_IGNORE_CHECK is set in the flags, omit moves that would leave the
    king in check, and (3) if F_ATOMIC_CAPTURE is set in the flags, omit
    moves that would destroy your own king.  The CASTLE_OK flags are
    true if castling is not yet ruled out by a move of the king or
    rook.  Return TRUE if the player on move is currently in check and
-   F_IGNORE_CHECK is not set.  */
-int GenLegal(board, flags, epfile, callback, closure)
+   F_IGNORE_CHECK is not set.  [HGM] add castlingRights parameter */
+int GenLegal(board, flags, epfile, castlingRights, callback, closure)
      Board board;
      int flags;
      int epfile;
+     char castlingRights[];
      MoveCallback callback;
      VOIDSTAR closure;
 {
     GenLegalClosure cl;
-    int ff;
+    int ff, ft, k, left, right;
     int ignoreCheck = (flags & F_IGNORE_CHECK) != 0;
+    ChessSquare wKing = WhiteKing, bKing = BlackKing;
 
     cl.cb = callback;
     cl.cl = closure;
@@ -461,69 +769,157 @@ int GenLegal(board, flags, epfile, callback, closure)
        CheckTest(board, flags, -1, -1, -1, -1, FALSE)) return TRUE;
 
     /* Generate castling moves */
-    for (ff = 4; ff >= 3; ff-- /*ics wild 1*/) {
+    if(gameInfo.variant == VariantKnightmate) { /* [HGM] Knightmate */
+        wKing = WhiteUnicorn; bKing = BlackUnicorn;
+    }
+
+    for (ff = BOARD_WIDTH>>1; ff >= (BOARD_WIDTH-1)>>1; ff-- /*ics wild 1*/) {
        if ((flags & F_WHITE_ON_MOVE) &&
            (flags & F_WHITE_KCASTLE_OK) &&
-           board[0][ff] == WhiteKing &&
-           board[0][ff + 1] == EmptySquare &&
-           board[0][ff + 2] == EmptySquare &&
-           board[0][6] == EmptySquare &&
-           board[0][7] == WhiteRook &&
-           (ignoreCheck ||
+            board[0][ff] == wKing &&
+            board[0][ff + 1] == EmptySquare &&
+            board[0][ff + 2] == EmptySquare &&
+            board[0][BOARD_RGHT-3] == EmptySquare &&
+            board[0][BOARD_RGHT-2] == EmptySquare &&
+            board[0][BOARD_RGHT-1] == WhiteRook &&
+            castlingRights[0] >= 0 && /* [HGM] check rights */
+            ( castlingRights[2] == ff || castlingRights[6] == ff ) &&
+            (ignoreCheck ||                             
             (!CheckTest(board, flags, 0, ff, 0, ff + 1, FALSE) &&
+              !CheckTest(board, flags, 0, ff, 0, BOARD_RGHT-3, FALSE) &&
+              (gameInfo.variant != VariantJanus || !CheckTest(board, flags, 0, ff, 0, BOARD_RGHT-2, FALSE)) &&
              !CheckTest(board, flags, 0, ff, 0, ff + 2, FALSE)))) {
 
            callback(board, flags,
-                    ff==4 ? WhiteKingSideCastle : WhiteKingSideCastleWild,
-                    0, ff, 0, ff + 2, closure);
+                     ff==BOARD_WIDTH>>1 ? WhiteKingSideCastle : WhiteKingSideCastleWild,
+                     0, ff, 0, ff + ((gameInfo.boardWidth+2)>>2) + (gameInfo.variant == VariantJanus), closure);
        }
        if ((flags & F_WHITE_ON_MOVE) &&
            (flags & F_WHITE_QCASTLE_OK) &&
-           board[0][ff] == WhiteKing &&
+            board[0][ff] == wKing &&
            board[0][ff - 1] == EmptySquare &&
            board[0][ff - 2] == EmptySquare &&
-           board[0][1] == EmptySquare &&
-           board[0][0] == WhiteRook &&
+            board[0][BOARD_LEFT+2] == EmptySquare &&
+            board[0][BOARD_LEFT+1] == EmptySquare &&
+            board[0][BOARD_LEFT+0] == WhiteRook &&
+            castlingRights[1] >= 0 && /* [HGM] check rights */
+            ( castlingRights[2] == ff || castlingRights[6] == ff ) &&
            (ignoreCheck ||
             (!CheckTest(board, flags, 0, ff, 0, ff - 1, FALSE) &&
+              !CheckTest(board, flags, 0, ff, 0, BOARD_LEFT+3, FALSE) &&
              !CheckTest(board, flags, 0, ff, 0, ff - 2, FALSE)))) {
 
            callback(board, flags,
-                    ff==4 ? WhiteQueenSideCastle : WhiteQueenSideCastleWild,
-                    0, ff, 0, ff - 2, closure);
+                    ff==BOARD_WIDTH>>1 ? WhiteQueenSideCastle : WhiteQueenSideCastleWild,
+                     0, ff, 0, ff - ((gameInfo.boardWidth+2)>>2), closure);
        }
        if (!(flags & F_WHITE_ON_MOVE) &&
            (flags & F_BLACK_KCASTLE_OK) &&
-           board[7][ff] == BlackKing &&
-           board[7][ff + 1] == EmptySquare &&
-           board[7][ff + 2] == EmptySquare &&
-           board[7][6] == EmptySquare &&
-           board[7][7] == BlackRook &&
+            board[BOARD_HEIGHT-1][ff] == bKing &&
+           board[BOARD_HEIGHT-1][ff + 1] == EmptySquare &&
+           board[BOARD_HEIGHT-1][ff + 2] == EmptySquare &&
+            board[BOARD_HEIGHT-1][BOARD_RGHT-3] == EmptySquare &&
+            board[BOARD_HEIGHT-1][BOARD_RGHT-2] == EmptySquare &&
+            board[BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook &&
+            castlingRights[3] >= 0 && /* [HGM] check rights */
+            ( castlingRights[5] == ff || castlingRights[7] == ff ) &&
            (ignoreCheck ||
-            (!CheckTest(board, flags, 7, ff, 7, ff + 1, FALSE) &&
-             !CheckTest(board, flags, 7, ff, 7, ff + 2, FALSE)))) {
+            (!CheckTest(board, flags, BOARD_HEIGHT-1, ff, BOARD_HEIGHT-1, ff + 1, FALSE) &&
+              !CheckTest(board, flags, BOARD_HEIGHT-1, ff, BOARD_HEIGHT-1, BOARD_RGHT-3, FALSE) &&
+              (gameInfo.variant != VariantJanus || !CheckTest(board, flags, BOARD_HEIGHT-1, ff, BOARD_HEIGHT-1, BOARD_RGHT-2, FALSE)) &&
+             !CheckTest(board, flags, BOARD_HEIGHT-1, ff, BOARD_HEIGHT-1, ff + 2, FALSE)))) {
 
            callback(board, flags,
-                    ff==4 ? BlackKingSideCastle : BlackKingSideCastleWild,
-                    7, ff, 7, ff + 2, closure);
+                    ff==BOARD_WIDTH>>1 ? BlackKingSideCastle : BlackKingSideCastleWild,
+                     BOARD_HEIGHT-1, ff, BOARD_HEIGHT-1, ff + ((gameInfo.boardWidth+2)>>2) + (gameInfo.variant == VariantJanus), closure);
        }
        if (!(flags & F_WHITE_ON_MOVE) &&
            (flags & F_BLACK_QCASTLE_OK) &&
-           board[7][ff] == BlackKing &&
-           board[7][ff - 1] == EmptySquare &&
-           board[7][ff - 2] == EmptySquare &&
-           board[7][1] == EmptySquare &&
-           board[7][0] == BlackRook &&
+            board[BOARD_HEIGHT-1][ff] == bKing &&
+           board[BOARD_HEIGHT-1][ff - 1] == EmptySquare &&
+           board[BOARD_HEIGHT-1][ff - 2] == EmptySquare &&
+            board[BOARD_HEIGHT-1][BOARD_LEFT+2] == EmptySquare &&
+            board[BOARD_HEIGHT-1][BOARD_LEFT+1] == EmptySquare &&
+            board[BOARD_HEIGHT-1][BOARD_LEFT+0] == BlackRook &&
+            castlingRights[4] >= 0 && /* [HGM] check rights */
+            ( castlingRights[5] == ff || castlingRights[7] == ff ) &&
            (ignoreCheck ||
-            (!CheckTest(board, flags, 7, ff, 7, ff - 1, FALSE) &&
-             !CheckTest(board, flags, 7, ff, 7, ff - 1, FALSE)))) {
+            (!CheckTest(board, flags, BOARD_HEIGHT-1, ff, BOARD_HEIGHT-1, ff - 1, FALSE) &&
+              !CheckTest(board, flags, BOARD_HEIGHT-1, ff, BOARD_HEIGHT-1, BOARD_LEFT+3, FALSE) &&
+              !CheckTest(board, flags, BOARD_HEIGHT-1, ff, BOARD_HEIGHT-1, ff - 2, FALSE)))) {
 
            callback(board, flags,
-                    ff==4 ? BlackQueenSideCastle : BlackQueenSideCastleWild,
-                    7, ff, 7, ff - 2, closure);
+                    ff==BOARD_WIDTH>>1 ? BlackQueenSideCastle : BlackQueenSideCastleWild,
+                     BOARD_HEIGHT-1, ff, BOARD_HEIGHT-1, ff - ((gameInfo.boardWidth+2)>>2), closure);
        }
     }
 
+  if(gameInfo.variant == VariantFischeRandom) {
+
+    /* generate all potential FRC castling moves (KxR), ignoring flags */
+    /* [HGM] test if the Rooks we find have castling rights */
+
+
+    if ((flags & F_WHITE_ON_MOVE) != 0) {
+        ff = castlingRights[2]; /* King file if we have any rights */
+        if(ff > 0 && board[0][ff] == WhiteKing) {
+    if (appData.debugMode) {
+        fprintf(debugFP, "FRC castling, %d %d %d %d %d %d\n",
+                castlingRights[0],castlingRights[1],ff,castlingRights[3],castlingRights[4],castlingRights[5]);
+    }
+            ft = castlingRights[0]; /* Rook file if we have H-side rights */
+            left  = ff+1;
+            right = BOARD_RGHT-2;
+            if(ff == BOARD_RGHT-2) left = right = ff-1;    /* special case */
+            for(k=left; k<=right && ft >= 0; k++) /* first test if blocked */
+                if(k != ft && board[0][k] != EmptySquare) ft = -1;
+            for(k=left; k<right && ft >= 0; k++) /* then if not checked */
+                if(!ignoreCheck && CheckTest(board, flags, 0, ff, 0, k, FALSE)) ft = -1;
+            if(ft >= 0 && board[0][ft] == WhiteRook)
+                callback(board, flags, WhiteHSideCastleFR, 0, ff, 0, ft, closure);
+
+            ft = castlingRights[1]; /* Rook file if we have A-side rights */
+            left  = BOARD_LEFT+2;
+            right = ff-1;
+            if(ff <= BOARD_LEFT+2) { left = ff+1; right = BOARD_LEFT+3; }
+            for(k=left; k<=right && ft >= 0; k++) /* first test if blocked */
+                if(k != ft && board[0][k] != EmptySquare) ft = -1;
+            if(ff > BOARD_LEFT+2) 
+            for(k=left+1; k<=right && ft >= 0; k++) /* then if not checked */
+                if(!ignoreCheck && CheckTest(board, flags, 0, ff, 0, k, FALSE)) ft = -1;
+            if(ft >= 0 && board[0][ft] == WhiteRook)
+                callback(board, flags, WhiteASideCastleFR, 0, ff, 0, ft, closure);
+        }
+    } else {
+        ff = castlingRights[5]; /* King file if we have any rights */
+        if(ff > 0 && board[BOARD_HEIGHT-1][ff] == BlackKing) {
+            ft = castlingRights[3]; /* Rook file if we have H-side rights */
+            left  = ff+1;
+            right = BOARD_RGHT-2;
+            if(ff == BOARD_RGHT-2) left = right = ff-1;    /* special case */
+            for(k=left; k<=right && ft >= 0; k++) /* first test if blocked */
+                if(k != ft && board[BOARD_HEIGHT-1][k] != EmptySquare) ft = -1;
+            for(k=left; k<right && ft >= 0; k++) /* then if not checked */
+                if(!ignoreCheck && CheckTest(board, flags, BOARD_HEIGHT-1, ff, BOARD_HEIGHT-1, k, FALSE)) ft = -1;
+            if(ft >= 0 && board[BOARD_HEIGHT-1][ft] == BlackRook)
+                callback(board, flags, BlackHSideCastleFR, BOARD_HEIGHT-1, ff, BOARD_HEIGHT-1, ft, closure);
+
+            ft = castlingRights[4]; /* Rook file if we have A-side rights */
+            left  = BOARD_LEFT+2;
+            right = ff-1;
+            if(ff <= BOARD_LEFT+2) { left = ff+1; right = BOARD_LEFT+3; }
+            for(k=left; k<=right && ft >= 0; k++) /* first test if blocked */
+                if(k != ft && board[BOARD_HEIGHT-1][k] != EmptySquare) ft = -1;
+            if(ff > BOARD_LEFT+2) 
+            for(k=left+1; k<=right && ft >= 0; k++) /* then if not checked */
+                if(!ignoreCheck && CheckTest(board, flags, BOARD_HEIGHT-1, ff, BOARD_HEIGHT-1, k, FALSE)) ft = -1;
+            if(ft >= 0 && board[BOARD_HEIGHT-1][ft] == BlackRook)
+                callback(board, flags, BlackASideCastleFR, BOARD_HEIGHT-1, ff, BOARD_HEIGHT-1, ft, closure);
+        }
+    }
+
+  }
+
     return FALSE;
 }
 
@@ -569,6 +965,11 @@ int CheckTest(board, flags, rf, ff, rt, ft, enPassant)
     ChessSquare captured = EmptySquare;
     /*  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 (rf >= 0) {
        if (enPassant) {
            captured = board[rf][ft];
@@ -584,9 +985,20 @@ int CheckTest(board, flags, rf, ff, rt, ft, enPassant)
        order a1, a2, a3, ... b1, b2, ..., h8 to find the first king,
        and we test only whether that one is in check. */
     cl.check = 0;
-    for (cl.fking = 0; cl.fking <= 7; cl.fking++)
-       for (cl.rking = 0; cl.rking <= 7; cl.rking++) {
-         if (board[cl.rking][cl.fking] == king) {
+    for (cl.fking = BOARD_LEFT+0; cl.fking < BOARD_RGHT; cl.fking++)
+       for (cl.rking = 0; cl.rking < BOARD_HEIGHT; cl.rking++) {
+          if (board[cl.rking][cl.fking] == king) {
+              if(gameInfo.variant == VariantXiangqi) {
+                  /* [HGM] In Xiangqi opposing Kings means check as well */
+                  int i, dir;
+                  dir = (king >= BlackPawn) ? -1 : 1;
+                  for( i=cl.rking+dir; i>=0 && i<BOARD_HEIGHT &&
+                                board[i][cl.fking] == EmptySquare; i+=dir );
+                  if(i>=0 && i<BOARD_HEIGHT &&
+                      board[i][cl.fking] == (dir>0 ? BlackWazir : WhiteWazir) )
+                          cl.check++;
+              }
+
              GenPseudoLegal(board, flags ^ F_WHITE_ON_MOVE, -1,
                             CheckTestCallback, (VOIDSTAR) &cl);
              goto undo_move;  /* 2-level break */
@@ -605,15 +1017,10 @@ int CheckTest(board, flags, rf, ff, rt, ft, enPassant)
        }
     }
 
-    return cl.check;
+    return cl.fking < BOARD_RGHT ? cl.check : 1000; // [HGM] atomic: return 1000 if we have no king
 }
 
 
-typedef struct {
-    int rf, ff, rt, ft;
-    ChessMove kind;
-} LegalityTestClosure;
-
 extern void LegalityTestCallback P((Board board, int flags, ChessMove kind,
                                    int rf, int ff, int rt, int ft,
                                    VOIDSTAR closure));
@@ -627,24 +1034,82 @@ void LegalityTestCallback(board, flags, kind, rf, ff, rt, ft, closure)
 {
     register LegalityTestClosure *cl = (LegalityTestClosure *) closure;
 
+//    if (appData.debugMode) {
+//        fprintf(debugFP, "Legality test: %c%c%c%c\n", ff+AAA, rf+ONE, ft+AAA, rt+ONE);
+//    }
+    if(board[rt][ft] != EmptySquare || kind==WhiteCapturesEnPassant || kind==BlackCapturesEnPassant)
+       cl->captures++; // [HGM] losers: count legal captures
     if (rf == cl->rf && ff == cl->ff && rt == cl->rt && ft == cl->ft)
       cl->kind = kind;
 }
 
-ChessMove LegalityTest(board, flags, epfile, rf, ff, rt, ft, promoChar)
+ChessMove LegalityTest(board, flags, epfile, castlingRights, rf, ff, rt, ft, promoChar)
      Board board;
      int flags, epfile;
      int rf, ff, rt, ft, promoChar;
+     char castlingRights[];
 {
-    LegalityTestClosure cl;
+    LegalityTestClosure cl; ChessSquare piece = board[rf][ff];
     
+    if (appData.debugMode) {
+        int i;
+        for(i=0; i<6; i++) fprintf(debugFP, "%d ", castlingRights[i]);
+        fprintf(debugFP, "Legality test? %c%c%c%c\n", ff+AAA, rf+ONE, ft+AAA, rt+ONE);
+    }
+    /* [HGM] Lance, Cobra and Falcon are wildcard pieces; consider all their moves legal */
+    /* (perhaps we should disallow moves that obviously leave us in check?)              */
+    if(piece == WhiteFalcon || piece == BlackFalcon ||
+       piece == WhiteCobra  || piece == BlackCobra  ||
+       piece == WhiteLance  || piece == BlackLance)
+        return CheckTest(board, flags, rf, ff, rt, ft, FALSE) ? IllegalMove : NormalMove;
+
     cl.rf = rf;
     cl.ff = ff;
     cl.rt = rt;
     cl.ft = ft;
     cl.kind = IllegalMove;
-    GenLegal(board, flags, epfile, LegalityTestCallback, (VOIDSTAR) &cl);
+    cl.captures = 0; // [HGM] losers: prepare to count legal captures.
+    GenLegal(board, flags, epfile, castlingRights, LegalityTestCallback, (VOIDSTAR) &cl);
+    if((flags & F_MANDATORY_CAPTURE) && cl.captures && board[rt][ft] == EmptySquare
+               && cl.kind != WhiteCapturesEnPassant && cl.kind != BlackCapturesEnPassant)
+       return(IllegalMove); // [HGM] losers: if there are legal captures, non-capts are illegal
+
+    if(gameInfo.variant == VariantShogi) {
+        /* [HGM] Shogi promotions. '=' means defer */
+        if(rf != DROP_RANK && cl.kind == NormalMove) {
+            ChessSquare piece = board[rf][ff];
+
+            if(promoChar == PieceToChar(BlackQueen)) promoChar = NULLCHAR; /* [HGM] Kludge */
+            if(promoChar != NULLCHAR && promoChar != 'x' &&
+               promoChar != '+' && promoChar != '=' &&
+               ToUpper(PieceToChar(PROMOTED piece)) != ToUpper(promoChar) )
+                    cl.kind = IllegalMove;
+            else if(flags & F_WHITE_ON_MOVE) {
+                if( (int) piece < (int) WhiteWazir &&
+                     (rf > BOARD_HEIGHT-4 || rt > BOARD_HEIGHT-4) ) {
+                    if( (piece == WhitePawn || piece == WhiteQueen) && rt > BOARD_HEIGHT-2 ||
+                         piece == WhiteKnight && rt > BOARD_HEIGHT-3) /* promotion mandatory */
+                             cl.kind = promoChar == '=' ? IllegalMove : WhitePromotionKnight;
+                    else /* promotion optional, default is promote */
+                             cl.kind = promoChar == '=' ? NormalMove  : WhitePromotionQueen;
+                   
+                } else cl.kind = (promoChar == NULLCHAR || promoChar == 'x' || promoChar == '=') ?
+                                            NormalMove : IllegalMove;
+            } else {
+                if( (int) piece < (int) BlackWazir && (rf < 3 || rt < 3) ) {
+                    if( (piece == BlackPawn || piece == BlackQueen) && rt < 1 ||
+                         piece == BlackKnight && rt < 2 ) /* promotion obligatory */
+                             cl.kind = promoChar == '=' ? IllegalMove : BlackPromotionKnight;
+                    else /* promotion optional, default is promote */
+                             cl.kind = promoChar == '=' ? NormalMove  : BlackPromotionQueen;
+
+                } else cl.kind = (promoChar == NULLCHAR || promoChar == 'x' || promoChar == '=') ?
+                                            NormalMove : IllegalMove;
+            }
+        }
+    } else
     if (promoChar != NULLCHAR && promoChar != 'x') {
+       if(promoChar == '=') cl.kind = IllegalMove; else // [HGM] shogi: no deferred promotion outside Shogi
        if (cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen) {
            cl.kind = 
              PromoCharToMoveType((flags & F_WHITE_ON_MOVE) != 0, promoChar);
@@ -652,6 +1117,7 @@ ChessMove LegalityTest(board, flags, epfile, rf, ff, rt, ft, promoChar)
            cl.kind = IllegalMove;
        }
     }
+    /* [HGM] For promotions, 'ToQueen' = optional, 'ToKnight' = mandatory */
     return cl.kind;
 }
 
@@ -676,19 +1142,51 @@ void MateTestCallback(board, flags, kind, rf, ff, rt, ft, closure)
 }
 
 /* Return MT_NONE, MT_CHECK, MT_CHECKMATE, or MT_STALEMATE */
-int MateTest(board, flags, epfile)
+int MateTest(board, flags, epfile, castlingRights)
      Board board;
      int flags, epfile;
+     char castlingRights[];
 {
     MateTestClosure cl;
-    int inCheck;
+    int inCheck, r, f, myPieces=0, hisPieces=0, nrKing=0;
+    ChessSquare king = flags & F_WHITE_ON_MOVE ? WhiteKing : BlackKing;
 
+    for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) { 
+        // [HGM] losers: Count pieces and kings, to detect other unorthodox winning conditions
+       nrKing += (board[r][f] == king);   // stm has king
+        if( board[r][f] != EmptySquare ) {
+           if((int)board[r][f] <= (int)king && (int)board[r][f] >= (int)king - (int)WhiteKing + (int)WhitePawn)
+                myPieces++;
+           else hisPieces++;
+       }
+    }
+    if(appData.debugMode) fprintf(debugFP, "MateTest: K=%d, my=%d, his=%d\n", nrKing, myPieces, hisPieces);
+    switch(gameInfo.variant) { // [HGM] losers: extinction wins
+       case VariantShatranj:
+               if(hisPieces == 1) return myPieces > 1 ? MT_BARE : MT_DRAW;
+       default:
+               break;
+       case VariantAtomic:
+               if(nrKing == 0) return MT_NOKING;
+               break;
+       case VariantLosers:
+               if(myPieces == 1) return MT_BARE;
+    }
     cl.count = 0;
-    inCheck = GenLegal(board, flags, epfile, MateTestCallback, (VOIDSTAR) &cl);
+    inCheck = GenLegal(board, flags, epfile, castlingRights, MateTestCallback, (VOIDSTAR) &cl);
+    // [HGM] 3check: yet to do!
     if (cl.count > 0) {
        return inCheck ? MT_CHECK : MT_NONE;
     } else {
-       return inCheck ? MT_CHECKMATE : MT_STALEMATE;
+       if(gameInfo.variant == VariantSuicide) // [HGM] losers: always stalemate, since no check, but result varies
+               return myPieces == hisPieces ? MT_STALEMATE :
+                                       myPieces > hisPieces ? MT_STAINMATE : MT_STEALMATE;
+       else if(gameInfo.variant == VariantLosers) return inCheck ? MT_TRICKMATE : MT_STEALMATE;
+       else if(gameInfo.variant == VariantGiveaway) return MT_STEALMATE; // no check exists, stalemated = win
+                                           
+        return inCheck ? MT_CHECKMATE 
+                      : (gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShatranj) ? 
+                         MT_STAINMATE : MT_STALEMATE;
     }
 }
 
@@ -705,19 +1203,29 @@ void DisambiguateCallback(board, flags, kind, rf, ff, rt, ft, closure)
      VOIDSTAR closure;
 {
     register DisambiguateClosure *cl = (DisambiguateClosure *) closure;
-
-    if ((cl->pieceIn == EmptySquare || cl->pieceIn == board[rf][ff]) &&
+    int wildCard = FALSE; ChessSquare piece = board[rf][ff];
+
+    // [HGM] wild: for wild-card pieces rt and rf are dummies
+    if(piece == WhiteFalcon || piece == BlackFalcon ||
+       piece == WhiteCobra  || piece == BlackCobra  ||
+       piece == WhiteLance  || piece == BlackLance)
+        wildCard = TRUE;
+
+    if ((cl->pieceIn == EmptySquare || cl->pieceIn == board[rf][ff]
+         || PieceToChar(board[rf][ff]) == '~'
+              && cl->pieceIn == (ChessSquare)(DEMOTED board[rf][ff])
+                                                                      ) &&
        (cl->rfIn == -1 || cl->rfIn == rf) &&
        (cl->ffIn == -1 || cl->ffIn == ff) &&
-       (cl->rtIn == -1 || cl->rtIn == rt) &&
-       (cl->ftIn == -1 || cl->ftIn == ft)) {
+       (cl->rtIn == -1 || cl->rtIn == rt || wildCard) &&
+       (cl->ftIn == -1 || cl->ftIn == ft || wildCard)) {
 
        cl->count++;
-       cl->piece = board[rf][ff];
+        cl->piece = board[rf][ff];
        cl->rf = rf;
        cl->ff = ff;
-       cl->rt = rt;
-       cl->ft = ft;
+       cl->rt = wildCard ? cl->rtIn : rt;
+       cl->ft = wildCard ? cl->ftIn : ft;
        cl->kind = kind;
     }
 }
@@ -727,21 +1235,66 @@ void Disambiguate(board, flags, epfile, closure)
      int flags, epfile;
      DisambiguateClosure *closure;
 {
-    int illegal = 0;
+    int illegal = 0; char c = closure->promoCharIn;
+
     closure->count = 0;
     closure->rf = closure->ff = closure->rt = closure->ft = 0;
     closure->kind = ImpossibleMove;
-    GenLegal(board, flags, epfile, DisambiguateCallback, (VOIDSTAR) closure);
+    if (appData.debugMode) {
+        fprintf(debugFP, "Disambiguate in:  %d(%d,%d)-(%d,%d) = %d (%c)\n",
+                             closure->pieceIn,closure->ffIn,closure->rfIn,closure->ftIn,closure->rtIn,
+                             closure->promoCharIn, closure->promoCharIn >= ' ' ? closure->promoCharIn : '-');
+    }
+    GenLegal(board, flags, epfile, initialRights, DisambiguateCallback, (VOIDSTAR) closure);
     if (closure->count == 0) {
        /* See if it's an illegal move due to check */
         illegal = 1;
-       GenLegal(board, flags|F_IGNORE_CHECK, epfile, DisambiguateCallback,
+        GenLegal(board, flags|F_IGNORE_CHECK, epfile, initialRights, DisambiguateCallback,
                 (VOIDSTAR) closure);   
        if (closure->count == 0) {
            /* No, it's not even that */
+    if (appData.debugMode) { int i, j;
+       for(i=BOARD_HEIGHT-1; i>=0; i--) {
+               for(j=0; j<BOARD_WIDTH; j++)
+                       fprintf(debugFP, "%3d", (int) board[i][j]);
+               fprintf(debugFP, "\n");
+       }
+    }
            return;
        }
     }
+
+    if(gameInfo.variant == VariantShogi) {
+        /* [HGM] Shogi promotions. '=' means defer */
+        if(closure->rfIn != DROP_RANK && closure->kind == NormalMove) {
+            ChessSquare piece = closure->piece;
+            if(c != NULLCHAR && c != 'x' && c != '+' && c != '=' &&
+               ToUpper(PieceToChar(PROMOTED piece)) != ToUpper(c) ) 
+                    closure->kind = IllegalMove;
+            else if(flags & F_WHITE_ON_MOVE) {
+                if( (int) piece < (int) WhiteWazir &&
+                     (closure->rf > BOARD_HEIGHT-4 || closure->rt > BOARD_HEIGHT-4) ) {
+                    if( (piece == WhitePawn || piece == WhiteQueen) && closure->rt > BOARD_HEIGHT-2 ||
+                         piece == WhiteKnight && closure->rt > BOARD_HEIGHT-3) /* promotion mandatory */
+                             closure->kind = c == '=' ? IllegalMove : WhitePromotionKnight;
+                    else /* promotion optional, default is promote */
+                             closure->kind = c == '=' ? NormalMove  : WhitePromotionQueen;
+                   
+                } else closure->kind = (c == NULLCHAR || c == 'x' || c == '=') ?
+                                            NormalMove : IllegalMove;
+            } else {
+                if( (int) piece < (int) BlackWazir && (closure->rf < 3 || closure->rt < 3) ) {
+                    if( (piece == BlackPawn || piece == BlackQueen) && closure->rt < 1 ||
+                         piece == BlackKnight && closure->rt < 2 ) /* promotion obligatory */
+                             closure->kind = c == '=' ? IllegalMove : BlackPromotionKnight;
+                    else /* promotion optional, default is promote */
+                             closure->kind = c == '=' ? NormalMove  : BlackPromotionQueen;
+
+                } else closure->kind = (c == NULLCHAR || c == 'x' || c == '=') ?
+                                            NormalMove : IllegalMove;
+            }
+        }
+    } else
     if (closure->promoCharIn != NULLCHAR && closure->promoCharIn != 'x') {
        if (closure->kind == WhitePromotionQueen
            || closure->kind == BlackPromotionQueen) {
@@ -752,7 +1305,10 @@ void Disambiguate(board, flags, epfile, closure)
            closure->kind = IllegalMove;
        }
     }
-    closure->promoChar = ToLower(PieceToChar(PromoPiece(closure->kind)));
+    /* [HGM] returns 'q' for optional promotion, 'n' for mandatory */
+    if(closure->promoCharIn != '=')
+        closure->promoChar = ToLower(closure->promoCharIn);
+    else closure->promoChar = '=';
     if (closure->promoChar == 'x') closure->promoChar = NULLCHAR;
     if (closure->count > 1) {
        closure->kind = AmbiguousMove;
@@ -764,6 +1320,14 @@ void Disambiguate(board, flags, epfile, closure)
        */
        closure->kind = IllegalMove;
     }
+    if(closure->kind == IllegalMove)
+    /* [HGM] might be a variant we don't understand, pass on promotion info */
+        closure->promoChar = ToLower(closure->promoCharIn);
+    if (appData.debugMode) {
+        fprintf(debugFP, "Disambiguate out: %d(%d,%d)-(%d,%d) = %d (%c)\n",
+        closure->piece,closure->ff,closure->rf,closure->ft,closure->rt,closure->promoChar,
+       closure->promoChar >= ' ' ? closure->promoChar:'-');
+    }
 }
 
 
@@ -793,7 +1357,10 @@ void CoordsToAlgebraicCallback(board, flags, kind, rf, ff, rt, ft, closure)
       (CoordsToAlgebraicClosure *) closure;
 
     if (rt == cl->rt && ft == cl->ft &&
-       board[rf][ff] == cl->piece) {
+        (board[rf][ff] == cl->piece
+         || PieceToChar(board[rf][ff]) == '~' &&
+            (ChessSquare) (DEMOTED board[rf][ff]) == cl->piece)
+                                     ) {
        if (rf == cl->rf) {
            if (ff == cl->ff) {
                cl->kind = kind; /* this is the move we want */
@@ -823,71 +1390,94 @@ ChessMove CoordsToAlgebraic(board, flags, epfile,
 {
     ChessSquare piece;
     ChessMove kind;
-    char *outp = out;
+    char *outp = out, c;
     CoordsToAlgebraicClosure cl;
     
     if (rf == DROP_RANK) {
        /* Bughouse piece drop */
        *outp++ = ToUpper(PieceToChar((ChessSquare) ff));
        *outp++ = '@';
-       *outp++ = ft + 'a';
-       *outp++ = rt + '1';
+        *outp++ = ft + AAA;
+        if(rt+ONE <= '9')
+           *outp++ = rt + ONE;
+        else { *outp++ = (rt+ONE-'0')/10 + '0';*outp++ = (rt+ONE-'0')%10 + '0'; }
        *outp = NULLCHAR;
        return (flags & F_WHITE_ON_MOVE) ? WhiteDrop : BlackDrop;
     }
 
     if (promoChar == 'x') promoChar = NULLCHAR;
     piece = board[rf][ff];
+    if(PieceToChar(piece)=='~') piece = (ChessSquare)(DEMOTED piece);
+
+  if (appData.debugMode)
+          fprintf(debugFP, "CoordsToAlgebraic, piece=%d (%d,%d)-(%d,%d) %c\n", (int)piece,ff,rf,ft,rt,promoChar >= ' ' ? promoChar : '-');
     switch (piece) {
       case WhitePawn:
       case BlackPawn:
-       kind = LegalityTest(board, flags, epfile, rf, ff, rt, ft, promoChar);
+        kind = LegalityTest(board, flags, epfile, initialRights, rf, ff, rt, ft, promoChar);
        if (kind == IllegalMove && !(flags&F_IGNORE_CHECK)) {
            /* Keep short notation if move is illegal only because it
                leaves the player in check, but still return IllegalMove */
-           kind = LegalityTest(board, flags|F_IGNORE_CHECK, epfile,
+            kind = LegalityTest(board, flags|F_IGNORE_CHECK, epfile, initialRights,
                               rf, ff, rt, ft, promoChar);
            if (kind == IllegalMove) break;
            kind = IllegalMove;
        }
        /* Pawn move */
-       *outp++ = ff + 'a';
-       if (ff == ft) {
+        *outp++ = ff + AAA;
+        if (ff == ft && board[rt][ft] == EmptySquare) { /* [HGM] Xiangqi has straight noncapts! */
            /* Non-capture; use style "e5" */
-           *outp++ = rt + '1';
+            if(rt+ONE <= '9')
+               *outp++ = rt + ONE;
+            else { *outp++ = (rt+ONE-'0')/10 + '0';*outp++ = (rt+ONE-'0')%10 + '0'; }
        } else {
            /* Capture; use style "exd5" */
-           *outp++ = 'x';
-           *outp++ = ft + 'a';
-           *outp++ = rt + '1';
+            if(gameInfo.variant != VariantXiangqi || board[rt][ft] != EmptySquare )
+            *outp++ = 'x';  /* [HGM] Xiangqi has sideway noncaptures across river! */
+            *outp++ = ft + AAA;
+            if(rt+ONE <= '9')
+               *outp++ = rt + ONE;
+            else { *outp++ = (rt+ONE-'0')/10 + '0';*outp++ = (rt+ONE-'0')%10 + '0'; }
        }
        /* Use promotion suffix style "=Q" */
-       if (promoChar != NULLCHAR && promoChar != 'x') {
-           *outp++ = '=';
-           *outp++ = ToUpper(promoChar);
-       }
        *outp = NULLCHAR;
-       return kind;
+  if (appData.debugMode)
+          fprintf(debugFP, "movetype=%d, promochar=%d=%c\n", (int)kind, promoChar, promoChar >= ' ' ? promoChar : '-');
+        if (promoChar != NULLCHAR) {
+            if(gameInfo.variant == VariantShogi) {
+                /* [HGM] ... but not in Shogi! */
+                *outp++ = promoChar == '=' ? '=' : '+';
+            } else {
+                *outp++ = '=';
+                *outp++ = ToUpper(promoChar);
+            }
+            *outp = NULLCHAR;
+       }
+        return kind;
 
        
       case WhiteKing:
       case BlackKing:
+        /* Fabien moved code: FRC castling first (if KxR), wild castling second */
+       /* Code added by Tord:  FRC castling. */
+       if((piece == WhiteKing && board[rt][ft] == WhiteRook) ||
+          (piece == BlackKing && board[rt][ft] == BlackRook)) {
+         if(ft > ff) strcpy(out, "O-O"); else strcpy(out, "O-O-O");
+            return LegalityTest(board, flags, epfile, initialRights,
+                               rf, ff, rt, ft, promoChar);
+       }
+       /* End of code added by Tord */
        /* Test for castling or ICS wild castling */
        /* Use style "O-O" (oh-oh) for PGN compatibility */
-       if (rf == rt &&
-           rf == ((piece == WhiteKing) ? 0 : 7) &&
-           ((ff == 4 && (ft == 2 || ft == 6)) ||
-            (ff == 3 && (ft == 1 || ft == 5)))) {
-           switch (ft) {
-             case 1:
-             case 6:
+       else if (rf == rt &&
+           rf == ((piece == WhiteKing) ? 0 : BOARD_HEIGHT-1) &&
+            ((ff == BOARD_WIDTH>>1 && (ft == BOARD_LEFT+2 || ft == BOARD_RGHT-2)) ||
+             (ff == (BOARD_WIDTH-1)>>1 && (ft == BOARD_LEFT+1 || ft == BOARD_RGHT-3)))) {
+            if(ft==BOARD_LEFT+1 || ft==BOARD_RGHT-2)
                strcpy(out, "O-O");
-               break;
-             case 2:
-             case 5:
+            else
                strcpy(out, "O-O-O");
-               break;
-           }
+
            /* This notation is always unambiguous, unless there are
               kings on both the d and e files, with "wild castling"
               possible for the king on the d file and normal castling
@@ -896,11 +1486,11 @@ ChessMove CoordsToAlgebraic(board, flags, epfile,
               this situation.  So I am not going to worry about it;
               I'll just generate an ambiguous O-O in this case.
            */
-           return LegalityTest(board, flags, epfile,
+            return LegalityTest(board, flags, epfile, initialRights,
                                rf, ff, rt, ft, promoChar);
        }
+
        /* else fall through */
-       
       default:
        /* Piece move */
        cl.rf = rf;
@@ -910,14 +1500,14 @@ ChessMove CoordsToAlgebraic(board, flags, epfile,
        cl.piece = piece;
        cl.kind = IllegalMove;
        cl.rank = cl.file = cl.either = 0;
-       GenLegal(board, flags, epfile,
+        GenLegal(board, flags, epfile, initialRights,
                 CoordsToAlgebraicCallback, (VOIDSTAR) &cl);
 
        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, epfile,
+            GenLegal(board, flags|F_IGNORE_CHECK, epfile, initialRights,
                     CoordsToAlgebraicCallback, (VOIDSTAR) &cl);
            if (cl.kind == IllegalMove) break;
            cl.kind = IllegalMove;
@@ -928,23 +1518,77 @@ ChessMove CoordsToAlgebraic(board, flags, epfile,
           else "N1f3" or "N5xf7",
           else "Ng1f3" or "Ng5xf7".
        */
-       *outp++ = ToUpper(PieceToChar(piece));
-       
+        c = PieceToChar(piece) ;
+        if( c == '~' || c == '+') {
+           /* [HGM] print nonexistent piece as its demoted version */
+           piece = (ChessSquare) (DEMOTED piece);
+        }
+        if(c=='+') *outp++ = c;
+        *outp++ = ToUpper(PieceToChar(piece));
+
        if (cl.file || (cl.either && !cl.rank)) {
-           *outp++ = ff + 'a';
+            *outp++ = ff + AAA;
        }
        if (cl.rank) {
-           *outp++ = rf + '1';
+            if(rf+ONE <= '9')
+                *outp++ = rf + ONE;
+            else { *outp++ = (rf+ONE-'0')/10 + '0';*outp++ = (rf+ONE-'0')%10 + '0'; }
        }
 
        if(board[rt][ft] != EmptySquare)
          *outp++ = 'x';
 
-       *outp++ = ft + 'a';
-       *outp++ = rt + '1';
+        *outp++ = ft + AAA;
+        if(rt+ONE <= '9')
+           *outp++ = rt + ONE;
+        else { *outp++ = (rt+ONE-'0')/10 + '0';*outp++ = (rt+ONE-'0')%10 + '0'; }
        *outp = NULLCHAR;
-       return cl.kind;
+        if (gameInfo.variant == VariantShogi) {
+            /* [HGM] in Shogi non-pawns can promote */
+            if(flags & F_WHITE_ON_MOVE) {
+                if( (int) cl.piece < (int) WhiteWazir &&
+                     (rf > BOARD_HEIGHT-4 || rt > BOARD_HEIGHT-4) ) {
+                    if( (piece == WhitePawn || piece == WhiteQueen) && rt > BOARD_HEIGHT-2 ||
+                         piece == WhiteKnight && rt > BOARD_HEIGHT-3) /* promotion mandatory */
+                             cl.kind = promoChar == '=' ? IllegalMove : WhitePromotionKnight;
+                    else cl.kind =  WhitePromotionQueen; /* promotion optional */
+                   
+                } else cl.kind = (promoChar == NULLCHAR || promoChar == 'x' || promoChar == '=') ?
+                                            NormalMove : IllegalMove;
+            } else {
+                if( (int) cl.piece < (int) BlackWazir && (rf < 3 || rt < 3) ) {
+                    if( (piece == BlackPawn || piece == BlackQueen) && rt < 1 ||
+                         piece == BlackKnight && rt < 2 ) /* promotion obligatory */
+                             cl.kind = promoChar == '=' ? IllegalMove : BlackPromotionKnight;
+                    else cl.kind =  BlackPromotionQueen; /* promotion optional */
+                } else cl.kind = (promoChar == NULLCHAR || promoChar == 'x' || promoChar == '=') ?
+                                            NormalMove : IllegalMove;
+            }
+            if(cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen) {
+                /* for optional promotions append '+' or '=' */
+                if(promoChar == '=') {
+                    *outp++ = '=';
+                    cl.kind = NormalMove;
+                } else *outp++ = '+';
+                *outp = NULLCHAR;
+            } else if(cl.kind == IllegalMove) {
+                /* Illegal move specifies any given promotion */
+                if(promoChar != NULLCHAR && promoChar != 'x') {
+                    *outp++ = '=';
+                    *outp++ = ToUpper(promoChar);
+                    *outp = NULLCHAR;
+                }
+            }
+        }
+        return cl.kind;
        
+      /* [HGM] Always long notation for fairies we don't know */
+      case WhiteFalcon:
+      case BlackFalcon:
+      case WhiteLance:
+      case BlackLance:
+      case WhiteGrasshopper:
+      case BlackGrasshopper:
       case EmptySquare:
        /* Moving a nonexistent piece */
        break;
@@ -962,11 +1606,15 @@ ChessMove CoordsToAlgebraic(board, flags, epfile,
     if (piece != EmptySquare && piece != WhitePawn && piece != BlackPawn) {
        *outp++ = ToUpper(PieceToChar(piece));
     }
-    *outp++ = ff + 'a';
-    *outp++ = rf + '1';
+    *outp++ = ff + AAA;
+    if(rf+ONE <= '9')
+       *outp++ = rf + ONE;
+    else { *outp++ = (rf+ONE-'0')/10 + '0';*outp++ = (rf+ONE-'0')%10 + '0'; }
     if (board[rt][ft] != EmptySquare) *outp++ = 'x';
-    *outp++ = ft + 'a';
-    *outp++ = rt + '1';
+    *outp++ = ft + AAA;
+    if(rt+ONE <= '9')
+       *outp++ = rt + ONE;
+    else { *outp++ = (rt+ONE-'0')/10 + '0';*outp++ = (rt+ONE-'0')%10 + '0'; }
     /* Use promotion suffix style "=Q" */
     if (promoChar != NULLCHAR && promoChar != 'x') {
        *outp++ = '=';
@@ -976,3 +1624,222 @@ ChessMove CoordsToAlgebraic(board, flags, epfile,
 
     return IllegalMove;
 }
+
+// [HGM] XQ: the following code serves to detect perpetual chasing (Asian rules)
+
+typedef struct {
+    /* Input */
+    int rf, ff, rt, ft;
+    /* Output */
+    int recaptures;
+} ChaseClosure;
+
+// I guess the following variables logically belong in the closure too, but I was too lazy and used globals
+
+int preyStackPointer, chaseStackPointer;
+
+struct {
+char rf, ff, rt, ft;
+} chaseStack[100];
+
+struct {
+char rank, file;
+} preyStack[100];
+
+
+
+
+// there are three new callbacks for use with GenLegal: for adding captures, deleting them, and finding a recapture
+
+extern void AtacksCallback P((Board board, int flags, ChessMove kind,
+                               int rf, int ff, int rt, int ft,
+                               VOIDSTAR closure));
+
+void AttacksCallback(board, flags, kind, rf, ff, rt, ft, closure)
+     Board board;
+     int flags;
+     ChessMove kind;
+     int rf, ff, rt, ft;
+     VOIDSTAR closure;
+{   // For adding captures that can lead to chase indictment to the chaseStack
+    if(board[rt][ft] == EmptySquare) return;                               // non-capture
+    if(board[rt][ft] == WhitePawn && rt <  BOARD_HEIGHT/2) return;         // Pawn before river can be chased
+    if(board[rt][ft] == BlackPawn && rt >= BOARD_HEIGHT/2) return;         // Pawn before river can be chased
+    if(board[rf][ff] == WhitePawn  || board[rf][ff] == BlackPawn)  return; // Pawns are allowed to chase
+    if(board[rf][ff] == WhiteWazir || board[rf][ff] == BlackWazir) return; // King is allowed to chase
+    // move cannot be excluded from being a chase trivially (based on attacker and victim); save it on chaseStack
+    chaseStack[chaseStackPointer].rf = rf;
+    chaseStack[chaseStackPointer].ff = ff;
+    chaseStack[chaseStackPointer].rt = rt;
+    chaseStack[chaseStackPointer].ft = ft;
+    chaseStackPointer++;
+}
+
+extern void ExistingAtacksCallback P((Board board, int flags, ChessMove kind,
+                               int rf, int ff, int rt, int ft,
+                               VOIDSTAR closure));
+
+void ExistingAttacksCallback(board, flags, kind, rf, ff, rt, ft, closure)
+     Board board;
+     int flags;
+     ChessMove kind;
+     int rf, ff, rt, ft;
+     VOIDSTAR closure;
+{   // for removing pre-exsting captures from the chaseStack, to be left with newly created ones
+    int i;
+    register ChaseClosure *cl = (ChaseClosure *) closure; //closure tells us the move played in the repeat loop
+
+    if(board[rt][ft] == EmptySquare) return; // no capture
+    if(rf == cl->rf && ff == cl->ff) { // attacks with same piece from new position are not considered new
+       rf = cl->rt; ff = cl->ft;      // doctor their fromSquare so they will be recognized in chaseStack
+    }
+    // search move in chaseStack, and delete it if it occurred there (as we know now it is not a new capture)
+    for(i=0; i<chaseStackPointer; i++) {
+       if(chaseStack[i].rf == rf && chaseStack[i].ff == ff && 
+          chaseStack[i].rt == rt && chaseStack[i].ft == ft   ) { 
+           // move found on chaseStack, delete it by overwriting with move popped from top of chaseStack
+           chaseStack[i] = chaseStack[--chaseStackPointer];
+           break;
+       }
+    }
+}
+
+extern void ProtectedCallback P((Board board, int flags, ChessMove kind,
+                               int rf, int ff, int rt, int ft,
+                               VOIDSTAR closure));
+
+void ProtectedCallback(board, flags, kind, rf, ff, rt, ft, closure)
+     Board board;
+     int flags;
+     ChessMove kind;
+     int rf, ff, rt, ft;
+     VOIDSTAR closure;
+{   // for determining if a piece (given through the closure) is protected
+    register ChaseClosure *cl = (ChaseClosure *) closure; // closure tells us where to recapture
+
+    if(rt == cl->rt && ft == cl->ft) cl->recaptures++;    // count legal recaptures to this square
+    if(appData.debugMode && board[rt][ft] != EmptySquare)
+       fprintf(debugFP, "try %c%c%c%c=%d\n", ff+AAA, rf+ONE,ft+AAA, rt+ONE, cl->recaptures);
+}
+
+extern char moveList[MAX_MOVES][MOVE_LEN];
+
+int PerpetualChase(int first, int last)
+{   // this routine detects if the side to move in the 'first' position is perpetually chasing (when not checking)
+    int i, j, k, tail;
+    ChaseClosure cl;
+    ChessSquare captured;
+
+    preyStackPointer = 0;        // clear stack of chased pieces
+    for(i=first; i<last; i+=2) { // for all positions with same side to move
+        if(appData.debugMode) fprintf(debugFP, "judge position %i\n", i);
+       chaseStackPointer = 0;   // clear stack that is going to hold possible chases
+       // determine all captures possible after the move, and put them on chaseStack
+       GenLegal(boards[i+1], PosFlags(i), EP_NONE, initialRights, AttacksCallback, &cl);
+       if(appData.debugMode) { int n; 
+           for(n=0; n<chaseStackPointer; n++) 
+                fprintf(debugFP, "%c%c%c%c ", chaseStack[n].ff+AAA, chaseStack[n].rf+ONE, 
+                                              chaseStack[n].ft+AAA, chaseStack[n].rt+ONE);
+            fprintf(debugFP, ": all capts\n");
+       }
+       // determine all captures possible before the move, and delete them from chaseStack
+       cl.rf = moveList[i][1]-ONE; // prepare closure to pass move that led from i to i+1
+       cl.ff = moveList[i][0]-AAA+BOARD_LEFT;
+       cl.rt = moveList[i][3]-ONE;
+       cl.ft = moveList[i][2]-AAA+BOARD_LEFT;
+       GenLegal(boards[i],   PosFlags(i), EP_NONE, initialRights, ExistingAttacksCallback, &cl);
+       if(appData.debugMode) { int n; 
+           for(n=0; n<chaseStackPointer; n++) 
+                fprintf(debugFP, "%c%c%c%c ", chaseStack[n].ff+AAA, chaseStack[n].rf+ONE, 
+                                              chaseStack[n].ft+AAA, chaseStack[n].rt+ONE);
+            fprintf(debugFP, ": new capts after %c%c%c%c\n", cl.ff+AAA, cl.rf+ONE, cl.ft+AAA, cl.rt+ONE);
+       }
+       // chaseSack now contains all captures made possible by the move
+       for(j=0; j<chaseStackPointer; j++) { // run through chaseStack to identify true chases
+            int attacker = (int)boards[i+1][chaseStack[j].rf][chaseStack[j].ff];
+            int victim   = (int)boards[i+1][chaseStack[j].rt][chaseStack[j].ft];
+
+           if(attacker >= (int) BlackPawn) attacker = BLACK_TO_WHITE attacker; // convert to white, as piecee type
+           if(victim   >= (int) BlackPawn) victim   = BLACK_TO_WHITE victim;
+
+           if((attacker == WhiteKnight || attacker == WhiteCannon) && victim == WhiteRook) 
+               continue; // C or H attack on R is always chase; leave on chaseStack
+
+           if(attacker == victim) {
+                if(LegalityTest(boards[i+1], PosFlags(i+1), EP_NONE, initialRights, chaseStack[j].rt, 
+                   chaseStack[j].ft, chaseStack[j].rf, chaseStack[j].ff, NULLCHAR) == NormalMove) {
+                       // we can capture back with equal piece, so this is no chase but a sacrifice
+                        chaseStack[j] = chaseStack[--chaseStackPointer]; // delete the capture from the chaseStack
+                       j--; /* ! */ continue;
+               }
+
+           }
+
+           // the attack is on a lower piece, or on a pinned or blocked equal one
+            // test if the victim is protected by a true protector. First make the capture.
+           captured = boards[i+1][chaseStack[j].rt][chaseStack[j].ft];
+           boards[i+1][chaseStack[j].rt][chaseStack[j].ft] = boards[i+1][chaseStack[j].rf][chaseStack[j].ff];
+           boards[i+1][chaseStack[j].rf][chaseStack[j].ff] = EmptySquare;
+           // Then test if the opponent can recapture
+           cl.recaptures = 0;         // prepare closure to pass recapture square and count moves to it
+           cl.rt = chaseStack[j].rt;
+           cl.ft = chaseStack[j].ft;
+           if(appData.debugMode) {
+               fprintf(debugFP, "test if we can recapture %c%c\n", cl.ft+AAA, cl.rt+ONE);
+           }
+            GenLegal(boards[i+1], PosFlags(i+1), EP_NONE, initialRights, ProtectedCallback, &cl); // try all moves
+           // unmake the capture
+           boards[i+1][chaseStack[j].rf][chaseStack[j].ff] = boards[i+1][chaseStack[j].rt][chaseStack[j].ft];
+            boards[i+1][chaseStack[j].rt][chaseStack[j].ft] = captured;
+           // if a recapture was found, piece is protected, and we are not chasing it.
+           if(cl.recaptures) { // attacked piece was defended by true protector, no chase
+               chaseStack[j] = chaseStack[--chaseStackPointer]; // so delete from chaseStack
+               j--; /* ! */ 
+           }
+       }
+       // chaseStack now contains all moves that chased
+       if(appData.debugMode) { int n; 
+           for(n=0; n<chaseStackPointer; n++) 
+                fprintf(debugFP, "%c%c%c%c ", chaseStack[n].ff+AAA, chaseStack[n].rf+ONE, 
+                                              chaseStack[n].ft+AAA, chaseStack[n].rt+ONE);
+            fprintf(debugFP, ": chases\n");
+       }
+        if(i == first) { // copy all people chased by first move of repeat cycle to preyStack
+           for(j=0; j<chaseStackPointer; j++) {
+                preyStack[j].rank = chaseStack[j].rt;
+                preyStack[j].file = chaseStack[j].ft;
+           }
+           preyStackPointer = chaseStackPointer;
+       }
+       tail = 0;
+        for(j=0; j<chaseStackPointer; j++) {
+           for(k=0; k<preyStackPointer; k++) {
+               // search the victim of each chase move on the preyStack (first occurrence)
+               if(chaseStack[j].ft == preyStack[k].file && chaseStack[j].rt == preyStack[k].rank ) {
+                   if(k < tail) break; // piece was already identified as still being chased
+                   preyStack[preyStackPointer] = preyStack[tail]; // move chased piece to bottom part of preyStack
+                   preyStack[tail] = preyStack[k];                // by swapping
+                   preyStack[k] = preyStack[preyStackPointer];
+                   tail++;
+                   break;
+               }
+           }
+       }
+        preyStackPointer = tail; // keep bottom part of preyStack, popping pieces unchased on move i.
+       if(appData.debugMode) { int n; 
+            for(n=0; n<preyStackPointer; n++) 
+                fprintf(debugFP, "%c%c ", preyStack[n].file+AAA, preyStack[n].rank+ONE);
+            fprintf(debugFP, "always chased upto ply %d\n", i);
+       }
+        // now adjust the location of the chased pieces according to opponent move
+        for(j=0; j<preyStackPointer; j++) {
+            if(preyStack[j].rank == moveList[i+1][1]-ONE &&
+               preyStack[j].file == moveList[i+1][0]-AAA+BOARD_LEFT) {
+                preyStack[j].rank = moveList[i+1][3]-ONE;
+                preyStack[j].file = moveList[i+1][2]-AAA+BOARD_LEFT;
+                break;
+            }
+        }
+    }
+    return preyStackPointer; // if any piece was left on preyStack, it has been perpetually chased
+}