Implement Grand Chess
[xboard.git] / moves.c
diff --git a/moves.c b/moves.c
index bed2064..89728e1 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, 2010, 2011 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"
 
 # include <strings.h>
 #endif /* not HAVE_STRING_H */
 #include "common.h"
-#include "backend.h" 
+#include "backend.h"
 #include "moves.h"
 #include "parser.h"
 
 int WhitePiece P((ChessSquare));
 int BlackPiece P((ChessSquare));
 int SameColor P((ChessSquare, ChessSquare));
+int PosFlags(int index);
+
+extern signed char initialRights[BOARD_FILES]; /* [HGM] all rights enabled, set in InitPosition */
 
 
 int 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);
-}
-
-ChessSquare PromoPiece(moveType)
-     ChessMove moveType;
-{
-    switch (moveType) {
-      default:
-       return EmptySquare;
-      case WhitePromotionQueen:
-       return WhiteQueen;
-      case BlackPromotionQueen:
-       return BlackQueen;
-      case WhitePromotionRook:
-       return WhiteRook;
-      case BlackPromotionRook:
-       return BlackRook;
-      case WhitePromotionBishop:
-       return WhiteBishop;
-      case BlackPromotionBishop:
-       return BlackBishop;
-      case WhitePromotionKnight:
-       return WhiteKnight;
-      case BlackPromotionKnight:
-       return BlackKnight;
-      case WhitePromotionKing:
-       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;
-       }
-    }
+            (int) piece2 <  (int) EmptySquare);
 }
 
 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 pieceNickName[EmptySquare];
 
 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;
+     if(c == '.') return EmptySquare;
+     for(i=0; i< (int) EmptySquare; i++)
+          if(pieceNickName[i] == c) return (ChessSquare) i;
+     for(i=0; i< (int) EmptySquare; i++)
+          if(pieceToChar[i] == c) return (ChessSquare) i;
+     return EmptySquare;
 }
 
 void CopyBoard(to, from)
      Board 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];
+    for (j = 0; j < BOARD_FILES-1; j++) // [HGM] gamestate: copy castling rights and ep status
+       to[CASTLING][j] = from[CASTLING][j];
+    to[HOLDINGS_SET] = 0; // flag used in ICS play
 }
 
 int CompareBoards(board1, board2)
      Board 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;
     }
@@ -231,144 +169,399 @@ int CompareBoards(board1, board2)
    EP_UNKNOWN if we don't know and want to allow all e.p. captures.
    Promotion moves generated are to Queen only.
 */
-void GenPseudoLegal(board, flags, epfile, callback, closure)
+void GenPseudoLegal(board, flags, callback, closure, filter)
      Board board;
      int flags;
-     int epfile;
      MoveCallback callback;
      VOIDSTAR closure;
+     ChessSquare filter; // [HGM] speed: only do moves with this piece type
 {
     int rf, ff;
-    int i, j, d, s, fs, rs, rt, ft;
+    int i, j, d, s, fs, rs, rt, ft, m;
+    int epfile = (signed char)board[EP_STATUS]; // [HGM] gamestate: extract ep status from board
+    int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
+
+    for (rf = 0; rf < BOARD_HEIGHT; rf++)
+      for (ff = BOARD_LEFT; ff < BOARD_RGHT; ff++) {
+          ChessSquare piece;
+          int rookRange = 1000;
 
-    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(filter != EmptySquare && piece != filter) continue;
+          if(gameInfo.variant == VariantShogi)
+                 piece = (ChessSquare) ( SHOGI piece );
+
+          switch ((int)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-1-promoRank ? WhitePromotion : 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);
+             if (rf <= (BOARD_HEIGHT>>1)-3 && board[rf+1][ff] == EmptySquare && // [HGM] grand: also on 3rd rank on 10-board
+                  gameInfo.variant != VariantShatranj && /* [HGM] */
+                  gameInfo.variant != VariantCourier  && /* [HGM] */
+                  board[rf+2][ff] == EmptySquare ) {
+                      callback(board, flags, NormalMove,
+                               rf, ff, rf+2, 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,
+                     callback(board, flags,
+                              rf >= BOARD_HEIGHT-1-promoRank ? WhitePromotion : NormalMove,
                               rf, ff, rf + 1, ff + s, closure);
                  }
-                 if (rf == 4) {
-                     if (ff + s >= 0 && ff + s <= 7 &&
-                         (epfile == ff + s || epfile == EP_UNKNOWN) &&
-                         board[4][ff + s] == BlackPawn &&
-                         board[5][ff + s] == EmptySquare) {
+                 if (rf >= BOARD_HEIGHT+1>>1) {// [HGM] grand: 4th & 5th rank on 10-board
+                      if (ff + s >= BOARD_LEFT && ff + s < BOARD_RGHT &&
+                         (epfile == ff + s || epfile == EP_UNKNOWN) && rf < BOARD_HEIGHT-3 &&
+                          board[rf][ff + s] == BlackPawn &&
+                          board[rf+1][ff + s] == EmptySquare) {
                          callback(board, flags, WhiteCapturesEnPassant,
-                                  rf, ff, 5, ff + s, closure);
+                                  rf, ff, rf+1, ff + s, 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,
+                 callback(board, flags,
+                          rf <= promoRank ? BlackPromotion : NormalMove,
                           rf, ff, rf - 1, ff, closure);
              }
-             if (rf == 6 && board[5][ff] == EmptySquare &&
-                 board[4][ff] == EmptySquare) {
+             if (rf >= (BOARD_HEIGHT+1>>1)+2 && board[rf-1][ff] == EmptySquare && // [HGM] grand
+                  gameInfo.variant != VariantShatranj && /* [HGM] */
+                  gameInfo.variant != VariantCourier  && /* [HGM] */
+                 board[rf-2][ff] == EmptySquare) {
                  callback(board, flags, NormalMove,
-                          rf, ff, 4, ff, closure);
+                          rf, ff, rf-2, 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, 
-                              rf == 1 ? BlackPromotionQueen : NormalMove,
+                     callback(board, flags,
+                              rf <= promoRank ? BlackPromotion : NormalMove,
                               rf, ff, rf - 1, ff + s, closure);
                  }
-                 if (rf == 3) {
-                     if (ff + s >= 0 && ff + s <= 7 &&
-                         (epfile == ff + s || epfile == EP_UNKNOWN) &&
-                         board[3][ff + s] == WhitePawn &&
-                         board[2][ff + s] == EmptySquare) {
+                 if (rf < BOARD_HEIGHT>>1) {
+                      if (ff + s >= BOARD_LEFT && ff + s < BOARD_RGHT &&
+                         (epfile == ff + s || epfile == EP_UNKNOWN) && rf > 2 &&
+                         board[rf][ff + s] == WhitePawn &&
+                         board[rf-1][ff + s] == EmptySquare) {
                          callback(board, flags, BlackCapturesEnPassant,
-                                  rf, ff, 2, ff + s, closure);
+                                  rf, ff, rf-1, ff + s, 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);
+                      if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier
+                                                             || gameInfo.variant == VariantXiangqi) continue; // classical Alfil
+                      rt = rf + rs; // in unknown variant we assume Modern Elephant, which can also do one step
+                      ft = ff + fs;
+                      if (!(rt < 0 || rt >= BOARD_HEIGHT || ft < BOARD_LEFT || ft >= BOARD_RGHT)
+                          && !SameColor(board[rf][ff], board[rt][ft]))
+                               callback(board, flags, NormalMove,
+                                        rf, ff, rt, ft, closure);
+                 }
+                if(gameInfo.variant == VariantSpartan)
+                   for(fs = -1; fs <= 1; fs += 2) {
+                      ft = ff + fs;
+                      if (!(ft < BOARD_LEFT || ft >= BOARD_RGHT) && board[rf][ft] == EmptySquare)
+                               callback(board, flags, NormalMove, rf, ff, rf, ft, closure);
+                   }
+                break;
+
+            /* Make Dragon-Horse also do Dababba moves outside Shogi, for better disambiguation in variant Fairy */
+           case WhiteCardinal:
+           case BlackCardinal:
+              for (d = 0; d <= 1; d++) // Dababba moves that Rook cannot do
+                for (s = -2; s <= 2; s += 4) {
+                     rt = rf + s * d;
+                     ft = ff + s * (1 - d);
+                      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);
+                 }
+
+            /* 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 (rs = -1; rs <= 1; rs += 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;
+
+            /* Make Dragon-King Dababba & Rook-like outside Shogi, for better disambiguation in variant Fairy */
+           case WhiteDragon:
+           case BlackDragon:
+              for (d = 0; d <= 1; d++) // Dababba moves that Rook cannot do
+                for (s = -2; s <= 2; s += 4) {
+                     rt = rf + s * d;
+                     ft = ff + s * (1 - d);
+                      if (rt < 0 || rt >= BOARD_HEIGHT || ft < BOARD_LEFT || ft >= BOARD_RGHT || board[rf+rt>>1][ff+ft>>1] == EmptySquare) continue;
+                     if (SameColor(board[rf][ff], board[rt][ft])) continue;
+                     callback(board, flags, NormalMove, rf, ff, rt, ft, closure);
+                 }
+              if(gameInfo.variant == VariantSpartan) rookRange = 2; // in Spartan Chess restrict range to modern Dababba
+              goto doRook;
+              
+            /* 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++;
+              m += (gameInfo.variant == VariantSpartan); // in Spartan Chess Chancellor is used for Dragon King.
+
+            /* 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)
+          doRook:
+              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 (board[rt][ft] != EmptySquare || i == rookRange) break;
                  }
+                if(m==1) goto mounted;
+                if(m==2) goto finishSilver;
              break;
 
            case WhiteQueen:
            case BlackQueen:
-             for (rs = -1; rs <= 1; rs++) 
+             for (rs = -1; rs <= 1; rs++)
                for (fs = -1; fs <= 1; fs++) {
                    if (rs == 0 && fs == 0) continue;
                    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 +570,140 @@ 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 WhiteMan:
+                if(gameInfo.variant != VariantMakruk) goto commoner;
+            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 BlackMan:
+                if(gameInfo.variant != VariantMakruk) goto commoner;
+            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
+            commoner:
+            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;
+
+           Amazon:
+             /* First do Bishop,then continue like Chancellor */
+             for (rs = -1; rs <= 1; rs += 2)
+                for (fs = -1; fs <= 1; fs += 2)
+                 for (i = 1;; i++) {
+                     rt = rf + (i * rs);
+                     ft = ff + (i * fs);
+                      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;
+                 }
+             m++;
+             goto doRook;
+
+           // Use Lance as Berolina / Spartan Pawn.
+           case WhiteLance:
+             if(gameInfo.variant == VariantSuper) goto Amazon;
+             if (rf < BOARD_HEIGHT-1 && BlackPiece(board[rf + 1][ff]))
+                 callback(board, flags,
+                          rf >= BOARD_HEIGHT-1-promoRank ? WhitePromotion : NormalMove,
+                          rf, ff, rf + 1, ff, closure);
+             for (s = -1; s <= 1; s += 2) {
+                 if (rf < BOARD_HEIGHT-1 && ff + s >= BOARD_LEFT && ff + s < BOARD_RGHT && board[rf + 1][ff + s] == EmptySquare)
+                     callback(board, flags, 
+                              rf >= BOARD_HEIGHT-1-promoRank ? WhitePromotion : NormalMove,
+                              rf, ff, rf + 1, ff + s, closure);
+                 if (rf == 1 && ff + 2*s >= BOARD_LEFT && ff + 2*s < BOARD_RGHT && board[3][ff + 2*s] == EmptySquare )
+                     callback(board, flags, NormalMove, rf, ff, 3, ff + 2*s, closure);
+             }
+             break;
+               
+           case BlackLance:
+             if(gameInfo.variant == VariantSuper) goto Amazon;
+             if (rf > 0 && WhitePiece(board[rf - 1][ff]))
+                 callback(board, flags,
+                          rf <= promoRank ? BlackPromotion : NormalMove,
+                          rf, ff, rf - 1, ff, closure);
+             for (s = -1; s <= 1; s += 2) {
+                 if (rf > 0 && ff + s >= BOARD_LEFT && ff + s < BOARD_RGHT && board[rf - 1][ff + s] == EmptySquare)
+                     callback(board, flags, 
+                              rf <= promoRank ? BlackPromotion : NormalMove,
+                              rf, ff, rf - 1, ff + s, closure);
+                 if (rf == BOARD_HEIGHT-2 && ff + 2*s >= BOARD_LEFT && ff + 2*s < BOARD_RGHT && board[rf-2][ff + 2*s] == EmptySquare )
+                     callback(board, flags, NormalMove, rf, ff, rf-2, ff + 2*s, closure);
+             }
+            break;
+
+           case WhiteFalcon: // [HGM] wild: for wildcards, self-capture symbolizes move to anywhere
+           case BlackFalcon:
+           case WhiteCobra:
+           case BlackCobra:
+             callback(board, flags, NormalMove, rf, ff, rf, ff, closure);
+             break;
+
          }
       }
 }
@@ -400,6 +714,8 @@ typedef struct {
     VOIDSTAR cl;
 } GenLegalClosure;
 
+int rFilter, fFilter; // [HGM] speed: sorry, but I get a bit tired of this closure madness
+
 extern void GenLegalCallback P((Board board, int flags, ChessMove kind,
                                int rf, int ff, int rt, int ft,
                                VOIDSTAR closure));
@@ -413,10 +729,17 @@ void GenLegalCallback(board, flags, kind, rf, ff, rt, ft, closure)
 {
     register GenLegalClosure *cl = (GenLegalClosure *) closure;
 
-    if (!(flags & F_IGNORE_CHECK) &&
-       CheckTest(board, flags, rf, ff, rt, ft,
+    if(rFilter >= 0 && rFilter != rt || fFilter >= 0 && fFilter != ft) return; // [HGM] speed: ignore moves with wrong to-square
+
+    if (!(flags & F_IGNORE_CHECK) ) {
+      int check, promo = (gameInfo.variant == VariantSpartan && kind == BlackPromotion);
+      if(promo) board[rf][ff] = BlackKing; // [HGM] spartan: promote to King before check-test
+       check = CheckTest(board, flags, rf, ff, rt, ft,
                  kind == WhiteCapturesEnPassant ||
-                 kind == BlackCapturesEnPassant)) return;
+                 kind == BlackCapturesEnPassant);
+       if(promo) board[rf][ff] = BlackLance;
+      if(check) return;
+    }
     if (flags & F_ATOMIC_CAPTURE) {
       if (board[rt][ft] != EmptySquare ||
          kind == WhiteCapturesEnPassant || kind == BlackCapturesEnPassant) {
@@ -425,7 +748,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,95 +758,193 @@ 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, callback, closure, filter)
      Board board;
      int flags;
-     int epfile;
      MoveCallback callback;
      VOIDSTAR closure;
+     ChessSquare filter;
 {
     GenLegalClosure cl;
-    int ff;
+    int ff, ft, k, left, right, swap;
     int ignoreCheck = (flags & F_IGNORE_CHECK) != 0;
+    ChessSquare wKing = WhiteKing, bKing = BlackKing, *castlingRights = board[CASTLING];
 
     cl.cb = callback;
     cl.cl = closure;
-    GenPseudoLegal(board, flags, epfile, GenLegalCallback, (VOIDSTAR) &cl);
+    if(filter == EmptySquare) rFilter = fFilter = -1; // [HGM] speed: do not filter on square if we do not filter on piece
+    GenPseudoLegal(board, flags, GenLegalCallback, (VOIDSTAR) &cl, filter);
 
     if (!ignoreCheck &&
        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] != NoRights && /* [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] != NoRights && /* [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] != NoRights && /* [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] != NoRights && /* [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((swap = gameInfo.variant == VariantSChess) || flags & F_FRC_TYPE_CASTLING) {
+
+    /* generate all potential FRC castling moves (KxR), ignoring flags */
+    /* [HGM] test if the Rooks we find have castling rights */
+    /* In S-Chess we generate RxK for allowed castlings, for gating at Rook square */
+
+
+    if ((flags & F_WHITE_ON_MOVE) != 0) {
+        ff = castlingRights[2]; /* King file if we have any rights */
+        if(ff != NoRights && 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 != NoRights; k++) /* first test if blocked */
+                if(k != ft && board[0][k] != EmptySquare) ft = NoRights;
+            for(k=left; k<right && ft != NoRights; k++) /* then if not checked */
+                if(!ignoreCheck && CheckTest(board, flags, 0, ff, 0, k, FALSE)) ft = NoRights;
+            if(ft != NoRights && board[0][ft] == WhiteRook)
+                callback(board, flags, WhiteHSideCastleFR, 0, swap ? ft : ff, 0, swap ? ff : 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 != NoRights; k++) /* first test if blocked */
+                if(k != ft && board[0][k] != EmptySquare) ft = NoRights;
+            if(ff > BOARD_LEFT+2)
+            for(k=left+1; k<=right && ft != NoRights; k++) /* then if not checked */
+                if(!ignoreCheck && CheckTest(board, flags, 0, ff, 0, k, FALSE)) ft = NoRights;
+            if(ft != NoRights && board[0][ft] == WhiteRook)
+                callback(board, flags, WhiteASideCastleFR, 0, swap ? ft : ff, 0, swap ? ff : ft, closure);
+        }
+    } else {
+        ff = castlingRights[5]; /* King file if we have any rights */
+        if(ff != NoRights && 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 != NoRights; k++) /* first test if blocked */
+                if(k != ft && board[BOARD_HEIGHT-1][k] != EmptySquare) ft = NoRights;
+            for(k=left; k<right && ft != NoRights; k++) /* then if not checked */
+                if(!ignoreCheck && CheckTest(board, flags, BOARD_HEIGHT-1, ff, BOARD_HEIGHT-1, k, FALSE)) ft = NoRights;
+            if(ft != NoRights && board[BOARD_HEIGHT-1][ft] == BlackRook)
+                callback(board, flags, BlackHSideCastleFR, BOARD_HEIGHT-1, swap ? ft : ff, BOARD_HEIGHT-1, swap ? ff : 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 != NoRights; k++) /* first test if blocked */
+                if(k != ft && board[BOARD_HEIGHT-1][k] != EmptySquare) ft = NoRights;
+            if(ff > BOARD_LEFT+2)
+            for(k=left+1; k<=right && ft != NoRights; k++) /* then if not checked */
+                if(!ignoreCheck && CheckTest(board, flags, BOARD_HEIGHT-1, ff, BOARD_HEIGHT-1, k, FALSE)) ft = NoRights;
+            if(ft != NoRights && board[BOARD_HEIGHT-1][ft] == BlackRook)
+                callback(board, flags, BlackASideCastleFR, BOARD_HEIGHT-1, swap ? ft : ff, BOARD_HEIGHT-1, swap ? ff : ft, closure);
+        }
+    }
+
+  }
+
     return FALSE;
 }
 
@@ -558,7 +979,7 @@ void CheckTestCallback(board, flags, kind, rf, ff, rt, ft, closure)
    e.p. capture.  The possibility of castling out of a check along the
    back rank is not accounted for (i.e., we still return nonzero), as
    this is illegal anyway.  Return value is the number of times the
-   king is in check. */ 
+   king is in check. */
 int CheckTest(board, flags, rf, ff, rt, ft, enPassant)
      Board board;
      int flags;
@@ -569,6 +990,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];
@@ -578,18 +1004,28 @@ int CheckTest(board, flags, rf, ff, rt, ft, enPassant)
        }
        board[rt][ft] = board[rf][ff];
        board[rf][ff] = EmptySquare;
-    }
+    } else board[rt][ft] = ff; // [HGM] drop
 
     /* For compatibility with ICS wild 9, we scan the board in the
        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) {
-             GenPseudoLegal(board, flags ^ F_WHITE_ON_MOVE, -1,
-                            CheckTestCallback, (VOIDSTAR) &cl);
-             goto undo_move;  /* 2-level break */
+    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) {
+             cl.check = 0;
+              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, CheckTestCallback, (VOIDSTAR) &cl, EmptySquare);
+             if(gameInfo.variant != VariantSpartan || cl.check == 0) // in Spartan Chess go on to test if other King is checked too
+                goto undo_move;  /* 2-level break */
          }
       }
 
@@ -603,16 +1039,46 @@ int CheckTest(board, flags, rf, ff, rt, ft, enPassant)
        } else {
            board[rt][ft] = captured;
        }
-    }
+    } else board[rt][ft] = EmptySquare; // [HGM] drop
 
-    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;
+ChessMove LegalDrop(board, flags, piece, rt, ft)
+     Board board;
+     int flags;
+     ChessSquare piece;
+     int rt, ft;
+{   // [HGM] put drop legality testing in separate routine for clarity
+    int n;
+if(appData.debugMode) fprintf(debugFP, "LegalDrop: %d @ %d,%d)\n", piece, ft, rt);
+    if(board[rt][ft] != EmptySquare) return ImpossibleMove; // must drop to empty square
+    n = PieceToNumber(piece);
+    if(gameInfo.holdingsWidth == 0 || (flags & F_WHITE_ON_MOVE ? board[n][BOARD_WIDTH-1] : board[BOARD_HEIGHT-1-n][0]) != piece)
+        return ImpossibleMove; // piece not available
+    if(gameInfo.variant == VariantShogi) { // in Shogi lots of drops are forbidden!
+        if((piece == WhitePawn || piece == WhiteQueen) && rt == BOARD_HEIGHT-1 ||
+           (piece == BlackPawn || piece == BlackQueen) && rt == 0 ||
+            piece == WhiteKnight && rt > BOARD_HEIGHT-3 ||
+            piece == BlackKnight && rt < 2 ) return IllegalMove; // e.g. where dropped piece has no moves
+        if(piece == WhitePawn || piece == BlackPawn) {
+            int r;
+            for(r=1; r<BOARD_HEIGHT-1; r++)
+                if(board[r][ft] == piece) return IllegalMove; // or there already is a Pawn in file
+            // should still test if we mate with this Pawn
+        }
+    } else if(gameInfo.variant == VariantSChess) { // only back-rank drops
+        if (rt != (piece < BlackPawn ? 0 : BOARD_HEIGHT-1)) return IllegalMove;
+    } else {
+        if( (piece == WhitePawn || piece == BlackPawn) &&
+            (rt == 0 || rt == BOARD_HEIGHT -1 ) )
+            return IllegalMove; /* no pawn drops on 1st/8th */
+    }
+if(appData.debugMode) fprintf(debugFP, "LegalDrop: %d @ %d,%d)\n", piece, ft, rt);
+    if (!(flags & F_IGNORE_CHECK) &&
+       CheckTest(board, flags, DROP_RANK, piece, rt, ft, FALSE) ) return IllegalMove;
+    return flags & F_WHITE_ON_MOVE ? WhiteDrop : BlackDrop;
+}
 
 extern void LegalityTestCallback P((Board board, int flags, ChessMove kind,
                                    int rf, int ff, int rt, int ft,
@@ -627,27 +1093,113 @@ 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, rf, ff, rt, ft, promoChar)
      Board board;
-     int flags, epfile;
+     int flags;
      int rf, ff, rt, ft, promoChar;
 {
-    LegalityTestClosure cl;
-    
+    LegalityTestClosure cl; ChessSquare piece, filterPiece, *castlingRights = board[CASTLING];
+
+    if(rf == DROP_RANK) return LegalDrop(board, flags, ff, rt, ft);
+    piece = filterPiece = board[rf][ff];
+    if(PieceToChar(piece) == '~') filterPiece = DEMOTED piece; 
+
+    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] 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)
+        return CheckTest(board, flags, rf, ff, rt, ft, FALSE) ? IllegalMove : NormalMove;
+
     cl.rf = rf;
     cl.ff = ff;
-    cl.rt = rt;
-    cl.ft = ft;
+    cl.rt = rFilter = rt; // [HGM] speed: filter on to-square
+    cl.ft = fFilter = ft;
     cl.kind = IllegalMove;
-    GenLegal(board, flags, epfile, LegalityTestCallback, (VOIDSTAR) &cl);
-    if (promoChar != NULLCHAR && promoChar != 'x') {
-       if (cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen) {
-           cl.kind = 
-             PromoCharToMoveType((flags & F_WHITE_ON_MOVE) != 0, promoChar);
+    cl.captures = 0; // [HGM] losers: prepare to count legal captures.
+    if(flags & F_MANDATORY_CAPTURE) filterPiece = EmptySquare; // [HGM] speed: do not filter in suicide, to find all captures
+    GenLegal(board, flags, LegalityTestCallback, (VOIDSTAR) &cl, filterPiece);
+    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(promoChar == 'x') promoChar = NULLCHAR; // [HGM] is this ever the case?
+    if(gameInfo.variant == VariantSChess && promoChar && promoChar != '=' && board[rf][ff] != WhitePawn && board[rf][ff] != BlackPawn) {
+        if(board[rf][ff] < BlackPawn) { // white
+            if(rf != 0) return IllegalMove; // must be on back rank
+            if(board[PieceToNumber(CharToPiece(ToUpper(promoChar)))][BOARD_WIDTH-2] == 0) return ImpossibleMove;// must be in stock
+        } else {
+            if(rf != BOARD_HEIGHT-1) return IllegalMove;
+            if(board[BOARD_HEIGHT-1-PieceToNumber(CharToPiece(ToLower(promoChar)))][1] == 0) return ImpossibleMove;
+        }
+    } else
+    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 == 'd' && (piece == WhiteRook   || piece == BlackRook)   ||
+               promoChar == 'h' && (piece == WhiteBishop || piece == BlackBishop) ||
+               promoChar == 'g' && (piece <= WhiteFerz || piece <= BlackFerz && piece >= BlackPawn) )
+                  promoChar = '+'; // allowed ICS notations
+if(appData.debugMode)fprintf(debugFP,"SHOGI promoChar = %c\n", promoChar ? promoChar : '-');
+            if(promoChar != NULLCHAR && promoChar != '+' && promoChar != '=')
+                return CharToPiece(promoChar) == EmptySquare ? ImpossibleMove : IllegalMove;
+            else if(flags & F_WHITE_ON_MOVE) {
+                if( (int) piece < (int) WhiteWazir &&
+                     (rf >= BOARD_HEIGHT*2/3 || rt >= BOARD_HEIGHT*2/3) ) {
+                    if( (piece == WhitePawn || piece == WhiteQueen) && rt > BOARD_HEIGHT-2 ||
+                         piece == WhiteKnight && rt > BOARD_HEIGHT-3) /* promotion mandatory */
+                       cl.kind = promoChar == '=' ? IllegalMove : WhitePromotion;
+                    else /* promotion optional, default is defer */
+                       cl.kind = promoChar == '+' ? WhitePromotion : WhiteNonPromotion;
+                } else cl.kind = promoChar == '+' ? IllegalMove : NormalMove;
+            } else {
+                if( (int) piece < (int) BlackWazir && (rf < BOARD_HEIGHT/3 || rt < BOARD_HEIGHT/3) ) {
+                    if( (piece == BlackPawn || piece == BlackQueen) && rt < 1 ||
+                         piece == BlackKnight && rt < 2 ) /* promotion obligatory */
+                       cl.kind = promoChar == '=' ? IllegalMove : BlackPromotion;
+                    else /* promotion optional, default is defer */
+                       cl.kind = promoChar == '+' ? BlackPromotion : BlackNonPromotion;
+                } else cl.kind = promoChar == '+' ? IllegalMove : NormalMove;
+            }
+        }
+    } else
+    if (promoChar != NULLCHAR) {
+       if(promoChar == '=') cl.kind = IllegalMove; else // [HGM] shogi: no deferred promotion outside Shogi
+       if (cl.kind == WhitePromotion || cl.kind == BlackPromotion) {
+           ChessSquare piece = CharToPiece(flags & F_WHITE_ON_MOVE ? ToUpper(promoChar) : ToLower(promoChar));
+           if(piece == EmptySquare)
+                cl.kind = ImpossibleMove; // non-existing piece
+           if(gameInfo.variant == VariantSpartan && cl.kind == BlackPromotion ) {
+               if(promoChar != PieceToChar(BlackKing)) {
+                   if(CheckTest(board, flags, rf, ff, rt, ft, FALSE)) cl.kind = IllegalMove; // [HGM] spartan: only promotion to King was possible
+                   if(piece == BlackLance) cl.kind = ImpossibleMove;
+               } else { // promotion to King allowed only if we do not haave two yet
+                   int r, f, kings = 0;
+                   for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) kings += (board[r][f] == BlackKing);
+                   if(kings == 2) cl.kind = IllegalMove;
+               }
+           } else if(piece == WhitePawn && rt == BOARD_HEIGHT-1 ||
+                         piece == BlackPawn && rt == 0) cl.kind = IllegalMove; // cannot stay Pawn on last rank in any variant
+           else if((piece == WhiteUnicorn || piece == BlackUnicorn) && gameInfo.variant == VariantKnightmate)
+             cl.kind = IllegalMove; // promotion to Royal Knight not allowed
+           else if((piece == WhiteKing || piece == BlackKing) && gameInfo.variant != VariantSuicide && gameInfo.variant != VariantGiveaway)
+             cl.kind = IllegalMove; // promotion to King usually not allowed
        } else {
            cl.kind = IllegalMove;
        }
@@ -676,23 +1228,64 @@ 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)
      Board board;
-     int flags, epfile;
+     int flags;
 {
     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, MateTestCallback, (VOIDSTAR) &cl, EmptySquare);
+    // [HGM] 3check: yet to do!
     if (cl.count > 0) {
        return inCheck ? MT_CHECK : MT_NONE;
     } else {
-       return inCheck ? MT_CHECKMATE : MT_STALEMATE;
+        if(gameInfo.holdingsWidth && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
+                                                                      && gameInfo.variant != VariantGrand) { // drop game
+            int r, f, n, holdings = flags & F_WHITE_ON_MOVE ? BOARD_WIDTH-1 : 0;
+            for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) if(board[r][f] == EmptySquare) // all empty squares
+                for(n=0; n<BOARD_HEIGHT; n++) // all pieces in hand
+                    if(board[n][holdings] != EmptySquare) {
+                        int moveType = LegalDrop(board, flags, board[n][holdings], r, f);
+                        if(moveType == WhiteDrop || moveType == BlackDrop) return (inCheck ? MT_CHECK : MT_NONE); // we have legal drop
+                    }
+        }
+       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;
     }
 }
 
-     
+
 extern void DisambiguateCallback P((Board board, int flags, ChessMove kind,
                                    int rf, int ff, int rt, int ft,
                                    VOIDSTAR closure));
@@ -705,55 +1298,128 @@ void DisambiguateCallback(board, flags, kind, rf, ff, rt, ft, closure)
      VOIDSTAR closure;
 {
     register DisambiguateClosure *cl = (DisambiguateClosure *) closure;
+    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)
+        wildCard = TRUE;
 
-    if ((cl->pieceIn == EmptySquare || cl->pieceIn == board[rf][ff]) &&
+    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->rf = rf;
-       cl->ff = ff;
-       cl->rt = rt;
-       cl->ft = ft;
-       cl->kind = kind;
+       if(cl->count == 1 || board[rt][ft] != EmptySquare) {
+         // [HGM] oneclick: if multiple moves, be sure we remember capture
+         cl->piece = board[rf][ff];
+         cl->rf = rf;
+         cl->ff = ff;
+         cl->rt = wildCard ? cl->rtIn : rt;
+         cl->ft = wildCard ? cl->ftIn : ft;
+         cl->kind = kind;
+       }
+       cl->captures += (board[rt][ft] != EmptySquare); // [HGM] oneclick: count captures
     }
 }
 
-void Disambiguate(board, flags, epfile, closure)
+void Disambiguate(board, flags, closure)
      Board board;
-     int flags, epfile;
+     int flags;
      DisambiguateClosure *closure;
 {
-    int illegal = 0;
-    closure->count = 0;
+    int illegal = 0; char c = closure->promoCharIn;
+
+    closure->count = closure->captures = 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 : '-');
+    }
+    rFilter = closure->rtIn; // [HGM] speed: only consider moves to given to-square
+    fFilter = closure->ftIn;
+    GenLegal(board, flags, DisambiguateCallback, (VOIDSTAR) closure, closure->pieceIn); // [HGM] speed: only pieces of requested type
     if (closure->count == 0) {
        /* See if it's an illegal move due to check */
         illegal = 1;
-       GenLegal(board, flags|F_IGNORE_CHECK, epfile, DisambiguateCallback,
-                (VOIDSTAR) closure);   
+        GenLegal(board, flags|F_IGNORE_CHECK, DisambiguateCallback, (VOIDSTAR) closure, closure->pieceIn);
        if (closure->count == 0) {
            /* No, it's not even that */
-           return;
+    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");
        }
     }
-    if (closure->promoCharIn != NULLCHAR && closure->promoCharIn != 'x') {
-       if (closure->kind == WhitePromotionQueen
-           || closure->kind == BlackPromotionQueen) {
-           closure->kind = 
-             PromoCharToMoveType((flags & F_WHITE_ON_MOVE) != 0,
-                                 closure->promoCharIn);
-       } else {
-           closure->kind = IllegalMove;
+           return;
        }
     }
-    closure->promoChar = ToLower(PieceToChar(PromoPiece(closure->kind)));
-    if (closure->promoChar == 'x') closure->promoChar = NULLCHAR;
+
+    if (c == 'x') c = NULLCHAR; // get rid of any 'x' (which should never happen?)
+    if(gameInfo.variant == VariantSChess && c && c != '=' && closure->piece != WhitePawn && closure->piece != BlackPawn) {
+        if(closure->piece < BlackPawn) { // white
+            if(closure->rf != 0) closure->kind = IllegalMove; // must be on back rank
+            if(board[PieceToNumber(CharToPiece(ToUpper(c)))][BOARD_WIDTH-2] == 0) closure->kind = ImpossibleMove;// must be in stock
+        } else {
+            if(closure->rf != BOARD_HEIGHT-1) closure->kind = IllegalMove;
+            if(board[BOARD_HEIGHT-1-PieceToNumber(CharToPiece(ToLower(c)))][1] == 0) closure->kind = ImpossibleMove;
+        }
+    } else
+    if(gameInfo.variant == VariantShogi) {
+        /* [HGM] Shogi promotions. On input, '=' means defer, '+' promote. Afterwards, c is set to '+' for promotions, NULL other */
+        if(closure->rfIn != DROP_RANK && closure->kind == NormalMove) {
+            ChessSquare piece = closure->piece;
+            if (c == 'd' && (piece == WhiteRook   || piece == BlackRook)   ||
+                c == 'h' && (piece == WhiteBishop || piece == BlackBishop) ||
+                c == 'g' && (piece <= WhiteFerz || piece <= BlackFerz && piece >= BlackPawn) )
+                   c = '+'; // allowed ICS notations
+            if(c != NULLCHAR && c != '+' && c != '=') closure->kind = IllegalMove; // otherwise specifying a piece is illegal
+            else if(flags & F_WHITE_ON_MOVE) {
+                if( (int) piece < (int) WhiteWazir &&
+                     (closure->rf >= BOARD_HEIGHT-(BOARD_HEIGHT/3) || closure->rt >= BOARD_HEIGHT-(BOARD_HEIGHT/3)) ) {
+                    if( (piece == WhitePawn || piece == WhiteQueen) && closure->rt > BOARD_HEIGHT-2 ||
+                         piece == WhiteKnight && closure->rt > BOARD_HEIGHT-3) /* promotion mandatory */
+                       closure->kind = c == '=' ? IllegalMove : WhitePromotion;
+                    else /* promotion optional, default is defer */
+                       closure->kind = c == '+' ? WhitePromotion : WhiteNonPromotion; 
+                } else closure->kind = c == '+' ? IllegalMove : NormalMove;
+            } else {
+                if( (int) piece < (int) BlackWazir && (closure->rf < BOARD_HEIGHT/3 || closure->rt < BOARD_HEIGHT/3) ) {
+                    if( (piece == BlackPawn || piece == BlackQueen) && closure->rt < 1 ||
+                         piece == BlackKnight && closure->rt < 2 ) /* promotion obligatory */
+                       closure->kind = c == '=' ? IllegalMove : BlackPromotion;
+                    else /* promotion optional, default is defer */
+                       closure->kind = c == '+' ? BlackPromotion : BlackNonPromotion;
+                } else closure->kind = c == '+' ? IllegalMove : NormalMove;
+            }
+        }
+        if(closure->kind == WhitePromotion || closure->kind == BlackPromotion) c = '+'; else
+        if(closure->kind == WhiteNonPromotion || closure->kind == BlackNonPromotion) c = '=';
+    } else
+    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)
+                c = PieceToChar(BlackFerz);
+            else if(gameInfo.variant == VariantGreat)
+                c = PieceToChar(BlackMan);
+            else if(gameInfo.variant == VariantGrand)
+                   closure->kind = closure->rt != 0 && closure->rt != BOARD_HEIGHT-1 ? NormalMove : AmbiguousMove; // no default in Grand Chess
+            else
+                c = PieceToChar(BlackQueen);
+        } else if(c == '=') closure->kind = IllegalMove; // no deferral outside Shogi
+    } else if (c != NULLCHAR) closure->kind = IllegalMove;
+
+    closure->promoChar = ToLower(c); // this can be NULLCHAR! Note we keep original promoChar even if illegal.
+    if(c != '+' && c != '=' && c != NULLCHAR && CharToPiece(flags & F_WHITE_ON_MOVE ? ToUpper(c) : ToLower(c)) == EmptySquare)
+       closure->kind = ImpossibleMove; // but we cannot handle non-existing piece types!
     if (closure->count > 1) {
        closure->kind = AmbiguousMove;
     }
@@ -764,6 +1430,11 @@ void Disambiguate(board, flags, epfile, closure)
        */
        closure->kind = IllegalMove;
     }
+    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:'-');
+    }
 }
 
 
@@ -792,8 +1463,11 @@ void CoordsToAlgebraicCallback(board, flags, kind, rf, ff, rt, ft, closure)
     register CoordsToAlgebraicClosure *cl =
       (CoordsToAlgebraicClosure *) closure;
 
-    if (rt == cl->rt && ft == cl->ft &&
-       board[rf][ff] == cl->piece) {
+    if ((rt == cl->rt && ft == cl->ft || rt == rf && ft == ff) && // [HGM] null move matches any toSquare
+        (board[rf][ff] == cl->piece
+         || PieceToChar(board[rf][ff]) == '~' &&
+            (ChessSquare) (DEMOTED board[rf][ff]) == cl->piece)
+                                     ) {
        if (rf == cl->rf) {
            if (ff == cl->ff) {
                cl->kind = kind; /* this is the move we want */
@@ -806,88 +1480,112 @@ void CoordsToAlgebraicCallback(board, flags, kind, rf, ff, rt, ft, closure)
            } else {
                cl->either++; /* rank or file will rule out this move */
            }
-       }           
+       }
     }
 }
 
 /* Convert coordinates to normal algebraic notation.
    promoChar must be NULLCHAR or 'x' if not a promotion.
 */
-ChessMove CoordsToAlgebraic(board, flags, epfile,
-                           rf, ff, rt, ft, promoChar, out)
+ChessMove CoordsToAlgebraic(board, flags, rf, ff, rt, ft, promoChar, out)
      Board board;
-     int flags, epfile;
+     int flags;
      int rf, ff, rt, ft;
      int promoChar;
      char out[MOVE_LEN];
 {
     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, 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,
-                              rf, ff, rt, ft, promoChar);
+            kind = LegalityTest(board, flags|F_IGNORE_CHECK, 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)
+           safeStrCpy(out, "O-O", MOVE_LEN);
+         else
+           safeStrCpy(out, "O-O-O", MOVE_LEN);
+         return LegalityTest(board, flags, 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:
-               strcpy(out, "O-O");
-               break;
-             case 2:
-             case 5:
-               strcpy(out, "O-O-O");
-               break;
-           }
+       else if (rf == rt &&
+           rf == ((piece == WhiteKing) ? 0 : BOARD_HEIGHT-1) &&
+            (ft - ff > 1 || ff - ft > 1) &&  // No castling if legal King move (on narrow boards!)
+            ((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)
+             snprintf(out, MOVE_LEN, "O-O%c%c", promoChar ? '/' : 0, ToUpper(promoChar));
+            else
+             snprintf(out, MOVE_LEN, "O-O-O%c%c", promoChar ? '/' : 0, ToUpper(promoChar));
+
            /* 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,29 +1594,27 @@ 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,
-                               rf, ff, rt, ft, promoChar);
+            return LegalityTest(board, flags, rf, ff, rt, ft, promoChar);
        }
+
        /* else fall through */
-       
       default:
        /* Piece move */
        cl.rf = rf;
        cl.ff = ff;
-       cl.rt = rt;
-       cl.ft = ft;
+       cl.rt = rFilter = rt; // [HGM] speed: filter on to-square
+       cl.ft = fFilter = ft;
        cl.piece = piece;
        cl.kind = IllegalMove;
        cl.rank = cl.file = cl.either = 0;
-       GenLegal(board, flags, epfile,
-                CoordsToAlgebraicCallback, (VOIDSTAR) &cl);
+        c = PieceToChar(piece) ;
+        GenLegal(board, flags, CoordsToAlgebraicCallback, (VOIDSTAR) &cl, c!='~' ? piece : (DEMOTED piece)); // [HGM] speed
 
        if (cl.kind == IllegalMove && !(flags&F_IGNORE_CHECK)) {
            /* Generate pretty moves for moving into check, but
               still return IllegalMove.
            */
-           GenLegal(board, flags|F_IGNORE_CHECK, epfile,
-                    CoordsToAlgebraicCallback, (VOIDSTAR) &cl);
+            GenLegal(board, flags|F_IGNORE_CHECK, CoordsToAlgebraicCallback, (VOIDSTAR) &cl, c!='~' ? piece : (DEMOTED piece));
            if (cl.kind == IllegalMove) break;
            cl.kind = IllegalMove;
        }
@@ -928,30 +1624,52 @@ ChessMove CoordsToAlgebraic(board, flags, epfile,
           else "N1f3" or "N5xf7",
           else "Ng1f3" or "Ng5xf7".
        */
-       *outp++ = ToUpper(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'; }
+        if (gameInfo.variant == VariantShogi) {
+            /* [HGM] in Shogi non-pawns can promote */
+            *outp++ = promoChar; // Don't bother to correct move type, return value is never used!
+        }
+        else if (gameInfo.variant != VariantSuper && promoChar && 
+                 (piece == WhiteLance || piece == BlackLance) ) { // Lance sometimes represents Pawn
+            *outp++ = '=';
+            *outp++ = ToUpper(promoChar);
+        }
+        else if (gameInfo.variant == VariantSChess && promoChar) { // and in S-Chess we have gating
+            *outp++ = '/';
+            *outp++ = ToUpper(promoChar);
+        }
        *outp = NULLCHAR;
-       return cl.kind;
+        return cl.kind;
        
       case EmptySquare:
        /* Moving a nonexistent piece */
        break;
     }
-    
+
     /* Not a legal move, even ignoring check.
-       If there was a piece on the from square, 
+       If there was a piece on the from square,
        use style "Ng1g3" or "Ng1xe8";
        if there was a pawn or nothing (!),
        use style "g1g3" or "g1xe8".  Use "x"
@@ -959,14 +1677,24 @@ ChessMove CoordsToAlgebraic(board, flags, epfile,
        a piece of the same color.
     */
     outp = out;
+    c = 0;
     if (piece != EmptySquare && piece != WhitePawn && piece != BlackPawn) {
+       int r, f;
+      for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<=BOARD_RGHT; f++)
+               c += (board[r][f] == piece); // count on-board pieces of given type
        *outp++ = ToUpper(PieceToChar(piece));
     }
-    *outp++ = ff + 'a';
-    *outp++ = rf + '1';
+  if(c != 1) { // [HGM] but if there is only one piece of the mentioned type, no from-square, thank you!
+    *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 +1704,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 {
+unsigned char rf, ff, rt, ft;
+} chaseStack[100];
+
+struct {
+unsigned 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), AttacksCallback, &cl, EmptySquare);
+       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), ExistingAttacksCallback, &cl, EmptySquare);
+       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), 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), ProtectedCallback, &cl, EmptySquare); // 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
+}