moved a lot of function from the option menu to gtk
[xboard.git] / backend.c
index 5fbe8f0..81d22e6 100644 (file)
--- a/backend.c
+++ b/backend.c
@@ -1,9 +1,13 @@
 /*
  * backend.c -- Common back end for X and Windows NT versions of
- * XBoard $Id: backend.c,v 2.6 2003/11/28 09:37:36 mann Exp $
  *
- * Copyright 1991 by Digital Equipment Corporation, Maynard, Massachusetts.
- * Enhancements Copyright 1992-2001 Free Software Foundation, Inc.
+ * Copyright 1991 by Digital Equipment Corporation, Maynard,
+ * Massachusetts.
+ *
+ * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
+ * 2007, 2008, 2009 Free Software Foundation, Inc.
+ *
+ * Enhancements Copyright 2005 Alessandro Scotti
  *
  * The following terms apply to Digital Equipment Corporation's copyright
  * interest in XBoard:
  * SOFTWARE.
  * ------------------------------------------------------------------------
  *
- * The following terms apply to the enhanced version of XBoard distributed
- * by the Free Software Foundation:
+ * The following terms apply to the enhanced version of XBoard
+ * distributed by the Free Software Foundation:
  * ------------------------------------------------------------------------
- * This program is free software; you can redistribute it and/or modify
+ *
+ * GNU XBoard is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
  *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
+ * GNU XBoard is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- * ------------------------------------------------------------------------
+ * along with this program. If not, see http://www.gnu.org/licenses/.  *
  *
- * See the file ChangeLog for a revision history.  */
+ *------------------------------------------------------------------------
+ ** See the file ChangeLog for a revision history.  */
 
 /* [AS] Also useful here for debugging */
 #ifdef WIN32
@@ -55,7 +59,7 @@
 
 #else
 
-#define DoSleep( n )
+#define DoSleep( n ) if( (n) >= 0) sleep(n)
 
 #endif
 
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <math.h>
+#include <ctype.h>
 
 #if STDC_HEADERS
 # include <stdlib.h>
 # include <string.h>
+# include <stdarg.h>
 #else /* not STDC_HEADERS */
 # if HAVE_STRING_H
 #  include <string.h>
@@ -120,6 +126,16 @@ extern int gettimeofday(struct timeval *, struct timezone *);
 # include "zippy.h"
 #endif
 #include "backendz.h"
+#include "gettext.h"
+
+#ifdef ENABLE_NLS
+# define _(s) gettext (s)
+# define N_(s) gettext_noop (s)
+#else
+# define _(s) (s)
+# define N_(s) s
+#endif
+
 
 /* A point in time */
 typedef struct {
@@ -127,33 +143,16 @@ typedef struct {
     int ms;    /* Assuming this is >= 16 bits */
 } TimeMark;
 
-/* Search stats from chessprogram */
-typedef struct {
-  char movelist[2*MSG_SIZ]; /* Last PV we were sent */
-  int depth;              /* Current search depth */
-  int nr_moves;           /* Total nr of root moves */
-  int moves_left;         /* Moves remaining to be searched */
-  char move_name[MOVE_LEN];  /* Current move being searched, if provided */
-  unsigned long nodes;    /* # of nodes searched */
-  int time;               /* Search time (centiseconds) */
-  int score;              /* Score (centipawns) */
-  int got_only_move;      /* If last msg was "(only move)" */
-  int got_fail;           /* 0 - nothing, 1 - got "--", 2 - got "++" */
-  int ok_to_send;         /* handshaking between send & recv */
-  int line_is_book;       /* 1 if movelist is book moves */
-  int seen_stat;          /* 1 if we've seen the stat01: line */
-} ChessProgramStats;
-
 int establish P((void));
 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
                         char *buf, int count, int error));
 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
                      char *buf, int count, int error));
+void ics_printf P((char *format, ...));
 void SendToICS P((char *s));
 void SendToICSDelayed P((char *s, long msdelay));
 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
                      int toX, int toY));
-void InitPosition P((int redraw));
 void HandleMachineMove P((char *message, ChessProgramState *cps));
 int AutoPlayOneMove P((void));
 int LoadGameOneMove P((ChessMove readAhead));
@@ -161,10 +160,10 @@ int LoadGameFromFile P((char *filename, int n, char *title, int useList));
 int LoadPositionFromFile P((char *filename, int n, char *title));
 int SavePositionToFile P((char *filename));
 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
-                 Board board));
+                 Board board, char *castle, char *ep));
 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
 void ShowMove P((int fromX, int fromY, int toX, int toY));
-void FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
+int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
                   /*char*/int promoChar));
 void BackwardInner P((int target));
 void ForwardInner P((int target));
@@ -214,12 +213,41 @@ int string_to_rating P((char *str));
 void ParseFeatures P((char* args, ChessProgramState *cps));
 void InitBackEnd3 P((void));
 void FeatureDone P((ChessProgramState* cps, int val));
-void InitChessProgram P((ChessProgramState *cps));
+void InitChessProgram P((ChessProgramState *cps, int setup));
+void OutputKibitz(int window, char *text);
+int PerpetualChase(int first, int last);
+int EngineOutputIsUp();
+void InitDrawingSizes(int x, int y);
 
-void GetInfoFromComment( int, char * );
+#ifdef WIN32
+       extern void ConsoleCreate();
+#endif
+
+ChessProgramState *WhitePlayer();
+void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
+int VerifyDisplayMode P(());
+
+char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
+void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
+char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
+char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
+void ics_update_width P((int new_width));
+extern char installDir[MSG_SIZ];
 
 extern int tinyLayout, smallLayout;
-static ChessProgramStats programStats;
+ChessProgramStats programStats;
+static int exiting = 0; /* [HGM] moved to top */
+static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
+int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
+char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
+int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
+VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
+int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
+int opponentKibitzes;
+int lastSavedGame; /* [HGM] save: ID of game */
+char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
+extern int chatCount;
+int chattingPartner;
 
 /* States for ics_getting_history */
 #define H_FALSE 0
@@ -235,6 +263,8 @@ static ChessProgramStats programStats;
 #define GE_PLAYER 2
 #define GE_FILE 3
 #define GE_XBOARD 4
+#define GE_ENGINE1 5
+#define GE_ENGINE2 6
 
 /* Maximum number of games in a cmail message */
 #define CMAIL_MAX_GAMES 20
@@ -272,25 +302,33 @@ static char * safeStrCpy( char * dst, const char * src, size_t count )
     return dst;
 }
 
-static char * safeStrCat( char * dst, const char * src, size_t count )
-{
-    size_t  dst_len;
-
-    assert( dst != NULL );
-    assert( src != NULL );
-    assert( count > 0 );
-
-    dst_len = strlen(dst);
-
-    assert( count > dst_len ); /* Buffer size must be greater than current length */
-
-    safeStrCpy( dst + dst_len, src, count - dst_len );
+/* Some compiler can't cast u64 to double
+ * This function do the job for us:
 
-    return dst;
+ * We use the highest bit for cast, this only
+ * works if the highest bit is not
+ * in use (This should not happen)
+ *
+ * We used this for all compiler
+ */
+double
+u64ToDouble(u64 value)
+{
+  double r;
+  u64 tmp = value & u64Const(0x7fffffffffffffff);
+  r = (double)(s64)tmp;
+  if (value & u64Const(0x8000000000000000))
+       r +=  9.2233720368547758080e18; /* 2^63 */
+ return r;
 }
 
 /* Fake up flags for now, as we aren't keeping track of castling
-   availability yet */
+   availability yet. [HGM] Change of logic: the flag now only
+   indicates the type of castlings allowed by the rule of the game.
+   The actual rights themselves are maintained in the array
+   castlingRights, as part of the game history, and are not probed
+   by this function.
+ */
 int
 PosFlags(index)
 {
@@ -298,9 +336,11 @@ PosFlags(index)
   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
   switch (gameInfo.variant) {
   case VariantSuicide:
-  case VariantGiveaway:
-    flags |= F_IGNORE_CHECK;
     flags &= ~F_ALL_CASTLE_OK;
+  case VariantGiveaway:                // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
+    flags |= F_IGNORE_CHECK;
+  case VariantLosers:
+    flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
     break;
   case VariantAtomic:
     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
@@ -308,7 +348,12 @@ PosFlags(index)
   case VariantKriegspiel:
     flags |= F_KRIEGSPIEL_CAPTURE;
     break;
+  case VariantCapaRandom:
+  case VariantFischeRandom:
+    flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
   case VariantNoCastle:
+  case VariantShatranj:
+  case VariantCourier:
     flags &= ~F_ALL_CASTLE_OK;
     break;
   default:
@@ -344,12 +389,8 @@ int gotPremove = 0;
 Boolean alarmSounded;
 /* end premove variables */
 
-#define ICS_GENERIC 0
-#define ICS_ICC 1
-#define ICS_FICS 2
-#define ICS_CHESSNET 3 /* not really supported */
-int ics_type = ICS_GENERIC;
 char *ics_prefix = "$";
+int ics_type = ICS_GENERIC;
 
 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
 int pauseExamForwardMostMove = 0;
@@ -381,6 +422,7 @@ int have_sent_ICS_logon = 0;
 int movesPerSession;
 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
 long timeControl_2; /* [AS] Allow separate time controls */
+char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
 long timeRemaining[2][MAX_MOVES];
 int matchGame = 0;
 TimeMark programStartTime;
@@ -399,45 +441,137 @@ GameInfo gameInfo;
 AppData appData;
 
 Board boards[MAX_MOVES];
-Board initialPosition = {
+/* [HGM] Following 7 needed for accurate legality tests: */
+signed char  epStatus[MAX_MOVES];
+signed char  castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
+signed char  castlingRank[BOARD_SIZE]; // and corresponding ranks
+signed char  initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
+int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
+int   initialRulePlies, FENrulePlies;
+char  FENepStatus;
+FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
+int loadFlag = 0;
+int shuffleOpenings;
+int mute; // mute all sounds
+
+ChessSquare  FIDEArray[2][BOARD_SIZE] = {
     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
        WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
-    { WhitePawn, WhitePawn, WhitePawn, WhitePawn,
-       WhitePawn, WhitePawn, WhitePawn, WhitePawn },
-    { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
-       EmptySquare, EmptySquare, EmptySquare, EmptySquare },
-    { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
-       EmptySquare, EmptySquare, EmptySquare, EmptySquare },
-    { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
-       EmptySquare, EmptySquare, EmptySquare, EmptySquare },
-    { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
-       EmptySquare, EmptySquare, EmptySquare, EmptySquare },
-    { BlackPawn, BlackPawn, BlackPawn, BlackPawn,
-       BlackPawn, BlackPawn, BlackPawn, BlackPawn },
     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
        BlackKing, BlackBishop, BlackKnight, BlackRook }
 };
-Board twoKingsPosition = {
+
+ChessSquare twoKingsArray[2][BOARD_SIZE] = {
     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
        WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
-    { WhitePawn, WhitePawn, WhitePawn, WhitePawn,
-       WhitePawn, WhitePawn, WhitePawn, WhitePawn },
-    { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
-       EmptySquare, EmptySquare, EmptySquare, EmptySquare },
-    { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
-       EmptySquare, EmptySquare, EmptySquare, EmptySquare },
-    { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
-       EmptySquare, EmptySquare, EmptySquare, EmptySquare },
-    { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
-       EmptySquare, EmptySquare, EmptySquare, EmptySquare },
-    { BlackPawn, BlackPawn, BlackPawn, BlackPawn,
-       BlackPawn, BlackPawn, BlackPawn, BlackPawn },
     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
-       BlackKing, BlackKing, BlackKnight, BlackRook }
+        BlackKing, BlackKing, BlackKnight, BlackRook }
+};
+
+ChessSquare  KnightmateArray[2][BOARD_SIZE] = {
+    { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
+        WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
+    { BlackRook, BlackMan, BlackBishop, BlackQueen,
+        BlackUnicorn, BlackBishop, BlackMan, BlackRook }
+};
+
+ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
+    { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
+        WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
+    { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
+       BlackKing, BlackBishop, BlackKnight, BlackRook }
+};
+
+ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
+    { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
+        WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
+    { BlackRook, BlackKnight, BlackAlfil, BlackKing,
+        BlackFerz, BlackAlfil, BlackKnight, BlackRook }
 };
 
 
+#if (BOARD_SIZE>=10)
+ChessSquare ShogiArray[2][BOARD_SIZE] = {
+    { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
+        WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
+    { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
+        BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
+};
+
+ChessSquare XiangqiArray[2][BOARD_SIZE] = {
+    { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
+        WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
+    { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
+        BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
+};
+
+ChessSquare CapablancaArray[2][BOARD_SIZE] = {
+    { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
+        WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
+    { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
+        BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
+};
+
+ChessSquare GreatArray[2][BOARD_SIZE] = {
+    { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
+        WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
+    { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
+        BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
+};
+
+ChessSquare JanusArray[2][BOARD_SIZE] = {
+    { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
+        WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
+    { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
+        BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
+};
+
+#ifdef GOTHIC
+ChessSquare GothicArray[2][BOARD_SIZE] = {
+    { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
+        WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
+    { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
+        BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
+};
+#else // !GOTHIC
+#define GothicArray CapablancaArray
+#endif // !GOTHIC
+
+#ifdef FALCON
+ChessSquare FalconArray[2][BOARD_SIZE] = {
+    { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
+        WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
+    { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
+        BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
+};
+#else // !FALCON
+#define FalconArray CapablancaArray
+#endif // !FALCON
+
+#else // !(BOARD_SIZE>=10)
+#define XiangqiPosition FIDEArray
+#define CapablancaArray FIDEArray
+#define GothicArray FIDEArray
+#define GreatArray FIDEArray
+#endif // !(BOARD_SIZE>=10)
+
+#if (BOARD_SIZE>=12)
+ChessSquare CourierArray[2][BOARD_SIZE] = {
+    { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
+        WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
+    { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
+        BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
+};
+#else // !(BOARD_SIZE>=12)
+#define CourierArray CapablancaArray
+#endif // !(BOARD_SIZE>=12)
+
+
+Board initialPosition;
+
+
 /* Convert str to a rating. Checks for special cases of "----",
+
    "++++", etc. Also strips ()'s */
 int
 string_to_rating(str)
@@ -459,7 +593,7 @@ ClearProgramStats()
     programStats.nr_moves = 0;
     programStats.moves_left = 0;
     programStats.nodes = 0;
-    programStats.time = 100;
+    programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
     programStats.score = 0;
     programStats.got_only_move = 0;
     programStats.got_fail = 0;
@@ -471,7 +605,10 @@ InitBackEnd1()
 {
     int matched, min, sec;
 
+    ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
+
     GetTimeMark(&programStartTime);
+    srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level
 
     ClearProgramStats();
     programStats.ok_to_send = 1;
@@ -489,7 +626,7 @@ InitBackEnd1()
     if (appData.icsActive) {
        appData.matchMode = FALSE;
        appData.matchGames = 0;
-#if ZIPPY      
+#if ZIPPY
        appData.noChessProgram = !appData.zippyPlay;
 #else
        appData.zippyPlay = FALSE;
@@ -504,12 +641,14 @@ InitBackEnd1()
        appData.zippyTalk = appData.zippyPlay = FALSE;
     }
 
-    /* [AS] Initialize pv info list */
+    /* [AS] Initialize pv info list [HGM] and game state */
     {
-        int i;
+        int i, j;
 
         for( i=0; i<MAX_MOVES; i++ ) {
-            pvInfoList[i].depth = 0;
+            pvInfoList[i].depth = -1;
+            epStatus[i]=EP_NONE;
+            for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
         }
     }
 
@@ -519,7 +658,7 @@ InitBackEnd1()
     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
                          appData.movesPerSession)) {
        char buf[MSG_SIZ];
-       sprintf(buf, "bad timeControl option %s", appData.timeControl);
+       snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
        DisplayFatalError(buf, 0, 2);
     }
 
@@ -534,11 +673,11 @@ InitBackEnd1()
            searchTime = min * 60 + sec;
        } else {
            char buf[MSG_SIZ];
-           sprintf(buf, "bad searchTime option %s", appData.searchTime);
+           snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
            DisplayFatalError(buf, 0, 2);
        }
     }
-    
+
     /* [AS] Adjudication threshold */
     adjudicateLossThreshold = appData.adjudicateLossThreshold;
 
@@ -572,6 +711,8 @@ InitBackEnd1()
     first.useSigterm = second.useSigterm = TRUE;
     first.reuse = appData.reuseFirst;
     second.reuse = appData.reuseSecond;
+    first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
+    second.nps = appData.secondNPS;
     first.useSetboard = second.useSetboard = FALSE;
     first.useSAN = second.useSAN = FALSE;
     first.usePing = second.usePing = FALSE;
@@ -597,6 +738,33 @@ InitBackEnd1()
     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
     /* End of new features added by Tord. */
+    first.fenOverride  = appData.fenOverride1;
+    second.fenOverride = appData.fenOverride2;
+
+    /* [HGM] time odds: set factor for each machine */
+    first.timeOdds  = appData.firstTimeOdds;
+    second.timeOdds = appData.secondTimeOdds;
+    { int norm = 1;
+        if(appData.timeOddsMode) {
+            norm = first.timeOdds;
+            if(norm > second.timeOdds) norm = second.timeOdds;
+        }
+        first.timeOdds /= norm;
+        second.timeOdds /= norm;
+    }
+
+    /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
+    first.accumulateTC = appData.firstAccumulateTC;
+    second.accumulateTC = appData.secondAccumulateTC;
+    first.maxNrOfSessions = second.maxNrOfSessions = 1;
+
+    /* [HGM] debug */
+    first.debug = second.debug = FALSE;
+    first.supportsNPS = second.supportsNPS = UNKNOWN;
+
+    /* [HGM] options */
+    first.optionSettings  = appData.firstOptions;
+    second.optionSettings = appData.secondOptions;
 
     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
@@ -608,7 +776,7 @@ InitBackEnd1()
     if (appData.firstProtocolVersion > PROTOVER ||
        appData.firstProtocolVersion < 1) {
       char buf[MSG_SIZ];
-      sprintf(buf, "protocol version %d not supported",
+      sprintf(buf, _("protocol version %d not supported"),
              appData.firstProtocolVersion);
       DisplayFatalError(buf, 0, 2);
     } else {
@@ -618,7 +786,7 @@ InitBackEnd1()
     if (appData.secondProtocolVersion > PROTOVER ||
        appData.secondProtocolVersion < 1) {
       char buf[MSG_SIZ];
-      sprintf(buf, "protocol version %d not supported",
+      sprintf(buf, _("protocol version %d not supported"),
              appData.secondProtocolVersion);
       DisplayFatalError(buf, 0, 2);
     } else {
@@ -631,7 +799,7 @@ InitBackEnd1()
        appData.clockMode = FALSE;
        first.sendTime = second.sendTime = 0;
     }
-    
+
 #if ZIPPY
     /* Override some settings from environment variables, for backward
        compatibility.  Unfortunately it's not feasible to have the env
@@ -641,21 +809,14 @@ InitBackEnd1()
       ZippyInit();
     }
 #endif
-    
+
     if (appData.noChessProgram) {
-       programVersion = (char*) malloc(5 + strlen(PRODUCT) + strlen(VERSION)
-                                       + strlen(PATCHLEVEL));
-       sprintf(programVersion, "%s %s.%s", PRODUCT, VERSION, PATCHLEVEL);
+       programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
+       sprintf(programVersion, "%s", PACKAGE_STRING);
     } else {
-       char *p, *q;
-       q = first.program;
-       while (*q != ' ' && *q != NULLCHAR) q++;
-       p = q;
-       while (p > first.program && *(p-1) != '/') p--;
-       programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)
-                                       + strlen(PATCHLEVEL) + (q - p));
-       sprintf(programVersion, "%s %s.%s + ", PRODUCT, VERSION, PATCHLEVEL);
-       strncat(programVersion, p, q - p);
+      /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
+      programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
+      sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
     }
 
     if (!appData.icsActive) {
@@ -669,7 +830,7 @@ InitBackEnd1()
       case VariantBughouse:     /* need four players and two boards */
       case VariantKriegspiel:   /* need to hide pieces and move details */
       /* case VariantFischeRandom: (Fabien: moved below) */
-       sprintf(buf, "Variant %s supported only in ICS mode", appData.variant);
+       sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
        DisplayFatalError(buf, 0, 2);
        return;
 
@@ -684,16 +845,25 @@ InitBackEnd1()
       case Variant35:
       case Variant36:
       default:
-       sprintf(buf, "Unknown variant name %s", appData.variant);
+       sprintf(buf, _("Unknown variant name %s"), appData.variant);
        DisplayFatalError(buf, 0, 2);
        return;
 
+      case VariantXiangqi:    /* [HGM] repetition rules not implemented */
+      case VariantFairy:      /* [HGM] TestLegality definitely off! */
+      case VariantGothic:     /* [HGM] should work */
+      case VariantCapablanca: /* [HGM] should work */
+      case VariantCourier:    /* [HGM] initial forced moves not implemented */
+      case VariantShogi:      /* [HGM] drops not tested for legality */
+      case VariantKnightmate: /* [HGM] should work */
+      case VariantCylinder:   /* [HGM] untested */
+      case VariantFalcon:     /* [HGM] untested */
+      case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
+                                offboard interposition not understood */
       case VariantNormal:     /* definitely works! */
       case VariantWildCastle: /* pieces not automatically shuffled */
       case VariantNoCastle:   /* pieces not automatically shuffled */
-      case VariantFischeRandom: /* Fabien: pieces not automatically shuffled */
-      case VariantCrazyhouse: /* holdings not shown,
-                                offboard interposition not understood */
+      case VariantFischeRandom: /* [HGM] works and shuffles pieces */
       case VariantLosers:     /* should work except for win condition,
                                 and doesn't know captures are mandatory */
       case VariantSuicide:    /* should work except for win condition,
@@ -703,10 +873,18 @@ InitBackEnd1()
       case VariantTwoKings:   /* should work */
       case VariantAtomic:     /* should work except for win condition */
       case Variant3Check:     /* should work except for win condition */
-      case VariantShatranj:   /* might work if TestLegality is off */
+      case VariantShatranj:   /* should work except for all win conditions */
+      case VariantBerolina:   /* might work if TestLegality is off */
+      case VariantCapaRandom: /* should work */
+      case VariantJanus:      /* should work */
+      case VariantSuper:      /* experimental */
+      case VariantGreat:      /* experimental, requires legality testing to be off */
        break;
       }
     }
+
+    InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
+    InitEngineUCI( installDir, &second );
 }
 
 int NextIntegerFromString( char ** str, long * value )
@@ -751,22 +929,51 @@ int NextTimeControlFromString( char ** str, long * value )
     return result;
 }
 
-int GetTimeControlForWhite()
-{
-    int result = timeControl;
+int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
+{   /* [HGM] routine added to read '+moves/time' for secondary time control */
+    int result = -1; long temp, temp2;
 
+    if(**str != '+') return -1; // old params remain in force!
+    (*str)++;
+    if( NextTimeControlFromString( str, &temp ) ) return -1;
+
+    if(**str != '/') {
+        /* time only: incremental or sudden-death time control */
+        if(**str == '+') { /* increment follows; read it */
+            (*str)++;
+            if(result = NextIntegerFromString( str, &temp2)) return -1;
+            *inc = temp2 * 1000;
+        } else *inc = 0;
+        *moves = 0; *tc = temp * 1000;
+        return 0;
+    } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
+
+    (*str)++; /* classical time control */
+    result = NextTimeControlFromString( str, &temp2);
+    if(result == 0) {
+        *moves = temp/60;
+        *tc    = temp2 * 1000;
+        *inc   = 0;
+    }
     return result;
 }
 
-int GetTimeControlForBlack()
-{
-    int result = timeControl;
+int GetTimeQuota(int movenr)
+{   /* [HGM] get time to add from the multi-session time-control string */
+    int moves=1; /* kludge to force reading of first session */
+    long time, increment;
+    char *s = fullTimeControlString;
 
-    if( timeControl_2 > 0 ) {
-        result = timeControl_2;
-    }
+    if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
+    do {
+        if(moves) NextSessionFromString(&s, &moves, &time, &increment);
+        if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
+        if(movenr == -1) return time;    /* last move before new session     */
+        if(!moves) return increment;     /* current session is incremental   */
+        if(movenr >= 0) movenr -= moves; /* we already finished this session */
+    } while(movenr >= -1);               /* try again for next session       */
 
-    return result;
+    return 0; // no new time quota on this move
 }
 
 int
@@ -775,81 +982,87 @@ ParseTimeControl(tc, ti, mps)
      int ti;
      int mps;
 {
-#if 0
-    int matched, min, sec;
-
-    matched = sscanf(tc, "%d:%d", &min, &sec);
-    if (matched == 1) {
-       timeControl = min * 60 * 1000;
-    } else if (matched == 2) {
-       timeControl = (min * 60 + sec) * 1000;
-    } else {
-       return FALSE;
-    }
-#else
-    long tc1;
-    long tc2;
-
-    if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
-        return FALSE;
-    }
-
-    if( *tc == '/' ) {
-        /* Parse second time control */
-        tc++;
-
-        if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
-            return FALSE;
-        }
-
-        if( tc2 == 0 ) {
-            return FALSE;
-        }
-
-        timeControl_2 = tc2 * 1000;
-    }
-    else {
-        timeControl_2 = 0;
-    }
-
-    if( tc1 == 0 ) {
-        return FALSE;
+  long tc1;
+  long tc2;
+  char buf[MSG_SIZ];
+  
+  if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
+  if(ti > 0) {
+    if(mps)
+      sprintf(buf, "+%d/%s+%d", mps, tc, ti);
+    else sprintf(buf, "+%s+%d", tc, ti);
+  } else {
+    if(mps)
+             sprintf(buf, "+%d/%s", mps, tc);
+    else sprintf(buf, "+%s", tc);
+  }
+  fullTimeControlString = StrSave(buf);
+  
+  if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
+    return FALSE;
+  }
+  
+  if( *tc == '/' ) {
+    /* Parse second time control */
+    tc++;
+    
+    if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
+      return FALSE;
     }
-
-    timeControl = tc1 * 1000;
-#endif
-
-    if (ti >= 0) {
-       timeIncrement = ti * 1000;  /* convert to ms */
-       movesPerSession = 0;
-    } else {
-       timeIncrement = 0;
-       movesPerSession = mps;
+    
+    if( tc2 == 0 ) {
+      return FALSE;
     }
-    return TRUE;
+    
+    timeControl_2 = tc2 * 1000;
+  }
+  else {
+    timeControl_2 = 0;
+  }
+  
+  if( tc1 == 0 ) {
+    return FALSE;
+  }
+  
+  timeControl = tc1 * 1000;
+  
+  if (ti >= 0) {
+    timeIncrement = ti * 1000;  /* convert to ms */
+    movesPerSession = 0;
+  } else {
+    timeIncrement = 0;
+    movesPerSession = mps;
+  }
+  return TRUE;
 }
 
 void
 InitBackEnd2()
 {
-    if (appData.debugMode) {
-       fprintf(debugFP, "%s\n", programVersion);
-    }
+  if (appData.debugMode) {
+    fprintf(debugFP, "%s\n", programVersion);
+  }
 
-    if (appData.matchGames > 0) {
-       appData.matchMode = TRUE;
-    } else if (appData.matchMode) {
-       appData.matchGames = 1;
-    }
-    Reset(TRUE, FALSE);
-    if (appData.noChessProgram || first.protocolVersion == 1) {
-      InitBackEnd3();
-    } else {
-      /* kludge: allow timeout for initial "feature" commands */
-      FreezeUI();
-      DisplayMessage("", "Starting chess program");
-      ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
-    }
+  if (appData.matchGames > 0) {
+    appData.matchMode = TRUE;
+  } else if (appData.matchMode) {
+    appData.matchGames = 1;
+  }
+  if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
+    appData.matchGames = appData.sameColorGames;
+  if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
+    if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
+    if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
+  }
+  Reset(TRUE, FALSE);
+  if (appData.noChessProgram || first.protocolVersion == 1) {
+    InitBackEnd3();
+  } else {
+    /* kludge: allow timeout for initial "feature" commands */
+    FreezeUI();
+    DisplayMessage("", _("Starting chess program"));
+    ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
+  }
 }
 
 void
@@ -859,16 +1072,21 @@ InitBackEnd3 P((void))
     char buf[MSG_SIZ];
     int err;
 
-    InitChessProgram(&first);
+    InitChessProgram(&first, startedFromSetupPosition);
+
 
     if (appData.icsActive) {
+#ifdef WIN32
+        /* [DM] Make a console window if needed [HGM] merged ifs */
+        ConsoleCreate();
+#endif
        err = establish();
        if (err != 0) {
            if (*appData.icsCommPort != NULLCHAR) {
-               sprintf(buf, "Could not open comm port %s",  
+               sprintf(buf, _("Could not open comm port %s"),
                        appData.icsCommPort);
            } else {
-               sprintf(buf, "Could not connect to host %s, port %s",  
+               snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
                        appData.icsHost, appData.icsPort);
            }
            DisplayFatalError(buf, err, 1);
@@ -891,7 +1109,7 @@ InitBackEnd3 P((void))
        cmailISR =
          AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
     }
-    
+
     ThawUI();
     DisplayMessage("", "");
     if (StrCaseCmp(appData.initialMode, "") == 0) {
@@ -899,7 +1117,7 @@ InitBackEnd3 P((void))
     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
       initialMode = TwoMachinesPlay;
     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
-      initialMode = AnalyzeFile; 
+      initialMode = AnalyzeFile;
     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
       initialMode = AnalyzeMode;
     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
@@ -913,7 +1131,7 @@ InitBackEnd3 P((void))
     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
       initialMode = Training;
     } else {
-      sprintf(buf, "Unknown initialMode %s", appData.initialMode);
+      sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
       DisplayFatalError(buf, 0, 2);
       return;
     }
@@ -921,24 +1139,28 @@ InitBackEnd3 P((void))
     if (appData.matchMode) {
        /* Set up machine vs. machine match */
        if (appData.noChessProgram) {
-           DisplayFatalError("Can't have a match with no chess programs",
+           DisplayFatalError(_("Can't have a match with no chess programs"),
                              0, 2);
            return;
        }
        matchMode = TRUE;
        matchGame = 1;
        if (*appData.loadGameFile != NULLCHAR) {
+           int index = appData.loadGameIndex; // [HGM] autoinc
+           if(index<0) lastIndex = index = 1;
            if (!LoadGameFromFile(appData.loadGameFile,
-                                 appData.loadGameIndex,
+                                 index,
                                  appData.loadGameFile, FALSE)) {
-               DisplayFatalError("Bad game file", 0, 1);
+               DisplayFatalError(_("Bad game file"), 0, 1);
                return;
            }
        } else if (*appData.loadPositionFile != NULLCHAR) {
+           int index = appData.loadPositionIndex; // [HGM] autoinc
+           if(index<0) lastIndex = index = 1;
            if (!LoadPositionFromFile(appData.loadPositionFile,
-                                     appData.loadPositionIndex,
+                                     index,
                                      appData.loadPositionFile)) {
-               DisplayFatalError("Bad position file", 0, 1);
+               DisplayFatalError(_("Bad position file"), 0, 1);
                return;
            }
        }
@@ -950,7 +1172,7 @@ InitBackEnd3 P((void))
        /* Set up other modes */
        if (initialMode == AnalyzeFile) {
          if (*appData.loadGameFile == NULLCHAR) {
-           DisplayFatalError("AnalyzeFile mode requires a game file", 0, 1);
+           DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
            return;
          }
        }
@@ -962,53 +1184,67 @@ InitBackEnd3 P((void))
            (void) LoadPositionFromFile(appData.loadPositionFile,
                                        appData.loadPositionIndex,
                                        appData.loadPositionFile);
+            /* [HGM] try to make self-starting even after FEN load */
+            /* to allow automatic setup of fairy variants with wtm */
+            if(initialMode == BeginningOfGame && !blackPlaysFirst) {
+                gameMode = BeginningOfGame;
+                setboardSpoiledMachineBlack = 1;
+            }
+            /* [HGM] loadPos: make that every new game uses the setup */
+            /* from file as long as we do not switch variant          */
+            if(!blackPlaysFirst) { int i;
+                startedFromPositionFile = TRUE;
+                CopyBoard(filePosition, boards[0]);
+                for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
+            }
        }
        if (initialMode == AnalyzeMode) {
          if (appData.noChessProgram) {
-           DisplayFatalError("Analysis mode requires a chess engine", 0, 2);
+           DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
            return;
          }
          if (appData.icsActive) {
-           DisplayFatalError("Analysis mode does not work with ICS mode",0,2);
+           DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
            return;
          }
          AnalyzeModeEvent();
        } else if (initialMode == AnalyzeFile) {
-         ShowThinkingEvent(TRUE);
+         appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
+         ShowThinkingEvent();
          AnalyzeFileEvent();
          AnalysisPeriodicEvent(1);
        } else if (initialMode == MachinePlaysWhite) {
          if (appData.noChessProgram) {
-           DisplayFatalError("MachineWhite mode requires a chess engine",
+           DisplayFatalError(_("MachineWhite mode requires a chess engine"),
                              0, 2);
            return;
          }
          if (appData.icsActive) {
-           DisplayFatalError("MachineWhite mode does not work with ICS mode",
+           DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
                              0, 2);
            return;
          }
          MachineWhiteEvent();
        } else if (initialMode == MachinePlaysBlack) {
          if (appData.noChessProgram) {
-           DisplayFatalError("MachineBlack mode requires a chess engine",
+           DisplayFatalError(_("MachineBlack mode requires a chess engine"),
                              0, 2);
            return;
          }
          if (appData.icsActive) {
-           DisplayFatalError("MachineBlack mode does not work with ICS mode",
+           DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
                              0, 2);
            return;
          }
          MachineBlackEvent();
        } else if (initialMode == TwoMachinesPlay) {
          if (appData.noChessProgram) {
-           DisplayFatalError("TwoMachines mode requires a chess engine",
+           DisplayFatalError(_("TwoMachines mode requires a chess engine"),
                              0, 2);
            return;
          }
          if (appData.icsActive) {
-           DisplayFatalError("TwoMachines mode does not work with ICS mode",
+           DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
                              0, 2);
            return;
          }
@@ -1019,7 +1255,7 @@ InitBackEnd3 P((void))
          EditPositionEvent();
        } else if (initialMode == Training) {
          if (*appData.loadGameFile == NULLCHAR) {
-           DisplayFatalError("Training mode requires a game file", 0, 2);
+           DisplayFatalError(_("Training mode requires a game file"), 0, 2);
            return;
          }
          TrainingEvent();
@@ -1045,19 +1281,19 @@ establish()
     } else if (*appData.gateway != NULLCHAR) {
        if (*appData.remoteShell == NULLCHAR) {
            /* Use the rcmd protocol to run telnet program on a gateway host */
-           sprintf(buf, "%s %s %s",
+           snprintf(buf, sizeof(buf), "%s %s %s",
                    appData.telnetProgram, appData.icsHost, appData.icsPort);
            return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
 
        } else {
            /* Use the rsh program to run telnet program on a gateway host */
            if (*appData.remoteUser == NULLCHAR) {
-               sprintf(buf, "%s %s %s %s %s", appData.remoteShell,
+               snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
                        appData.gateway, appData.telnetProgram,
                        appData.icsHost, appData.icsPort);
            } else {
-               sprintf(buf, "%s %s -l %s %s %s %s",
-                       appData.remoteShell, appData.gateway, 
+               snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
+                       appData.remoteShell, appData.gateway,
                        appData.remoteUser, appData.telnetProgram,
                        appData.icsHost, appData.icsPort);
            }
@@ -1167,18 +1403,38 @@ read_from_player(isr, closure, message, count, error)
        gotEof = 0;
        outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
        if (outCount < count) {
-           DisplayFatalError("Error writing to ICS", outError, 1);
+            DisplayFatalError(_("Error writing to ICS"), outError, 1);
        }
     } else if (count < 0) {
        RemoveInputSource(isr);
-       DisplayFatalError("Error reading from keyboard", error, 1);
+       DisplayFatalError(_("Error reading from keyboard"), error, 1);
     } else if (gotEof++ > 0) {
        RemoveInputSource(isr);
-       DisplayFatalError("Got end of file from keyboard", 0, 0);
+       DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
     }
 }
 
 void
+KeepAlive()
+{   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
+    SendToICS("date\n");
+    if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
+}
+
+/* added routine for printf style output to ics */
+void ics_printf(char *format, ...)
+{
+    char buffer[MSG_SIZ];
+    va_list args;
+
+    va_start(args, format);
+    vsnprintf(buffer, sizeof(buffer), format, args);
+    buffer[sizeof(buffer)-1] = '\0';
+    SendToICS(buffer);
+    va_end(args);
+}
+
+void
 SendToICS(s)
      char *s;
 {
@@ -1189,7 +1445,7 @@ SendToICS(s)
     count = strlen(s);
     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
     if (outCount < count) {
-       DisplayFatalError("Error writing to ICS", outError, 1);
+       DisplayFatalError(_("Error writing to ICS"), outError, 1);
     }
 }
 
@@ -1214,13 +1470,13 @@ SendToICSDelayed(s,msdelay)
     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
                                      msdelay);
     if (outCount < count) {
-       DisplayFatalError("Error writing to ICS", outError, 1);
+       DisplayFatalError(_("Error writing to ICS"), outError, 1);
     }
 }
 
 
 /* Remove all highlighting escape sequences in s
-   Also deletes any suffix starting with '(' 
+   Also deletes any suffix starting with '('
    */
 char *
 StripHighlightAndTitle(s)
@@ -1289,7 +1545,17 @@ StringToVariant(e)
     char buf[MSG_SIZ];
 
     if (!e) return v;
-    
+
+    /* [HGM] skip over optional board-size prefixes */
+    if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
+        sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
+        while( *e++ != '_');
+    }
+
+    if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
+       v = VariantNormal;
+       found = TRUE;
+    } else
     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
       if (StrCaseStr(e, variantNames[i])) {
        v = (VariantClass) i;
@@ -1300,7 +1566,8 @@ StringToVariant(e)
 
     if (!found) {
       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
-         || StrCaseStr(e, "wild/fr")) {
+         || StrCaseStr(e, "wild/fr")
+         || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
         v = VariantFischeRandom;
       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
                 (i = 1, p = StrCaseStr(e, "w"))) {
@@ -1377,7 +1644,7 @@ StringToVariant(e)
          v = VariantShatranj;
          break;
 
-       /* Temporary names for future ICC types.  The name *will* change in 
+       /* Temporary names for future ICC types.  The name *will* change in
           the next xboard/WinBoard release after ICC defines it. */
        case 29:
          v = Variant29;
@@ -1403,14 +1670,55 @@ StringToVariant(e)
        case 36:
          v = Variant36;
          break;
-
+        case 37:
+          v = VariantShogi;
+         break;
+        case 38:
+          v = VariantXiangqi;
+         break;
+        case 39:
+          v = VariantCourier;
+         break;
+        case 40:
+          v = VariantGothic;
+         break;
+        case 41:
+          v = VariantCapablanca;
+         break;
+        case 42:
+          v = VariantKnightmate;
+         break;
+        case 43:
+          v = VariantFairy;
+          break;
+        case 44:
+          v = VariantCylinder;
+         break;
+        case 45:
+          v = VariantFalcon;
+         break;
+        case 46:
+          v = VariantCapaRandom;
+         break;
+        case 47:
+          v = VariantBerolina;
+         break;
+        case 48:
+          v = VariantJanus;
+         break;
+        case 49:
+          v = VariantSuper;
+         break;
+        case 50:
+          v = VariantGreat;
+         break;
        case -1:
          /* Found "wild" or "w" in the string but no number;
             must assume it's normal chess. */
          v = VariantNormal;
          break;
        default:
-         sprintf(buf, "Unknown wild type %d", wnum);
+         sprintf(buf, _("Unknown wild type %d"), wnum);
          DisplayError(buf, 0);
          v = VariantUnknown;
          break;
@@ -1418,7 +1726,7 @@ StringToVariant(e)
       }
     }
     if (appData.debugMode) {
-      fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
+      fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
              e, wnum, VariantName(v));
     }
     return v;
@@ -1443,7 +1751,7 @@ looking_at(buf, index, pattern)
     char *bufp = &buf[*index], *patternp = pattern;
     int star_count = 0;
     char *matchp = star_match[0];
-    
+
     for (;;) {
        if (*patternp == NULLCHAR) {
            *index = leftover_start = bufp - buf;
@@ -1483,7 +1791,7 @@ SendToPlayer(data, length)
     int error, outCount;
     outCount = OutputToProcess(NoProc, data, length, &error);
     if (outCount < length) {
-       DisplayFatalError("Error writing to display", error, 1);
+       DisplayFatalError(_("Error writing to display"), error, 1);
     }
 }
 
@@ -1569,7 +1877,7 @@ TelnetRequest(ddww, option)
     msg[2] = option;
     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
     if (outCount < 3) {
-       DisplayFatalError("Error writing to ICS", outError, 1);
+       DisplayFatalError(_("Error writing to ICS"), outError, 1);
     }
 }
 
@@ -1587,6 +1895,127 @@ DontEcho()
     TelnetRequest(TN_DONT, TN_ECHO);
 }
 
+void
+CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
+{
+    /* put the holdings sent to us by the server on the board holdings area */
+    int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
+    char p;
+    ChessSquare piece;
+
+    if(gameInfo.holdingsWidth < 2)  return;
+
+    if( (int)lowestPiece >= BlackPawn ) {
+        holdingsColumn = 0;
+        countsColumn = 1;
+        holdingsStartRow = BOARD_HEIGHT-1;
+        direction = -1;
+    } else {
+        holdingsColumn = BOARD_WIDTH-1;
+        countsColumn = BOARD_WIDTH-2;
+        holdingsStartRow = 0;
+        direction = 1;
+    }
+
+    for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
+        board[i][holdingsColumn] = EmptySquare;
+        board[i][countsColumn]   = (ChessSquare) 0;
+    }
+    while( (p=*holdings++) != NULLCHAR ) {
+        piece = CharToPiece( ToUpper(p) );
+        if(piece == EmptySquare) continue;
+        /*j = (int) piece - (int) WhitePawn;*/
+        j = PieceToNumber(piece);
+        if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
+        if(j < 0) continue;               /* should not happen */
+        piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
+        board[holdingsStartRow+j*direction][holdingsColumn] = piece;
+        board[holdingsStartRow+j*direction][countsColumn]++;
+    }
+
+}
+
+
+void
+VariantSwitch(Board board, VariantClass newVariant)
+{
+   int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
+
+   startedFromPositionFile = FALSE;
+   if(gameInfo.variant == newVariant) return;
+
+   /* [HGM] This routine is called each time an assignment is made to
+    * gameInfo.variant during a game, to make sure the board sizes
+    * are set to match the new variant. If that means adding or deleting
+    * holdings, we shift the playing board accordingly
+    * This kludge is needed because in ICS observe mode, we get boards
+    * of an ongoing game without knowing the variant, and learn about the
+    * latter only later. This can be because of the move list we requested,
+    * in which case the game history is refilled from the beginning anyway,
+    * but also when receiving holdings of a crazyhouse game. In the latter
+    * case we want to add those holdings to the already received position.
+    */
+   
+   if (appData.debugMode) {
+     fprintf(debugFP, "Switch board from %s to %s\n",
+            VariantName(gameInfo.variant), VariantName(newVariant));
+     setbuf(debugFP, NULL);
+   }
+   shuffleOpenings = 0;       /* [HGM] shuffle */
+   gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
+   switch(newVariant) 
+     {
+     case VariantShogi:
+       newWidth = 9;  newHeight = 9;
+       gameInfo.holdingsSize = 7;
+     case VariantBughouse:
+     case VariantCrazyhouse:
+       newHoldingsWidth = 2; break;
+     case VariantGreat:
+       newWidth = 10;
+     case VariantSuper:
+       newHoldingsWidth = 2;
+       gameInfo.holdingsSize = 8;
+       return;
+     case VariantGothic:
+     case VariantCapablanca:
+     case VariantCapaRandom:
+       newWidth = 10;
+     default:
+       newHoldingsWidth = gameInfo.holdingsSize = 0;
+     };
+   
+   if(newWidth  != gameInfo.boardWidth  ||
+      newHeight != gameInfo.boardHeight ||
+      newHoldingsWidth != gameInfo.holdingsWidth ) {
+     
+     /* shift position to new playing area, if needed */
+     if(newHoldingsWidth > gameInfo.holdingsWidth) {
+       for(i=0; i<BOARD_HEIGHT; i++) 
+        for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
+          board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
+            board[i][j];
+       for(i=0; i<newHeight; i++) {
+        board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
+        board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
+       }
+     } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
+       for(i=0; i<BOARD_HEIGHT; i++)
+        for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
+          board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
+            board[i][j];
+     }
+     gameInfo.boardWidth  = newWidth;
+     gameInfo.boardHeight = newHeight;
+     gameInfo.holdingsWidth = newHoldingsWidth;
+     gameInfo.variant = newVariant;
+     InitDrawingSizes(-2, 0);
+     InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
+   } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
+   
+   DrawPosition(TRUE, boards[currentMove]);
+}
+
 static int loggedOn = FALSE;
 
 /*-- Game start info cache: --*/
@@ -1599,6 +2028,7 @@ static int player2Rating = -1;
 /*----------------------------*/
 
 ColorClass curColor = ColorNormal;
+int suppressKibitz = 0;
 
 void
 read_from_ics(isr, closure, data, count, error)
@@ -1617,7 +2047,7 @@ read_from_ics(isr, closure, data, count, error)
 #define STARTED_CHATTER 5
 #define STARTED_COMMENT 6
 #define STARTED_MOVES_NOHIDE 7
-    
+
     static int started = STARTED_NONE;
     static char parse[20000];
     static int parse_pos = 0;
@@ -1630,9 +2060,11 @@ read_from_ics(isr, closure, data, count, error)
     int buf_len;
     int next_out;
     int tkind;
+    int backup;    /* [DM] For zippy color lines */
     char *p;
+    char talker[MSG_SIZ]; // [HGM] chat
+    int channel;
 
-#ifdef WIN32
     if (appData.debugMode) {
       if (!error) {
        fprintf(debugFP, "<ICS: ");
@@ -1640,8 +2072,11 @@ read_from_ics(isr, closure, data, count, error)
        fprintf(debugFP, "\n");
       }
     }
-#endif
 
+    if (appData.debugMode) { int f = forwardMostMove;
+        fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
+                castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
+    }
     if (count > 0) {
        /* If last read ended with a partial line that we couldn't parse,
           prepend it to the new read and try again. */
@@ -1655,12 +2090,18 @@ read_from_ics(isr, closure, data, count, error)
        for (i = 0; i < count; i++) {
            if (data[i] != NULLCHAR && data[i] != '\r')
              buf[buf_len++] = data[i];
+           if(!appData.noJoin && buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' && 
+                               buf[buf_len-3]==' '  && buf[buf_len-2]==' '  && buf[buf_len-1]==' ') {
+               buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
+               if(buf_len == 0 || buf[buf_len-1] != ' ')
+                  buf[buf_len++] = ' '; // add space (assumes ICS does not break lines within word)
+           }
        }
 
        buf[buf_len] = NULLCHAR;
        next_out = leftover_len;
        leftover_start = 0;
-       
+
        i = 0;
        while (i < buf_len) {
            /* Deal with part of the TELNET option negotiation
@@ -1772,18 +2213,19 @@ read_from_ics(isr, closure, data, count, error)
                  next_out = i;
                continue;
            }
-               
+
            /* OK, this at least will *usually* work */
            if (!loggedOn && looking_at(buf, &i, "ics%")) {
                loggedOn = TRUE;
            }
-           
+
            if (loggedOn && !intfSet) {
                if (ics_type == ICS_ICC) {
                  sprintf(str,
                          "/set-quietly interface %s\n/set-quietly style 12\n",
                          programVersion);
-
+          if (!appData.noJoin)
+              strcat(str, "/set-quietly wrap 0\n");
                } else if (ics_type == ICS_CHESSNET) {
                  sprintf(str, "/style 12\n");
                } else {
@@ -1793,9 +2235,12 @@ read_from_ics(isr, closure, data, count, error)
 #ifdef WIN32
                  strcat(str, "$iset nohighlight 1\n");
 #endif
+          if (!appData.noJoin)
+              strcat(str, "$iset nowrap 1\n");
                  strcat(str, "$iset lock 1\n$style 12\n");
                }
                SendToICS(str);
+               NotifyFrontendLogin();
                intfSet = TRUE;
            }
 
@@ -1804,7 +2249,40 @@ read_from_ics(isr, closure, data, count, error)
                parse[parse_pos++] = buf[i];
                if (buf[i] == '\n') {
                    parse[parse_pos] = NULLCHAR;
-                   AppendComment(forwardMostMove, StripHighlight(parse));
+                   if(chattingPartner>=0) {
+                       char mess[MSG_SIZ];
+                       sprintf(mess, "%s%s", talker, parse);
+                       OutputChatMessage(chattingPartner, mess);
+                       chattingPartner = -1;
+                   } else
+                   if(!suppressKibitz) // [HGM] kibitz
+                       AppendComment(forwardMostMove, StripHighlight(parse));
+                   else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
+                       int nrDigit = 0, nrAlph = 0, i;
+                       if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
+                       { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
+                       parse[parse_pos] = NULLCHAR;
+                       // try to be smart: if it does not look like search info, it should go to
+                       // ICS interaction window after all, not to engine-output window.
+                       for(i=0; i<parse_pos; i++) { // count letters and digits
+                           nrDigit += (parse[i] >= '0' && parse[i] <= '9');
+                           nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
+                           nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
+                       }
+                       if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
+                           int depth=0; float score;
+                           if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
+                               // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
+                               pvInfoList[forwardMostMove-1].depth = depth;
+                               pvInfoList[forwardMostMove-1].score = 100*score;
+                           }
+                           OutputKibitz(suppressKibitz, parse);
+                       } else {
+                           char tmp[MSG_SIZ];
+                           sprintf(tmp, _("your opponent kibitzes: %s"), parse);
+                           SendToPlayer(tmp, strlen(tmp));
+                       }
+                   }
                    started = STARTED_NONE;
                } else {
                    /* Don't match patterns against characters in chatter */
@@ -1861,7 +2339,7 @@ read_from_ics(isr, closure, data, count, error)
 
            if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
              char buf[MSG_SIZ];
-             sprintf(buf, "%s@%s", ics_handle, appData.icsHost);
+             snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
              DisplayIcsInteractionTitle(buf);
              have_set_title = TRUE;
            }
@@ -1885,24 +2363,100 @@ read_from_ics(isr, closure, data, count, error)
            }
 
            oldi = i;
+           // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
+           if (appData.autoKibitz && started == STARTED_NONE &&
+                !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
+               (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
+               if(looking_at(buf, &i, "* kibitzes: ") &&
+                  (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
+                   StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
+                       suppressKibitz = TRUE;
+                       if((StrStr(star_match[0], gameInfo.white) == star_match[0]
+                               && (gameMode == IcsPlayingWhite)) ||
+                          (StrStr(star_match[0], gameInfo.black) == star_match[0]
+                               && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
+                           started = STARTED_CHATTER; // own kibitz we simply discard
+                       else {
+                           started = STARTED_COMMENT; // make sure it will be collected in parse[]
+                           parse_pos = 0; parse[0] = NULLCHAR;
+                           savingComment = TRUE;
+                           suppressKibitz = gameMode != IcsObserving ? 2 :
+                               (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
+                       }
+                       continue;
+               } else
+               if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
+                   started = STARTED_CHATTER;
+                   suppressKibitz = TRUE;
+               }
+           } // [HGM] kibitz: end of patch
+
+//if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
+
+           // [HGM] chat: intercept tells by users for which we have an open chat window
+           channel = -1;
+           if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
+                                          looking_at(buf, &i, "* whispers:") ||
+                                          looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
+                                          looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
+               int p;
+               sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
+               chattingPartner = -1;
+
+               if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
+               for(p=0; p<MAX_CHAT; p++) {
+                   if(channel == atoi(chatPartner[p])) {
+                   talker[0] = '['; strcat(talker, "]");
+                   chattingPartner = p; break;
+                   }
+               } else
+               if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
+               for(p=0; p<MAX_CHAT; p++) {
+                   if(!strcmp("WHISPER", chatPartner[p])) {
+                       talker[0] = '['; strcat(talker, "]");
+                       chattingPartner = p; break;
+                   }
+               }
+               if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
+               for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
+                   talker[0] = 0;
+                   chattingPartner = p; break;
+               }
+               if(chattingPartner<0) i = oldi; else {
+                   started = STARTED_COMMENT;
+                   parse_pos = 0; parse[0] = NULLCHAR;
+                   savingComment = TRUE;
+                   suppressKibitz = TRUE;
+               }
+           } // [HGM] chat: end of patch
+
            if (appData.zippyTalk || appData.zippyPlay) {
+                /* [DM] Backup address for color zippy lines */
+                backup = i;
 #if ZIPPY
-               if (ZippyControl(buf, &i) ||
-                   ZippyConverse(buf, &i) ||
-                   (appData.zippyPlay && ZippyMatch(buf, &i))) {
-                   loggedOn = TRUE;
-                   continue;
+       #ifdef WIN32
+               if (loggedOn == TRUE)
+                       if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
+                          (appData.zippyPlay && ZippyMatch(buf, &backup)));
+       #else
+                if (ZippyControl(buf, &i) ||
+                    ZippyConverse(buf, &i) ||
+                    (appData.zippyPlay && ZippyMatch(buf, &i))) {
+                     loggedOn = TRUE;
+                      if (!appData.colorize) continue;
                }
+       #endif
 #endif
-           } else {
-               if (/* Don't color "message" or "messages" output */
-                   (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
-                   looking_at(buf, &i, "*. * at *:*: ") ||
-                   looking_at(buf, &i, "--* (*:*): ") ||
+           } // [DM] 'else { ' deleted
+               if (
                    /* Regular tells and says */
                    (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
                    looking_at(buf, &i, "* (your partner) tells you: ") ||
                    looking_at(buf, &i, "* says: ") ||
+                   /* Don't color "message" or "messages" output */
+                   (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
+                   looking_at(buf, &i, "*. * at *:*: ") ||
+                   looking_at(buf, &i, "--* (*:*): ") ||
                    /* Message notifications (same color as tells) */
                    looking_at(buf, &i, "* has left a message ") ||
                    looking_at(buf, &i, "* just sent you a message:\n") ||
@@ -2050,7 +2604,6 @@ read_from_ics(isr, closure, data, count, error)
                        curColor = ColorSeek;
                    }
                    continue;
-               }
            }
 
            if (looking_at(buf, &i, "\\   ")) {
@@ -2085,14 +2638,14 @@ read_from_ics(isr, closure, data, count, error)
                SendToICS("refresh\n");
                continue;
            }
-           
+
            if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
                ICSInitScript();
                have_sent_ICS_logon = 1;
                continue;
            }
-             
-           if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
+
+           if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
                (looking_at(buf, &i, "\n<12> ") ||
                 looking_at(buf, &i, "<12> "))) {
                loggedOn = TRUE;
@@ -2161,7 +2714,7 @@ read_from_ics(isr, closure, data, count, error)
                  case H_GOT_UNWANTED_HEADER:
                  case H_GETTING_MOVES:
                    /* Should not happen */
-                   DisplayError("Error gathering move list: two headers", 0);
+                   DisplayError(_("Error gathering move list: two headers"), 0);
                    ics_getting_history = H_FALSE;
                    break;
                }
@@ -2175,7 +2728,7 @@ read_from_ics(isr, closure, data, count, error)
                    gameInfo.whiteRating = string_to_rating(star_match[1]);
                    gameInfo.blackRating = string_to_rating(star_match[3]);
                    if (appData.debugMode)
-                     fprintf(debugFP, "Ratings from header: W %d, B %d\n", 
+                     fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
                              gameInfo.whiteRating, gameInfo.blackRating);
                }
                continue;
@@ -2185,11 +2738,11 @@ read_from_ics(isr, closure, data, count, error)
              "* * match, initial time: * minute*, increment: * second")) {
                /* Header for a move list -- second line */
                /* Initial board will follow if this is a wild game */
-
                if (gameInfo.event != NULL) free(gameInfo.event);
                sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
                gameInfo.event = StrSave(str);
-               gameInfo.variant = StringToVariant(gameInfo.event);
+                /* [HGM] we switched variant. Translate boards if needed. */
+                VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
                continue;
            }
 
@@ -2208,7 +2761,7 @@ read_from_ics(isr, closure, data, count, error)
                    break;
                  case H_GETTING_MOVES:
                    /* Should not happen */
-                   DisplayError("Error gathering move list: nested", 0);
+                   DisplayError(_("Error gathering move list: nested"), 0);
                    ics_getting_history = H_FALSE;
                    break;
                  case H_GOT_REQ_HEADER:
@@ -2229,11 +2782,11 @@ read_from_ics(isr, closure, data, count, error)
                    break;
                }
                continue;
-           }                           
-           
+           }
+
            if (looking_at(buf, &i, "% ") ||
                ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
-                && looking_at(buf, &i, "}*"))) {
+                && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
                savingComment = FALSE;
                switch (started) {
                  case STARTED_MOVES:
@@ -2248,15 +2801,14 @@ read_from_ics(isr, closure, data, count, error)
                            if (WhiteOnMove(forwardMostMove)) {
                                if (first.sendTime) {
                                  if (first.useColors) {
-                                   SendToProgram("black\n", &first); 
+                                   SendToProgram("black\n", &first);
                                  }
                                  SendTimeRemaining(&first, TRUE);
                                }
                                if (first.useColors) {
-                                 SendToProgram("white\ngo\n", &first);
-                               } else {
-                                 SendToProgram("go\n", &first);
+                                 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
                                }
+                               bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
                                first.maybeThinking = TRUE;
                            } else {
                                if (first.usePlayother) {
@@ -2278,10 +2830,9 @@ read_from_ics(isr, closure, data, count, error)
                                  SendTimeRemaining(&first, FALSE);
                                }
                                if (first.useColors) {
-                                 SendToProgram("black\ngo\n", &first);
-                               } else {
-                                 SendToProgram("go\n", &first);
+                                 SendToProgram("black\n", &first);
                                }
+                               bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
                                first.maybeThinking = TRUE;
                            } else {
                                if (first.usePlayother) {
@@ -2294,7 +2845,7 @@ read_from_ics(isr, closure, data, count, error)
                                  firstMove = TRUE;
                                }
                            }
-                       }                       
+                       }
                    }
 #endif
                    if (gameMode == IcsObserving && ics_gamenum == -1) {
@@ -2334,16 +2885,27 @@ read_from_ics(isr, closure, data, count, error)
                  default:
                    break;
                }
+               if(bookHit) { // [HGM] book: simulate book reply
+                   static char bookMove[MSG_SIZ]; // a bit generous?
+
+                   programStats.nodes = programStats.depth = programStats.time =
+                   programStats.score = programStats.got_only_move = 0;
+                   sprintf(programStats.movelist, "%s (xbook)", bookHit);
+
+                   strcpy(bookMove, "move ");
+                   strcat(bookMove, bookHit);
+                   HandleMachineMove(bookMove, &first);
+               }
                continue;
            }
-           
+
            if ((started == STARTED_MOVES || started == STARTED_BOARD ||
                 started == STARTED_HOLDINGS ||
                 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
                /* Accumulate characters in move list or board */
                parse[parse_pos++] = buf[i];
            }
-           
+
            /* Start of game messages.  Mostly we detect start of game
               when the first board image arrives.  On some versions
               of the ICS, though, we need to do a "refresh" after starting
@@ -2376,7 +2938,7 @@ read_from_ics(isr, closure, data, count, error)
                player2Rating = string_to_rating(star_match[3]);
 
                if (appData.debugMode)
-                 fprintf(debugFP, 
+                 fprintf(debugFP,
                          "Ratings from 'Game notification:' %s %d, %s %d\n",
                          player1Name, player1Rating,
                          player2Name, player2Rating);
@@ -2406,25 +2968,26 @@ read_from_ics(isr, closure, data, count, error)
                    SendToICS("refresh\n");
                }
                continue;
-           }    
-           
+           }
+
            /* Error messages */
-           if (ics_user_moved) {
+//         if (ics_user_moved) {
+           if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
                if (looking_at(buf, &i, "Illegal move") ||
                    looking_at(buf, &i, "Not a legal move") ||
                    looking_at(buf, &i, "Your king is in check") ||
                    looking_at(buf, &i, "It isn't your turn") ||
                    looking_at(buf, &i, "It is not your move")) {
                    /* Illegal move */
-                   ics_user_moved = 0;
-                   if (forwardMostMove > backwardMostMove) {
+                   if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
                        currentMove = --forwardMostMove;
                        DisplayMove(currentMove - 1); /* before DMError */
-                       DisplayMoveError("Illegal move (rejected by ICS)");
                        DrawPosition(FALSE, boards[currentMove]);
                        SwitchClocks();
                        DisplayBothClocks();
                    }
+                   DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
+                   ics_user_moved = 0;
                    continue;
                }
            }
@@ -2469,7 +3032,7 @@ read_from_ics(isr, closure, data, count, error)
                   2    empty, white, or black (IGNORED)
                   3    player 2 name (not necessarily black)
                   4    player 2 rating
-                  
+
                   The names/ratings are sorted out when the game
                   actually starts (below).
                */
@@ -2479,14 +3042,14 @@ read_from_ics(isr, closure, data, count, error)
                player2Rating = string_to_rating(star_match[4]);
 
                if (appData.debugMode)
-                 fprintf(debugFP, 
+                 fprintf(debugFP,
                          "Ratings from 'Creating:' %s %d, %s %d\n",
                          player1Name, player1Rating,
                          player2Name, player2Rating);
 
                continue;
            }
-           
+
            /* Improved generic start/end-of-game messages */
            if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
                (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
@@ -2562,6 +3125,7 @@ read_from_ics(isr, closure, data, count, error)
                    /* Send "new" early, in case this command takes
                       a long time to finish, so that we'll be ready
                       for the next challenge. */
+                   gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
                    Reset(TRUE, TRUE);
                }
 #endif /*ZIPPY*/
@@ -2574,6 +3138,11 @@ read_from_ics(isr, closure, data, count, error)
                if (gameMode == IcsObserving &&
                    atoi(star_match[0]) == ics_gamenum)
                  {
+                      /* icsEngineAnalyze */
+                      if (appData.icsEngineAnalyze) {
+                            ExitAnalyzeMode();
+                            ModeHighlight();
+                      }
                      StopClocks();
                      gameMode = IcsIdle;
                      ics_gamenum = -1;
@@ -2635,9 +3204,9 @@ read_from_ics(isr, closure, data, count, error)
                        ClearPremoveHighlights();
                        if (appData.debugMode)
                          fprintf(debugFP, "Sending premove:\n");
-                         UserMoveEvent(premoveFromX, premoveFromY, 
-                                       premoveToX, premoveToY, 
-                                       premovePromoChar);
+                          UserMoveEvent(premoveFromX, premoveFromY,
+                                       premoveToX, premoveToY,
+                                        premovePromoChar);
                      }
                    }
 
@@ -2654,11 +3223,17 @@ read_from_ics(isr, closure, data, count, error)
                    started = STARTED_NONE;
                    parse[parse_pos] = NULLCHAR;
                    if (appData.debugMode)
-                     fprintf(debugFP, "Parsing holdings: %s\n", parse);
+                      fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
+                                                        parse, currentMove);
                    if (sscanf(parse, " game %d", &gamenum) == 1 &&
                        gamenum == ics_gamenum) {
                        if (gameInfo.variant == VariantNormal) {
-                         gameInfo.variant = VariantCrazyhouse; /*temp guess*/
+                          /* [HGM] We seem to switch variant during a game!
+                           * Presumably no holdings were displayed, so we have
+                           * to move the position two files to the right to
+                           * create room for them!
+                           */
+                          VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
                          /* Get a move list just to see the header, which
                             will tell us whether this is really bug or zh */
                          if (ics_getting_history == H_FALSE) {
@@ -2673,6 +3248,9 @@ read_from_ics(isr, closure, data, count, error)
                               new_piece);
                         white_holding[strlen(white_holding)-1] = NULLCHAR;
                         black_holding[strlen(black_holding)-1] = NULLCHAR;
+                        /* [HGM] copy holdings to board holdings area */
+                        CopyHoldings(boards[currentMove], white_holding, WhitePawn);
+                        CopyHoldings(boards[currentMove], black_holding, BlackPawn);
 #if ZIPPY
                        if (appData.zippyPlay && first.initDone) {
                            ZippyHoldings(white_holding, black_holding,
@@ -2690,7 +3268,8 @@ read_from_ics(isr, closure, data, count, error)
                                    gameInfo.white, white_holding,
                                    gameInfo.black, black_holding);
                        }
-                       DrawPosition(FALSE, NULL);
+
+                        DrawPosition(FALSE, boards[currentMove]);
                        DisplayTitle(str);
                    }
                    /* Suppress following prompt */
@@ -2704,37 +3283,38 @@ read_from_ics(isr, closure, data, count, error)
 
            i++;                /* skip unparsed character and loop back */
        }
-       
-       if (started != STARTED_MOVES && started != STARTED_BOARD &&
+
+       if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
            started != STARTED_HOLDINGS && i > next_out) {
            SendToPlayer(&buf[next_out], i - next_out);
            next_out = i;
        }
-       
+       suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
+
        leftover_len = buf_len - leftover_start;
        /* if buffer ends with something we couldn't parse,
           reparse it after appending the next read */
-       
+
     } else if (count == 0) {
        RemoveInputSource(isr);
-        DisplayFatalError("Connection closed by ICS", 0, 0);
+        DisplayFatalError(_("Connection closed by ICS"), 0, 0);
     } else {
-       DisplayFatalError("Error reading from ICS", error, 1);
+       DisplayFatalError(_("Error reading from ICS"), error, 1);
     }
 }
 
 
 /* Board style 12 looks like this:
-   
+
    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
-   
+
  * The "<12> " is stripped before it gets to this routine.  The two
  * trailing 0's (flip state and clock ticking) are later addition, and
  * some chess servers may not have them, or may have only the first.
- * Additional trailing fields may be added in the future.  
+ * Additional trailing fields may be added in the future.
  */
 
-#define PATTERN "%72c%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
+#define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
 
 #define RELATION_OBSERVING_PLAYED    0
 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
@@ -2747,12 +3327,12 @@ read_from_ics(isr, closure, data, count, error)
 void
 ParseBoard12(string)
      char *string;
-{ 
+{
     GameMode newGameMode;
-    int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
-    int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
+    int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
+    int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
-    char to_play, board_chars[72];
+    char to_play, board_chars[200];
     char move_str[500], str[500], elapsed_time[500];
     char black[32], white[32];
     Board board;
@@ -2761,25 +3341,38 @@ ParseBoard12(string)
     ChessMove moveType;
     int fromX, fromY, toX, toY;
     char promoChar;
+    int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
+    char *bookHit = NULL; // [HGM] book
 
     fromX = fromY = toX = toY = -1;
-    
+
     newGame = FALSE;
 
     if (appData.debugMode)
-      fprintf(debugFP, "Parsing board: %s\n", string);
+      fprintf(debugFP, _("Parsing board: %s\n"), string);
 
     move_str[0] = NULLCHAR;
     elapsed_time[0] = NULLCHAR;
-    n = sscanf(string, PATTERN, board_chars, &to_play, &double_push,
+    {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
+        int  i = 0, j;
+        while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
+           if(string[i] == ' ') { ranks++; files = 0; }
+            else files++;
+           i++;
+       }
+       for(j = 0; j <i; j++) board_chars[j] = string[j];
+        board_chars[i] = '\0';
+       string += i + 1;
+    }
+    n = sscanf(string, PATTERN, &to_play, &double_push,
               &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
               &gamenum, white, black, &relation, &basetime, &increment,
               &white_stren, &black_stren, &white_time, &black_time,
               &moveNum, str, elapsed_time, move_str, &ics_flip,
               &ticking);
 
-    if (n < 22) {
-       sprintf(str, "Failed to parse board string:\n\"%s\"", string);
+    if (n < 21) {
+        snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
        DisplayError(str, 0);
        return;
     }
@@ -2788,11 +3381,11 @@ ParseBoard12(string)
     moveNum = (moveNum - 1) * 2;
     if (to_play == 'B') moveNum++;
     if (moveNum >= MAX_MOVES) {
-      DisplayFatalError("Game too long; increase MAX_MOVES and recompile",
+      DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
                        0, 1);
       return;
     }
-    
+
     switch (relation) {
       case RELATION_OBSERVING_PLAYED:
       case RELATION_OBSERVING_STATIC:
@@ -2814,14 +3407,14 @@ ParseBoard12(string)
       case RELATION_ISOLATED_BOARD:
       default:
        /* Just display this board.  If user was doing something else,
-          we will forget about it until the next board comes. */ 
+          we will forget about it until the next board comes. */
        newGameMode = IcsIdle;
        break;
       case RELATION_STARTING_POSITION:
        newGameMode = gameMode;
        break;
     }
-    
+
     /* Modify behavior for initial board display on move listing
        of wild games.
        */
@@ -2850,16 +3443,16 @@ ParseBoard12(string)
        return;
       case H_GETTING_MOVES:
        /* Should not happen */
-       DisplayError("Error gathering move list: extra board", 0);
+       DisplayError(_("Error gathering move list: extra board"), 0);
        ics_getting_history = H_FALSE;
        return;
     }
-    
+
     /* Take action if this is the first board of a new game, or of a
        different game than is currently being displayed.  */
     if (gamenum != ics_gamenum || newGameMode != gameMode ||
        relation == RELATION_ISOLATED_BOARD) {
-       
+
        /* Forget the old game and get the history (if any) of the new one */
        if (gameMode != BeginningOfGame) {
          Reset(FALSE, TRUE);
@@ -2876,14 +3469,14 @@ ParseBoard12(string)
            sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
            SendToICS(str);
        }
-       
+
        /* Initially flip the board to have black on the bottom if playing
           black or if the ICS flip flag is set, but let the user change
           it with the Flip View button. */
-       flipView = appData.autoFlipView ? 
+       flipView = appData.autoFlipView ?
          (newGameMode == IcsPlayingBlack) || ics_flip :
          appData.flipView;
-       
+
        /* Done with values from previous mode; copy in new ones */
        gameMode = newGameMode;
        ModeHighlight();
@@ -2906,9 +3499,15 @@ ParseBoard12(string)
        timeIncrement = increment * 1000;
        movesPerSession = 0;
        gameInfo.timeControl = TimeControlTagValue();
-       gameInfo.variant = StringToVariant(gameInfo.event);
+        VariantSwitch(board, StringToVariant(gameInfo.event) );
+  if (appData.debugMode) {
+    fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
+    fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
+    setbuf(debugFP, NULL);
+  }
+
         gameInfo.outOfBook = NULL;
-       
+
        /* Do we have the ratings? */
        if (strcmp(player1Name, white) == 0 &&
            strcmp(player2Name, black) == 0) {
@@ -2934,7 +3533,7 @@ ParseBoard12(string)
            SendToICS("set shout 0\n");
        }
     }
-    
+
     /* Deal with midgame name changes */
     if (!newGame) {
        if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
@@ -2946,7 +3545,7 @@ ParseBoard12(string)
            gameInfo.black = StrSave(black);
        }
     }
-    
+
     /* Throw away game result if anything actually changes in examine mode */
     if (gameMode == IcsExamining && !newGame) {
        gameInfo.result = GameUnfinished;
@@ -2955,7 +3554,7 @@ ParseBoard12(string)
            gameInfo.resultDetails = NULL;
        }
     }
-    
+
     /* In pausing && IcsExamining mode, we ignore boards coming
        in if they are in a different variation than we are. */
     if (pauseExamInvalid) return;
@@ -2966,26 +3565,95 @@ ParseBoard12(string)
            return;
        }
     }
-    
+
+  if (appData.debugMode) {
+    fprintf(debugFP, "load %dx%d board\n", files, ranks);
+  }
     /* Parse the board */
-    for (k = 0; k < 8; k++)
-      for (j = 0; j < 8; j++)
-       board[k][j] = CharToPiece(board_chars[(7-k)*9 + j]);
+    for (k = 0; k < ranks; k++) {
+      for (j = 0; j < files; j++)
+        board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
+      if(gameInfo.holdingsWidth > 1) {
+           board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
+           board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
+      }
+    }
     CopyBoard(boards[moveNum], board);
     if (moveNum == 0) {
        startedFromSetupPosition =
          !CompareBoards(board, initialPosition);
-    }
-    
+        if(startedFromSetupPosition)
+            initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
+    }
+
+    /* [HGM] Set castling rights. Take the outermost Rooks,
+       to make it also work for FRC opening positions. Note that board12
+       is really defective for later FRC positions, as it has no way to
+       indicate which Rook can castle if they are on the same side of King.
+       For the initial position we grant rights to the outermost Rooks,
+       and remember thos rights, and we then copy them on positions
+       later in an FRC game. This means WB might not recognize castlings with
+       Rooks that have moved back to their original position as illegal,
+       but in ICS mode that is not its job anyway.
+    */
+    if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
+    { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
+
+        for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
+            if(board[0][i] == WhiteRook) j = i;
+        initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
+        for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
+            if(board[0][i] == WhiteRook) j = i;
+        initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
+        for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
+            if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
+        initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
+        for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
+            if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
+        initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
+
+       if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
+        for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
+            if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
+        for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
+            if(board[BOARD_HEIGHT-1][k] == bKing)
+                initialRights[5] = castlingRights[moveNum][5] = k;
+    } else { int r;
+        r = castlingRights[moveNum][0] = initialRights[0];
+        if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
+        r = castlingRights[moveNum][1] = initialRights[1];
+        if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
+        r = castlingRights[moveNum][3] = initialRights[3];
+        if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
+        r = castlingRights[moveNum][4] = initialRights[4];
+        if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
+        /* wildcastle kludge: always assume King has rights */
+        r = castlingRights[moveNum][2] = initialRights[2];
+        r = castlingRights[moveNum][5] = initialRights[5];
+    }
+    /* [HGM] e.p. rights. Assume that ICS sends file number here? */
+    epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
+
+
     if (ics_getting_history == H_GOT_REQ_HEADER ||
        ics_getting_history == H_GOT_UNREQ_HEADER) {
        /* This was an initial position from a move list, not
           the current position */
        return;
     }
-    
+
     /* Update currentMove and known move number limits */
     newMove = newGame || moveNum > forwardMostMove;
+
+    /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
+    if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
+        takeback = forwardMostMove - moveNum;
+        for (i = 0; i < takeback; i++) {
+             if (appData.debugMode) fprintf(debugFP, "take back move\n");
+             SendToProgram("undo\n", &first);
+        }
+    }
+
     if (newGame) {
        forwardMostMove = backwardMostMove = currentMove = moveNum;
        if (gameMode == IcsExamining && moveNum == 0) {
@@ -3002,7 +3670,7 @@ ParseBoard12(string)
        if (!pausing || currentMove > forwardMostMove)
          currentMove = forwardMostMove;
     } else {
-       /* New part of history that is not contiguous with old part */ 
+       /* New part of history that is not contiguous with old part */
        if (pausing && gameMode == IcsExamining) {
            pauseExamInvalid = TRUE;
            forwardMostMove = pauseExamForwardMostMove;
@@ -3015,7 +3683,7 @@ ParseBoard12(string)
            SendToICS(str);
        }
     }
-    
+
     /* Update the clocks */
     if (strchr(elapsed_time, '.')) {
       /* Time is in ms */
@@ -3026,7 +3694,7 @@ ParseBoard12(string)
       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
     }
-      
+
 
 #if ZIPPY
     if (appData.zippyPlay && newGame &&
@@ -3034,10 +3702,20 @@ ParseBoard12(string)
        gameMode != IcsExamining)
       ZippyFirstBoard(moveNum, basetime, increment);
 #endif
-    
+
     /* Put the move on the move list, first converting
        to canonical algebraic form. */
     if (moveNum > 0) {
+  if (appData.debugMode) {
+    if (appData.debugMode) { int f = forwardMostMove;
+        fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
+                castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
+    }
+    fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
+    fprintf(debugFP, "moveNum = %d\n", moveNum);
+    fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
+    setbuf(debugFP, NULL);
+  }
        if (moveNum <= backwardMostMove) {
            /* We don't know what the board looked like before
               this move.  Punt. */
@@ -3045,21 +3723,53 @@ ParseBoard12(string)
            strcat(parseList[moveNum - 1], " ");
            strcat(parseList[moveNum - 1], elapsed_time);
            moveList[moveNum - 1][0] = NULLCHAR;
-       } else if (ParseOneMove(move_str, moveNum - 1, &moveType,
-                               &fromX, &fromY, &toX, &toY, &promoChar)) {
+       } else if (strcmp(move_str, "none") == 0) {
+           // [HGM] long SAN: swapped order; test for 'none' before parsing move
+           /* Again, we don't know what the board looked like;
+              this is really the start of the game. */
+           parseList[moveNum - 1][0] = NULLCHAR;
+           moveList[moveNum - 1][0] = NULLCHAR;
+           backwardMostMove = moveNum;
+           startedFromSetupPosition = TRUE;
+           fromX = fromY = toX = toY = -1;
+       } else {
+         // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
+         //                 So we parse the long-algebraic move string in stead of the SAN move
+         int valid; char buf[MSG_SIZ], *prom;
+
+         // str looks something like "Q/a1-a2"; kill the slash
+         if(str[1] == '/')
+               sprintf(buf, "%c%s", str[0], str+2);
+         else  strcpy(buf, str); // might be castling
+         if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
+               strcat(buf, prom); // long move lacks promo specification!
+         if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
+               if(appData.debugMode)
+                       fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
+               strcpy(move_str, buf);
+          }
+         valid = ParseOneMove(move_str, moveNum - 1, &moveType,
+                               &fromX, &fromY, &toX, &toY, &promoChar)
+              || ParseOneMove(buf, moveNum - 1, &moveType,
+                               &fromX, &fromY, &toX, &toY, &promoChar);
+         // end of long SAN patch
+         if (valid) {
            (void) CoordsToAlgebraic(boards[moveNum - 1],
                                     PosFlags(moveNum - 1), EP_UNKNOWN,
                                     fromY, fromX, toY, toX, promoChar,
                                     parseList[moveNum-1]);
-           switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN)){
+            switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
+                             castlingRights[moveNum]) ) {
              case MT_NONE:
              case MT_STALEMATE:
              default:
                break;
              case MT_CHECK:
-               strcat(parseList[moveNum - 1], "+");
+                if(gameInfo.variant != VariantShogi)
+                    strcat(parseList[moveNum - 1], "+");
                break;
              case MT_CHECKMATE:
+             case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
                strcat(parseList[moveNum - 1], "#");
                break;
            }
@@ -3068,46 +3778,41 @@ ParseBoard12(string)
            /* currentMoveString is set as a side-effect of ParseOneMove */
            strcpy(moveList[moveNum - 1], currentMoveString);
            strcat(moveList[moveNum - 1], "\n");
-       } else if (strcmp(move_str, "none") == 0) {
-           /* Again, we don't know what the board looked like;
-              this is really the start of the game. */
-           parseList[moveNum - 1][0] = NULLCHAR;
-           moveList[moveNum - 1][0] = NULLCHAR;
-           backwardMostMove = moveNum;
-           startedFromSetupPosition = TRUE;
-           fromX = fromY = toX = toY = -1;
-       } else {
+         } else {
            /* Move from ICS was illegal!?  Punt. */
-#if 0
-           if (appData.testLegality && appData.debugMode) {
-               sprintf(str, "Illegal move \"%s\" from ICS", move_str);
-               DisplayError(str, 0);
-           }
-#endif
+  if (appData.debugMode) {
+    fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
+    fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
+  }
            strcpy(parseList[moveNum - 1], move_str);
            strcat(parseList[moveNum - 1], " ");
            strcat(parseList[moveNum - 1], elapsed_time);
            moveList[moveNum - 1][0] = NULLCHAR;
            fromX = fromY = toX = toY = -1;
+         }
        }
+  if (appData.debugMode) {
+    fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
+    setbuf(debugFP, NULL);
+  }
 
 #if ZIPPY
        /* Send move to chess program (BEFORE animating it). */
-       if (appData.zippyPlay && !newGame && newMove && 
+       if (appData.zippyPlay && !newGame && newMove &&
           (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
 
            if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
                (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
                if (moveList[moveNum - 1][0] == NULLCHAR) {
-                   sprintf(str, "Couldn't parse move \"%s\" from ICS",
+                   sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
                            move_str);
                    DisplayError(str, 0);
                } else {
                    if (first.sendTime) {
                        SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
                    }
-                   SendMoveToProgram(moveNum - 1, &first);
-                   if (firstMove) {
+                   bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
+                   if (firstMove && !bookHit) {
                        firstMove = FALSE;
                        if (first.useColors) {
                          SendToProgram(gameMode == IcsPlayingWhite ?
@@ -3121,9 +3826,10 @@ ParseBoard12(string)
                }
            } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
              if (moveList[moveNum - 1][0] == NULLCHAR) {
-               sprintf(str, "Couldn't parse move \"%s\" from ICS", move_str);
+               sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
                DisplayError(str, 0);
              } else {
+               if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
                SendMoveToProgram(moveNum - 1, &first);
              }
            }
@@ -3131,7 +3837,7 @@ ParseBoard12(string)
 #endif
     }
 
-    if (moveNum > 0 && !gotPremove) {
+    if (moveNum > 0 && !gotPremove && !appData.noGUI) {
        /* If move comes from a remote source, animate it.  If it
           isn't remote, it will have already been animated. */
        if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
@@ -3141,7 +3847,7 @@ ParseBoard12(string)
            SetHighlights(fromX, fromY, toX, toY);
        }
     }
-    
+
     /* Start the clocks */
     whiteFlag = blackFlag = FALSE;
     appData.clockMode = !(basetime == 0 && increment == 0);
@@ -3158,39 +3864,67 @@ ParseBoard12(string)
       DisplayBothClocks();
     else
       StartClocks();
-    
+
     /* Display opponents and material strengths */
     if (gameInfo.variant != VariantBughouse &&
-       gameInfo.variant != VariantCrazyhouse) {
+       gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
        if (tinyLayout || smallLayout) {
-           sprintf(str, "%s(%d) %s(%d) {%d %d}", 
+           if(gameInfo.variant == VariantNormal)
+               sprintf(str, "%s(%d) %s(%d) {%d %d}",
                    gameInfo.white, white_stren, gameInfo.black, black_stren,
                    basetime, increment);
+           else
+               sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
+                   gameInfo.white, white_stren, gameInfo.black, black_stren,
+                   basetime, increment, (int) gameInfo.variant);
        } else {
-           sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
+           if(gameInfo.variant == VariantNormal)
+               sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
                    gameInfo.white, white_stren, gameInfo.black, black_stren,
                    basetime, increment);
+           else
+               sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
+                   gameInfo.white, white_stren, gameInfo.black, black_stren,
+                   basetime, increment, VariantName(gameInfo.variant));
        }
        DisplayTitle(str);
+  if (appData.debugMode) {
+    fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
+  }
     }
 
-   
+
     /* Display the board */
-    if (!pausing) {
-      
+    if (!pausing && !appData.noGUI) {
       if (appData.premove)
-         if (!gotPremove || 
+         if (!gotPremove ||
             ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
             ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
              ClearPremoveHighlights();
 
       DrawPosition(FALSE, boards[currentMove]);
       DisplayMove(moveNum - 1);
-      if (appData.ringBellAfterMoves && !ics_user_moved)
-       RingBell();
+      if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
+           !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
+             (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
+       if(newMove) RingBell(); else PlayIcsUnfinishedSound();
+      }
     }
 
     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
+#if ZIPPY
+    if(bookHit) { // [HGM] book: simulate book reply
+       static char bookMove[MSG_SIZ]; // a bit generous?
+
+       programStats.nodes = programStats.depth = programStats.time =
+       programStats.score = programStats.got_only_move = 0;
+       sprintf(programStats.movelist, "%s (xbook)", bookHit);
+
+       strcpy(bookMove, "move ");
+       strcat(bookMove, bookHit);
+       HandleMachineMove(bookMove, &first);
+    }
+#endif
 }
 
 void
@@ -3222,12 +3956,19 @@ AnalysisPeriodicEvent(force)
     programStats.ok_to_send = 0;
 }
 
+void ics_update_width(new_width)
+       int new_width;
+{
+       ics_printf("set width %d\n", new_width);
+}
+
 void
 SendMoveToProgram(moveNum, cps)
      int moveNum;
      ChessProgramState *cps;
 {
     char buf[MSG_SIZ];
+
     if (cps->useUsermove) {
       SendToProgram("usermove ", cps);
     }
@@ -3243,18 +3984,24 @@ SendMoveToProgram(moveNum, cps)
       }
       SendToProgram(buf, cps);
     } else {
+      if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
+       AlphaRank(moveList[moveNum], 4);
+       SendToProgram(moveList[moveNum], cps);
+       AlphaRank(moveList[moveNum], 4); // and back
+      } else
       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
        * the engine. It would be nice to have a better way to identify castle
        * moves here. */
-      if(gameInfo.variant == VariantFischeRandom && cps->useOOCastle) {
-       int fromX = moveList[moveNum][0] - 'a';
-       int fromY = moveList[moveNum][1] - '1';
-       int toX = moveList[moveNum][2] - 'a';
-       int toY = moveList[moveNum][3] - '1';
-       if((boards[currentMove][fromY][fromX] == WhiteKing
-           && boards[currentMove][toY][toX] == WhiteRook)
-          || (boards[currentMove][fromY][fromX] == BlackKing
-              && boards[currentMove][toY][toX] == BlackRook)) {
+      if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
+                                                                        && cps->useOOCastle) {
+        int fromX = moveList[moveNum][0] - AAA;
+        int fromY = moveList[moveNum][1] - ONE;
+        int toX = moveList[moveNum][2] - AAA;
+        int toY = moveList[moveNum][3] - ONE;
+        if((boards[moveNum][fromY][fromX] == WhiteKing
+            && boards[moveNum][toY][toX] == WhiteRook)
+           || (boards[moveNum][fromY][fromX] == BlackKing
+               && boards[moveNum][toY][toX] == BlackRook)) {
          if(toX > fromX) SendToProgram("O-O\n", cps);
          else SendToProgram("O-O-O\n", cps);
        }
@@ -3263,6 +4010,21 @@ SendMoveToProgram(moveNum, cps)
       else SendToProgram(moveList[moveNum], cps);
       /* End of additions by Tord */
     }
+
+    /* [HGM] setting up the opening has brought engine in force mode! */
+    /*       Send 'go' if we are in a mode where machine should play. */
+    if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
+        (gameMode == TwoMachinesPlay   ||
+#ifdef ZIPPY
+         gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
+#endif
+         gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
+        SendToProgram("go\n", cps);
+  if (appData.debugMode) {
+    fprintf(debugFP, "(extra)\n");
+  }
+    }
+    setboardSpoiledMachineBlack = 0;
 }
 
 void
@@ -3274,7 +4036,7 @@ SendMoveToICS(moveType, fromX, fromY, toX, toY)
 
     switch (moveType) {
       default:
-       sprintf(user_move, "say Internal error; bad moveType %d (%d,%d-%d,%d)",
+       sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
                (int)moveType, fromX, fromY, toX, toY);
        DisplayError(user_move + strlen("say "), 0);
        break;
@@ -3308,25 +4070,40 @@ SendMoveToICS(moveType, fromX, fromY, toX, toY)
       case BlackPromotionKnight:
       case WhitePromotionKing:
       case BlackPromotionKing:
-       sprintf(user_move, "%c%c%c%c=%c\n",
-               'a' + fromX, '1' + fromY, 'a' + toX, '1' + toY,
+      case WhitePromotionChancellor:
+      case BlackPromotionChancellor:
+      case WhitePromotionArchbishop:
+      case BlackPromotionArchbishop:
+        if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
+            sprintf(user_move, "%c%c%c%c=%c\n",
+                AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
+               PieceToChar(WhiteFerz));
+        else if(gameInfo.variant == VariantGreat)
+            sprintf(user_move, "%c%c%c%c=%c\n",
+                AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
+               PieceToChar(WhiteMan));
+        else
+            sprintf(user_move, "%c%c%c%c=%c\n",
+                AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
                PieceToChar(PromoPiece(moveType)));
        break;
       case WhiteDrop:
       case BlackDrop:
        sprintf(user_move, "%c@%c%c\n",
                ToUpper(PieceToChar((ChessSquare) fromX)),
-               'a' + toX, '1' + toY);
+                AAA + toX, ONE + toY);
        break;
       case NormalMove:
       case WhiteCapturesEnPassant:
       case BlackCapturesEnPassant:
       case IllegalMove:  /* could be a variant we don't quite understand */
        sprintf(user_move, "%c%c%c%c\n",
-               'a' + fromX, '1' + fromY, 'a' + toX, '1' + toY);
+                AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
        break;
     }
     SendToICS(user_move);
+    if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
+       ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
 }
 
 void
@@ -3337,14 +4114,14 @@ CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
 {
     if (rf == DROP_RANK) {
        sprintf(move, "%c@%c%c\n",
-               ToUpper(PieceToChar((ChessSquare) ff)), 'a' + ft, '1' + rt);
+                ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
     } else {
        if (promoChar == 'x' || promoChar == NULLCHAR) {
            sprintf(move, "%c%c%c%c\n",
-                   'a' + ff, '1' + rf, 'a' + ft, '1' + rt);
+                    AAA + ff, ONE + rf, AAA + ft, ONE + rt);
        } else {
            sprintf(move, "%c%c%c%c%c\n",
-                   'a' + ff, '1' + rf, 'a' + ft, '1' + rt, promoChar);
+                    AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
        }
     }
 }
@@ -3363,6 +4140,56 @@ ProcessICSInitScript(f)
 }
 
 
+/* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
+void
+AlphaRank(char *move, int n)
+{
+//    char *p = move, c; int x, y;
+
+    if (appData.debugMode) {
+        fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
+    }
+
+    if(move[1]=='*' &&
+       move[2]>='0' && move[2]<='9' &&
+       move[3]>='a' && move[3]<='x'    ) {
+        move[1] = '@';
+        move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
+        move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
+    } else
+    if(move[0]>='0' && move[0]<='9' &&
+       move[1]>='a' && move[1]<='x' &&
+       move[2]>='0' && move[2]<='9' &&
+       move[3]>='a' && move[3]<='x'    ) {
+        /* input move, Shogi -> normal */
+        move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
+        move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
+        move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
+        move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
+    } else
+    if(move[1]=='@' &&
+       move[3]>='0' && move[3]<='9' &&
+       move[2]>='a' && move[2]<='x'    ) {
+        move[1] = '*';
+        move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
+        move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
+    } else
+    if(
+       move[0]>='a' && move[0]<='x' &&
+       move[3]>='0' && move[3]<='9' &&
+       move[2]>='a' && move[2]<='x'    ) {
+         /* output move, normal -> Shogi */
+        move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
+        move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
+        move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
+        move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
+        if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
+    }
+    if (appData.debugMode) {
+        fprintf(debugFP, "   out = '%s'\n", move);
+    }
+}
+
 /* Parser for moves from gnuchess, ICS, or user typein box */
 Boolean
 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
@@ -3371,9 +4198,17 @@ ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
      ChessMove *moveType;
      int *fromX, *fromY, *toX, *toY;
      char *promoChar;
-{       
+{
+    if (appData.debugMode) {
+        fprintf(debugFP, "move to parse: %s\n", move);
+    }
     *moveType = yylexstr(moveNum, move);
+
     switch (*moveType) {
+      case WhitePromotionChancellor:
+      case BlackPromotionChancellor:
+      case WhitePromotionArchbishop:
+      case BlackPromotionArchbishop:
       case WhitePromotionQueen:
       case BlackPromotionQueen:
       case WhitePromotionRook:
@@ -3402,13 +4237,16 @@ ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
       case BlackASideCastleFR:
       /* End of code added by Tord */
       case IllegalMove:                /* bug or odd chess variant */
-       *fromX = currentMoveString[0] - 'a';
-       *fromY = currentMoveString[1] - '1';
-       *toX = currentMoveString[2] - 'a';
-       *toY = currentMoveString[3] - '1';
+        *fromX = currentMoveString[0] - AAA;
+        *fromY = currentMoveString[1] - ONE;
+        *toX = currentMoveString[2] - AAA;
+        *toY = currentMoveString[3] - ONE;
        *promoChar = currentMoveString[4];
-       if (*fromX < 0 || *fromX > 7 || *fromY < 0 || *fromY > 7 ||
-           *toX < 0 || *toX > 7 || *toY < 0 || *toY > 7) {
+        if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
+            *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
+    if (appData.debugMode) {
+        fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
+    }
            *fromX = *fromY = *toX = *toY = 0;
            return FALSE;
        }
@@ -3422,10 +4260,10 @@ ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
       case BlackDrop:
        *fromX = *moveType == WhiteDrop ?
          (int) CharToPiece(ToUpper(currentMoveString[0])) :
-       (int) CharToPiece(ToLower(currentMoveString[0]));
+         (int) CharToPiece(ToLower(currentMoveString[0]));
        *fromY = DROP_RANK;
-       *toX = currentMoveString[2] - 'a';
-       *toY = currentMoveString[3] - '1';
+        *toX = currentMoveString[2] - AAA;
+        *toY = currentMoveString[3] - ONE;
        *promoChar = NULLCHAR;
        return TRUE;
 
@@ -3440,6 +4278,9 @@ ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
       case BlackWins:
       case GameIsDrawn:
       default:
+    if (appData.debugMode) {
+        fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
+    }
        /* bug? */
        *fromX = *fromY = *toX = *toY = 0;
        *promoChar = NULLCHAR;
@@ -3447,136 +4288,482 @@ ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
     }
 }
 
-/* [AS] FRC game initialization */
-static int FindEmptySquare( Board board, int n )
-{
-    int i = 0;
+// [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
+// All positions will have equal probability, but the current method will not provide a unique
+// numbering scheme for arrays that contain 3 or more pieces of the same kind.
+#define DARK 1
+#define LITE 2
+#define ANY 3
 
-    while( 1 ) {
-        while( board[0][i] != EmptySquare ) i++;
-        if( n == 0 )
-            break;
-        n--;
-        i++;
-    }
+int squaresLeft[4];
+int piecesLeft[(int)BlackPawn];
+int seed, nrOfShuffles;
+
+void GetPositionNumber()
+{      // sets global variable seed
+       int i;
 
-    return i;
+       seed = appData.defaultFrcPosition;
+       if(seed < 0) { // randomize based on time for negative FRC position numbers
+               for(i=0; i<50; i++) seed += random();
+               seed = random() ^ random() >> 8 ^ random() << 8;
+               if(seed<0) seed = -seed;
+       }
 }
 
-static void ShuffleFRC( Board board )
+int put(Board board, int pieceType, int rank, int n, int shade)
+// put the piece on the (n-1)-th empty squares of the given shade
 {
-    int i;
+       int i;
 
-    srand( time(0) );
+       for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
+               if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
+                       board[rank][i] = (ChessSquare) pieceType;
+                       squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
+                       squaresLeft[ANY]--;
+                       piecesLeft[pieceType]--;
+                       return i;
+               }
+       }
+        return -1;
+}
 
-    for( i=0; i<8; i++ ) {
-        board[0][i] = EmptySquare;
-    }
 
-    board[0][(rand() % 4)*2  ] = WhiteBishop; /* On dark square */
-    board[0][(rand() % 4)*2+1] = WhiteBishop; /* On lite square */
-    board[0][FindEmptySquare(board, rand() % 6)] = WhiteQueen;
-    board[0][FindEmptySquare(board, rand() % 5)] = WhiteKnight;
-    board[0][FindEmptySquare(board, rand() % 4)] = WhiteKnight;
-    board[0][FindEmptySquare(board, 0)] = WhiteRook;
-    board[0][FindEmptySquare(board, 0)] = WhiteKing;
-    board[0][FindEmptySquare(board, 0)] = WhiteRook;
+void AddOnePiece(Board board, int pieceType, int rank, int shade)
+// calculate where the next piece goes, (any empty square), and put it there
+{
+       int i;
 
-    for( i=0; i<8; i++ ) {
-        board[7][i] = board[0][i] + BlackPawn - WhitePawn;
-    }
+        i = seed % squaresLeft[shade];
+       nrOfShuffles *= squaresLeft[shade];
+       seed /= squaresLeft[shade];
+        put(board, pieceType, rank, i, shade);
 }
 
-static unsigned char FRC_KnightTable[10] = {
-    0x00, 0x01, 0x02, 0x03, 0x11, 0x12, 0x13, 0x22, 0x23, 0x33
-};
+void AddTwoPieces(Board board, int pieceType, int rank)
+// calculate where the next 2 identical pieces go, (any empty square), and put it there
+{
+       int i, n=squaresLeft[ANY], j=n-1, k;
+
+       k = n*(n-1)/2; // nr of possibilities, not counting permutations
+        i = seed % k;  // pick one
+       nrOfShuffles *= k;
+       seed /= k;
+       while(i >= j) i -= j--;
+        j = n - 1 - j; i += j;
+        put(board, pieceType, rank, j, ANY);
+        put(board, pieceType, rank, i, ANY);
+}
 
-static void SetupFRC( Board board, int pos_index )
+void SetUpShuffle(Board board, int number)
 {
-    int i;
-    unsigned char knights;
+       int i, p, first=1;
 
-    /* Bring the position index into a safe range (just in case...) */
-    if( pos_index < 0 ) pos_index = 0;
+       GetPositionNumber(); nrOfShuffles = 1;
 
-    pos_index %= 960;
+       squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
+       squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
+       squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
 
-    /* Clear the board */
-    for( i=0; i<8; i++ ) {
-        board[0][i] = EmptySquare;
-    }
+       for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
+
+       for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
+           p = (int) board[0][i];
+           if(p < (int) BlackPawn) piecesLeft[p] ++;
+           board[0][i] = EmptySquare;
+       }
+
+       if(PosFlags(0) & F_ALL_CASTLE_OK) {
+           // shuffles restricted to allow normal castling put KRR first
+           if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
+               put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
+           else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
+               put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
+           if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
+               put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
+           if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
+               put(board, WhiteRook, 0, 0, ANY);
+           // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
+       }
+
+       if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
+           // only for even boards make effort to put pairs of colorbound pieces on opposite colors
+           for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
+               if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
+               while(piecesLeft[p] >= 2) {
+                   AddOnePiece(board, p, 0, LITE);
+                   AddOnePiece(board, p, 0, DARK);
+               }
+               // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
+           }
+
+       for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
+           // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
+           // but we leave King and Rooks for last, to possibly obey FRC restriction
+           if(p == (int)WhiteRook) continue;
+           while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
+           if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
+       }
+
+       // now everything is placed, except perhaps King (Unicorn) and Rooks
+
+       if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
+           // Last King gets castling rights
+           while(piecesLeft[(int)WhiteUnicorn]) {
+               i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
+               initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
+           }
+
+           while(piecesLeft[(int)WhiteKing]) {
+               i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
+               initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
+           }
 
-    /* Place bishops and queen */
-    board[0][ (pos_index % 4)*2 + 1 ] = WhiteBishop; /* On lite square */
-    pos_index /= 4;
 
-    board[0][ (pos_index % 4)*2     ] = WhiteBishop; /* On dark square */
-    pos_index /= 4;
+       } else {
+           while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
+           while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
+       }
 
-    board[0][ FindEmptySquare(board, pos_index % 6) ] = WhiteQueen;
-    pos_index /= 6;
+       // Only Rooks can be left; simply place them all
+       while(piecesLeft[(int)WhiteRook]) {
+               i = put(board, WhiteRook, 0, 0, ANY);
+               if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
+                       if(first) {
+                               first=0;
+                               initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
+                       }
+                       initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
+               }
+       }
+       for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
+           board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
+       }
 
-    /* Place knigths */
-    knights = FRC_KnightTable[ pos_index ];
+       if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
+}
 
-    board[0][ FindEmptySquare(board, knights / 16) ] = WhiteKnight;
-    board[0][ FindEmptySquare(board, knights % 16) ] = WhiteKnight;
+int SetCharTable( char *table, const char * map )
+/* [HGM] moved here from winboard.c because of its general usefulness */
+/*       Basically a safe strcpy that uses the last character as King */
+{
+    int result = FALSE; int NrPieces;
 
-    /* Place rooks and king */
-    board[0][ FindEmptySquare(board, 0) ] = WhiteRook;
-    board[0][ FindEmptySquare(board, 0) ] = WhiteKing;
-    board[0][ FindEmptySquare(board, 0) ] = WhiteRook;
+    if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
+                    && NrPieces >= 12 && !(NrPieces&1)) {
+        int i; /* [HGM] Accept even length from 12 to 34 */
 
-    /* Mirror piece placement for black */
-    for( i=0; i<8; i++ ) {
-        board[7][i] = board[0][i] + BlackPawn - WhitePawn;
+        for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
+        for( i=0; i<NrPieces/2-1; i++ ) {
+            table[i] = map[i];
+            table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
+        }
+        table[(int) WhiteKing]  = map[NrPieces/2-1];
+        table[(int) BlackKing]  = map[NrPieces-1];
+
+        result = TRUE;
     }
+
+    return result;
+}
+
+void Prelude(Board board)
+{      // [HGM] superchess: random selection of exo-pieces
+       int i, j, k; ChessSquare p;
+       static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
+
+       GetPositionNumber(); // use FRC position number
+
+       if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
+           SetCharTable(pieceToChar, appData.pieceToCharTable);
+           for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
+               if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
+       }
+
+       j = seed%4;                 seed /= 4;
+       p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
+       board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
+       board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
+       j = seed%3 + (seed%3 >= j); seed /= 3;
+       p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
+       board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
+       board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
+       j = seed%3;                 seed /= 3;
+       p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
+       board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
+       board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
+       j = seed%2 + (seed%2 >= j); seed /= 2;
+       p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
+       board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
+       board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
+       j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
+       j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
+       j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
+       put(board, exoPieces[0],    0, 0, ANY);
+       for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
 }
 
 void
 InitPosition(redraw)
      int redraw;
 {
-    currentMove = forwardMostMove = backwardMostMove = 0;
+    ChessSquare (* pieces)[BOARD_SIZE];
+    int i, j, pawnRow, overrule,
+    oldx = gameInfo.boardWidth,
+    oldy = gameInfo.boardHeight,
+    oldh = gameInfo.holdingsWidth,
+    oldv = gameInfo.variant;
 
-    /* [AS] Initialize pv info list */
-    {
-        int i;
+    if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
 
+    /* [AS] Initialize pv info list [HGM] and game status */
+    {
         for( i=0; i<MAX_MOVES; i++ ) {
             pvInfoList[i].depth = 0;
+            epStatus[i]=EP_NONE;
+            for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
         }
+
+        initialRulePlies = 0; /* 50-move counter start */
+
+        castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
+        castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
     }
 
+
+    /* [HGM] logic here is completely changed. In stead of full positions */
+    /* the initialized data only consist of the two backranks. The switch */
+    /* selects which one we will use, which is than copied to the Board   */
+    /* initialPosition, which for the rest is initialized by Pawns and    */
+    /* empty squares. This initial position is then copied to boards[0],  */
+    /* possibly after shuffling, so that it remains available.            */
+
+    gameInfo.holdingsWidth = 0; /* default board sizes */
+    gameInfo.boardWidth    = 8;
+    gameInfo.boardHeight   = 8;
+    gameInfo.holdingsSize  = 0;
+    nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
+    for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
+    SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
+
     switch (gameInfo.variant) {
+    case VariantFischeRandom:
+      shuffleOpenings = TRUE;
     default:
-      CopyBoard(boards[0], initialPosition);
+      pieces = FIDEArray;
+      break;
+    case VariantShatranj:
+      pieces = ShatranjArray;
+      nrCastlingRights = 0;
+      SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
       break;
     case VariantTwoKings:
-      CopyBoard(boards[0], twoKingsPosition);
+      pieces = twoKingsArray;
+      break;
+    case VariantCapaRandom:
+      shuffleOpenings = TRUE;
+    case VariantCapablanca:
+      pieces = CapablancaArray;
+      gameInfo.boardWidth = 10;
+      SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
+      break;
+    case VariantGothic:
+      pieces = GothicArray;
+      gameInfo.boardWidth = 10;
+      SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
+      break;
+    case VariantJanus:
+      pieces = JanusArray;
+      gameInfo.boardWidth = 10;
+      SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
+      nrCastlingRights = 6;
+        castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
+        castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
+        castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
+        castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
+        castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
+        castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
+      break;
+    case VariantFalcon:
+      pieces = FalconArray;
+      gameInfo.boardWidth = 10;
+      SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
+      break;
+    case VariantXiangqi:
+      pieces = XiangqiArray;
+      gameInfo.boardWidth  = 9;
+      gameInfo.boardHeight = 10;
+      nrCastlingRights = 0;
+      SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
+      break;
+    case VariantShogi:
+      pieces = ShogiArray;
+      gameInfo.boardWidth  = 9;
+      gameInfo.boardHeight = 9;
+      gameInfo.holdingsSize = 7;
+      nrCastlingRights = 0;
+      SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
+      break;
+    case VariantCourier:
+      pieces = CourierArray;
+      gameInfo.boardWidth  = 12;
+      nrCastlingRights = 0;
+      SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
+      for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
+      break;
+    case VariantKnightmate:
+      pieces = KnightmateArray;
+      SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
+      break;
+    case VariantFairy:
+      pieces = fairyArray;
+      SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
+      break;
+    case VariantGreat:
+      pieces = GreatArray;
+      gameInfo.boardWidth = 10;
+      SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
+      gameInfo.holdingsSize = 8;
+      break;
+    case VariantSuper:
+      pieces = FIDEArray;
+      SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
+      gameInfo.holdingsSize = 8;
       startedFromSetupPosition = TRUE;
       break;
+    case VariantCrazyhouse:
+    case VariantBughouse:
+      pieces = FIDEArray;
+      SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
+      gameInfo.holdingsSize = 5;
+      break;
     case VariantWildCastle:
-      CopyBoard(boards[0], initialPosition);
+      pieces = FIDEArray;
       /* !!?shuffle with kings guaranteed to be on d or e file */
+      shuffleOpenings = 1;
       break;
     case VariantNoCastle:
-      CopyBoard(boards[0], initialPosition);
+      pieces = FIDEArray;
+      nrCastlingRights = 0;
+      for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
       /* !!?unconstrained back-rank shuffle */
+      shuffleOpenings = 1;
       break;
-    case VariantFischeRandom:
-      CopyBoard(boards[0], initialPosition);
-      if( appData.defaultFrcPosition < 0 ) {
-        ShuffleFRC( boards[0] );
-      }
-      else {
-        SetupFRC( boards[0], appData.defaultFrcPosition );
-      }
-      break;
     }
 
+    overrule = 0;
+    if(appData.NrFiles >= 0) {
+        if(gameInfo.boardWidth != appData.NrFiles) overrule++;
+        gameInfo.boardWidth = appData.NrFiles;
+    }
+    if(appData.NrRanks >= 0) {
+        gameInfo.boardHeight = appData.NrRanks;
+    }
+    if(appData.holdingsSize >= 0) {
+        i = appData.holdingsSize;
+        if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
+        gameInfo.holdingsSize = i;
+    }
+    if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
+    if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
+        DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
+
+    pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
+    if(pawnRow < 1) pawnRow = 1;
+
+    /* User pieceToChar list overrules defaults */
+    if(appData.pieceToCharTable != NULL)
+        SetCharTable(pieceToChar, appData.pieceToCharTable);
+
+    for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
+
+        if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
+            s = (ChessSquare) 0; /* account holding counts in guard band */
+        for( i=0; i<BOARD_HEIGHT; i++ )
+            initialPosition[i][j] = s;
+
+        if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
+        initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
+        initialPosition[pawnRow][j] = WhitePawn;
+        initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
+        if(gameInfo.variant == VariantXiangqi) {
+            if(j&1) {
+                initialPosition[pawnRow][j] =
+                initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
+                if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
+                   initialPosition[2][j] = WhiteCannon;
+                   initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
+                }
+            }
+        }
+        initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
+    }
+    if( (gameInfo.variant == VariantShogi) && !overrule ) {
+
+            j=BOARD_LEFT+1;
+            initialPosition[1][j] = WhiteBishop;
+            initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
+            j=BOARD_RGHT-2;
+            initialPosition[1][j] = WhiteRook;
+            initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
+    }
+
+    if( nrCastlingRights == -1) {
+        /* [HGM] Build normal castling rights (must be done after board sizing!) */
+        /*       This sets default castling rights from none to normal corners   */
+        /* Variants with other castling rights must set them themselves above    */
+        nrCastlingRights = 6;
+
+        castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
+        castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
+        castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
+        castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
+        castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
+        castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
+     }
+
+     if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
+     if(gameInfo.variant == VariantGreat) { // promotion commoners
+       initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
+       initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
+       initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
+       initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
+     }
+  if (appData.debugMode) {
+    fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
+  }
+    if(shuffleOpenings) {
+       SetUpShuffle(initialPosition, appData.defaultFrcPosition);
+       startedFromSetupPosition = TRUE;
+    }
+    if(startedFromPositionFile) {
+      /* [HGM] loadPos: use PositionFile for every new game */
+      CopyBoard(initialPosition, filePosition);
+      for(i=0; i<nrCastlingRights; i++)
+          castlingRights[0][i] = initialRights[i] = fileRights[i];
+      startedFromSetupPosition = TRUE;
+    }
+
+    CopyBoard(boards[0], initialPosition);
+    if(oldx != gameInfo.boardWidth ||
+       oldy != gameInfo.boardHeight ||
+       oldh != gameInfo.holdingsWidth
+#ifdef GOTHIC
+       || oldv == VariantGothic ||        // For licensing popups
+       gameInfo.variant == VariantGothic
+#endif
+#ifdef FALCON
+       || oldv == VariantFalcon ||
+       gameInfo.variant == VariantFalcon
+#endif
+                                         )
+      {
+            InitDrawingSizes(-2 ,0);
+      }
+
     if (redraw)
       DrawPosition(TRUE, boards[currentMove]);
+
 }
 
 void
@@ -3585,9 +4772,9 @@ SendBoard(cps, moveNum)
      int moveNum;
 {
     char message[MSG_SIZ];
-    
+
     if (cps->useSetboard) {
-      char* fen = PositionToFEN(moveNum, cps->useFEN960);
+      char* fen = PositionToFEN(moveNum, cps->fenOverride);
       sprintf(message, "setboard %s\n", fen);
       SendToProgram(message, cps);
       free(fen);
@@ -3602,51 +4789,99 @@ SendBoard(cps, moveNum)
 
       SendToProgram("edit\n", cps);
       SendToProgram("#\n", cps);
-      for (i = BOARD_SIZE - 1; i >= 0; i--) {
-       bp = &boards[moveNum][i][0];
-       for (j = 0; j < BOARD_SIZE; j++, bp++) {
+      for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
+       bp = &boards[moveNum][i][BOARD_LEFT];
+        for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
          if ((int) *bp < (int) BlackPawn) {
-           sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
-                   'a' + j, '1' + i);
+           sprintf(message, "%c%c%c\n", PieceToChar(*bp),
+                    AAA + j, ONE + i);
+            if(message[0] == '+' || message[0] == '~') {
+                sprintf(message, "%c%c%c+\n",
+                        PieceToChar((ChessSquare)(DEMOTED *bp)),
+                        AAA + j, ONE + i);
+            }
+            if(cps->alphaRank) { /* [HGM] shogi: translate coords */
+                message[1] = BOARD_RGHT   - 1 - j + '1';
+                message[2] = BOARD_HEIGHT - 1 - i + 'a';
+            }
            SendToProgram(message, cps);
          }
        }
       }
-    
+
       SendToProgram("c\n", cps);
-      for (i = BOARD_SIZE - 1; i >= 0; i--) {
-       bp = &boards[moveNum][i][0];
-       for (j = 0; j < BOARD_SIZE; j++, bp++) {
+      for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
+       bp = &boards[moveNum][i][BOARD_LEFT];
+        for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
          if (((int) *bp != (int) EmptySquare)
              && ((int) *bp >= (int) BlackPawn)) {
            sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
-                   'a' + j, '1' + i);
+                    AAA + j, ONE + i);
+            if(message[0] == '+' || message[0] == '~') {
+                sprintf(message, "%c%c%c+\n",
+                        PieceToChar((ChessSquare)(DEMOTED *bp)),
+                        AAA + j, ONE + i);
+            }
+            if(cps->alphaRank) { /* [HGM] shogi: translate coords */
+                message[1] = BOARD_RGHT   - 1 - j + '1';
+                message[2] = BOARD_HEIGHT - 1 - i + 'a';
+            }
            SendToProgram(message, cps);
          }
        }
       }
-    
+
       SendToProgram(".\n", cps);
     }
+    setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
 }
 
 int
 IsPromotion(fromX, fromY, toX, toY)
      int fromX, fromY, toX, toY;
 {
-    return gameMode != EditPosition &&
-      fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0 &&
-       ((boards[currentMove][fromY][fromX] == WhitePawn && toY == 7) ||
-        (boards[currentMove][fromY][fromX] == BlackPawn && toY == 0));
+    /* [HGM] add Shogi promotions */
+    int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
+    ChessSquare piece;
+
+    if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
+      !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
+   /* [HGM] Note to self: line above also weeds out drops */
+    piece = boards[currentMove][fromY][fromX];
+    if(gameInfo.variant == VariantShogi) {
+        promotionZoneSize = 3;
+        highestPromotingPiece = (int)WhiteKing;
+        /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
+           and if in normal chess we then allow promotion to King, why not
+           allow promotion of other piece in Shogi?                         */
+    }
+    if((int)piece >= BlackPawn) {
+        if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
+             return FALSE;
+        highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
+    } else {
+        if(  toY < BOARD_HEIGHT - promotionZoneSize &&
+           fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
+    }
+    return ( (int)piece <= highestPromotingPiece );
 }
 
+int
+InPalace(row, column)
+     int row, column;
+{   /* [HGM] for Xiangqi */
+    if( (row < 3 || row > BOARD_HEIGHT-4) &&
+         column < (BOARD_WIDTH + 4)/2 &&
+         column > (BOARD_WIDTH - 5)/2 ) return TRUE;
+    return FALSE;
+}
 
 int
 PieceForSquare (x, y)
      int x;
      int y;
 {
-  if (x < 0 || x >= BOARD_SIZE || y < 0 || y >= BOARD_SIZE)
+  if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
      return -1;
   else
      return boards[currentMove][y][x];
@@ -3670,7 +4905,7 @@ OKToStartUserMove(x, y)
     if (from_piece == EmptySquare) return FALSE;
 
     white_piece = (int)from_piece >= (int)WhitePawn &&
-      (int)from_piece <= (int)WhiteKing;
+      (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
 
     switch (gameMode) {
       case PlayFromGameFile:
@@ -3687,7 +4922,7 @@ OKToStartUserMove(x, y)
       case IcsPlayingBlack:
        if (appData.zippyPlay) return FALSE;
        if (white_piece) {
-           DisplayMoveError("You are playing Black");
+           DisplayMoveError(_("You are playing Black"));
            return FALSE;
        }
        break;
@@ -3696,20 +4931,20 @@ OKToStartUserMove(x, y)
       case IcsPlayingWhite:
        if (appData.zippyPlay) return FALSE;
        if (!white_piece) {
-           DisplayMoveError("You are playing White");
+           DisplayMoveError(_("You are playing White"));
            return FALSE;
        }
        break;
 
       case EditGame:
        if (!white_piece && WhiteOnMove(currentMove)) {
-           DisplayMoveError("It is White's turn");
+           DisplayMoveError(_("It is White's turn"));
            return FALSE;
-       }           
+       }
        if (white_piece && !WhiteOnMove(currentMove)) {
-           DisplayMoveError("It is Black's turn");
+           DisplayMoveError(_("It is Black's turn"));
            return FALSE;
-       }           
+       }
        if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
            /* Editing correspondence game history */
            /* Could disallow this or prompt for confirmation */
@@ -3727,21 +4962,21 @@ OKToStartUserMove(x, y)
        if (appData.icsActive) return FALSE;
        if (!appData.noChessProgram) {
            if (!white_piece) {
-               DisplayMoveError("You are playing White");
+               DisplayMoveError(_("You are playing White"));
                return FALSE;
            }
        }
        break;
-       
+
       case Training:
        if (!white_piece && WhiteOnMove(currentMove)) {
-           DisplayMoveError("It is White's turn");
+           DisplayMoveError(_("It is White's turn"));
            return FALSE;
-       }           
+       }
        if (white_piece && !WhiteOnMove(currentMove)) {
-           DisplayMoveError("It is Black's turn");
+           DisplayMoveError(_("It is Black's turn"));
            return FALSE;
-       }           
+       }
        break;
 
       default:
@@ -3750,7 +4985,7 @@ OKToStartUserMove(x, y)
     }
     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
        && gameMode != AnalyzeFile && gameMode != Training) {
-       DisplayMoveError("Displayed position is not current");
+       DisplayMoveError(_("Displayed position is not current"));
        return FALSE;
     }
     return TRUE;
@@ -3762,19 +4997,38 @@ int lastLoadGameUseList = FALSE;
 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
 ChessMove lastLoadGameStart = (ChessMove) 0;
 
-
-void
-UserMoveEvent(fromX, fromY, toX, toY, promoChar)
+ChessMove
+UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
      int fromX, fromY, toX, toY;
      int promoChar;
+     Boolean captureOwn;
 {
     ChessMove moveType;
+    ChessSquare pdown, pup;
+
+    if (fromX < 0 || fromY < 0) return ImpossibleMove;
+
+    /* [HGM] suppress all moves into holdings area and guard band */
+    if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
+            return ImpossibleMove;
+
+    /* [HGM] <sameColor> moved to here from winboard.c */
+    /* note: capture of own piece can be legal as drag-drop premove. For click-click it is selection of new piece. */
+    pdown = boards[currentMove][fromY][fromX];
+    pup = boards[currentMove][toY][toX];
+    if (    gameMode != EditPosition && !captureOwn &&
+            (WhitePawn <= pdown && pdown < BlackPawn &&
+             WhitePawn <= pup && pup < BlackPawn  ||
+             BlackPawn <= pdown && pdown < EmptySquare &&
+             BlackPawn <= pup && pup < EmptySquare
+            ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
+                    (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
+                     pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 ||
+                     pup == WhiteKing && pdown == WhiteRook && fromY == 0 && toY == 0|| // also allow RxK
+                     pup == BlackKing && pdown == BlackRook && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1  ) 
+        )           )
+         return Comment;
 
-    if (fromX < 0 || fromY < 0) return;
-    if ((fromX == toX) && (fromY == toY)) {
-       return;
-    }
-       
     /* Check if the user is playing in turn.  This is complicated because we
        let the user "pick up" a piece before it is his turn.  So the piece he
        tried to pick up may have been captured by the time he puts it down!
@@ -3795,21 +5049,21 @@ UserMoveEvent(fromX, fromY, toX, toY, promoChar)
       case IcsIdle:
        /* We switched into a game mode where moves are not accepted,
            perhaps while the mouse button was down. */
-       return;
+        return ImpossibleMove;
 
       case MachinePlaysWhite:
        /* User is moving for Black */
        if (WhiteOnMove(currentMove)) {
-           DisplayMoveError("It is White's turn");
-           return;
+           DisplayMoveError(_("It is White's turn"));
+            return ImpossibleMove;
        }
        break;
 
       case MachinePlaysBlack:
        /* User is moving for White */
        if (!WhiteOnMove(currentMove)) {
-           DisplayMoveError("It is Black's turn");
-           return;
+           DisplayMoveError(_("It is Black's turn"));
+            return ImpossibleMove;
        }
        break;
 
@@ -3819,17 +5073,17 @@ UserMoveEvent(fromX, fromY, toX, toY, promoChar)
       case AnalyzeMode:
       case Training:
        if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
-           (int) boards[currentMove][fromY][fromX] <= (int) BlackKing) {
+            (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
            /* User is moving for Black */
            if (WhiteOnMove(currentMove)) {
-               DisplayMoveError("It is White's turn");
-               return;
+               DisplayMoveError(_("It is White's turn"));
+                return ImpossibleMove;
            }
        } else {
            /* User is moving for White */
            if (!WhiteOnMove(currentMove)) {
-               DisplayMoveError("It is Black's turn");
-               return;
+               DisplayMoveError(_("It is Black's turn"));
+                return ImpossibleMove;
            }
        }
        break;
@@ -3838,7 +5092,7 @@ UserMoveEvent(fromX, fromY, toX, toY, promoChar)
        /* User is moving for Black */
        if (WhiteOnMove(currentMove)) {
            if (!appData.premove) {
-               DisplayMoveError("It is White's turn");
+               DisplayMoveError(_("It is White's turn"));
            } else if (toX >= 0 && toY >= 0) {
                premoveToX = toX;
                premoveToY = toY;
@@ -3846,12 +5100,12 @@ UserMoveEvent(fromX, fromY, toX, toY, promoChar)
                premoveFromY = fromY;
                premovePromoChar = promoChar;
                gotPremove = 1;
-               if (appData.debugMode) 
+               if (appData.debugMode)
                    fprintf(debugFP, "Got premove: fromX %d,"
                            "fromY %d, toX %d, toY %d\n",
                            fromX, fromY, toX, toY);
            }
-           return;
+            return ImpossibleMove;
        }
        break;
 
@@ -3859,7 +5113,7 @@ UserMoveEvent(fromX, fromY, toX, toY, promoChar)
        /* User is moving for White */
        if (!WhiteOnMove(currentMove)) {
            if (!appData.premove) {
-               DisplayMoveError("It is Black's turn");
+               DisplayMoveError(_("It is Black's turn"));
            } else if (toX >= 0 && toY >= 0) {
                premoveToX = toX;
                premoveToY = toY;
@@ -3867,12 +5121,12 @@ UserMoveEvent(fromX, fromY, toX, toY, promoChar)
                premoveFromY = fromY;
                premovePromoChar = promoChar;
                gotPremove = 1;
-               if (appData.debugMode) 
+               if (appData.debugMode)
                    fprintf(debugFP, "Got premove: fromX %d,"
                            "fromY %d, toX %d, toY %d\n",
                            fromX, fromY, toX, toY);
            }
-           return;
+            return ImpossibleMove;
        }
        break;
 
@@ -3880,81 +5134,164 @@ UserMoveEvent(fromX, fromY, toX, toY, promoChar)
        break;
 
       case EditPosition:
+       /* EditPosition, empty square, or different color piece;
+          click-click move is possible */
        if (toX == -2 || toY == -2) {
            boards[0][fromY][fromX] = EmptySquare;
-           DrawPosition(FALSE, boards[currentMove]);
+           return AmbiguousMove;
        } else if (toX >= 0 && toY >= 0) {
            boards[0][toY][toX] = boards[0][fromY][fromX];
            boards[0][fromY][fromX] = EmptySquare;
-           DrawPosition(FALSE, boards[currentMove]);
+           return AmbiguousMove;
        }
-       return;
+        return ImpossibleMove;
+    }
+
+    /* [HGM] If move started in holdings, it means a drop */
+    if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
+         if( pup != EmptySquare ) return ImpossibleMove;
+         if(appData.testLegality) {
+             /* it would be more logical if LegalityTest() also figured out
+              * which drops are legal. For now we forbid pawns on back rank.
+              * Shogi is on its own here...
+              */
+             if( (pdown == WhitePawn || pdown == BlackPawn) &&
+                 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
+                 return(ImpossibleMove); /* no pawn drops on 1st/8th */
+         }
+         return WhiteDrop; /* Not needed to specify white or black yet */
     }
 
-    if (toX < 0 || toY < 0) return;
     userOfferedDraw = FALSE;
-       
+
+    /* [HGM] always test for legality, to get promotion info */
+    moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
+                          epStatus[currentMove], castlingRights[currentMove],
+                                         fromY, fromX, toY, toX, promoChar);
+    /* [HGM] but possibly ignore an IllegalMove result */
     if (appData.testLegality) {
-       moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
-                               EP_UNKNOWN, fromY, fromX, toY, toX, promoChar);
        if (moveType == IllegalMove || moveType == ImpossibleMove) {
-           DisplayMoveError("Illegal move");
-           return;
+           DisplayMoveError(_("Illegal move"));
+            return ImpossibleMove;
        }
-    } else {
-       moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
     }
+    if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
+    return moveType;
+    /* [HGM] <popupFix> in stead of calling FinishMove directly, this
+       function is made into one that returns an OK move type if FinishMove
+       should be called. This to give the calling driver routine the
+       opportunity to finish the userMove input with a promotion popup,
+       without bothering the user with this for invalid or illegal moves */
 
-    if (gameMode == Training) {
+/*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
+}
+
+/* Common tail of UserMoveEvent and DropMenuEvent */
+int
+FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
+     ChessMove moveType;
+     int fromX, fromY, toX, toY;
+     /*char*/int promoChar;
+{
+  char *bookHit = 0;
+
+  if(appData.debugMode)
+    fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
+
+  if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR)
+    {
+      // [HGM] superchess: suppress promotions to non-available piece
+      int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
+      if(WhiteOnMove(currentMove))
+       {
+         if(!boards[currentMove][k][BOARD_WIDTH-2])
+           return 0;
+       }
+      else
+       {
+         if(!boards[currentMove][BOARD_HEIGHT-1-k][1])
+           return 0;
+       }
+    }
+  
+  /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
+     move type in caller when we know the move is a legal promotion */
+  if(moveType == NormalMove && promoChar)
+    moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
+
+  if(appData.debugMode) 
+    fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
+
+  /* [HGM] convert drag-and-drop piece drops to standard form */
+  if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) 
+    {
+      moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
+      if(appData.debugMode) 
+       fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
+               moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
+      //         fromX = boards[currentMove][fromY][fromX];
+      // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
+      if(fromX == 0) 
+       fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
+
+      fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
+
+      while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) 
+       fromX++; 
+
+      fromY = DROP_RANK;
+    }
+
+  /* [HGM] <popupFix> The following if has been moved here from
+     UserMoveEvent(). Because it seemed to belon here (why not allow
+     piece drops in training games?), and because it can only be
+     performed after it is known to what we promote. */
+  if (gameMode == Training)
+    {
       /* compare the move played on the board to the next move in the
-       * game. If they match, display the move and the opponent's response. 
+       * game. If they match, display the move and the opponent's response.
        * If they don't match, display an error message.
        */
       int saveAnimate;
-      Board testBoard;
+      Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
       CopyBoard(testBoard, boards[currentMove]);
-      ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
+      ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
 
-      if (CompareBoards(testBoard, boards[currentMove+1])) {
-       ForwardInner(currentMove+1);
+      if (CompareBoards(testBoard, boards[currentMove+1]))
+       {
+         ForwardInner(currentMove+1);
 
-       /* Autoplay the opponent's response.
-        * if appData.animate was TRUE when Training mode was entered,
-        * the response will be animated.
-        */
-       saveAnimate = appData.animate;
-       appData.animate = animateTraining;
-       ForwardInner(currentMove+1);
-       appData.animate = saveAnimate;
-
-       /* check for the end of the game */
-       if (currentMove >= forwardMostMove) {
-         gameMode = PlayFromGameFile;
-         ModeHighlight();
-         SetTrainingModeOff();
-         DisplayInformation("End of game");
+         /* Autoplay the opponent's response.
+          * if appData.animate was TRUE when Training mode was entered,
+          * the response will be animated.
+          */
+         saveAnimate = appData.animate;
+         appData.animate = animateTraining;
+         ForwardInner(currentMove+1);
+         appData.animate = saveAnimate;
+
+         /* check for the end of the game */
+         if (currentMove >= forwardMostMove)
+           {
+             gameMode = PlayFromGameFile;
+             ModeHighlight();
+             SetTrainingModeOff();
+             DisplayInformation(_("End of game"));
+           }
        }
-      } else {
-       DisplayError("Incorrect move", 0);
-      }
-      return;
+      else
+       {
+         DisplayError(_("Incorrect move"), 0);
+       }
+      return 1;
     }
 
-    FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
-}
-
-/* Common tail of UserMoveEvent and DropMenuEvent */
-void
-FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
-     ChessMove moveType;
-     int fromX, fromY, toX, toY;
-     /*char*/int promoChar;
-{
   /* Ok, now we know that the move is good, so we can kill
      the previous line in Analysis Mode */
-  if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
-    forwardMostMove = currentMove;
-  }
+  if (gameMode == AnalyzeMode && currentMove < forwardMostMove)
+    {
+      forwardMostMove = currentMove;
+    }
 
   /* If we need the chess program but it's dead, restart it */
   ResurrectChessProgram();
@@ -3967,88 +5304,206 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
 
   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
 
-  if (gameMode == BeginningOfGame) {
-    if (appData.noChessProgram) {
-      gameMode = EditGame;
-      SetGameInfo();
-    } else {
-      char buf[MSG_SIZ];
-      gameMode = MachinePlaysBlack;
-      SetGameInfo();
-      sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
-      DisplayTitle(buf);
-      if (first.sendName) {
-       sprintf(buf, "name %s\n", gameInfo.white);
-       SendToProgram(buf, &first);
-      }
+  if (gameMode == BeginningOfGame)
+    {
+      if (appData.noChessProgram)
+       {
+         gameMode = EditGame;
+         SetGameInfo();
+       }
+      else
+       {
+         char buf[MSG_SIZ];
+         gameMode = MachinePlaysBlack;
+         StartClocks();
+         SetGameInfo();
+         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
+         DisplayTitle(buf);
+         if (first.sendName)
+           {
+             sprintf(buf, "name %s\n", gameInfo.white);
+             SendToProgram(buf, &first);
+           }
+         StartClocks();
+       }
+      ModeHighlight();
     }
-    ModeHighlight();
-  }
+  if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
 
   /* Relay move to ICS or chess engine */
-  if (appData.icsActive) {
-    if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
-       gameMode == IcsExamining) {
-      SendMoveToICS(moveType, fromX, fromY, toX, toY);
-      ics_user_moved = 1;
-    }
-  } else {
-    if (first.sendTime && (gameMode == BeginningOfGame ||
-                          gameMode == MachinePlaysWhite ||
-                          gameMode == MachinePlaysBlack)) {
-      SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
-    }
-    SendMoveToProgram(forwardMostMove-1, &first);
-    if (gameMode != EditGame && gameMode != PlayFromGameFile) {
-      first.maybeThinking = TRUE;
+  if (appData.icsActive)
+    {
+      if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
+         gameMode == IcsExamining)
+       {
+         SendMoveToICS(moveType, fromX, fromY, toX, toY);
+         ics_user_moved = 1;
+       }
     }
-    if (currentMove == cmailOldMove + 1) {
-      cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
+  else
+    {
+      if (first.sendTime && (gameMode == BeginningOfGame ||
+                            gameMode == MachinePlaysWhite ||
+                            gameMode == MachinePlaysBlack))
+       {
+         SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
+       }
+      if (gameMode != EditGame && gameMode != PlayFromGameFile)
+       {
+         // [HGM] book: if program might be playing, let it use book
+         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
+         first.maybeThinking = TRUE;
+       }
+      else
+       SendMoveToProgram(forwardMostMove-1, &first);
+      if (currentMove == cmailOldMove + 1)
+       {
+         cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
+       }
     }
-  }
 
   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
 
-  switch (gameMode) {
-  case EditGame:
-    switch (MateTest(boards[currentMove], PosFlags(currentMove),
-                    EP_UNKNOWN)) {
-    case MT_NONE:
-    case MT_CHECK:
+  switch (gameMode)
+    {
+    case EditGame:
+      switch (MateTest(boards[currentMove], PosFlags(currentMove),
+                      EP_UNKNOWN, castlingRights[currentMove]) )
+       {
+       case MT_NONE:
+       case MT_CHECK:
+         break;
+       case MT_CHECKMATE:
+       case MT_STAINMATE:
+         if (WhiteOnMove(currentMove))
+           {
+             GameEnds(BlackWins, "Black mates", GE_PLAYER);
+           }
+         else
+           {
+             GameEnds(WhiteWins, "White mates", GE_PLAYER);
+           }
+         break;
+       case MT_STALEMATE:
+         GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
+         break;
+    }
       break;
-    case MT_CHECKMATE:
-      if (WhiteOnMove(currentMove)) {
-       GameEnds(BlackWins, "Black mates", GE_PLAYER);
-      } else {
-       GameEnds(WhiteWins, "White mates", GE_PLAYER);
-      }
+
+    case MachinePlaysBlack:
+    case MachinePlaysWhite:
+      /* disable certain menu options while machine is thinking */
+      SetMachineThinkingEnables();
       break;
-    case MT_STALEMATE:
-      GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
+
+    default:
       break;
     }
-    break;
-    
-  case MachinePlaysBlack:
-  case MachinePlaysWhite:
-    /* disable certain menu options while machine is thinking */
-    SetMachineThinkingEnables();
-    break;
 
-  default:
-    break;
-  }
+  if(bookHit)
+    { // [HGM] book: simulate book reply
+      static char bookMove[MSG_SIZ]; // a bit generous?
+
+      programStats.nodes = programStats.depth = programStats.time =
+       programStats.score = programStats.got_only_move = 0;
+      sprintf(programStats.movelist, "%s (xbook)", bookHit);
+
+      strcpy(bookMove, "move ");
+      strcat(bookMove, bookHit);
+      HandleMachineMove(bookMove, &first);
+    }
+
+  return 1;
+}
+
+void
+UserMoveEvent(fromX, fromY, toX, toY, promoChar)
+     int fromX, fromY, toX, toY;
+     int promoChar;
+{
+    /* [HGM] This routine was added to allow calling of its two logical
+       parts from other modules in the old way. Before, UserMoveEvent()
+       automatically called FinishMove() if the move was OK, and returned
+       otherwise. I separated the two, in order to make it possible to
+       slip a promotion popup in between. But that it always needs two
+       calls, to the first part, (now called UserMoveTest() ), and to
+       FinishMove if the first part succeeded. Calls that do not need
+       to do anything in between, can call this routine the old way.
+    */
+    ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
+if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
+    if(moveType == AmbiguousMove)
+       DrawPosition(FALSE, boards[currentMove]);
+    else if(moveType != ImpossibleMove && moveType != Comment)
+        FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
+}
+
+void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
+{
+//    char * hint = lastHint;
+    FrontEndProgramStats stats;
+
+    stats.which = cps == &first ? 0 : 1;
+    stats.depth = cpstats->depth;
+    stats.nodes = cpstats->nodes;
+    stats.score = cpstats->score;
+    stats.time = cpstats->time;
+    stats.pv = cpstats->movelist;
+    stats.hint = lastHint;
+    stats.an_move_index = 0;
+    stats.an_move_count = 0;
+
+    if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
+        stats.hint = cpstats->move_name;
+        stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
+        stats.an_move_count = cpstats->nr_moves;
+    }
+
+    SetProgramStats( &stats );
 }
 
-void SendProgramStatsToFrontend( ChessProgramState * cps )
+char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
+{   // [HGM] book: this routine intercepts moves to simulate book replies
+    char *bookHit = NULL;
+
+    //first determine if the incoming move brings opponent into his book
+    if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
+       bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
+    if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
+    if(bookHit != NULL && !cps->bookSuspend) {
+       // make sure opponent is not going to reply after receiving move to book position
+       SendToProgram("force\n", cps);
+       cps->bookSuspend = TRUE; // flag indicating it has to be restarted
+    }
+    if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
+    // now arrange restart after book miss
+    if(bookHit) {
+       // after a book hit we never send 'go', and the code after the call to this routine
+       // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
+       char buf[MSG_SIZ];
+       if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
+       sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
+       SendToProgram(buf, cps);
+       if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
+    } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
+       SendToProgram("go\n", cps);
+       cps->bookSuspend = FALSE; // after a 'go' we are never suspended
+    } else { // 'go' might be sent based on 'firstMove' after this routine returns
+       if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
+           SendToProgram("go\n", cps);
+       cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
+    }
+    return bookHit; // notify caller of hit, so it can take action to send move to opponent
+}
+
+char *savedMessage;
+ChessProgramState *savedState;
+void DeferredBookMove(void)
 {
-    SetProgramStats( cps == &first ? 0 : 1,
-        programStats.depth,
-        programStats.nodes,
-        programStats.score,
-        programStats.time,
-        programStats.movelist,
-        lastHint );
+       if(savedState->lastPing != savedState->lastPong)
+                   ScheduleDelayedEvent(DeferredBookMove, 10);
+       else
+       HandleMachineMove(savedMessage, savedState);
 }
 
 void
@@ -4063,13 +5518,20 @@ HandleMachineMove(message, cps)
     char promoChar;
     char *p;
     int machineWhite;
+    char *bookHit;
 
+FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
     /*
      * Kludge to ignore BEL characters
      */
     while (*message == '\007') message++;
 
     /*
+     * [HGM] engine debug message: ignore lines starting with '#' character
+     */
+    if(cps->debug && *message == '#') return;
+
+    /*
      * Look for book output
      */
     if (cps == &first && bookRequested) {
@@ -4109,7 +5571,8 @@ HandleMachineMove(message, cps)
                fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
                        cps->which, gameMode);
            }
-           SendToProgram("undo\n", cps);
+
+            SendToProgram("undo\n", cps);
          }
          return;
        }
@@ -4160,19 +5623,67 @@ HandleMachineMove(message, cps)
            return;
        }
 
-       if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
-                             &fromX, &fromY, &toX, &toY, &promoChar)) {
+    if (appData.debugMode) { int f = forwardMostMove;
+        fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
+                castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
+    }
+        if(cps->alphaRank) AlphaRank(machineMove, 4);
+        if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
+                              &fromX, &fromY, &toX, &toY, &promoChar)) {
            /* Machine move could not be parsed; ignore it. */
-           sprintf(buf1, "Illegal move \"%s\" from %s machine",
+            sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
                    machineMove, cps->which);
            DisplayError(buf1, 0);
+            sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
+                    machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
            if (gameMode == TwoMachinesPlay) {
              GameEnds(machineWhite ? BlackWins : WhiteWins,
-                      "Forfeit due to illegal move", GE_XBOARD);
+                       buf1, GE_XBOARD);
            }
            return;
        }
 
+        /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
+        /* So we have to redo legality test with true e.p. status here,  */
+        /* to make sure an illegal e.p. capture does not slip through,   */
+        /* to cause a forfeit on a justified illegal-move complaint      */
+        /* of the opponent.                                              */
+        if( gameMode==TwoMachinesPlay && appData.testLegality
+            && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
+                                                              ) {
+           ChessMove moveType;
+           moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
+                        epStatus[forwardMostMove], castlingRights[forwardMostMove],
+                             fromY, fromX, toY, toX, promoChar);
+           if (appData.debugMode) {
+                int i;
+                for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
+                    castlingRights[forwardMostMove][i], castlingRank[i]);
+                fprintf(debugFP, "castling rights\n");
+           }
+            if(moveType == IllegalMove) {
+                sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
+                        machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
+                GameEnds(machineWhite ? BlackWins : WhiteWins,
+                           buf1, GE_XBOARD);
+               return;
+           } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
+           /* [HGM] Kludge to handle engines that send FRC-style castling
+              when they shouldn't (like TSCP-Gothic) */
+           switch(moveType) {
+             case WhiteASideCastleFR:
+             case BlackASideCastleFR:
+               toX+=2;
+               currentMoveString[2]++;
+               break;
+             case WhiteHSideCastleFR:
+             case BlackHSideCastleFR:
+               toX--;
+               currentMoveString[2]--;
+               break;
+            default: ; // nothing to do, but suppresses warning of pedantic compilers
+           }
+        }
        hintRequested = FALSE;
        lastHint[0] = NULLCHAR;
        bookRequested = FALSE;
@@ -4186,23 +5697,36 @@ HandleMachineMove(message, cps)
            first.initDone) {
          SendMoveToICS(moveType, fromX, fromY, toX, toY);
          ics_user_moved = 1;
+         if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
+               char buf[3*MSG_SIZ];
+
+               sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
+                       programStats.score / 100.,
+                       programStats.depth,
+                       programStats.time / 100.,
+                       (unsigned int)programStats.nodes,
+                       (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
+                       programStats.movelist);
+               SendToICS(buf);
+if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
+         }
        }
 #endif
        /* currentMoveString is set as a side-effect of ParseOneMove */
        strcpy(machineMove, currentMoveString);
        strcat(machineMove, "\n");
        strcpy(moveList[forwardMostMove], machineMove);
-    
+
         /* [AS] Save move info and clear stats for next move */
         pvInfoList[ forwardMostMove ].score = programStats.score;
         pvInfoList[ forwardMostMove ].depth = programStats.depth;
-        pvInfoList[ forwardMostMove ].time = -1;
+        pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
         ClearProgramStats();
         thinkOutput[0] = NULLCHAR;
         hiddenThinkOutputState = 0;
 
        MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
-    
+
         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
             int count = 0;
@@ -4232,21 +5756,353 @@ HandleMachineMove(message, cps)
             }
         }
 
-        if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
-           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
+       if( gameMode == TwoMachinesPlay ) {
+         // [HGM] some adjudications useful with buggy engines
+            int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
+         if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
+
+
+           if( appData.testLegality )
+           {   /* [HGM] Some more adjudications for obstinate engines */
+               int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
+                    NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
+                    NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
+               static int moveCount = 6;
+               ChessMove result;
+               char *reason = NULL;
+
+                /* Count what is on board. */
+               for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
+               {   ChessSquare p = boards[forwardMostMove][i][j];
+                   int m=i;
+
+                   switch((int) p)
+                   {   /* count B,N,R and other of each side */
+                        case WhiteKing:
+                        case BlackKing:
+                            NrK++; break; // [HGM] atomic: count Kings
+                        case WhiteKnight:
+                             NrWN++; break;
+                        case WhiteBishop:
+                        case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
+                             bishopsColor |= 1 << ((i^j)&1);
+                             NrWB++; break;
+                        case BlackKnight:
+                             NrBN++; break;
+                        case BlackBishop:
+                        case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
+                             bishopsColor |= 1 << ((i^j)&1);
+                             NrBB++; break;
+                        case WhiteRook:
+                             NrWR++; break;
+                        case BlackRook:
+                             NrBR++; break;
+                        case WhiteQueen:
+                             NrWQ++; break;
+                        case BlackQueen:
+                             NrBQ++; break;
+                        case EmptySquare:
+                             break;
+                        case BlackPawn:
+                             m = 7-i;
+                        case WhitePawn:
+                             PawnAdvance += m; NrPawns++;
+                    }
+                    NrPieces += (p != EmptySquare);
+                    NrW += ((int)p < (int)BlackPawn);
+                   if(gameInfo.variant == VariantXiangqi &&
+                     (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
+                       NrPieces--; // [HGM] XQ: do not count purely defensive pieces
+                        NrW -= ((int)p < (int)BlackPawn);
+                   }
+                }
+
+               /* Some material-based adjudications that have to be made before stalemate test */
+               if(gameInfo.variant == VariantAtomic && NrK < 2) {
+                   // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
+                    epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
+                    if(appData.checkMates) {
+                        SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
+                         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
+                         GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
+                                                       "Xboard adjudication: King destroyed", GE_XBOARD );
+                         return;
+                    }
+               }
 
-            GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
+               /* Bare King in Shatranj (loses) or Losers (wins) */
+                if( NrW == 1 || NrPieces - NrW == 1) {
+                  if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
+                    epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
+                    if(appData.checkMates) {
+                        SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
+                         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
+                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
+                                                       "Xboard adjudication: Bare king", GE_XBOARD );
+                         return;
+                    }
+                 } else
+                  if( gameInfo.variant == VariantShatranj && --bare < 0)
+                  {    /* bare King */
+                       epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
+                       if(appData.checkMates) {
+                           /* but only adjudicate if adjudication enabled */
+                           SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
+                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
+                           GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
+                                                       "Xboard adjudication: Bare king", GE_XBOARD );
+                           return;
+                       }
+                 }
+                } else bare = 1;
 
-            return;
+
+            // don't wait for engine to announce game end if we can judge ourselves
+            switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
+                                       castlingRights[forwardMostMove]) ) {
+             case MT_CHECK:
+               if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
+                   int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
+                   for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
+                       if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
+                           checkCnt++;
+                       if(checkCnt >= 2) {
+                           reason = "Xboard adjudication: 3rd check";
+                           epStatus[forwardMostMove] = EP_CHECKMATE;
+                           break;
+                       }
+                   }
+               }
+             case MT_NONE:
+             default:
+               break;
+             case MT_STALEMATE:
+             case MT_STAINMATE:
+               reason = "Xboard adjudication: Stalemate";
+               if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
+                   epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
+                   if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
+                       epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
+                   else if(gameInfo.variant == VariantSuicide) // in suicide it depends
+                       epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
+                                                  ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
+                                                                       EP_CHECKMATE : EP_WINS);
+                   else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
+                       epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
+               }
+               break;
+             case MT_CHECKMATE:
+               reason = "Xboard adjudication: Checkmate";
+               epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
+               break;
+           }
+
+               switch(i = epStatus[forwardMostMove]) {
+                   case EP_STALEMATE:
+                       result = GameIsDrawn; break;
+                   case EP_CHECKMATE:
+                       result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
+                   case EP_WINS:
+                       result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
+                   default:
+                       result = (ChessMove) 0;
+               }
+                if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
+                   SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
+                   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
+                   GameEnds( result, reason, GE_XBOARD );
+                   return;
+               }
+
+                /* Next absolutely insufficient mating material. */
+                if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
+                                    gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
+                       (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
+                        NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
+                {    /* KBK, KNK, KK of KBKB with like Bishops */
+
+                     /* always flag draws, for judging claims */
+                     epStatus[forwardMostMove] = EP_INSUF_DRAW;
+
+                     if(appData.materialDraws) {
+                         /* but only adjudicate them if adjudication enabled */
+                        SendToProgram("force\n", cps->other); // suppress reply
+                        SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
+                         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
+                         GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
+                         return;
+                     }
+                }
+
+                /* Then some trivial draws (only adjudicate, cannot be claimed) */
+                if(NrPieces == 4 &&
+                   (   NrWR == 1 && NrBR == 1 /* KRKR */
+                   || NrWQ==1 && NrBQ==1     /* KQKQ */
+                   || NrWN==2 || NrBN==2     /* KNNK */
+                   || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
+                  ) ) {
+                     if(--moveCount < 0 && appData.trivialDraws)
+                     {    /* if the first 3 moves do not show a tactical win, declare draw */
+                         SendToProgram("force\n", cps->other); // suppress reply
+                         SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
+                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
+                          GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
+                          return;
+                     }
+                } else moveCount = 6;
+           }
+         }
+         
+         if (appData.debugMode) { int i;
+           fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
+                   forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
+                   appData.drawRepeats);
+           for( i=forwardMostMove; i>=backwardMostMove; i-- )
+             fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
+           
+         }
+
+                /* Check for rep-draws */
+                count = 0;
+                for(k = forwardMostMove-2;
+                    k>=backwardMostMove && k>=forwardMostMove-100 &&
+                        epStatus[k] < EP_UNKNOWN &&
+                        epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
+                    k-=2)
+                {   int rights=0;
+                    if(CompareBoards(boards[k], boards[forwardMostMove])) {
+                        /* compare castling rights */
+                        if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
+                             (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
+                                rights++; /* King lost rights, while rook still had them */
+                        if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
+                            if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
+                                castlingRights[forwardMostMove][1] != castlingRights[k][1] )
+                                   rights++; /* but at least one rook lost them */
+                        }
+                        if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
+                             (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
+                                rights++;
+                        if( castlingRights[forwardMostMove][5] >= 0 ) {
+                            if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
+                                castlingRights[forwardMostMove][4] != castlingRights[k][4] )
+                                   rights++;
+                        }
+                        if( rights == 0 && ++count > appData.drawRepeats-2
+                            && appData.drawRepeats > 1) {
+                             /* adjudicate after user-specified nr of repeats */
+                            SendToProgram("force\n", cps->other); // suppress reply
+                            SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
+                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
+                            if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
+                               // [HGM] xiangqi: check for forbidden perpetuals
+                               int m, ourPerpetual = 1, hisPerpetual = 1;
+                               for(m=forwardMostMove; m>k; m-=2) {
+                                   if(MateTest(boards[m], PosFlags(m),
+                                                       EP_NONE, castlingRights[m]) != MT_CHECK)
+                                       ourPerpetual = 0; // the current mover did not always check
+                                   if(MateTest(boards[m-1], PosFlags(m-1),
+                                                       EP_NONE, castlingRights[m-1]) != MT_CHECK)
+                                       hisPerpetual = 0; // the opponent did not always check
+                               }
+                               if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
+                                                                       ourPerpetual, hisPerpetual);
+                               if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
+                                   GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
+                                          "Xboard adjudication: perpetual checking", GE_XBOARD );
+                                   return;
+                               }
+                               if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
+                                   break; // (or we would have caught him before). Abort repetition-checking loop.
+                               // Now check for perpetual chases
+                               if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
+                                   hisPerpetual = PerpetualChase(k, forwardMostMove);
+                                   ourPerpetual = PerpetualChase(k+1, forwardMostMove);
+                                   if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
+                                       GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
+                                                     "Xboard adjudication: perpetual chasing", GE_XBOARD );
+                                       return;
+                                   }
+                                   if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
+                                       break; // Abort repetition-checking loop.
+                               }
+                               // if neither of us is checking or chasing all the time, or both are, it is draw
+                            }
+                             GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
+                             return;
+                        }
+                        if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
+                             epStatus[forwardMostMove] = EP_REP_DRAW;
+                    }
+                }
+
+                /* Now we test for 50-move draws. Determine ply count */
+                count = forwardMostMove;
+                /* look for last irreversble move */
+                while( epStatus[count] <= EP_NONE && count > backwardMostMove )
+                    count--;
+                /* if we hit starting position, add initial plies */
+                if( count == backwardMostMove )
+                    count -= initialRulePlies;
+                count = forwardMostMove - count;
+                if( count >= 100)
+                         epStatus[forwardMostMove] = EP_RULE_DRAW;
+                         /* this is used to judge if draw claims are legal */
+                if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
+                        SendToProgram("force\n", cps->other); // suppress reply
+                        SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
+                         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
+                         GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
+                         return;
+                }
+
+                /* if draw offer is pending, treat it as a draw claim
+                 * when draw condition present, to allow engines a way to
+                 * claim draws before making their move to avoid a race
+                 * condition occurring after their move
+                 */
+                if( cps->other->offeredDraw || cps->offeredDraw ) {
+                         char *p = NULL;
+                         if(epStatus[forwardMostMove] == EP_RULE_DRAW)
+                             p = "Draw claim: 50-move rule";
+                         if(epStatus[forwardMostMove] == EP_REP_DRAW)
+                             p = "Draw claim: 3-fold repetition";
+                         if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
+                             p = "Draw claim: insufficient mating material";
+                         if( p != NULL ) {
+                            SendToProgram("force\n", cps->other); // suppress reply
+                            SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
+                             GameEnds( GameIsDrawn, p, GE_XBOARD );
+                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
+                             return;
+                         }
+                }
+
+
+               if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
+                   SendToProgram("force\n", cps->other); // suppress reply
+                   SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
+                   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
+
+                   GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
+
+                   return;
+               }
         }
 
+       bookHit = NULL;
        if (gameMode == TwoMachinesPlay) {
+            /* [HGM] relaying draw offers moved to after reception of move */
+            /* and interpreting offer as claim if it brings draw condition */
+            if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
+                SendToProgram("draw\n", cps->other);
+            }
            if (cps->other->sendTime) {
                SendTimeRemaining(cps->other,
                                  cps->other->twoMachinesColor[0] == 'w');
            }
-           SendMoveToProgram(forwardMostMove-1, cps->other);
-           if (firstMove) {
+           bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
+           if (firstMove && !bookHit) {
                firstMove = FALSE;
                if (cps->other->useColors) {
                  SendToProgram(cps->other->twoMachinesColor, cps->other);
@@ -4258,17 +6114,40 @@ HandleMachineMove(message, cps)
 
        ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
 
-       if (!pausing && appData.ringBellAfterMoves) {
+        if (!pausing && appData.ringBellAfterMoves) {
            RingBell();
        }
 
-       /* 
+       /*
         * Reenable menu items that were disabled while
         * machine was thinking
         */
        if (gameMode != TwoMachinesPlay)
            SetUserThinkingEnables();
 
+       // [HGM] book: after book hit opponent has received move and is now in force mode
+       // force the book reply into it, and then fake that it outputted this move by jumping
+       // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
+       if(bookHit) {
+               static char bookMove[MSG_SIZ]; // a bit generous?
+
+               strcpy(bookMove, "move ");
+               strcat(bookMove, bookHit);
+               message = bookMove;
+               cps = cps->other;
+               programStats.nodes = programStats.depth = programStats.time =
+               programStats.score = programStats.got_only_move = 0;
+               sprintf(programStats.movelist, "%s (xbook)", bookHit);
+
+               if(cps->lastPing != cps->lastPong) {
+                   savedMessage = message; // args for deferred call
+                   savedState = cps;
+                   ScheduleDelayedEvent(DeferredBookMove, 10);
+                   return;
+               }
+               goto FakeBookMove;
+       }
+
        return;
     }
 
@@ -4281,6 +6160,35 @@ HandleMachineMove(message, cps)
        cps->useSigint = FALSE;
        cps->useSigterm = FALSE;
     }
+    if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
+      ParseFeatures(message+8, cps);
+      return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
+    }
+
+    /* [HGM] Allow engine to set up a position. Don't ask me why one would
+     * want this, I was asked to put it in, and obliged.
+     */
+    if (!strncmp(message, "setboard ", 9)) {
+        Board initial_position; int i;
+
+        GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
+
+        if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
+            DisplayError(_("Bad FEN received from engine"), 0);
+            return ;
+        } else {
+           Reset(FALSE, FALSE);
+           CopyBoard(boards[0], initial_position);
+           initialRulePlies = FENrulePlies;
+           epStatus[0] = FENepStatus;
+           for( i=0; i<nrCastlingRights; i++ )
+                castlingRights[0][i] = FENcastlingRights[i];
+           if(blackPlaysFirst) gameMode = MachinePlaysWhite;
+           else gameMode = MachinePlaysBlack;
+           DrawPosition(FALSE, boards[currentMove]);
+        }
+       return;
+    }
 
     /*
      * Look for communication commands
@@ -4296,7 +6204,7 @@ HandleMachineMove(message, cps)
     if (!strncmp(message, "tellopponent ", 13)) {
       if (appData.icsActive) {
        if (loggedOn) {
-         sprintf(buf1, "%ssay %s\n", ics_prefix, message + 13);
+         snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
          SendToICS(buf1);
        }
       } else {
@@ -4307,7 +6215,7 @@ HandleMachineMove(message, cps)
     if (!strncmp(message, "tellothers ", 11)) {
       if (appData.icsActive) {
        if (loggedOn) {
-         sprintf(buf1, "%swhisper %s\n", ics_prefix, message + 11);
+         snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
          SendToICS(buf1);
        }
       }
@@ -4316,7 +6224,7 @@ HandleMachineMove(message, cps)
     if (!strncmp(message, "tellall ", 8)) {
       if (appData.icsActive) {
        if (loggedOn) {
-         sprintf(buf1, "%skibitz %s\n", ics_prefix, message + 8);
+         snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
          SendToICS(buf1);
        }
       } else {
@@ -4335,8 +6243,8 @@ HandleMachineMove(message, cps)
        AskQuestion(realname, buf2, buf1, cps->pr);
        return;
     }
-    /* Commands from the engine directly to ICS.  We don't allow these to be 
-     *  sent until we are logged on. Crafty kibitzes have been known to 
+    /* Commands from the engine directly to ICS.  We don't allow these to be
+     *  sent until we are logged on. Crafty kibitzes have been known to
      *  interfere with the login process.
      */
     if (loggedOn) {
@@ -4360,11 +6268,8 @@ HandleMachineMove(message, cps)
            return;
        }
     }
-    if (strncmp(message, "feature ", 8) == 0) {
-      ParseFeatures(message+8, cps);
-    }
     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
-      return;
+       return;
     }
     /*
      * If the move is illegal, cancel it and redraw the board.
@@ -4373,7 +6278,7 @@ HandleMachineMove(message, cps)
      */
     if (strncmp(message + 1, "llegal move", 11) == 0 ||
        strncmp(message, "Error", 5) == 0) {
-       if (StrStr(message, "name") || 
+       if (StrStr(message, "name") ||
            StrStr(message, "rating") || StrStr(message, "?") ||
            StrStr(message, "result") || StrStr(message, "board") ||
            StrStr(message, "bk") || StrStr(message, "computer") ||
@@ -4408,7 +6313,7 @@ HandleMachineMove(message, cps)
            cps->analysisSupport = FALSE;
            cps->analyzing = FALSE;
            Reset(FALSE, TRUE);
-           sprintf(buf2, "%s does not support analysis", cps->tidy);
+           sprintf(buf2, _("%s does not support analysis"), cps->tidy);
            DisplayError(buf2, 0);
            return;
        }
@@ -4428,21 +6333,36 @@ HandleMachineMove(message, cps)
                          searchTime);
          return;
        }
-       if (!StrStr(message, "llegal")) return;
+        if (!StrStr(message, "llegal")) {
+            return;
+        }
        if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
            gameMode == IcsIdle) return;
        if (forwardMostMove <= backwardMostMove) return;
-#if 0
-       /* Following removed: it caused a bug where a real illegal move
-          message in analyze mored would be ignored. */
-       if (cps == &first && programStats.ok_to_send == 0) {
-           /* Bogus message from Crafty responding to "."  This filtering
-              can miss some of the bad messages, but fortunately the bug 
-              is fixed in current Crafty versions, so it doesn't matter. */
-           return;
-       }
-#endif
        if (pausing) PauseEvent();
+      if(appData.forceIllegal) {
+           // [HGM] illegal: machine refused move; force position after move into it
+          SendToProgram("force\n", cps);
+          if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
+               // we have a real problem now, as SendBoard will use the a2a3 kludge
+               // when black is to move, while there might be nothing on a2 or black
+               // might already have the move. So send the board as if white has the move.
+               // But first we must change the stm of the engine, as it refused the last move
+               SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
+               if(WhiteOnMove(forwardMostMove)) {
+                   SendToProgram("a7a6\n", cps); // for the engine black still had the move
+                   SendBoard(cps, forwardMostMove); // kludgeless board
+               } else {
+                   SendToProgram("a2a3\n", cps); // for the engine white still had the move
+                   CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
+                   SendBoard(cps, forwardMostMove+1); // kludgeless board
+               }
+          } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
+           if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
+                gameMode == TwoMachinesPlay)
+              SendToProgram("go\n", cps);
+           return;
+      } else
        if (gameMode == PlayFromGameFile) {
            /* Stop reading this game file */
            gameMode = EditGame;
@@ -4452,10 +6372,17 @@ HandleMachineMove(message, cps)
        DisplayMove(currentMove-1); /* before DisplayMoveError */
        SwitchClocks();
        DisplayBothClocks();
-       sprintf(buf1, "Illegal move \"%s\" (rejected by %s chess program)",
+       sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
                parseList[currentMove], cps->which);
        DisplayMoveError(buf1);
        DrawPosition(FALSE, boards[currentMove]);
+
+        /* [HGM] illegal-move claim should forfeit game when Xboard */
+        /* only passes fully legal moves                            */
+        if( appData.testLegality && gameMode == TwoMachinesPlay ) {
+            GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
+                                "False illegal-move claim", GE_XBOARD );
+        }
        return;
     }
     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
@@ -4464,7 +6391,7 @@ HandleMachineMove(message, cps)
           Don't use it. */
        cps->sendTime = 0;
     }
-    
+
     /*
      * If chess program startup fails, exit with an error message.
      * Attempts to recover here are futile.
@@ -4477,14 +6404,14 @@ HandleMachineMove(message, cps)
        || (StrStr(message, "Permission denied") != NULL)) {
 
        cps->maybeThinking = FALSE;
-       sprintf(buf1, "Failed to start %s chess program %s on %s: %s\n",
+       snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
                cps->which, cps->program, cps->host, message);
        RemoveInputSource(cps->isr);
        DisplayFatalError(buf1, 0, 1);
        return;
     }
-    
-    /* 
+
+    /*
      * Look for hint output
      */
     if (sscanf(message, "Hint: %s", buf1) == 1) {
@@ -4495,12 +6422,12 @@ HandleMachineMove(message, cps)
                (void) CoordsToAlgebraic(boards[forwardMostMove],
                                    PosFlags(forwardMostMove), EP_UNKNOWN,
                                    fromY, fromX, toY, toX, promoChar, buf1);
-               sprintf(buf2, "Hint: %s", buf1);
+               snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
                DisplayInformation(buf2);
            } else {
                /* Hint move could not be parsed!? */
-               sprintf(buf2,
-                       "Illegal hint move \"%s\"\nfrom %s chess program",
+             snprintf(buf2, sizeof(buf2),
+                       _("Illegal hint move \"%s\"\nfrom %s chess program"),
                        buf1, cps->which);
                DisplayError(buf2, 0);
            }
@@ -4529,7 +6456,7 @@ HandleMachineMove(message, cps)
                r = p + 1;
            }
        }
-       GameEnds(WhiteWins, r, GE_ENGINE);
+        GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
        return;
     } else if (strncmp(message, "0-1", 3) == 0) {
        char *p, *q, *r = "";
@@ -4543,10 +6470,10 @@ HandleMachineMove(message, cps)
        }
        /* Kludge for Arasan 4.1 bug */
        if (strcmp(r, "Black resigns") == 0) {
-           GameEnds(WhiteWins, r, GE_ENGINE);
+            GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
            return;
        }
-       GameEnds(BlackWins, r, GE_ENGINE);
+        GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
        return;
     } else if (strncmp(message, "1/2", 3) == 0) {
        char *p, *q, *r = "";
@@ -4558,40 +6485,45 @@ HandleMachineMove(message, cps)
                r = p + 1;
            }
        }
-       GameEnds(GameIsDrawn, r, GE_ENGINE);
+
+        GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
        return;
 
     } else if (strncmp(message, "White resign", 12) == 0) {
-       GameEnds(BlackWins, "White resigns", GE_ENGINE);
+        GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
        return;
     } else if (strncmp(message, "Black resign", 12) == 0) {
-       GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
+        GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
        return;
+    } else if (strncmp(message, "White matches", 13) == 0 ||
+               strncmp(message, "Black matches", 13) == 0   ) {
+        /* [HGM] ignore GNUShogi noises */
+        return;
     } else if (strncmp(message, "White", 5) == 0 &&
               message[5] != '(' &&
               StrStr(message, "Black") == NULL) {
-       GameEnds(WhiteWins, "White mates", GE_ENGINE);
+        GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
        return;
     } else if (strncmp(message, "Black", 5) == 0 &&
               message[5] != '(') {
-       GameEnds(BlackWins, "Black mates", GE_ENGINE);
+        GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
        return;
     } else if (strcmp(message, "resign") == 0 ||
               strcmp(message, "computer resigns") == 0) {
        switch (gameMode) {
          case MachinePlaysBlack:
          case IcsPlayingBlack:
-           GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
+            GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
            break;
          case MachinePlaysWhite:
          case IcsPlayingWhite:
-           GameEnds(BlackWins, "White resigns", GE_ENGINE);
+            GameEnds(BlackWins, "White resigns", GE_ENGINE);
            break;
          case TwoMachinesPlay:
            if (cps->twoMachinesColor[0] == 'w')
-             GameEnds(BlackWins, "White resigns", GE_ENGINE);
+              GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
            else
-             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
+              GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
            break;
          default:
            /* can't happen */
@@ -4602,17 +6534,17 @@ HandleMachineMove(message, cps)
        switch (gameMode) {
          case MachinePlaysBlack:
          case IcsPlayingBlack:
-           GameEnds(WhiteWins, "White mates", GE_ENGINE);
+            GameEnds(WhiteWins, "White mates", GE_ENGINE);
            break;
          case MachinePlaysWhite:
          case IcsPlayingWhite:
-           GameEnds(BlackWins, "Black mates", GE_ENGINE);
+            GameEnds(BlackWins, "Black mates", GE_ENGINE);
            break;
          case TwoMachinesPlay:
            if (cps->twoMachinesColor[0] == 'w')
-             GameEnds(BlackWins, "Black mates", GE_ENGINE);
+              GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
            else
-             GameEnds(WhiteWins, "White mates", GE_ENGINE);
+              GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
            break;
          default:
            /* can't happen */
@@ -4623,17 +6555,17 @@ HandleMachineMove(message, cps)
        switch (gameMode) {
          case MachinePlaysBlack:
          case IcsPlayingBlack:
-           GameEnds(BlackWins, "Black mates", GE_ENGINE);
+            GameEnds(BlackWins, "Black mates", GE_ENGINE1);
            break;
          case MachinePlaysWhite:
          case IcsPlayingWhite:
-           GameEnds(WhiteWins, "White mates", GE_ENGINE);
+            GameEnds(WhiteWins, "White mates", GE_ENGINE);
            break;
          case TwoMachinesPlay:
            if (cps->twoMachinesColor[0] == 'w')
-             GameEnds(WhiteWins, "White mates", GE_ENGINE);
+              GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
            else
-             GameEnds(BlackWins, "Black mates", GE_ENGINE);
+              GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
            break;
          default:
            /* can't happen */
@@ -4642,14 +6574,14 @@ HandleMachineMove(message, cps)
        return;
     } else if (strncmp(message, "checkmate", 9) == 0) {
        if (WhiteOnMove(forwardMostMove)) {
-           GameEnds(BlackWins, "Black mates", GE_ENGINE);
+            GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
        } else {
-           GameEnds(WhiteWins, "White mates", GE_ENGINE);
+            GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
        }
        return;
     } else if (strstr(message, "Draw") != NULL ||
               strstr(message, "game is a draw") != NULL) {
-       GameEnds(GameIsDrawn, "Draw", GE_ENGINE);
+        GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
        return;
     } else if (strstr(message, "offer") != NULL &&
               strstr(message, "draw") != NULL) {
@@ -4664,30 +6596,30 @@ HandleMachineMove(message, cps)
        if (gameMode == TwoMachinesPlay) {
            if (cps->other->offeredDraw) {
                GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
-           } else {
-               if (cps->other->sendDrawOffers) {
-                   SendToProgram("draw\n", cps->other);
-               }
+            /* [HGM] in two-machine mode we delay relaying draw offer      */
+            /* until after we also have move, to see if it is really claim */
            }
        } else if (gameMode == MachinePlaysWhite ||
                   gameMode == MachinePlaysBlack) {
          if (userOfferedDraw) {
-           DisplayInformation("Machine accepts your draw offer");
+           DisplayInformation(_("Machine accepts your draw offer"));
            GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
          } else {
-            DisplayInformation("Machine offers a draw\nSelect Action / Draw to agree");
+            DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
          }
        }
     }
 
-    
+
     /*
      * Look for thinking output
      */
-    if ( appData.showThinking) {
+    if ( appData.showThinking // [HGM] thinking: test all options that cause this output
+         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
+                               ) {
        int plylev, mvleft, mvtot, curscore, time;
        char mvname[MOVE_LEN];
-       unsigned long nodes;
+       u64 nodes; // [DM]
        char plyext;
        int ignore = FALSE;
        int prefixHint = FALSE;
@@ -4704,6 +6636,9 @@ HandleMachineMove(message, cps)
            break;
          case AnalyzeMode:
          case AnalyzeFile:
+            break;
+          case IcsObserving: /* [DM] icsEngineAnalyze */
+            if (!appData.icsEngineAnalyze) ignore = TRUE;
            break;
          case TwoMachinesPlay:
            if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
@@ -4717,7 +6652,7 @@ HandleMachineMove(message, cps)
 
        if (!ignore) {
            buf1[0] = NULLCHAR;
-           if (sscanf(message, "%d%c %d %d %lu %[^\n]\n",
+           if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
                       &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
 
                if (plyext != ' ' && plyext != '\t') {
@@ -4738,6 +6673,16 @@ HandleMachineMove(message, cps)
                programStats.score = curscore;
                programStats.got_only_move = 0;
 
+               if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
+                       int ticklen;
+
+                       if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
+                       else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
+                       if(WhiteOnMove(forwardMostMove))
+                            whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
+                       else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
+               }
+
                /* Buffer overflow protection */
                if (buf1[0] != NULLCHAR) {
                    if (strlen(buf1) >= sizeof(programStats.movelist)
@@ -4763,15 +6708,15 @@ HandleMachineMove(message, cps)
                } else {
                    programStats.line_is_book = 0;
                }
-                 
-                SendProgramStatsToFrontend( cps );
+
+                SendProgramStatsToFrontend( cps, &programStats );
 
                 /*
                     [AS] Protect the thinkOutput buffer from overflow... this
                     is only useful if buf1 hasn't overflowed first!
                 */
                sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
-                       plylev, 
+                       plylev,
                        (gameMode == TwoMachinesPlay ?
                         ToUpper(cps->twoMachinesColor[0]) : ' '),
                        ((double) curscore) / 100.0,
@@ -4791,7 +6736,8 @@ HandleMachineMove(message, cps)
                     strcat( thinkOutput, buf1 );
                 }
 
-               if (currentMove == forwardMostMove || gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
+                if (currentMove == forwardMostMove || gameMode == AnalyzeMode
+                        || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
                    DisplayMove(currentMove - 1);
                    DisplayAnalysis();
                }
@@ -4815,15 +6761,16 @@ HandleMachineMove(message, cps)
                   mean "line isn't going to change" (Crafty
                   isn't searching, so stats won't change) */
                programStats.line_is_book = 1;
-                 
-                SendProgramStatsToFrontend( cps );
 
-               if (currentMove == forwardMostMove || gameMode==AnalyzeMode || gameMode == AnalyzeFile) {
+                SendProgramStatsToFrontend( cps, &programStats );
+
+               if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
+                           gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
                    DisplayMove(currentMove - 1);
                    DisplayAnalysis();
                }
                return;
-           } else if (sscanf(message,"stat01: %d %lu %d %d %d %s",
+           } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
                              &time, &nodes, &plylev, &mvleft,
                              &mvtot, mvname) >= 5) {
                /* The stat01: line is from Crafty (9.29+) in response
@@ -4841,8 +6788,9 @@ HandleMachineMove(message, cps)
                programStats.nr_moves = mvtot;
                strcpy(programStats.move_name, mvname);
                programStats.ok_to_send = 1;
+                programStats.movelist[0] = '\0';
 
-                SendProgramStatsToFrontend( cps );
+                SendProgramStatsToFrontend( cps, &programStats );
 
                DisplayAnalysis();
                return;
@@ -4868,16 +6816,17 @@ HandleMachineMove(message, cps)
 
                 /* [AS] Avoid buffer overflow */
                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
-               strcat(thinkOutput, " ");
-               strcat(thinkOutput, p);
+                   strcat(thinkOutput, " ");
+                   strcat(thinkOutput, p);
                 }
 
                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
-               strcat(programStats.movelist, " ");
-               strcat(programStats.movelist, p);
+                   strcat(programStats.movelist, " ");
+                   strcat(programStats.movelist, p);
                 }
 
-               if (currentMove == forwardMostMove || gameMode==AnalyzeMode || gameMode == AnalyzeFile) {
+               if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
+                           gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
                    DisplayMove(currentMove - 1);
                    DisplayAnalysis();
                }
@@ -4887,9 +6836,11 @@ HandleMachineMove(message, cps)
         else {
            buf1[0] = NULLCHAR;
 
-           if (sscanf(message, "%d%c %d %d %lu %[^\n]\n",
+           if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
                       &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
             {
+                ChessProgramStats cpstats;
+
                if (plyext != ' ' && plyext != '\t') {
                    time *= 100;
                }
@@ -4899,23 +6850,23 @@ HandleMachineMove(message, cps)
                     curscore = -curscore;
                 }
 
-               programStats.depth = plylev;
-               programStats.nodes = nodes;
-               programStats.time = time;
-               programStats.score = curscore;
-               programStats.got_only_move = 0;
-                programStats.movelist[0] = '\0';
+               cpstats.depth = plylev;
+               cpstats.nodes = nodes;
+               cpstats.time = time;
+               cpstats.score = curscore;
+               cpstats.got_only_move = 0;
+                cpstats.movelist[0] = '\0';
 
                if (buf1[0] != NULLCHAR) {
-                    safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
+                    safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
                }
 
-               programStats.ok_to_send = 0;
-               programStats.line_is_book = 0;
-               programStats.nr_moves = 0;
-               programStats.moves_left = 0;
+               cpstats.ok_to_send = 0;
+               cpstats.line_is_book = 0;
+               cpstats.nr_moves = 0;
+               cpstats.moves_left = 0;
 
-                SendProgramStatsToFrontend( cps );
+                SendProgramStatsToFrontend( cps, &cpstats );
             }
         }
     }
@@ -4924,7 +6875,7 @@ HandleMachineMove(message, cps)
 
 /* Parse a game score from the character string "game", and
    record it as the history of the current game.  The game
-   score is NOT assumed to start from the standard position. 
+   score is NOT assumed to start from the standard position.
    The display is not updated in any way.
    */
 void
@@ -4964,6 +6915,16 @@ ParseGameHistory(game)
        yyboardindex = boardIndex;
        moveType = (ChessMove) yylex();
        switch (moveType) {
+         case IllegalMove:             /* maybe suicide chess, etc. */
+  if (appData.debugMode) {
+    fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
+    fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
+    setbuf(debugFP, NULL);
+  }
+          case WhitePromotionChancellor:
+          case BlackPromotionChancellor:
+          case WhitePromotionArchbishop:
+          case BlackPromotionArchbishop:
          case WhitePromotionQueen:
          case BlackPromotionQueen:
          case WhitePromotionRook:
@@ -4991,11 +6952,10 @@ ParseGameHistory(game)
           case BlackHSideCastleFR:
           case BlackASideCastleFR:
           /* POP Fabien */
-         case IllegalMove:             /* maybe suicide chess, etc. */
-           fromX = currentMoveString[0] - 'a';
-           fromY = currentMoveString[1] - '1';
-           toX = currentMoveString[2] - 'a';
-           toY = currentMoveString[3] - '1';
+            fromX = currentMoveString[0] - AAA;
+            fromY = currentMoveString[1] - ONE;
+            toX = currentMoveString[2] - AAA;
+            toY = currentMoveString[3] - ONE;
            promoChar = currentMoveString[4];
            break;
          case WhiteDrop:
@@ -5004,24 +6964,34 @@ ParseGameHistory(game)
              (int) CharToPiece(ToUpper(currentMoveString[0])) :
            (int) CharToPiece(ToLower(currentMoveString[0]));
            fromY = DROP_RANK;
-           toX = currentMoveString[2] - 'a';
-           toY = currentMoveString[3] - '1';
+            toX = currentMoveString[2] - AAA;
+            toY = currentMoveString[3] - ONE;
            promoChar = NULLCHAR;
            break;
          case AmbiguousMove:
            /* bug? */
-           sprintf(buf, "Ambiguous move in ICS output: \"%s\"", yy_text);
+           sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
+  if (appData.debugMode) {
+    fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
+    fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
+    setbuf(debugFP, NULL);
+  }
            DisplayError(buf, 0);
            return;
          case ImpossibleMove:
            /* bug? */
-           sprintf(buf, "Illegal move in ICS output: \"%s\"", yy_text);
+           sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
+  if (appData.debugMode) {
+    fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
+    fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
+    setbuf(debugFP, NULL);
+  }
            DisplayError(buf, 0);
            return;
          case (ChessMove) 0:   /* end of file */
            if (boardIndex < backwardMostMove) {
                /* Oops, gap.  How did that happen? */
-               DisplayError("Gap in move list", 0);
+               DisplayError(_("Gap in move list"), 0);
                return;
            }
            backwardMostMove =  blackPlaysFirst ? 1 : 0;
@@ -5076,21 +7046,25 @@ ParseGameHistory(game)
                                 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
                                 parseList[boardIndex]);
        CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
+        {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
        /* currentMoveString is set as a side-effect of yylex */
        strcpy(moveList[boardIndex], currentMoveString);
        strcat(moveList[boardIndex], "\n");
        boardIndex++;
-       ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
-       switch (MateTest(boards[boardIndex],
-                        PosFlags(boardIndex), EP_UNKNOWN)) {
+       ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex],
+                                       castlingRights[boardIndex], &epStatus[boardIndex]);
+        switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
+                                 EP_UNKNOWN, castlingRights[boardIndex]) ) {
          case MT_NONE:
          case MT_STALEMATE:
          default:
            break;
          case MT_CHECK:
-           strcat(parseList[boardIndex - 1], "+");
+            if(gameInfo.variant != VariantShogi)
+                strcat(parseList[boardIndex - 1], "+");
            break;
          case MT_CHECKMATE:
+         case MT_STAINMATE:
            strcat(parseList[boardIndex - 1], "#");
            break;
        }
@@ -5100,99 +7074,160 @@ ParseGameHistory(game)
 
 /* Apply a move to the given board  */
 void
-ApplyMove(fromX, fromY, toX, toY, promoChar, board)
+ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
      int fromX, fromY, toX, toY;
      int promoChar;
      Board board;
-{
-    ChessSquare captured = board[toY][toX];
-    if (fromY == DROP_RANK) {
-       /* must be first */
-       board[toY][toX] = (ChessSquare) fromX;
-    } else if (fromX == toX && fromY == toY) {
-       return;
+     char *castling;
+     char *ep;
+{
+  ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
+
+    /* [HGM] compute & store e.p. status and castling rights for new position */
+    /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
+    { int i;
+
+      if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
+      oldEP = *ep;
+      *ep = EP_NONE;
+
+      if( board[toY][toX] != EmptySquare )
+           *ep = EP_CAPTURE;
+
+      if( board[fromY][fromX] == WhitePawn ) {
+           if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
+              *ep = EP_PAWN_MOVE;
+           if( toY-fromY==2) {
+               if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
+                       gameInfo.variant != VariantBerolina || toX < fromX)
+                     *ep = toX | berolina;
+               if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
+                       gameInfo.variant != VariantBerolina || toX > fromX)
+                     *ep = toX;
+          }
+      } else
+      if( board[fromY][fromX] == BlackPawn ) {
+           if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
+              *ep = EP_PAWN_MOVE;
+           if( toY-fromY== -2) {
+               if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
+                       gameInfo.variant != VariantBerolina || toX < fromX)
+                     *ep = toX | berolina;
+               if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
+                       gameInfo.variant != VariantBerolina || toX > fromX)
+                     *ep = toX;
+          }
+       }
+
+       for(i=0; i<nrCastlingRights; i++) {
+           if(castling[i] == fromX && castlingRank[i] == fromY ||
+              castling[i] == toX   && castlingRank[i] == toY
+             ) castling[i] = -1; // revoke for moved or captured piece
+       }
+
     }
 
+  /* [HGM] In Shatranj and Courier all promotions are to Ferz */
+  if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
+       && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
+
+  if (fromX == toX && fromY == toY) return;
+
+  if (fromY == DROP_RANK) {
+       /* must be first */
+        piece = board[toY][toX] = (ChessSquare) fromX;
+  } else {
+     piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
+     king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
+     if(gameInfo.variant == VariantKnightmate)
+         king += (int) WhiteUnicorn - (int) WhiteKing;
+
     /* Code added by Tord: */
     /* FRC castling assumed when king captures friendly rook. */
-    else if (board[fromY][fromX] == WhiteKing &&
+    if (board[fromY][fromX] == WhiteKing &&
             board[toY][toX] == WhiteRook) {
       board[fromY][fromX] = EmptySquare;
       board[toY][toX] = EmptySquare;
       if(toX > fromX) {
-       board[0][6] = WhiteKing; board[0][5] = WhiteRook;
+        board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
       } else {
-       board[0][2] = WhiteKing; board[0][3] = WhiteRook;
+        board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
       }
     } else if (board[fromY][fromX] == BlackKing &&
               board[toY][toX] == BlackRook) {
       board[fromY][fromX] = EmptySquare;
       board[toY][toX] = EmptySquare;
       if(toX > fromX) {
-       board[7][6] = BlackKing; board[7][5] = BlackRook;
+        board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
       } else {
-       board[7][2] = BlackKing; board[7][3] = BlackRook;
+        board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
       }
     /* End of code added by Tord */
 
-    } else if (fromY == 0 && fromX == 4
-       && board[fromY][fromX] == WhiteKing
-       && toY == 0 && toX == 6) {
+    } else if (board[fromY][fromX] == king
+        && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
+        && toY == fromY && toX > fromX+1) {
        board[fromY][fromX] = EmptySquare;
-       board[toY][toX] = WhiteKing;
-       board[fromY][7] = EmptySquare;
-       board[toY][5] = WhiteRook;
-    } else if (fromY == 0 && fromX == 4
-              && board[fromY][fromX] == WhiteKing
-              && toY == 0 && toX == 2) {
-       board[fromY][fromX] = EmptySquare;
-       board[toY][toX] = WhiteKing;
-       board[fromY][0] = EmptySquare;
-       board[toY][3] = WhiteRook;
-    } else if (fromY == 0 && fromX == 3
-              && board[fromY][fromX] == WhiteKing
-              && toY == 0 && toX == 5) {
-       board[fromY][fromX] = EmptySquare;
-       board[toY][toX] = WhiteKing;
-       board[fromY][7] = EmptySquare;
-       board[toY][4] = WhiteRook;
-    } else if (fromY == 0 && fromX == 3
-              && board[fromY][fromX] == WhiteKing
-              && toY == 0 && toX == 1) {
+        board[toY][toX] = king;
+        board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
+        board[fromY][BOARD_RGHT-1] = EmptySquare;
+    } else if (board[fromY][fromX] == king
+        && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
+               && toY == fromY && toX < fromX-1) {
        board[fromY][fromX] = EmptySquare;
-       board[toY][toX] = WhiteKing;
-       board[fromY][0] = EmptySquare;
-       board[toY][2] = WhiteRook;
+        board[toY][toX] = king;
+        board[toY][toX+1] = board[fromY][BOARD_LEFT];
+        board[fromY][BOARD_LEFT] = EmptySquare;
     } else if (board[fromY][fromX] == WhitePawn
-              && toY == 7) {
+               && toY == BOARD_HEIGHT-1
+               && gameInfo.variant != VariantXiangqi
+               ) {
        /* white pawn promotion */
-       board[7][toX] = CharToPiece(ToUpper(promoChar));
-       if (board[7][toX] == EmptySquare) {
-           board[7][toX] = WhiteQueen;
+        board[toY][toX] = CharToPiece(ToUpper(promoChar));
+        if (board[toY][toX] == EmptySquare) {
+            board[toY][toX] = WhiteQueen;
        }
+        if(gameInfo.variant==VariantBughouse ||
+           gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
+            board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
        board[fromY][fromX] = EmptySquare;
-    } else if ((fromY == 4)
+    } else if ((fromY == BOARD_HEIGHT-4)
               && (toX != fromX)
+               && gameInfo.variant != VariantXiangqi
+               && gameInfo.variant != VariantBerolina
               && (board[fromY][fromX] == WhitePawn)
               && (board[toY][toX] == EmptySquare)) {
        board[fromY][fromX] = EmptySquare;
        board[toY][toX] = WhitePawn;
        captured = board[toY - 1][toX];
        board[toY - 1][toX] = EmptySquare;
-    } else if (fromY == 7 && fromX == 4
-              && board[fromY][fromX] == BlackKing
-              && toY == 7 && toX == 6) {
+    } else if ((fromY == BOARD_HEIGHT-4)
+              && (toX == fromX)
+               && gameInfo.variant == VariantBerolina
+              && (board[fromY][fromX] == WhitePawn)
+              && (board[toY][toX] == EmptySquare)) {
        board[fromY][fromX] = EmptySquare;
-       board[toY][toX] = BlackKing;
-       board[fromY][7] = EmptySquare;
-       board[toY][5] = BlackRook;
-    } else if (fromY == 7 && fromX == 4
-              && board[fromY][fromX] == BlackKing
-              && toY == 7 && toX == 2) {
+       board[toY][toX] = WhitePawn;
+       if(oldEP & EP_BEROLIN_A) {
+               captured = board[fromY][fromX-1];
+               board[fromY][fromX-1] = EmptySquare;
+       }else{  captured = board[fromY][fromX+1];
+               board[fromY][fromX+1] = EmptySquare;
+       }
+    } else if (board[fromY][fromX] == king
+        && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
+               && toY == fromY && toX > fromX+1) {
        board[fromY][fromX] = EmptySquare;
-       board[toY][toX] = BlackKing;
-       board[fromY][0] = EmptySquare;
-       board[toY][3] = BlackRook;
+        board[toY][toX] = king;
+        board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
+        board[fromY][BOARD_RGHT-1] = EmptySquare;
+    } else if (board[fromY][fromX] == king
+        && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
+               && toY == fromY && toX < fromX-1) {
+       board[fromY][fromX] = EmptySquare;
+        board[toY][toX] = king;
+        board[toY][toX+1] = board[fromY][BOARD_LEFT];
+        board[fromY][BOARD_LEFT] = EmptySquare;
     } else if (fromY == 7 && fromX == 3
               && board[fromY][fromX] == BlackKing
               && toY == 7 && toX == 5) {
@@ -5208,47 +7243,111 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board)
        board[fromY][0] = EmptySquare;
        board[toY][2] = BlackRook;
     } else if (board[fromY][fromX] == BlackPawn
-              && toY == 0) {
+              && toY == 0
+               && gameInfo.variant != VariantXiangqi
+               ) {
        /* black pawn promotion */
        board[0][toX] = CharToPiece(ToLower(promoChar));
        if (board[0][toX] == EmptySquare) {
            board[0][toX] = BlackQueen;
        }
+        if(gameInfo.variant==VariantBughouse ||
+           gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
+            board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
        board[fromY][fromX] = EmptySquare;
     } else if ((fromY == 3)
               && (toX != fromX)
+               && gameInfo.variant != VariantXiangqi
+               && gameInfo.variant != VariantBerolina
               && (board[fromY][fromX] == BlackPawn)
               && (board[toY][toX] == EmptySquare)) {
        board[fromY][fromX] = EmptySquare;
        board[toY][toX] = BlackPawn;
        captured = board[toY + 1][toX];
        board[toY + 1][toX] = EmptySquare;
+    } else if ((fromY == 3)
+              && (toX == fromX)
+               && gameInfo.variant == VariantBerolina
+              && (board[fromY][fromX] == BlackPawn)
+              && (board[toY][toX] == EmptySquare)) {
+       board[fromY][fromX] = EmptySquare;
+       board[toY][toX] = BlackPawn;
+       if(oldEP & EP_BEROLIN_A) {
+               captured = board[fromY][fromX-1];
+               board[fromY][fromX-1] = EmptySquare;
+       }else{  captured = board[fromY][fromX+1];
+               board[fromY][fromX+1] = EmptySquare;
+       }
     } else {
        board[toY][toX] = board[fromY][fromX];
        board[fromY][fromX] = EmptySquare;
     }
-    if (gameInfo.variant == VariantCrazyhouse) {
-#if 0
-      /* !!A lot more code needs to be written to support holdings */
+
+    /* [HGM] now we promote for Shogi, if needed */
+    if(gameInfo.variant == VariantShogi && promoChar == 'q')
+        board[toY][toX] = (ChessSquare) (PROMOTED piece);
+  }
+
+    if (gameInfo.holdingsWidth != 0) {
+
+      /* !!A lot more code needs to be written to support holdings  */
+      /* [HGM] OK, so I have written it. Holdings are stored in the */
+      /* penultimate board files, so they are automaticlly stored   */
+      /* in the game history.                                       */
       if (fromY == DROP_RANK) {
-       /* Delete from holdings */
-       if (holdings[(int) fromX] > 0) holdings[(int) fromX]--;
+        /* Delete from holdings, by decreasing count */
+        /* and erasing image if necessary            */
+        p = (int) fromX;
+        if(p < (int) BlackPawn) { /* white drop */
+             p -= (int)WhitePawn;
+             if(p >= gameInfo.holdingsSize) p = 0;
+             if(--board[p][BOARD_WIDTH-2] == 0)
+                  board[p][BOARD_WIDTH-1] = EmptySquare;
+        } else {                  /* black drop */
+             p -= (int)BlackPawn;
+             if(p >= gameInfo.holdingsSize) p = 0;
+             if(--board[BOARD_HEIGHT-1-p][1] == 0)
+                  board[BOARD_HEIGHT-1-p][0] = EmptySquare;
+        }
       }
-      if (captured != EmptySquare) {
-       /* Add to holdings */
-       if (captured < BlackPawn) {
-         holdings[(int)captured - (int)BlackPawn + (int)WhitePawn]++;
+      if (captured != EmptySquare && gameInfo.holdingsSize > 0
+          && gameInfo.variant != VariantBughouse        ) {
+        /* [HGM] holdings: Add to holdings, if holdings exist */
+       if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
+               // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
+               captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
+       }
+        p = (int) captured;
+        if (p >= (int) BlackPawn) {
+          p -= (int)BlackPawn;
+          if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
+                  /* in Shogi restore piece to its original  first */
+                  captured = (ChessSquare) (DEMOTED captured);
+                  p = DEMOTED p;
+          }
+          p = PieceToNumber((ChessSquare)p);
+          if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
+          board[p][BOARD_WIDTH-2]++;
+          board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
        } else {
-         holdings[(int)captured - (int)WhitePawn + (int)BlackPawn]++;
+          p -= (int)WhitePawn;
+          if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
+                  captured = (ChessSquare) (DEMOTED captured);
+                  p = DEMOTED p;
+          }
+          p = PieceToNumber((ChessSquare)p);
+          if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
+          board[BOARD_HEIGHT-1-p][1]++;
+          board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
        }
       }
-#endif
+
     } else if (gameInfo.variant == VariantAtomic) {
       if (captured != EmptySquare) {
        int y, x;
        for (y = toY-1; y <= toY+1; y++) {
          for (x = toX-1; x <= toX+1; x++) {
-           if (y >= 0 && y <= 7 && x >= 0 && x <= 7 &&
+            if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
                board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
              board[y][x] = EmptySquare;
            }
@@ -5257,6 +7356,24 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board)
        board[toY][toX] = EmptySquare;
       }
     }
+    if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
+        /* [HGM] Shogi promotions */
+        board[toY][toX] = (ChessSquare) (PROMOTED piece);
+    }
+
+    if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
+               && promoChar != NULLCHAR && gameInfo.holdingsSize) {
+       // [HGM] superchess: take promotion piece out of holdings
+       int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
+       if((int)piece < (int)BlackPawn) { // determine stm from piece color
+           if(!--board[k][BOARD_WIDTH-2])
+               board[k][BOARD_WIDTH-1] = EmptySquare;
+       } else {
+           if(!--board[BOARD_HEIGHT-1-k][1])
+               board[BOARD_HEIGHT-1-k][0] = EmptySquare;
+       }
+    }
+
 }
 
 /* Updates forwardMostMove */
@@ -5265,21 +7382,67 @@ MakeMove(fromX, fromY, toX, toY, promoChar)
      int fromX, fromY, toX, toY;
      int promoChar;
 {
-    forwardMostMove++;
-    if (forwardMostMove >= MAX_MOVES) {
-      DisplayFatalError("Game too long; increase MAX_MOVES and recompile",
+//    forwardMostMove++; // [HGM] bare: moved downstream
+
+    if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
+        int timeLeft; static int lastLoadFlag=0; int king, piece;
+        piece = boards[forwardMostMove][fromY][fromX];
+        king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
+        if(gameInfo.variant == VariantKnightmate)
+            king += (int) WhiteUnicorn - (int) WhiteKing;
+        if(forwardMostMove == 0) {
+            if(blackPlaysFirst)
+                fprintf(serverMoves, "%s;", second.tidy);
+            fprintf(serverMoves, "%s;", first.tidy);
+            if(!blackPlaysFirst)
+                fprintf(serverMoves, "%s;", second.tidy);
+        } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
+        lastLoadFlag = loadFlag;
+        // print base move
+        fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
+        // print castling suffix
+        if( toY == fromY && piece == king ) {
+            if(toX-fromX > 1)
+                fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
+            if(fromX-toX >1)
+                fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
+        }
+        // e.p. suffix
+        if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
+             boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
+             boards[forwardMostMove][toY][toX] == EmptySquare
+             && fromX != toX )
+                fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
+        // promotion suffix
+        if(promoChar != NULLCHAR)
+                fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
+        if(!loadFlag) {
+            fprintf(serverMoves, "/%d/%d",
+               pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
+            if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
+            else                      timeLeft = blackTimeRemaining/1000;
+            fprintf(serverMoves, "/%d", timeLeft);
+        }
+        fflush(serverMoves);
+    }
+
+    if (forwardMostMove+1 >= MAX_MOVES) {
+      DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
                        0, 1);
       return;
     }
-    SwitchClocks();
+    if (commentList[forwardMostMove+1] != NULL) {
+       free(commentList[forwardMostMove+1]);
+       commentList[forwardMostMove+1] = NULL;
+    }
+    CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
+    {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
+    ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1],
+                               castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
+    forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
+    SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
-    if (commentList[forwardMostMove] != NULL) {
-       free(commentList[forwardMostMove]);
-       commentList[forwardMostMove] = NULL;
-    }
-    CopyBoard(boards[forwardMostMove], boards[forwardMostMove - 1]);
-    ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove]);
     gameInfo.result = GameUnfinished;
     if (gameInfo.resultDetails != NULL) {
        free(gameInfo.resultDetails);
@@ -5291,19 +7454,26 @@ MakeMove(fromX, fromY, toX, toY, promoChar)
                             PosFlags(forwardMostMove - 1), EP_UNKNOWN,
                             fromY, fromX, toY, toX, promoChar,
                             parseList[forwardMostMove - 1]);
-    switch (MateTest(boards[forwardMostMove],
-                    PosFlags(forwardMostMove), EP_UNKNOWN)){
+    switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
+                       epStatus[forwardMostMove], /* [HGM] use true e.p. */
+                            castlingRights[forwardMostMove]) ) {
       case MT_NONE:
       case MT_STALEMATE:
       default:
        break;
       case MT_CHECK:
-       strcat(parseList[forwardMostMove - 1], "+");
+        if(gameInfo.variant != VariantShogi)
+            strcat(parseList[forwardMostMove - 1], "+");
        break;
       case MT_CHECKMATE:
+      case MT_STAINMATE:
        strcat(parseList[forwardMostMove - 1], "#");
        break;
     }
+    if (appData.debugMode) {
+        fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
+    }
+
 }
 
 /* Updates currentMove if not pausing */
@@ -5312,16 +7482,24 @@ ShowMove(fromX, fromY, toX, toY)
 {
     int instant = (gameMode == PlayFromGameFile) ?
        (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
-    if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
-       if (!instant) {
-           if (forwardMostMove == currentMove + 1) {
-               AnimateMove(boards[forwardMostMove - 1],
-                           fromX, fromY, toX, toY);
-           }
-           if (appData.highlightLastMove) {
+
+    if(appData.noGUI) return;
+
+    if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile)
+      {
+       if (!instant)
+         {
+           if (forwardMostMove == currentMove + 1)
+             {
+//TODO
+//             AnimateMove(boards[forwardMostMove - 1],
+//                         fromX, fromY, toX, toY);
+             }
+           if (appData.highlightLastMove)
+             {
                SetHighlights(fromX, fromY, toX, toY);
-           }
-       }
+             }
+         }
        currentMove = forwardMostMove;
     }
 
@@ -5331,31 +7509,129 @@ ShowMove(fromX, fromY, toX, toY)
     DrawPosition(FALSE, boards[currentMove]);
     DisplayBothClocks();
     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
+
+    return;
 }
 
+void SendEgtPath(ChessProgramState *cps)
+{       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
+       char buf[MSG_SIZ], name[MSG_SIZ], *p;
+
+       if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
+
+       while(*p) {
+           char c, *q = name+1, *r, *s;
+
+           name[0] = ','; // extract next format name from feature and copy with prefixed ','
+           while(*p && *p != ',') *q++ = *p++;
+           *q++ = ':'; *q = 0;
+           if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
+               strcmp(name, ",nalimov:") == 0 ) {
+               // take nalimov path from the menu-changeable option first, if it is defined
+               sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
+               SendToProgram(buf,cps);     // send egtbpath command for nalimov
+           } else
+           if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
+               (s = StrStr(appData.egtFormats, name)) != NULL) {
+               // format name occurs amongst user-supplied formats, at beginning or immediately after comma
+               s = r = StrStr(s, ":") + 1; // beginning of path info
+               while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
+               c = *r; *r = 0;             // temporarily null-terminate path info
+                   *--q = 0;               // strip of trailig ':' from name
+                   sprintf(buf, "egtpath %s %s\n", name+1, s);
+               *r = c;
+               SendToProgram(buf,cps);     // send egtbpath command for this format
+           }
+           if(*p == ',') p++; // read away comma to position for next format name
+       }
+}
 
 void
-InitChessProgram(cps)
+InitChessProgram(cps, setup)
      ChessProgramState *cps;
+     int setup; /* [HGM] needed to setup FRC opening position */
 {
-    char buf[MSG_SIZ];
+    char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
     if (appData.noChessProgram) return;
     hintRequested = FALSE;
     bookRequested = FALSE;
+
+    /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
+    /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
+    if(cps->memSize) { /* [HGM] memory */
+       sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
+       SendToProgram(buf, cps);
+    }
+    SendEgtPath(cps); /* [HGM] EGT */
+    if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
+       sprintf(buf, "cores %d\n", appData.smpCores);
+       SendToProgram(buf, cps);
+    }
+
     SendToProgram(cps->initString, cps);
     if (gameInfo.variant != VariantNormal &&
-       gameInfo.variant != VariantLoadable) {
+       gameInfo.variant != VariantLoadable
+        /* [HGM] also send variant if board size non-standard */
+        || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
+                                            ) {
       char *v = VariantName(gameInfo.variant);
-      if (StrStr(cps->variants, v) == NULL) {
-       sprintf(buf, "Variant %s not supported by %s", v, cps->tidy);
+      if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
+        /* [HGM] in protocol 1 we have to assume all variants valid */
+       sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
        DisplayFatalError(buf, 0, 1);
        return;
       }
-      sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
+
+      /* [HGM] make prefix for non-standard board size. Awkward testing... */
+      overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
+      if( gameInfo.variant == VariantXiangqi )
+           overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
+      if( gameInfo.variant == VariantShogi )
+           overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
+      if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
+           overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
+      if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
+                               gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
+           overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
+      if( gameInfo.variant == VariantCourier )
+           overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
+      if( gameInfo.variant == VariantSuper )
+           overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
+      if( gameInfo.variant == VariantGreat )
+           overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
+
+      if(overruled) {
+           sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
+                               gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
+           /* [HGM] varsize: try first if this defiant size variant is specifically known */
+           if(StrStr(cps->variants, b) == NULL) {
+               // specific sized variant not known, check if general sizing allowed
+               if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
+                   if(StrStr(cps->variants, "boardsize") == NULL) {
+                       sprintf(buf, "Board size %dx%d+%d not supported by %s",
+                            gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
+                       DisplayFatalError(buf, 0, 1);
+                       return;
+                   }
+                   /* [HGM] here we really should compare with the maximum supported board size */
+               }
+           }
+      } else sprintf(b, "%s", VariantName(gameInfo.variant));
+      sprintf(buf, "variant %s\n", b);
       SendToProgram(buf, cps);
     }
+    currentlyInitializedVariant = gameInfo.variant;
+
+    /* [HGM] send opening position in FRC to first engine */
+    if(setup) {
+          SendToProgram("force\n", cps);
+          SendBoard(cps, 0);
+          /* engine is now in force mode! Set flag to wake it up after first move. */
+          setboardSpoiledMachineBlack = 1;
+    }
+
     if (cps->sendICS) {
-      sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-");
+      snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
       SendToProgram(buf, cps);
     }
     cps->maybeThinking = FALSE;
@@ -5365,7 +7641,10 @@ InitChessProgram(cps)
                        timeIncrement, appData.searchDepth,
                        searchTime);
     }
-    if (appData.showThinking) {
+    if (appData.showThinking
+       // [HGM] thinking: four options require thinking output to be sent
+       || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
+                               ) {
        SendToProgram("post\n", cps);
     }
     SendToProgram("hard\n", cps);
@@ -5381,7 +7660,7 @@ InitChessProgram(cps)
       SendToProgram(buf, cps);
     }
     cps->initDone = TRUE;
-}   
+}
 
 
 void
@@ -5400,26 +7679,28 @@ StartChessProgram(cps)
        err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
     } else {
        if (*appData.remoteUser == NULLCHAR) {
-           sprintf(buf, "%s %s %s", appData.remoteShell, cps->host,
+         snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
                    cps->program);
        } else {
-           sprintf(buf, "%s %s -l %s %s", appData.remoteShell,
+         snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
                    cps->host, appData.remoteUser, cps->program);
        }
        err = StartChildProcess(buf, "", &cps->pr);
     }
-    
+
     if (err != 0) {
-       sprintf(buf, "Startup failure on '%s'", cps->program);
+       sprintf(buf, _("Startup failure on '%s'"), cps->program);
        DisplayFatalError(buf, err, 1);
        cps->pr = NoProc;
        cps->isr = NULL;
        return;
     }
-    
+
     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
     if (cps->protocolVersion > 1) {
       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
+      cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
+      cps->comboCnt = 0;  //                and values of combo boxes
       SendToProgram(buf, cps);
     } else {
       SendToProgram("xboard\n", cps);
@@ -5431,13 +7712,13 @@ void
 TwoMachinesEventIfReady P((void))
 {
   if (first.lastPing != first.lastPong) {
-    DisplayMessage("", "Waiting for first chess program");
-    ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000);
+    DisplayMessage("", _("Waiting for first chess program"));
+    ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
     return;
   }
   if (second.lastPing != second.lastPong) {
-    DisplayMessage("", "Waiting for second chess program");
-    ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000);
+    DisplayMessage("", _("Waiting for second chess program"));
+    ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
     return;
   }
   ThawUI();
@@ -5447,14 +7728,25 @@ TwoMachinesEventIfReady P((void))
 void
 NextMatchGame P((void))
 {
+    int index; /* [HGM] autoinc: step lod index during match */
     Reset(FALSE, TRUE);
     if (*appData.loadGameFile != NULLCHAR) {
+       index = appData.loadGameIndex;
+       if(index < 0) { // [HGM] autoinc
+           lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
+           if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
+       }
        LoadGameFromFile(appData.loadGameFile,
-                        appData.loadGameIndex,
+                        index,
                         appData.loadGameFile, FALSE);
     } else if (*appData.loadPositionFile != NULLCHAR) {
+       index = appData.loadPositionIndex;
+       if(index < 0) { // [HGM] autoinc
+           lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
+           if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
+       }
        LoadPositionFromFile(appData.loadPositionFile,
-                            appData.loadPositionIndex,
+                            index,
                             appData.loadPositionFile);
     }
     TwoMachinesEventIfReady();
@@ -5477,6 +7769,28 @@ void UserAdjudicationEvent( int result )
 }
 
 
+// [HGM] save: calculate checksum of game to make games easily identifiable
+int StringCheckSum(char *s)
+{
+       int i = 0;
+       if(s==NULL) return 0;
+       while(*s) i = i*259 + *s++;
+       return i;
+}
+
+int GameCheckSum()
+{
+       int i, sum=0;
+       for(i=backwardMostMove; i<forwardMostMove; i++) {
+               sum += pvInfoList[i].depth;
+               sum += StringCheckSum(parseList[i]);
+               sum += StringCheckSum(commentList[i]);
+               sum *= 261;
+       }
+       if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
+       return sum + StringCheckSum(commentList[i]);
+} // end of save patch
+
 void
 GameEnds(result, resultDetails, whosays)
      ChessMove result;
@@ -5485,16 +7799,20 @@ GameEnds(result, resultDetails, whosays)
 {
     GameMode nextGameMode;
     int isIcsGame;
+    char buf[MSG_SIZ];
+
+    if(endingGame) return; /* [HGM] crash: forbid recursion */
+    endingGame = 1;
 
     if (appData.debugMode) {
       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
              result, resultDetails ? resultDetails : "(null)", whosays);
     }
 
-    if (appData.icsActive && whosays == GE_ENGINE) {
+    if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
        /* If we are playing on ICS, the server decides when the
-          game is over, but the engine can offer to draw, claim 
-          a draw, or resign. 
+          game is over, but the engine can offer to draw, claim
+          a draw, or resign.
         */
 #if ZIPPY
        if (appData.zippyPlay && first.initDone) {
@@ -5508,7 +7826,8 @@ GameEnds(result, resultDetails, whosays)
            }
         }
 #endif
-       return;
+       endingGame = 0; /* [HGM] crash */
+        return;
     }
 
     /* If we're loading the game from a file, stop */
@@ -5518,26 +7837,130 @@ GameEnds(result, resultDetails, whosays)
     }
 
     /* Cancel draw offers */
-   first.offeredDraw = second.offeredDraw = 0;
+    first.offeredDraw = second.offeredDraw = 0;
 
     /* If this is an ICS game, only ICS can really say it's done;
        if not, anyone can. */
-    isIcsGame = (gameMode == IcsPlayingWhite || 
-                gameMode == IcsPlayingBlack || 
-                gameMode == IcsObserving    || 
+    isIcsGame = (gameMode == IcsPlayingWhite ||
+                gameMode == IcsPlayingBlack ||
+                gameMode == IcsObserving    ||
                 gameMode == IcsExamining);
 
     if (!isIcsGame || whosays == GE_ICS) {
        /* OK -- not an ICS game, or ICS said it was done */
        StopClocks();
-       if (!isIcsGame && !appData.noChessProgram) 
+       if (!isIcsGame && !appData.noChessProgram)
          SetUserThinkingEnables();
-    
-       if (resultDetails != NULL) {
+
+        /* [HGM] if a machine claims the game end we verify this claim */
+        if(gameMode == TwoMachinesPlay && appData.testClaims) {
+           if(appData.testLegality && whosays >= GE_ENGINE1 ) {
+                char claimer;
+               ChessMove trueResult = (ChessMove) -1;
+
+                claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
+                                            first.twoMachinesColor[0] :
+                                            second.twoMachinesColor[0] ;
+
+               // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
+               if(epStatus[forwardMostMove] == EP_CHECKMATE) {
+                   /* [HGM] verify: engine mate claims accepted if they were flagged */
+                   trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
+               } else
+               if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
+                   /* [HGM] verify: engine mate claims accepted if they were flagged */
+                   trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
+               } else
+               if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
+                   trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
+               }
+
+               // now verify win claims, but not in drop games, as we don't understand those yet
+                if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
+                                                || gameInfo.variant == VariantGreat) &&
+                    (result == WhiteWins && claimer == 'w' ||
+                     result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
+                     if (appData.debugMode) {
+                       fprintf(debugFP, "result=%d sp=%d move=%d\n",
+                               result, epStatus[forwardMostMove], forwardMostMove);
+                     }
+                     if(result != trueResult) {
+                             sprintf(buf, "False win claim: '%s'", resultDetails);
+                             result = claimer == 'w' ? BlackWins : WhiteWins;
+                             resultDetails = buf;
+                     }
+                } else
+                if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
+                    && (forwardMostMove <= backwardMostMove ||
+                        epStatus[forwardMostMove-1] > EP_DRAWS ||
+                        (claimer=='b')==(forwardMostMove&1))
+                                                                                  ) {
+                      /* [HGM] verify: draws that were not flagged are false claims */
+                      sprintf(buf, "False draw claim: '%s'", resultDetails);
+                      result = claimer == 'w' ? BlackWins : WhiteWins;
+                      resultDetails = buf;
+                }
+                /* (Claiming a loss is accepted no questions asked!) */
+           }
+
+           /* [HGM] bare: don't allow bare King to win */
+           if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
+              && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
+              && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
+              && result != GameIsDrawn)
+           {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
+               for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
+                       int p = (int)boards[forwardMostMove][i][j] - color;
+                       if(p >= 0 && p <= (int)WhiteKing) k++;
+               }
+               if (appData.debugMode) {
+                    fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
+                       result, resultDetails ? resultDetails : "(null)", whosays, k, color);
+               }
+               if(k <= 1) {
+                       result = GameIsDrawn;
+                       sprintf(buf, "%s but bare king", resultDetails);
+                       resultDetails = buf;
+               }
+           }
+        }
+
+        if(serverMoves != NULL && !loadFlag) { char c = '=';
+            if(result==WhiteWins) c = '+';
+            if(result==BlackWins) c = '-';
+            if(resultDetails != NULL)
+                fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
+        }
+       if (resultDetails != NULL) {
            gameInfo.result = result;
            gameInfo.resultDetails = StrSave(resultDetails);
 
+           /* display last move only if game was not loaded from file */
+           if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
+               DisplayMove(currentMove - 1);
+
+           if (forwardMostMove != 0) {
+               if (gameMode != PlayFromGameFile && gameMode != EditGame
+                   && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
+                                                               ) {
+                   if (*appData.saveGameFile != NULLCHAR) {
+                       SaveGameToFile(appData.saveGameFile, TRUE);
+                   } else if (appData.autoSaveGames) {
+                       AutoSaveGame();
+                   }
+                   if (*appData.savePositionFile != NULLCHAR) {
+                       SavePositionToFile(appData.savePositionFile);
+                   }
+               }
+           }
+
            /* Tell program how game ended in case it is learning */
+            /* [HGM] Moved this to after saving the PGN, just in case */
+            /* engine died and we got here through time loss. In that */
+            /* case we will get a fatal error writing the pipe, which */
+            /* would otherwise lose us the PGN.                       */
+            /* [HGM] crash: not needed anymore, but doesn't hurt;     */
+            /* output during GameEnds should never be fatal anymore   */
            if (gameMode == MachinePlaysWhite ||
                gameMode == MachinePlaysBlack ||
                gameMode == TwoMachinesPlay ||
@@ -5555,23 +7978,6 @@ GameEnds(result, resultDetails, whosays)
                    SendToProgram(buf, &second);
                }
            }
-
-           /* display last move only if game was not loaded from file */
-           if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
-               DisplayMove(currentMove - 1);
-    
-           if (forwardMostMove != 0) {
-               if (gameMode != PlayFromGameFile && gameMode != EditGame) {
-                   if (*appData.saveGameFile != NULLCHAR) {
-                       SaveGameToFile(appData.saveGameFile, TRUE);
-                   } else if (appData.autoSaveGames) {
-                       AutoSaveGame();
-                   }
-                   if (*appData.savePositionFile != NULLCHAR) {
-                       SavePositionToFile(appData.savePositionFile);
-                   }
-               }
-           }
        }
 
        if (appData.icsActive) {
@@ -5613,8 +8019,8 @@ GameEnds(result, resultDetails, whosays)
                }
            }
        } else if (gameMode == EditGame ||
-                  gameMode == PlayFromGameFile || 
-                  gameMode == AnalyzeMode || 
+                  gameMode == PlayFromGameFile ||
+                  gameMode == AnalyzeMode ||
                   gameMode == AnalyzeFile) {
            nextGameMode = gameMode;
        } else {
@@ -5629,7 +8035,8 @@ GameEnds(result, resultDetails, whosays)
     if (appData.noChessProgram) {
        gameMode = nextGameMode;
        ModeHighlight();
-       return;
+       endingGame = 0; /* [HGM] crash */
+        return;
     }
 
     if (first.reuse) {
@@ -5653,7 +8060,7 @@ GameEnds(result, resultDetails, whosays)
        if (first.isr != NULL)
          RemoveInputSource(first.isr);
        first.isr = NULL;
-    
+
        if (first.pr != NoProc) {
            ExitAnalyzeMode();
             DoSleep( appData.delayBeforeQuit );
@@ -5679,7 +8086,7 @@ GameEnds(result, resultDetails, whosays)
        if (second.isr != NULL)
          RemoveInputSource(second.isr);
        second.isr = NULL;
-    
+
        if (second.pr != NoProc) {
             DoSleep( appData.delayBeforeQuit );
            SendToProgram("quit\n", &second);
@@ -5710,17 +8117,22 @@ GameEnds(result, resultDetails, whosays)
        }
        if (matchGame < appData.matchGames) {
            char *tmp;
-           tmp = first.twoMachinesColor;
-           first.twoMachinesColor = second.twoMachinesColor;
-           second.twoMachinesColor = tmp;
+           if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
+               tmp = first.twoMachinesColor;
+               first.twoMachinesColor = second.twoMachinesColor;
+               second.twoMachinesColor = tmp;
+           }
            gameMode = nextGameMode;
            matchGame++;
-           ScheduleDelayedEvent(NextMatchGame, 10000);
+            if(appData.matchPause>10000 || appData.matchPause<10)
+                appData.matchPause = 10000; /* [HGM] make pause adjustable */
+            ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
+           endingGame = 0; /* [HGM] crash */
            return;
        } else {
            char buf[MSG_SIZ];
            gameMode = nextGameMode;
-           sprintf(buf, "Match %s vs. %s: final score %d-%d-%d",
+           sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
                    first.tidy, second.tidy,
                    first.matchWins, second.matchWins,
                    appData.matchGames - (first.matchWins + second.matchWins));
@@ -5732,24 +8144,36 @@ GameEnds(result, resultDetails, whosays)
       ExitAnalyzeMode();
     gameMode = nextGameMode;
     ModeHighlight();
+    endingGame = 0;  /* [HGM] crash */
 }
 
 /* Assumes program was just initialized (initString sent).
    Leaves program in force mode. */
 void
-FeedMovesToProgram(cps, upto) 
+FeedMovesToProgram(cps, upto)
      ChessProgramState *cps;
      int upto;
 {
     int i;
-    
+
     if (appData.debugMode)
       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
              startedFromSetupPosition ? "position and " : "",
              backwardMostMove, upto, cps->which);
+    if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
+        // [HGM] variantswitch: make engine aware of new variant
+       if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
+               return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
+       sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
+       SendToProgram(buf, cps);
+        currentlyInitializedVariant = gameInfo.variant;
+    }
     SendToProgram("force\n", cps);
     if (startedFromSetupPosition) {
        SendBoard(cps, backwardMostMove);
+    if (appData.debugMode) {
+        fprintf(debugFP, "feedMoves\n");
+    }
     }
     for (i = backwardMostMove; i < upto; i++) {
        SendMoveToProgram(i, cps);
@@ -5764,9 +8188,9 @@ ResurrectChessProgram()
        If so, restart it and feed it all the moves made so far. */
 
     if (appData.noChessProgram || first.pr != NoProc) return;
-    
+
     StartChessProgram(&first);
-    InitChessProgram(&first);
+    InitChessProgram(&first, FALSE);
     FeedMovesToProgram(&first, currentMove);
 
     if (!first.sendTime) {
@@ -5777,8 +8201,8 @@ ResurrectChessProgram()
        timeRemaining[1][currentMove] = blackTimeRemaining;
     }
 
-    if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
-       first.analysisSupport) {
+    if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
+                appData.icsEngineAnalyze) && first.analysisSupport) {
       SendToProgram("analyze\n", &first);
       first.analyzing = TRUE;
     }
@@ -5797,7 +8221,6 @@ Reset(redraw, init)
        fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
                redraw, init, gameMode);
     }
-
     pausing = pauseExamInvalid = FALSE;
     startedFromSetupPosition = blackPlaysFirst = FALSE;
     firstMove = TRUE;
@@ -5806,6 +8229,8 @@ Reset(redraw, init)
     hintRequested = bookRequested = FALSE;
     first.maybeThinking = FALSE;
     second.maybeThinking = FALSE;
+    first.bookSuspend = FALSE; // [HGM] book
+    second.bookSuspend = FALSE;
     thinkOutput[0] = NULLCHAR;
     lastHint[0] = NULLCHAR;
     ClearGameInfo(&gameInfo);
@@ -5815,7 +8240,8 @@ Reset(redraw, init)
     ics_gamenum = -1;
     white_holding[0] = black_holding[0] = NULLCHAR;
     ClearProgramStats();
-    
+    opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
+
     ResetFrontEnd();
     ClearHighlights();
     flipView = appData.flipView;
@@ -5824,9 +8250,25 @@ Reset(redraw, init)
     alarmSounded = FALSE;
 
     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
+    if(appData.serverMovesName != NULL) {
+        /* [HGM] prepare to make moves file for broadcasting */
+        clock_t t = clock();
+        if(serverMoves != NULL) fclose(serverMoves);
+        serverMoves = fopen(appData.serverMovesName, "r");
+        if(serverMoves != NULL) {
+            fclose(serverMoves);
+            /* delay 15 sec before overwriting, so all clients can see end */
+            while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
+        }
+        serverMoves = fopen(appData.serverMovesName, "w");
+    }
+
     ExitAnalyzeMode();
     gameMode = BeginningOfGame;
     ModeHighlight();
+
+    if(appData.icsActive) gameInfo.variant = VariantNormal;
+    currentMove = forwardMostMove = backwardMostMove = 0;
     InitPosition(redraw);
     for (i = 0; i < MAX_MOVES; i++) {
        if (commentList[i] != NULL) {
@@ -5834,16 +8276,22 @@ Reset(redraw, init)
            commentList[i] = NULL;
        }
     }
+
     ResetClocks();
     timeRemaining[0][0] = whiteTimeRemaining;
     timeRemaining[1][0] = blackTimeRemaining;
     if (first.pr == NULL) {
        StartChessProgram(&first);
     }
-    if (init) InitChessProgram(&first);
+    if (init) {
+           InitChessProgram(&first, startedFromSetupPosition);
+    }
+
     DisplayTitle("");
     DisplayMessage("", "");
     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
+    lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
+    return;
 }
 
 void
@@ -5883,17 +8331,17 @@ AutoPlayOneMove()
 
       return FALSE;
     }
-    
-    toX = moveList[currentMove][2] - 'a';
-    toY = moveList[currentMove][3] - '1';
+
+    toX = moveList[currentMove][2] - AAA;
+    toY = moveList[currentMove][3] - ONE;
 
     if (moveList[currentMove][1] == '@') {
        if (appData.highlightLastMove) {
            SetHighlights(-1, -1, toX, toY);
        }
     } else {
-       fromX = moveList[currentMove][0] - 'a';
-       fromY = moveList[currentMove][1] - '1';
+        fromX = moveList[currentMove][0] - AAA;
+        fromY = moveList[currentMove][1] - ONE;
 
         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
 
@@ -5907,9 +8355,8 @@ AutoPlayOneMove()
     SendMoveToProgram(currentMove++, &first);
     DisplayBothClocks();
     DrawPosition(FALSE, boards[currentMove]);
-    if (commentList[currentMove] != NULL) {
-       DisplayComment(currentMove - 1, commentList[currentMove]);
-    }
+    // [HGM] PV info: always display, routine tests if empty
+    DisplayComment(currentMove - 1, commentList[currentMove]);
     return TRUE;
 }
 
@@ -5923,13 +8370,13 @@ LoadGameOneMove(readAhead)
     ChessMove moveType;
     char move[MSG_SIZ];
     char *p, *q;
-    
-    if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
+
+    if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
        gameMode != AnalyzeMode && gameMode != Training) {
        gameFileFP = NULL;
        return FALSE;
     }
-    
+
     yyboardindex = forwardMostMove;
     if (readAhead != (ChessMove)0) {
       moveType = readAhead;
@@ -5938,11 +8385,11 @@ LoadGameOneMove(readAhead)
          return FALSE;
       moveType = (ChessMove) yylex();
     }
-    
+
     done = FALSE;
     switch (moveType) {
       case Comment:
-       if (appData.debugMode) 
+       if (appData.debugMode)
          fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
        p = yy_text;
        if (*p == '{' || *p == '[' || *p == '(') {
@@ -5957,6 +8404,12 @@ LoadGameOneMove(readAhead)
 
       case WhiteCapturesEnPassant:
       case BlackCapturesEnPassant:
+      case WhitePromotionChancellor:
+      case BlackPromotionChancellor:
+      case WhitePromotionArchbishop:
+      case BlackPromotionArchbishop:
+      case WhitePromotionCentaur:
+      case BlackPromotionCentaur:
       case WhitePromotionQueen:
       case BlackPromotionQueen:
       case WhitePromotionRook:
@@ -5984,10 +8437,10 @@ LoadGameOneMove(readAhead)
       /* POP Fabien */
        if (appData.debugMode)
          fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
-       fromX = currentMoveString[0] - 'a';
-       fromY = currentMoveString[1] - '1';
-       toX = currentMoveString[2] - 'a';
-       toY = currentMoveString[3] - '1';
+        fromX = currentMoveString[0] - AAA;
+        fromY = currentMoveString[1] - ONE;
+        toX = currentMoveString[2] - AAA;
+        toY = currentMoveString[3] - ONE;
        promoChar = currentMoveString[4];
        break;
 
@@ -5999,8 +8452,8 @@ LoadGameOneMove(readAhead)
          (int) CharToPiece(ToUpper(currentMoveString[0])) :
        (int) CharToPiece(ToLower(currentMoveString[0]));
        fromY = DROP_RANK;
-       toX = currentMoveString[2] - 'a';
-       toY = currentMoveString[3] - '1';
+        toX = currentMoveString[2] - AAA;
+        toY = currentMoveString[3] - ONE;
        break;
 
       case WhiteWins:
@@ -6034,11 +8487,12 @@ LoadGameOneMove(readAhead)
        if (appData.debugMode)
          fprintf(debugFP, "Parser hit end of file\n");
        switch (MateTest(boards[currentMove], PosFlags(currentMove),
-                        EP_UNKNOWN)) {
+                         EP_UNKNOWN, castlingRights[currentMove]) ) {
          case MT_NONE:
          case MT_CHECK:
            break;
          case MT_CHECKMATE:
+         case MT_STAINMATE:
            if (WhiteOnMove(currentMove)) {
                GameEnds(BlackWins, "Black mates", GE_FILE);
            } else {
@@ -6069,11 +8523,12 @@ LoadGameOneMove(readAhead)
        if (appData.debugMode)
          fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
        switch (MateTest(boards[currentMove], PosFlags(currentMove),
-                        EP_UNKNOWN)) {
+                         EP_UNKNOWN, castlingRights[currentMove]) ) {
          case MT_NONE:
          case MT_CHECK:
            break;
          case MT_CHECKMATE:
+         case MT_STAINMATE:
            if (WhiteOnMove(currentMove)) {
                GameEnds(BlackWins, "Black mates", GE_FILE);
            } else {
@@ -6099,7 +8554,7 @@ LoadGameOneMove(readAhead)
        if (appData.testLegality) {
            if (appData.debugMode)
              fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
-           sprintf(move, "Illegal move: %d.%s%s",
+           sprintf(move, _("Illegal move: %d.%s%s"),
                    (forwardMostMove / 2) + 1,
                    WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
            DisplayError(move, 0);
@@ -6108,10 +8563,10 @@ LoadGameOneMove(readAhead)
            if (appData.debugMode)
              fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
                      yy_text, currentMoveString);
-           fromX = currentMoveString[0] - 'a';
-           fromY = currentMoveString[1] - '1';
-           toX = currentMoveString[2] - 'a';
-           toY = currentMoveString[3] - '1';
+            fromX = currentMoveString[0] - AAA;
+            fromY = currentMoveString[1] - ONE;
+            toX = currentMoveString[2] - AAA;
+            toY = currentMoveString[3] - ONE;
            promoChar = currentMoveString[4];
        }
        break;
@@ -6119,7 +8574,7 @@ LoadGameOneMove(readAhead)
       case AmbiguousMove:
        if (appData.debugMode)
          fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
-       sprintf(move, "Ambiguous move: %d.%s%s",
+       sprintf(move, _("Ambiguous move: %d.%s%s"),
                (forwardMostMove / 2) + 1,
                WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
        DisplayError(move, 0);
@@ -6129,8 +8584,8 @@ LoadGameOneMove(readAhead)
       default:
       case ImpossibleMove:
        if (appData.debugMode)
-         fprintf(debugFP, "Parsed ImpossibleMove: %s\n", yy_text);
-       sprintf(move, "Illegal move: %d.%s%s",
+         fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
+       sprintf(move, _("Illegal move: %d.%s%s"),
                (forwardMostMove / 2) + 1,
                WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
        DisplayError(move, 0);
@@ -6142,7 +8597,7 @@ LoadGameOneMove(readAhead)
        if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
            DrawPosition(FALSE, boards[currentMove]);
            DisplayBothClocks();
-           if (!appData.matchMode && commentList[currentMove] != NULL)
+            if (!appData.matchMode) // [HGM] PV info: routine tests if empty
              DisplayComment(currentMove - 1, commentList[currentMove]);
        }
        (void) StopLoadGameTimer();
@@ -6153,7 +8608,7 @@ LoadGameOneMove(readAhead)
        /* currentMoveString is set as a side-effect of yylex */
        strcat(currentMoveString, "\n");
        strcpy(moveList[forwardMostMove], currentMoveString);
-       
+
        thinkOutput[0] = NULLCHAR;
        MakeMove(fromX, fromY, toX, toY, promoChar);
        currentMove = forwardMostMove;
@@ -6178,7 +8633,7 @@ LoadGameFromFile(filename, n, title, useList)
     } else {
        f = fopen(filename, "rb");
        if (f == NULL) {
-           sprintf(buf, "Can't open \"%s\"", filename);
+         snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
            DisplayError(buf, errno);
            return FALSE;
        }
@@ -6190,10 +8645,11 @@ LoadGameFromFile(filename, n, title, useList)
     if (useList && n == 0) {
        int error = GameListBuild(f);
        if (error) {
-           DisplayError("Cannot build game list", error);
+           DisplayError(_("Cannot build game list"), error);
        } else if (!ListEmpty(&gameList) &&
                   ((ListGame *) gameList.tailPred)->number > 1) {
-           GameListPopUp(f, title);
+         // TODO convert to GTK
+         //        GameListPopUp(f, title);
            return TRUE;
        }
        GameListDestroy();
@@ -6216,38 +8672,39 @@ MakeRegisteredMove()
            if (appData.debugMode)
              fprintf(debugFP, "Restoring %s for game %d\n",
                      cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
-    
+
            thinkOutput[0] = NULLCHAR;
            strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
-           fromX = cmailMove[lastLoadGameNumber - 1][0] - 'a';
-           fromY = cmailMove[lastLoadGameNumber - 1][1] - '1';
-           toX = cmailMove[lastLoadGameNumber - 1][2] - 'a';
-           toY = cmailMove[lastLoadGameNumber - 1][3] - '1';
+            fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
+            fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
+            toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
+            toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
            promoChar = cmailMove[lastLoadGameNumber - 1][4];
            MakeMove(fromX, fromY, toX, toY, promoChar);
            ShowMove(fromX, fromY, toX, toY);
-             
+
            switch (MateTest(boards[currentMove], PosFlags(currentMove),
-                            EP_UNKNOWN)) {
+                             EP_UNKNOWN, castlingRights[currentMove]) ) {
              case MT_NONE:
              case MT_CHECK:
                break;
-               
+
              case MT_CHECKMATE:
+             case MT_STAINMATE:
                if (WhiteOnMove(currentMove)) {
                    GameEnds(BlackWins, "Black mates", GE_PLAYER);
                } else {
                    GameEnds(WhiteWins, "White mates", GE_PLAYER);
                }
                break;
-               
+
              case MT_STALEMATE:
                GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
                break;
            }
 
            break;
-           
+
          case CMAIL_RESIGN:
            if (WhiteOnMove(currentMove)) {
                GameEnds(BlackWins, "White resigns", GE_PLAYER);
@@ -6255,11 +8712,11 @@ MakeRegisteredMove()
                GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
            }
            break;
-           
+
          case CMAIL_ACCEPT:
            GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
            break;
-             
+
          default:
            break;
        }
@@ -6279,7 +8736,7 @@ CmailLoadGame(f, gameNumber, title, useList)
     int retVal;
 
     if (gameNumber > nCmailGames) {
-       DisplayError("No more games in this message", 0);
+       DisplayError(_("No more games in this message"), 0);
        return FALSE;
     }
     if (f == lastLoadGameFP) {
@@ -6320,11 +8777,11 @@ ReloadGame(offset)
 {
     int gameNumber = lastLoadGameNumber + offset;
     if (lastLoadGameFP == NULL) {
-       DisplayError("No game has been loaded yet", 0);
+       DisplayError(_("No game has been loaded yet"), 0);
        return FALSE;
     }
     if (gameNumber <= 0) {
-       DisplayError("Can't back up any further", 0);
+       DisplayError(_("Can't back up any further"), 0);
        return FALSE;
     }
     if (cmailMsgLoaded) {
@@ -6353,73 +8810,92 @@ LoadGame(f, gameNumber, title, useList)
     int numPGNTags = 0;
     int err;
     GameMode oldGameMode;
+    VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
 
-    if (appData.debugMode) 
+    if (appData.debugMode)
        fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
 
     if (gameMode == Training )
        SetTrainingModeOff();
 
     oldGameMode = gameMode;
-    if (gameMode != BeginningOfGame) {
-      Reset(FALSE, TRUE);
-    }
+    if (gameMode != BeginningOfGame) 
+      {
+       Reset(FALSE, TRUE);
+      };
 
     gameFileFP = f;
-    if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
+    if (lastLoadGameFP != NULL && lastLoadGameFP != f) 
+      {
        fclose(lastLoadGameFP);
-    }
+      };
 
-    if (useList) {
+    if (useList) 
+      {
        lg = (ListGame *) ListElem(&gameList, gameNumber-1);
        
-       if (lg) {
+       if (lg) 
+         {
            fseek(f, lg->offset, 0);
            GameListHighlight(gameNumber);
            gn = 1;
-       }
-       else {
-           DisplayError("Game number out of range", 0);
+         }
+       else 
+         {
+           DisplayError(_("Game number out of range"), 0);
            return FALSE;
-       }
-    } else {
+         };
+      } 
+    else 
+      {
        GameListDestroy();
-       if (fseek(f, 0, 0) == -1) {
+       if (fseek(f, 0, 0) == -1) 
+         {
            if (f == lastLoadGameFP ?
                gameNumber == lastLoadGameNumber + 1 :
-               gameNumber == 1) {
+               gameNumber == 1) 
+             {
                gn = 1;
-           } else {
-               DisplayError("Can't seek on game file", 0);
+             } 
+           else 
+             {
+               DisplayError(_("Can't seek on game file"), 0);
                return FALSE;
-           }
-       }
-    }
-    lastLoadGameFP = f;
-    lastLoadGameNumber = gameNumber;
+             };
+         };
+      };
+
+    lastLoadGameFP     = f;
+    lastLoadGameNumber = gameNumber;
     strcpy(lastLoadGameTitle, title);
     lastLoadGameUseList = useList;
 
     yynewfile(f);
 
-
-    if (lg && lg->gameInfo.white && lg->gameInfo.black) {
-       sprintf(buf, "%s vs. %s", lg->gameInfo.white,
-               lg->gameInfo.black);
-           DisplayTitle(buf);
-    } else if (*title != NULLCHAR) {
-       if (gameNumber > 1) {
+    if (lg && lg->gameInfo.white && lg->gameInfo.black) 
+      {
+       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
+                lg->gameInfo.black);
+       DisplayTitle(buf);
+      } 
+    else if (*title != NULLCHAR) 
+      {
+       if (gameNumber > 1) 
+         {
            sprintf(buf, "%s %d", title, gameNumber);
            DisplayTitle(buf);
-       } else {
+         } 
+       else 
+         {
            DisplayTitle(title);
-       }
-    }
+         };
+      };
 
-    if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
+    if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) 
+      {
        gameMode = PlayFromGameFile;
        ModeHighlight();
-    }
+      };
 
     currentMove = forwardMostMove = backwardMostMove = 0;
     CopyBoard(boards[0], initialPosition);
@@ -6427,12 +8903,12 @@ LoadGame(f, gameNumber, title, useList)
 
     /*
      * Skip the first gn-1 games in the file.
-     * Also skip over anything that precedes an identifiable 
-     * start of game marker, to avoid being confused by 
-     * garbage at the start of the file.  Currently 
+     * Also skip over anything that precedes an identifiable
+     * start of game marker, to avoid being confused by
+     * garbage at the start of the file.  Currently
      * recognized start of game markers are the move number "1",
      * the pattern "gnuchess .* game", the pattern
-     * "^[#;%] [^ ]* game file", and a PGN tag block.  
+     * "^[#;%] [^ ]* game file", and a PGN tag block.
      * A game that starts with one of the latter two patterns
      * will also have a move number 1, possibly
      * following a position diagram.
@@ -6449,7 +8925,7 @@ LoadGame(f, gameNumber, title, useList)
                nCmailGames = CMAIL_MAX_GAMES - gn;
            } else {
                Reset(TRUE, TRUE);
-               DisplayError("Game not found in file", 0);
+               DisplayError(_("Game not found in file"), 0);
            }
            return FALSE;
 
@@ -6458,7 +8934,7 @@ LoadGame(f, gameNumber, title, useList)
            gn--;
            lastLoadGameStart = cm;
            break;
-           
+
          case MoveNumberOne:
            switch (lastLoadGameStart) {
              case GNUChessGame:
@@ -6526,7 +9002,7 @@ LoadGame(f, gameNumber, title, useList)
            break;
        }
     }
-    
+
     if (appData.debugMode)
       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
 
@@ -6552,21 +9028,31 @@ LoadGame(f, gameNumber, title, useList)
            free(gameInfo.event);
        }
        gameInfo.event = StrSave(yy_text);
-    }  
+    }
 
     startedFromSetupPosition = FALSE;
     while (cm == PGNTag) {
-       if (appData.debugMode) 
+       if (appData.debugMode)
          fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
        err = ParsePGNTag(yy_text, &gameInfo);
        if (!err) numPGNTags++;
 
+        /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
+        if(gameInfo.variant != oldVariant) {
+            startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
+           InitPosition(TRUE);
+            oldVariant = gameInfo.variant;
+           if (appData.debugMode)
+             fprintf(debugFP, "New variant %d\n", (int) oldVariant);
+        }
+
+
        if (gameInfo.fen != NULL) {
          Board initial_position;
          startedFromSetupPosition = TRUE;
          if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
            Reset(TRUE, TRUE);
-           DisplayError("Bad FEN position in file", 0);
+           DisplayError(_("Bad FEN position in file"), 0);
            return FALSE;
          }
          CopyBoard(boards[0], initial_position);
@@ -6584,6 +9070,13 @@ LoadGame(f, gameNumber, title, useList)
          } else {
            currentMove = forwardMostMove = backwardMostMove = 0;
          }
+          /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
+          {   int i;
+              initialRulePlies = FENrulePlies;
+              epStatus[forwardMostMove] = FENepStatus;
+              for( i=0; i< nrCastlingRights; i++ )
+                  initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
+          }
          yyboardindex = forwardMostMove;
          free(gameInfo.fen);
          gameInfo.fen = NULL;
@@ -6595,7 +9088,7 @@ LoadGame(f, gameNumber, title, useList)
        /* Handle comments interspersed among the tags */
        while (cm == Comment) {
            char *p;
-           if (appData.debugMode) 
+           if (appData.debugMode)
              fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
            p = yy_text;
            if (*p == '{' || *p == '[' || *p == '(') {
@@ -6619,10 +9112,10 @@ LoadGame(f, gameNumber, title, useList)
        }
        if (!matchMode) {
           if( appData.autoDisplayTags ) {
-         tags = PGNTags(&gameInfo);
-         TagsPopUp(tags, CmailMsg());
-         free(tags);
-       }
+           tags = PGNTags(&gameInfo);
+           TagsPopUp(tags, CmailMsg());
+           free(tags);
+          }
        }
     } else {
        /* Make something up, but don't display it now */
@@ -6640,8 +9133,8 @@ LoadGame(f, gameNumber, title, useList)
 
        if (!startedFromSetupPosition) {
            p = yy_text;
-           for (i = BOARD_SIZE - 1; i >= 0; i--)
-             for (j = 0; j < BOARD_SIZE; p++)
+            for (i = BOARD_HEIGHT - 1; i >= 0; i--)
+              for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
                switch (*p) {
                  case '[':
                  case '-':
@@ -6656,13 +9149,13 @@ LoadGame(f, gameNumber, title, useList)
                }
            while (*p == ' ' || *p == '\t' ||
                   *p == '\n' || *p == '\r') p++;
-       
+
            if (strncmp(p, "black", strlen("black"))==0)
              blackPlaysFirst = TRUE;
            else
              blackPlaysFirst = FALSE;
            startedFromSetupPosition = TRUE;
-       
+
            CopyBoard(boards[0], initial_position);
            if (blackPlaysFirst) {
                currentMove = forwardMostMove = backwardMostMove = 1;
@@ -6686,16 +9179,22 @@ LoadGame(f, gameNumber, title, useList)
     if (first.pr == NoProc) {
        StartChessProgram(&first);
     }
-    InitChessProgram(&first);
+    InitChessProgram(&first, FALSE);
     SendToProgram("force\n", &first);
     if (startedFromSetupPosition) {
        SendBoard(&first, forwardMostMove);
+    if (appData.debugMode) {
+        fprintf(debugFP, "Load Game\n");
+    }
        DisplayBothClocks();
-    }      
+    }
+
+    /* [HGM] server: flag to write setup moves in broadcast file as one */
+    loadFlag = appData.suppressLoadMoves;
 
     while (cm == Comment) {
        char *p;
-       if (appData.debugMode) 
+       if (appData.debugMode)
          fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
        p = yy_text;
        if (*p == '{' || *p == '[' || *p == '(') {
@@ -6711,7 +9210,7 @@ LoadGame(f, gameNumber, title, useList)
     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
        cm == WhiteWins || cm == BlackWins ||
        cm == GameIsDrawn || cm == GameUnfinished) {
-       DisplayMessage("", "No moves in game");
+       DisplayMessage("", _("No moves in game"));
        if (cmailMsgLoaded) {
            if (appData.debugMode)
              fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
@@ -6727,12 +9226,11 @@ LoadGame(f, gameNumber, title, useList)
        return TRUE;
     }
 
-    if (commentList[currentMove] != NULL) {
-      if (!matchMode && (pausing || appData.timeDelay != 0)) {
+    // [HGM] PV info: routine tests if comment empty
+    if (!matchMode && (pausing || appData.timeDelay != 0)) {
        DisplayComment(currentMove - 1, commentList[currentMove]);
-      }
     }
-    if (!matchMode && appData.timeDelay != 0) 
+    if (!matchMode && appData.timeDelay != 0)
       DrawPosition(FALSE, boards[currentMove]);
 
     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
@@ -6740,7 +9238,7 @@ LoadGame(f, gameNumber, title, useList)
     }
 
     /* if the first token after the PGN tags is a move
-     * and not move number 1, retrieve it from the parser 
+     * and not move number 1, retrieve it from the parser
      */
     if (cm != MoveNumberOne)
        LoadGameOneMove(cm);
@@ -6769,8 +9267,10 @@ LoadGame(f, gameNumber, title, useList)
       AutoPlayGameLoop();
     }
 
-    if (appData.debugMode) 
+    if (appData.debugMode)
        fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
+
+    loadFlag = 0; /* [HGM] true game starts */
     return TRUE;
 }
 
@@ -6781,11 +9281,11 @@ ReloadPosition(offset)
 {
     int positionNumber = lastLoadPositionNumber + offset;
     if (lastLoadPositionFP == NULL) {
-       DisplayError("No position has been loaded yet", 0);
+       DisplayError(_("No position has been loaded yet"), 0);
        return FALSE;
     }
     if (positionNumber <= 0) {
-       DisplayError("Can't back up any further", 0);
+       DisplayError(_("Can't back up any further"), 0);
        return FALSE;
     }
     return LoadPosition(lastLoadPositionFP, positionNumber,
@@ -6807,7 +9307,7 @@ LoadPositionFromFile(filename, n, title)
     } else {
        f = fopen(filename, "rb");
        if (f == NULL) {
-           sprintf(buf, "Can't open \"%s\"", filename);
+            snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
            DisplayError(buf, errno);
            return FALSE;
        } else {
@@ -6826,7 +9326,7 @@ LoadPosition(f, positionNumber, title)
     char *p, line[MSG_SIZ];
     Board initial_position;
     int i, j, fenMode, pn;
-    
+
     if (gameMode == Training )
        SetTrainingModeOff();
 
@@ -6842,13 +9342,13 @@ LoadPosition(f, positionNumber, title)
     strcpy(lastLoadPositionTitle, title);
     if (first.pr == NoProc) {
       StartChessProgram(&first);
-      InitChessProgram(&first);
-    }    
+      InitChessProgram(&first, FALSE);
+    }
     pn = positionNumber;
     if (positionNumber < 0) {
        /* Negative position number means to seek to that byte offset */
        if (fseek(f, -positionNumber, 0) == -1) {
-           DisplayError("Can't seek on position file", 0);
+           DisplayError(_("Can't seek on position file"), 0);
            return FALSE;
        };
        pn = 1;
@@ -6859,36 +9359,26 @@ LoadPosition(f, positionNumber, title)
                positionNumber == 1) {
                pn = 1;
            } else {
-               DisplayError("Can't seek on position file", 0);
+               DisplayError(_("Can't seek on position file"), 0);
                return FALSE;
            }
        }
     }
     /* See if this file is FEN or old-style xboard */
     if (fgets(line, MSG_SIZ, f) == NULL) {
-       DisplayError("Position not found in file", 0);
+       DisplayError(_("Position not found in file"), 0);
        return FALSE;
     }
-    switch (line[0]) {
-      case '#':  case 'x':
-      default:
-       fenMode = FALSE;
-       break;
-      case 'p':  case 'n':  case 'b':  case 'r':  case 'q':  case 'k':
-      case 'P':  case 'N':  case 'B':  case 'R':  case 'Q':  case 'K':
-      case '1':  case '2':  case '3':  case '4':  case '5':  case '6':
-      case '7':  case '8':
-       fenMode = TRUE;
-       break;
-    }
+    // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
+    fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
 
     if (pn >= 2) {
        if (fenMode || line[0] == '#') pn--;
        while (pn > 0) {
-           /* skip postions before number pn */
+           /* skip positions before number pn */
            if (fgets(line, MSG_SIZ, f) == NULL) {
                Reset(TRUE, TRUE);
-               DisplayError("Position not found in file", 0);
+               DisplayError(_("Position not found in file"), 0);
                return FALSE;
            }
            if (fenMode || line[0] == '#') pn--;
@@ -6897,22 +9387,22 @@ LoadPosition(f, positionNumber, title)
 
     if (fenMode) {
        if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
-           DisplayError("Bad FEN position in file", 0);
+           DisplayError(_("Bad FEN position in file"), 0);
            return FALSE;
        }
     } else {
        (void) fgets(line, MSG_SIZ, f);
        (void) fgets(line, MSG_SIZ, f);
-    
-       for (i = BOARD_SIZE - 1; i >= 0; i--) {
+
+        for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
            (void) fgets(line, MSG_SIZ, f);
-           for (p = line, j = 0; j < BOARD_SIZE; p++) {
+            for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
                if (*p == ' ')
                  continue;
                initial_position[i][j++] = CharToPiece(*p);
            }
        }
-    
+
        blackPlaysFirst = FALSE;
        if (!feof(f)) {
            (void) fgets(line, MSG_SIZ, f);
@@ -6921,7 +9411,7 @@ LoadPosition(f, positionNumber, title)
        }
     }
     startedFromSetupPosition = TRUE;
-    
+
     SendToProgram("force\n", &first);
     CopyBoard(boards[0], initial_position);
     if (blackPlaysFirst) {
@@ -6929,12 +9419,25 @@ LoadPosition(f, positionNumber, title)
        strcpy(moveList[0], "");
        strcpy(parseList[0], "");
        CopyBoard(boards[1], initial_position);
-       DisplayMessage("", "Black to play");
+       DisplayMessage("", _("Black to play"));
     } else {
        currentMove = forwardMostMove = backwardMostMove = 0;
-       DisplayMessage("", "White to play");
-    }
+       DisplayMessage("", _("White to play"));
+    }
+          /* [HGM] copy FEN attributes as well */
+          {   int i;
+              initialRulePlies = FENrulePlies;
+              epStatus[forwardMostMove] = FENepStatus;
+              for( i=0; i< nrCastlingRights; i++ )
+                  castlingRights[forwardMostMove][i] = FENcastlingRights[i];
+          }
     SendBoard(&first, forwardMostMove);
+    if (appData.debugMode) {
+int i, j;
+  for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
+  for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
+        fprintf(debugFP, "Load Position\n");
+    }
 
     if (positionNumber > 1) {
        sprintf(line, "%s %d", title, positionNumber);
@@ -6948,7 +9451,7 @@ LoadPosition(f, positionNumber, title)
     timeRemaining[0][1] = whiteTimeRemaining;
     timeRemaining[1][1] = blackTimeRemaining;
     DrawPosition(FALSE, boards[currentMove]);
-   
+
     return TRUE;
 }
 
@@ -7000,7 +9503,7 @@ SaveGameToFile(filename, append)
     } else {
        f = fopen(filename, append ? "a" : "w");
        if (f == NULL) {
-           sprintf(buf, "Can't open \"%s\"", filename);
+           snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
            DisplayError(buf, errno);
            return FALSE;
        } else {
@@ -7015,7 +9518,7 @@ SavePart(str)
 {
     static char buf[MSG_SIZ];
     char *p;
-    
+
     p = strchr(str, ' ');
     if (p == NULL) return str;
     strncpy(buf, str, p - str);
@@ -7028,6 +9531,7 @@ SavePart(str)
 #define PGN_SIDE_WHITE  0
 #define PGN_SIDE_BLACK  1
 
+/* [AS] */
 static int FindFirstMoveOutOfBook( int side )
 {
     int result = -1;
@@ -7046,13 +9550,14 @@ static int FindFirstMoveOutOfBook( int side )
             int score = pvInfoList[index].score;
             int in_book = 0;
 
-            if( depth == 0 ) {
-                in_book = 1; /* Yace */
+            if( depth <= 2 ) {
+                in_book = 1;
             }
-            if( score == 0 ) {
-                if( depth <= 1 || depth == 63 /* Zappa */ ) {
-                    in_book = 1;
-                }
+            else if( score == 0 && depth == 63 ) {
+                in_book = 1; /* Zappa */
+            }
+            else if( score == 2 && depth == 99 ) {
+                in_book = 1; /* Abrok */
             }
 
             has_book_hit += in_book;
@@ -7070,6 +9575,7 @@ static int FindFirstMoveOutOfBook( int side )
     return result;
 }
 
+/* [AS] */
 void GetOutOfBookInfo( char * buf )
 {
     int oob[2];
@@ -7106,19 +9612,19 @@ SaveGamePGN(f)
 {
     int i, offset, linelen, newblock;
     time_t tm;
-    char *movetext;
+//    char *movetext;
     char numtext[32];
     int movelen, numlen, blank;
     char move_buffer[100]; /* [AS] Buffer for move+PV info */
-    
+
     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
 
     tm = time((time_t *) NULL);
-    
+
     PrintPGNTags(f, &gameInfo);
-    
+
     if (backwardMostMove > 0 || startedFromSetupPosition) {
-        char *fen = PositionToFEN(backwardMostMove, 1);
+        char *fen = PositionToFEN(backwardMostMove, NULL);
         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
        fprintf(f, "\n{--------------\n");
        PrintPosition(f, backwardMostMove);
@@ -7177,23 +9683,12 @@ SaveGamePGN(f)
            fprintf(f, " ");
            linelen++;
        }
-       fprintf(f, numtext);
+       fprintf(f, "%s", numtext);
        linelen += numlen;
 
        /* Get move */
-       movetext = SavePart(parseList[i]);
-
-        /* [AS] Add PV info if present */
-        if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
-            sprintf( move_buffer, "%s {%s%.2f/%d}",
-                movetext,
-                pvInfoList[i].score >= 0 ? "+" : "",
-                pvInfoList[i].score / 100.0,
-                pvInfoList[i].depth );
-            movetext = move_buffer;
-        }
-
-       movelen = strlen(movetext);
+       strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
+       movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
 
        /* Print move */
        blank = linelen > 0 && movelen > 0;
@@ -7206,12 +9701,57 @@ SaveGamePGN(f)
            fprintf(f, " ");
            linelen++;
        }
-       fprintf(f, movetext);
+       fprintf(f, "%s", move_buffer);
        linelen += movelen;
 
+        /* [AS] Add PV info if present */
+        if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
+            /* [HGM] add time */
+            char buf[MSG_SIZ]; int seconds = 0;
+
+            if(i >= backwardMostMove) {
+               if(WhiteOnMove(i))
+                       seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
+                                 + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
+               else
+                       seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
+                                  + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
+            }
+            seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
+
+            if( seconds <= 0) buf[0] = 0; else
+            if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
+               seconds = (seconds + 4)/10; // round to full seconds
+               if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
+                                  sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
+           }
+
+            sprintf( move_buffer, "{%s%.2f/%d%s}",
+                pvInfoList[i].score >= 0 ? "+" : "",
+                pvInfoList[i].score / 100.0,
+                pvInfoList[i].depth,
+               buf );
+
+           movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
+
+           /* Print score/depth */
+           blank = linelen > 0 && movelen > 0;
+           if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
+               fprintf(f, "\n");
+               linelen = 0;
+               blank = 0;
+           }
+           if (blank) {
+               fprintf(f, " ");
+               linelen++;
+           }
+           fprintf(f, "%s", move_buffer);
+           linelen += movelen;
+        }
+
        i++;
     }
-    
+
     /* Start a new line */
     if (linelen > 0) fprintf(f, "\n");
 
@@ -7230,6 +9770,7 @@ SaveGamePGN(f)
     }
 
     fclose(f);
+    lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
     return TRUE;
 }
 
@@ -7240,12 +9781,12 @@ SaveGameOldStyle(f)
 {
     int i, offset;
     time_t tm;
-    
+
     tm = time((time_t *) NULL);
-    
+
     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
     PrintOpponents(f);
-    
+
     if (backwardMostMove > 0 || startedFromSetupPosition) {
        fprintf(f, "\n[--------------\n");
        PrintPosition(f, backwardMostMove);
@@ -7280,7 +9821,7 @@ SaveGameOldStyle(f)
            i++;
        }
     }
-    
+
     if (commentList[i] != NULL) {
        fprintf(f, "[%s]\n", commentList[i]);
     }
@@ -7306,6 +9847,7 @@ SaveGame(f, dummy, dummy2)
      char *dummy2;
 {
     if (gameMode == EditPosition) EditPositionDone();
+    lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
     if (appData.oldSaveStyle)
       return SaveGameOldStyle(f);
     else
@@ -7325,7 +9867,7 @@ SavePositionToFile(filename)
     } else {
        f = fopen(filename, "a");
        if (f == NULL) {
-           sprintf(buf, "Can't open \"%s\"", filename);
+           snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
            DisplayError(buf, errno);
            return FALSE;
        } else {
@@ -7344,17 +9886,17 @@ SavePosition(f, dummy, dummy2)
 {
     time_t tm;
     char *fen;
-    
+
     if (appData.oldSaveStyle) {
        tm = time((time_t *) NULL);
-    
+
        fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
        PrintOpponents(f);
        fprintf(f, "[--------------\n");
        PrintPosition(f, currentMove);
        fprintf(f, "--------------]\n");
     } else {
-       fen = PositionToFEN(currentMove, 1);
+       fen = PositionToFEN(currentMove, NULL);
        fprintf(f, "%s\n", fen);
        free(fen);
     }
@@ -7372,7 +9914,7 @@ ReloadCmailMsgEvent(unregister)
     int i;
     struct stat inbuf, outbuf;
     int status;
-    
+
     /* Any registered moves are unregistered if unregister is set, */
     /* i.e. invoked by the signal handler */
     if (unregister) {
@@ -7400,7 +9942,7 @@ ReloadCmailMsgEvent(unregister)
        outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
        sprintf(outFilename, "%s.out", appData.cmailGameName);
     }
-    
+
     status = stat(outFilename, &outbuf);
     if (status < 0) {
        cmailMailedMove = FALSE;
@@ -7408,10 +9950,10 @@ ReloadCmailMsgEvent(unregister)
        status = stat(inFilename, &inbuf);
        cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
     }
-    
+
     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
        counts the games, notes how each one terminated, etc.
-       
+
        It would be nice to remove this kludge and instead gather all
        the information while building the game list.  (And to keep it
        in the game list nodes instead of having a bunch of fixed-size
@@ -7421,7 +9963,7 @@ ReloadCmailMsgEvent(unregister)
        */
     cmailMsgLoaded = TRUE;
     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
-    
+
     /* Load first game in the file or popup game menu */
     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
 
@@ -7447,7 +9989,7 @@ RegisterMove()
        cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
        nCmailMovesRegistered --;
 
-       if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
+       if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
          {
              free(cmailCommentList[lastLoadGameNumber - 1]);
              cmailCommentList[lastLoadGameNumber - 1] = NULL;
@@ -7455,17 +9997,17 @@ RegisterMove()
     }
 
     if (cmailOldMove == -1) {
-       DisplayError("You have edited the game history.\nUse Reload Same Game and make your move again.", 0);
+       DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
        return FALSE;
     }
 
     if (currentMove > cmailOldMove + 1) {
-       DisplayError("You have entered too many moves.\nBack up to the correct position and try again.", 0);
+       DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
        return FALSE;
     }
 
     if (currentMove < cmailOldMove) {
-       DisplayError("Displayed position is not current.\nStep forward to the correct position and try again.", 0);
+       DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
        return FALSE;
     }
 
@@ -7494,11 +10036,11 @@ RegisterMove()
 
        sprintf(string,
                "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
-       
+
        f = fopen(string, "w");
        if (appData.oldSaveStyle) {
            SaveGameOldStyle(f); /* also closes the file */
-           
+
            sprintf(string, "%s.pos.out", appData.cmailGameName);
            f = fopen(string, "w");
            SavePosition(f, 0, NULL); /* also closes the file */
@@ -7506,14 +10048,14 @@ RegisterMove()
            fprintf(f, "{--------------\n");
            PrintPosition(f, currentMove);
            fprintf(f, "--------------}\n\n");
-           
+
            SaveGame(f, 0, NULL); /* also closes the file*/
        }
-       
+
        cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
        nCmailMovesRegistered ++;
     } else if (nCmailGames == 1) {
-       DisplayError("You have not made a move yet", 0);
+       DisplayError(_("You have not made a move yet"), 0);
        return FALSE;
     }
 
@@ -7534,33 +10076,33 @@ MailMoveEvent()
     char *arcDir;
 
     if (! cmailMsgLoaded) {
-       DisplayError("The cmail message is not loaded.\nUse Reload CMail Message and make your move again.", 0);
+       DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
        return;
     }
 
     if (nCmailGames == nCmailResults) {
-       DisplayError("No unfinished games", 0);
+       DisplayError(_("No unfinished games"), 0);
        return;
     }
 
 #if CMAIL_PROHIBIT_REMAIL
     if (cmailMailedMove) {
-       sprintf(msg, "You have already mailed a move.\nWait until a move arrives from your opponent.\nTo resend the same move, type\n\"cmail -remail -game %s\"\non the command line.", appData.cmailGameName);
+       sprintf(msg, _("You have already mailed a move.\nWait until a move arrives from your opponent.\nTo resend the same move, type\n\"cmail -remail -game %s\"\non the command line."), appData.cmailGameName);
        DisplayError(msg, 0);
        return;
     }
 #endif
 
     if (! (cmailMailedMove || RegisterMove())) return;
-    
+
     if (   cmailMailedMove
        || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
        sprintf(string, partCommandString,
                appData.debugMode ? " -v" : "", appData.cmailGameName);
-       commandOutput = popen(string, "rb");
+       commandOutput = popen(string, "r");
 
        if (commandOutput == NULL) {
-           DisplayError("Failed to invoke cmail", 0);
+           DisplayError(_("Failed to invoke cmail"), 0);
        } else {
            for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
                nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
@@ -7618,11 +10160,11 @@ CmailMsg()
     char number[5];
     char string[MSG_SIZ];      /* Space for game-list */
     int  i;
-    
+
     if (!cmailMsgLoaded) return "";
 
     if (cmailMailedMove) {
-       sprintf(cmailMsg, "Waiting for reply from opponent\n");
+       sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
     } else {
        /* Create a list of games left */
        sprintf(string, "[");
@@ -7635,7 +10177,7 @@ CmailMsg()
                    sprintf(number, "%d", i + 1);
                    prependComma = 1;
                }
-               
+
                strcat(string, number);
            }
        }
@@ -7645,17 +10187,17 @@ CmailMsg()
            switch (nCmailGames) {
              case 1:
                sprintf(cmailMsg,
-                       "Still need to make move for game\n");
+                       _("Still need to make move for game\n"));
                break;
-               
+
              case 2:
                sprintf(cmailMsg,
-                       "Still need to make moves for both games\n");
+                       _("Still need to make moves for both games\n"));
                break;
-               
+
              default:
                sprintf(cmailMsg,
-                       "Still need to make moves for all %d games\n",
+                       _("Still need to make moves for all %d games\n"),
                        nCmailGames);
                break;
            }
@@ -7663,21 +10205,21 @@ CmailMsg()
            switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
              case 1:
                sprintf(cmailMsg,
-                       "Still need to make a move for game %s\n",
+                       _("Still need to make a move for game %s\n"),
                        string);
                break;
-               
+
              case 0:
                if (nCmailResults == nCmailGames) {
-                   sprintf(cmailMsg, "No unfinished games\n");
+                   sprintf(cmailMsg, _("No unfinished games\n"));
                } else {
-                   sprintf(cmailMsg, "Ready to send mail\n");
+                   sprintf(cmailMsg, _("Ready to send mail\n"));
                }
                break;
-               
+
              default:
                sprintf(cmailMsg,
-                       "Still need to make moves for games %s\n",
+                       _("Still need to make moves for games %s\n"),
                        string);
            }
        }
@@ -7700,8 +10242,6 @@ ResetGameEvent()
     }
 }
 
-static int exiting = 0;
-
 void
 ExitEvent(status)
      int status;
@@ -7724,18 +10264,17 @@ ExitEvent(status)
     if (icsPR != NoProc) {
       DestroyChildProcess(icsPR, TRUE);
     }
-    /* Save game if resource set and not already saved by GameEnds() */
-    if (gameInfo.resultDetails == NULL && forwardMostMove > 0) {
-      if (*appData.saveGameFile != NULLCHAR) {
-       SaveGameToFile(appData.saveGameFile, TRUE);
-      } else if (appData.autoSaveGames) {
-       AutoSaveGame();
-      }
-      if (*appData.savePositionFile != NULLCHAR) {
-       SavePositionToFile(appData.savePositionFile);
-      }
+
+    /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
+    GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
+
+    /* [HGM] crash: the above GameEnds() is a dud if another one was running */
+    /* make sure this other one finishes before killing it!                  */
+    if(endingGame) { int count = 0;
+        if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
+        while(endingGame && count++ < 10) DoSleep(1);
+        if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
     }
-    GameEnds((ChessMove) 0, NULL, GE_PLAYER);
 
     /* Kill off chess programs */
     if (first.pr != NoProc) {
@@ -7778,7 +10317,7 @@ PauseEvent()
            DisplayBothClocks();
        }
        if (gameMode == PlayFromGameFile) {
-           if (appData.timeDelay >= 0) 
+           if (appData.timeDelay >= 0)
                AutoPlayGameLoop();
        } else if (gameMode == IcsExamining && pauseExamInvalid) {
            Reset(FALSE, TRUE);
@@ -7834,9 +10373,9 @@ EditCommentEvent()
     char title[MSG_SIZ];
 
     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
-       strcpy(title, "Edit comment");
+       strcpy(title, _("Edit comment"));
     } else {
-       sprintf(title, "Edit comment on %d.%s%s", (currentMove - 1) / 2 + 1,
+       sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
                WhiteOnMove(currentMove - 1) ? " " : ".. ",
                parseList[currentMove - 1]);
     }
@@ -7860,17 +10399,18 @@ AnalyzeModeEvent()
       return;
 
     if (gameMode != AnalyzeFile) {
-       EditGameEvent();
-       if (gameMode != EditGame) return;
+        if (!appData.icsEngineAnalyze) {
+               EditGameEvent();
+               if (gameMode != EditGame) return;
+        }
        ResurrectChessProgram();
        SendToProgram("analyze\n", &first);
        first.analyzing = TRUE;
        /*first.maybeThinking = TRUE;*/
        first.maybeThinking = FALSE; /* avoid killing GNU Chess */
-       AnalysisPopUp("Analysis",
-                     "Starting analysis mode...\nIf this message stays up, your chess program does not support analysis.");
+       EngineOutputPopUp();
     }
-    gameMode = AnalyzeMode;
+    if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
     pausing = FALSE;
     ModeHighlight();
     SetGameInfo();
@@ -7894,8 +10434,7 @@ AnalyzeFileEvent()
        first.analyzing = TRUE;
        /*first.maybeThinking = TRUE;*/
        first.maybeThinking = FALSE; /* avoid killing GNU Chess */
-       AnalysisPopUp("Analysis",
-                     "Starting analysis mode...\nIf this message stays up, your chess program does not support analysis.");
+       EngineOutputPopUp();
     }
     gameMode = AnalyzeFile;
     pausing = FALSE;
@@ -7911,34 +10450,39 @@ void
 MachineWhiteEvent()
 {
     char buf[MSG_SIZ];
+    char *bookHit = NULL;
 
     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
       return;
 
 
-    if (gameMode == PlayFromGameFile || 
-       gameMode == TwoMachinesPlay  || 
-       gameMode == Training         || 
-       gameMode == AnalyzeMode      || 
+    if (gameMode == PlayFromGameFile ||
+       gameMode == TwoMachinesPlay  ||
+       gameMode == Training         ||
+       gameMode == AnalyzeMode      ||
        gameMode == EndOfGame)
        EditGameEvent();
 
-    if (gameMode == EditPosition) 
+    if (gameMode == EditPosition)
         EditPositionDone();
 
     if (!WhiteOnMove(currentMove)) {
-       DisplayError("It is not White's turn", 0);
+       DisplayError(_("It is not White's turn"), 0);
        return;
     }
-  
+
     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
       ExitAnalyzeMode();
 
-    if (gameMode == EditGame || gameMode == AnalyzeMode || 
+    if (gameMode == EditGame || gameMode == AnalyzeMode ||
        gameMode == AnalyzeFile)
        TruncateGame();
 
     ResurrectChessProgram();   /* in case it isn't running */
+    if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
+       gameMode = MachinePlaysWhite;
+       ResetClocks();
+    } else
     gameMode = MachinePlaysWhite;
     pausing = FALSE;
     ModeHighlight();
@@ -7956,17 +10500,30 @@ MachineWhiteEvent()
       SendTimeRemaining(&first, TRUE);
     }
     if (first.useColors) {
-      SendToProgram("white\ngo\n", &first);
-    } else {
-      SendToProgram("go\n", &first);
+      SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
     }
+    bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
     SetMachineThinkingEnables();
     first.maybeThinking = TRUE;
     StartClocks();
+    firstMove = FALSE;
 
     if (appData.autoFlipView && !flipView) {
       flipView = !flipView;
       DrawPosition(FALSE, NULL);
+      DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
+    }
+
+    if(bookHit) { // [HGM] book: simulate book reply
+       static char bookMove[MSG_SIZ]; // a bit generous?
+
+       programStats.nodes = programStats.depth = programStats.time =
+       programStats.score = programStats.got_only_move = 0;
+       sprintf(programStats.movelist, "%s (xbook)", bookHit);
+
+       strcpy(bookMove, "move ");
+       strcat(bookMove, bookHit);
+       HandleMachineMove(bookMove, &first);
     }
 }
 
@@ -7974,30 +10531,31 @@ void
 MachineBlackEvent()
 {
     char buf[MSG_SIZ];
+   char *bookHit = NULL;
 
     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
        return;
 
 
-    if (gameMode == PlayFromGameFile || 
-       gameMode == TwoMachinesPlay  || 
-       gameMode == Training         || 
-       gameMode == AnalyzeMode      || 
+    if (gameMode == PlayFromGameFile ||
+       gameMode == TwoMachinesPlay  ||
+       gameMode == Training         ||
+       gameMode == AnalyzeMode      ||
        gameMode == EndOfGame)
         EditGameEvent();
 
-    if (gameMode == EditPosition) 
+    if (gameMode == EditPosition)
         EditPositionDone();
 
     if (WhiteOnMove(currentMove)) {
-       DisplayError("It is not Black's turn", 0);
+       DisplayError(_("It is not Black's turn"), 0);
        return;
     }
-    
+
     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
       ExitAnalyzeMode();
 
-    if (gameMode == EditGame || gameMode == AnalyzeMode || 
+    if (gameMode == EditGame || gameMode == AnalyzeMode ||
        gameMode == AnalyzeFile)
        TruncateGame();
 
@@ -8019,10 +10577,9 @@ MachineBlackEvent()
       SendTimeRemaining(&first, FALSE);
     }
     if (first.useColors) {
-      SendToProgram("black\ngo\n", &first);
-    } else {
-      SendToProgram("go\n", &first);
+      SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
     }
+    bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
     SetMachineThinkingEnables();
     first.maybeThinking = TRUE;
     StartClocks();
@@ -8030,6 +10587,18 @@ MachineBlackEvent()
     if (appData.autoFlipView && flipView) {
       flipView = !flipView;
       DrawPosition(FALSE, NULL);
+      DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
+    }
+    if(bookHit) { // [HGM] book: simulate book reply
+       static char bookMove[MSG_SIZ]; // a bit generous?
+
+       programStats.nodes = programStats.depth = programStats.time =
+       programStats.score = programStats.got_only_move = 0;
+       sprintf(programStats.movelist, "%s (xbook)", bookHit);
+
+       strcpy(bookMove, "move ");
+       strcat(bookMove, bookHit);
+       HandleMachineMove(bookMove, &first);
     }
 }
 
@@ -8062,7 +10631,8 @@ TwoMachinesEvent P((void))
     int i;
     char buf[MSG_SIZ];
     ChessProgramState *onmove;
-    
+    char *bookHit = NULL;
+
     if (appData.noChessProgram) return;
 
     switch (gameMode) {
@@ -8071,7 +10641,7 @@ TwoMachinesEvent P((void))
       case MachinePlaysWhite:
       case MachinePlaysBlack:
        if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
-           DisplayError("Wait until your turn,\nor select Move Now", 0);
+           DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
            return;
        }
        /* fall through */
@@ -8103,16 +10673,19 @@ TwoMachinesEvent P((void))
        } else {
          /* kludge: allow timeout for initial "feature" command */
          FreezeUI();
-         DisplayMessage("", "Starting second chess program");
+         DisplayMessage("", _("Starting second chess program"));
          ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
        }
        return;
     }
     DisplayMessage("", "");
-    InitChessProgram(&second);
+    InitChessProgram(&second, FALSE);
     SendToProgram("force\n", &second);
     if (startedFromSetupPosition) {
        SendBoard(&second, backwardMostMove);
+    if (appData.debugMode) {
+        fprintf(debugFP, "Two Machines\n");
+    }
     }
     for (i = backwardMostMove; i < forwardMostMove; i++) {
        SendMoveToProgram(i, &second);
@@ -8141,8 +10714,8 @@ TwoMachinesEvent P((void))
       SendToProgram(buf, &second);
     }
 
+    ResetClocks();
     if (!first.sendTime || !second.sendTime) {
-       ResetClocks();
        timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
        timeRemaining[1][forwardMostMove] = blackTimeRemaining;
     }
@@ -8155,11 +10728,24 @@ TwoMachinesEvent P((void))
     if (onmove->useColors) {
       SendToProgram(onmove->twoMachinesColor, onmove);
     }
-    SendToProgram("go\n", onmove);
+    bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
+//    SendToProgram("go\n", onmove);
     onmove->maybeThinking = TRUE;
     SetMachineThinkingEnables();
 
     StartClocks();
+
+    if(bookHit) { // [HGM] book: simulate book reply
+       static char bookMove[MSG_SIZ]; // a bit generous?
+
+       programStats.nodes = programStats.depth = programStats.time =
+       programStats.score = programStats.got_only_move = 0;
+       sprintf(programStats.movelist, "%s (xbook)", bookHit);
+
+       strcpy(bookMove, "move ");
+       strcat(bookMove, bookHit);
+       HandleMachineMove(bookMove, &first);
+    }
 }
 
 void
@@ -8168,7 +10754,7 @@ TrainingEvent()
     if (gameMode == Training) {
       SetTrainingModeOff();
       gameMode = PlayFromGameFile;
-      DisplayMessage("", "Training mode off");
+      DisplayMessage("", _("Training mode off"));
     } else {
       gameMode = Training;
       animateTraining = appData.animate;
@@ -8176,10 +10762,10 @@ TrainingEvent()
       /* make sure we are not already at the end of the game */
       if (currentMove < forwardMostMove) {
        SetTrainingModeOn();
-       DisplayMessage("", "Training mode on");
+       DisplayMessage("", _("Training mode on"));
       } else {
        gameMode = PlayFromGameFile;
-       DisplayError("Already at end of game", 0);
+       DisplayError(_("Already at end of game"), 0);
       }
     }
     ModeHighlight();
@@ -8209,7 +10795,7 @@ IcsClientEvent()
       case AnalyzeFile:
        ExitAnalyzeMode();
        break;
-       
+
       default:
        EditGameEvent();
        break;
@@ -8260,13 +10846,13 @@ EditGameEvent()
        break;
       case IcsPlayingBlack:
       case IcsPlayingWhite:
-       DisplayError("Warning: You are still playing a game", 0);
+       DisplayError(_("Warning: You are still playing a game"), 0);
        break;
       case IcsObserving:
-       DisplayError("Warning: You are still observing a game", 0);
+       DisplayError(_("Warning: You are still observing a game"), 0);
        break;
       case IcsExamining:
-       DisplayError("Warning: You are still examining a game", 0);
+       DisplayError(_("Warning: You are still examining a game"), 0);
        break;
       case IcsIdle:
        break;
@@ -8274,7 +10860,7 @@ EditGameEvent()
       default:
        return;
     }
-    
+
     pausing = FALSE;
     StopClocks();
     first.offeredDraw = second.offeredDraw = 0;
@@ -8301,8 +10887,8 @@ EditGameEvent()
            whiteFlag = blackFlag = 0;
        }
        DisplayTitle("");
-    }          
-    
+    }
+
     gameMode = EditGame;
     ModeHighlight();
     SetGameInfo();
@@ -8316,16 +10902,16 @@ EditPositionEvent()
        EditGameEvent();
        return;
     }
-    
+
     EditGameEvent();
     if (gameMode != EditGame) return;
-    
+
     gameMode = EditPosition;
     ModeHighlight();
     SetGameInfo();
     if (currentMove > 0)
       CopyBoard(boards[0], boards[currentMove]);
-    
+
     blackPlaysFirst = !WhiteOnMove(currentMove);
     ResetClocks();
     currentMove = forwardMostMove = backwardMostMove = 0;
@@ -8336,29 +10922,54 @@ EditPositionEvent()
 void
 ExitAnalyzeMode()
 {
+    /* [DM] icsEngineAnalyze - possible call from other functions */
+    if (appData.icsEngineAnalyze) {
+        appData.icsEngineAnalyze = FALSE;
+
+        DisplayMessage("",_("Close ICS engine analyze..."));
+    }
     if (first.analysisSupport && first.analyzing) {
       SendToProgram("exit\n", &first);
       first.analyzing = FALSE;
     }
-    AnalysisPopDown();
+    EngineOutputPopDown();
     thinkOutput[0] = NULLCHAR;
 }
 
 void
 EditPositionDone()
 {
+    int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
+
     startedFromSetupPosition = TRUE;
-    InitChessProgram(&first);
+    InitChessProgram(&first, FALSE);
+    castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
+    if(boards[0][0][BOARD_WIDTH>>1] == king) {
+       castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
+       castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
+    } else castlingRights[0][2] = -1;
+    if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
+       castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
+       castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
+    } else castlingRights[0][5] = -1;
     SendToProgram("force\n", &first);
     if (blackPlaysFirst) {
        strcpy(moveList[0], "");
        strcpy(parseList[0], "");
        currentMove = forwardMostMove = backwardMostMove = 1;
        CopyBoard(boards[1], boards[0]);
+       /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
+       { int i;
+         epStatus[1] = epStatus[0];
+         for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
+       }
     } else {
        currentMove = forwardMostMove = backwardMostMove = 0;
     }
     SendBoard(&first, forwardMostMove);
+    if (appData.debugMode) {
+        fprintf(debugFP, "EditPosDone\n");
+    }
     DisplayTitle("");
     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
@@ -8393,7 +11004,7 @@ SendMultiLineToICS(buf)
     len = strlen(buf);
     if (len > MSG_SIZ)
       len = MSG_SIZ;
-  
+
     strncpy(temp, buf, len);
     temp[len] = 0;
 
@@ -8441,6 +11052,7 @@ EditPositionMenuEvent(selection, x, y)
      int x, y;
 {
     char buf[MSG_SIZ];
+    ChessSquare piece = boards[0][y][x];
 
     if (gameMode != EditPosition && gameMode != IcsExamining) return;
 
@@ -8453,16 +11065,17 @@ EditPositionMenuEvent(selection, x, y)
            SendToICS(ics_prefix);
            SendToICS("clearboard\n");
        } else {
-           for (x = 0; x < BOARD_SIZE; x++) {
-               for (y = 0; y < BOARD_SIZE; y++) {
+            for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
+               if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
+                for (y = 0; y < BOARD_HEIGHT; y++) {
                    if (gameMode == IcsExamining) {
                        if (boards[currentMove][y][x] != EmptySquare) {
                            sprintf(buf, "%sx@%c%c\n", ics_prefix,
-                                   'a' + x, '1' + y);
+                                    AAA + x, ONE + y);
                            SendToICS(buf);
                        }
                    } else {
-                       boards[0][y][x] = EmptySquare;
+                       boards[0][y][x] = p;
                    }
                }
            }
@@ -8482,7 +11095,7 @@ EditPositionMenuEvent(selection, x, y)
 
       case EmptySquare:
        if (gameMode == IcsExamining) {
-           sprintf(buf, "%sx@%c%c\n", ics_prefix, 'a' + x, '1' + y);
+            sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
            SendToICS(buf);
        } else {
            boards[0][y][x] = EmptySquare;
@@ -8490,10 +11103,41 @@ EditPositionMenuEvent(selection, x, y)
        }
        break;
 
+      case PromotePiece:
+        if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
+           piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
+            selection = (ChessSquare) (PROMOTED piece);
+        } else if(piece == EmptySquare) selection = WhiteSilver;
+        else selection = (ChessSquare)((int)piece - 1);
+        goto defaultlabel;
+
+      case DemotePiece:
+        if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
+           piece > (int)BlackMan && piece <= (int)BlackKing   ) {
+            selection = (ChessSquare) (DEMOTED piece);
+        } else if(piece == EmptySquare) selection = BlackSilver;
+        else selection = (ChessSquare)((int)piece + 1);
+        goto defaultlabel;
+
+      case WhiteQueen:
+      case BlackQueen:
+        if(gameInfo.variant == VariantShatranj ||
+           gameInfo.variant == VariantXiangqi  ||
+           gameInfo.variant == VariantCourier    )
+            selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
+        goto defaultlabel;
+
+      case WhiteKing:
+      case BlackKing:
+        if(gameInfo.variant == VariantXiangqi)
+            selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
+        if(gameInfo.variant == VariantKnightmate)
+            selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
       default:
+        defaultlabel:
        if (gameMode == IcsExamining) {
            sprintf(buf, "%s%c@%c%c\n", ics_prefix,
-                   PieceToChar(selection), 'a' + x, '1' + y);
+                    PieceToChar(selection), AAA + x, ONE + y);
            SendToICS(buf);
        } else {
            boards[0][y][x] = selection;
@@ -8515,7 +11159,7 @@ DropMenuEvent(selection, x, y)
       case IcsPlayingWhite:
       case MachinePlaysBlack:
        if (!WhiteOnMove(currentMove)) {
-           DisplayMoveError("It is Black's turn");
+           DisplayMoveError(_("It is Black's turn"));
            return;
        }
        moveType = WhiteDrop;
@@ -8523,7 +11167,7 @@ DropMenuEvent(selection, x, y)
       case IcsPlayingBlack:
       case MachinePlaysWhite:
        if (WhiteOnMove(currentMove)) {
-           DisplayMoveError("It is White's turn");
+           DisplayMoveError(_("It is White's turn"));
            return;
        }
        moveType = BlackDrop;
@@ -8540,7 +11184,7 @@ DropMenuEvent(selection, x, y)
                                 + (int) BlackPawn - (int) WhitePawn);
     }
     if (boards[currentMove][y][x] != EmptySquare) {
-       DisplayMoveError("That square is occupied");
+       DisplayMoveError(_("That square is occupied"));
        return;
     }
 
@@ -8551,7 +11195,7 @@ void
 AcceptEvent()
 {
     /* Accept a pending offer of any kind from opponent */
-    
+
     if (appData.icsActive) {
         SendToICS(ics_prefix);
        SendToICS("accept\n");
@@ -8564,7 +11208,7 @@ AcceptEvent()
            GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
            cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
        } else {
-           DisplayError("There is no pending offer on this move", 0);
+           DisplayError(_("There is no pending offer on this move"), 0);
            cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
        }
     } else {
@@ -8576,7 +11220,7 @@ void
 DeclineEvent()
 {
     /* Decline a pending offer of any kind from opponent */
-    
+
     if (appData.icsActive) {
         SendToICS(ics_prefix);
        SendToICS("decline\n");
@@ -8590,7 +11234,7 @@ DeclineEvent()
            DisplayComment(cmailOldMove - 1, "Draw declined");
 #endif /*NOTDEF*/
        } else {
-           DisplayError("There is no pending offer on this move", 0);
+           DisplayError(_("There is no pending offer on this move"), 0);
        }
     } else {
        /* Not used for offers from chess program */
@@ -8626,7 +11270,7 @@ CallFlagEvent()
                else
                  GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
            } else {
-               DisplayError("Your opponent is not out of time", 0);
+               DisplayError(_("Your opponent is not out of time"), 0);
            }
            break;
          case MachinePlaysBlack:
@@ -8637,7 +11281,7 @@ CallFlagEvent()
                else
                  GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
            } else {
-               DisplayError("Your opponent is not out of time", 0);
+               DisplayError(_("Your opponent is not out of time"), 0);
            }
            break;
        }
@@ -8648,14 +11292,14 @@ void
 DrawEvent()
 {
     /* Offer draw or accept pending draw offer from opponent */
-    
+
     if (appData.icsActive) {
        /* Note: tournament rules require draw offers to be
           made after you make your move but before you punch
           your clock.  Currently ICS doesn't let you do that;
           instead, you immediately punch your clock after making
           a move, but you can offer a draw at any time. */
-       
+
         SendToICS(ics_prefix);
        SendToICS("draw\n");
     } else if (cmailMsgLoaded) {
@@ -8672,7 +11316,7 @@ DrawEvent()
            DisplayComment(currentMove - 1, offer);
            cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
        } else {
-           DisplayError("You must make your move before offering a draw", 0);
+           DisplayError(_("You must make your move before offering a draw"), 0);
            cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
        }
     } else if (first.offeredDraw) {
@@ -8689,7 +11333,7 @@ void
 AdjournEvent()
 {
     /* Offer Adjourn or accept pending Adjourn offer from opponent */
-    
+
     if (appData.icsActive) {
         SendToICS(ics_prefix);
        SendToICS("adjourn\n");
@@ -8703,7 +11347,7 @@ void
 AbortEvent()
 {
     /* Offer Abort or accept pending Abort offer from opponent */
-    
+
     if (appData.icsActive) {
         SendToICS(ics_prefix);
        SendToICS("abort\n");
@@ -8716,7 +11360,7 @@ void
 ResignEvent()
 {
     /* Resign.  You can do this even if it's not your turn. */
-    
+
     if (appData.icsActive) {
         SendToICS(ics_prefix);
        SendToICS("resign\n");
@@ -8777,25 +11421,25 @@ ForwardInner(target)
 
     if (gameMode == PlayFromGameFile && !pausing)
       PauseEvent();
-    
+
     if (gameMode == IcsExamining && pausing)
       limit = pauseExamForwardMostMove;
     else
       limit = forwardMostMove;
-    
+
     if (target > limit) target = limit;
 
     if (target > 0 && moveList[target - 1][0]) {
        int fromX, fromY, toX, toY;
-       toX = moveList[target - 1][2] - 'a';
-       toY = moveList[target - 1][3] - '1';
+        toX = moveList[target - 1][2] - AAA;
+        toY = moveList[target - 1][3] - ONE;
        if (moveList[target - 1][1] == '@') {
            if (appData.highlightLastMove) {
                SetHighlights(-1, -1, toX, toY);
            }
        } else {
-           fromX = moveList[target - 1][0] - 'a';
-           fromY = moveList[target - 1][1] - '1';
+            fromX = moveList[target - 1][0] - AAA;
+            fromY = moveList[target - 1][1] - ONE;
            if (target == currentMove + 1) {
                AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
            }
@@ -8804,8 +11448,8 @@ ForwardInner(target)
            }
        }
     }
-    if (gameMode == EditGame || gameMode == AnalyzeMode || 
-       gameMode == Training || gameMode == PlayFromGameFile || 
+    if (gameMode == EditGame || gameMode == AnalyzeMode ||
+       gameMode == Training || gameMode == PlayFromGameFile ||
        gameMode == AnalyzeFile) {
        while (currentMove < target) {
            SendMoveToProgram(currentMove++, &first);
@@ -8813,7 +11457,7 @@ ForwardInner(target)
     } else {
        currentMove = target;
     }
-    
+
     if (gameMode == EditGame || gameMode == EndOfGame) {
        whiteTimeRemaining = timeRemaining[0][currentMove];
        blackTimeRemaining = timeRemaining[1][currentMove];
@@ -8822,7 +11466,7 @@ ForwardInner(target)
     DisplayMove(currentMove - 1);
     DrawPosition(FALSE, boards[currentMove]);
     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
-    if (commentList[currentMove] && !matchMode && gameMode != Training) {
+    if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
        DisplayComment(currentMove - 1, commentList[currentMove]);
     }
 }
@@ -8846,13 +11490,13 @@ ToEndEvent()
        /* to optimze, we temporarily turn off analysis mode while we feed
         * the remaining moves to the engine. Otherwise we get analysis output
         * after each move.
-        */ 
+        */
         if (first.analysisSupport) {
          SendToProgram("exit\nforce\n", &first);
          first.analyzing = FALSE;
        }
     }
-       
+
     if (gameMode == IcsExamining && !pausing) {
         SendToICS(ics_prefix);
        SendToICS("forward 999999\n");
@@ -8887,18 +11531,18 @@ BackwardInner(target)
     }
     if (gameMode == PlayFromGameFile && !pausing)
       PauseEvent();
-    
+
     if (moveList[target][0]) {
        int fromX, fromY, toX, toY;
-       toX = moveList[target][2] - 'a';
-       toY = moveList[target][3] - '1';
+        toX = moveList[target][2] - AAA;
+        toY = moveList[target][3] - ONE;
        if (moveList[target][1] == '@') {
            if (appData.highlightLastMove) {
                SetHighlights(-1, -1, toX, toY);
            }
        } else {
-           fromX = moveList[target][0] - 'a';
-           fromY = moveList[target][1] - '1';
+            fromX = moveList[target][0] - AAA;
+            fromY = moveList[target][1] - ONE;
            if (target == currentMove - 1) {
                AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
            }
@@ -8916,7 +11560,7 @@ BackwardInner(target)
     } else {
        currentMove = target;
     }
-    
+
     if (gameMode == EditGame || gameMode == EndOfGame) {
        whiteTimeRemaining = timeRemaining[0][currentMove];
        blackTimeRemaining = timeRemaining[1][currentMove];
@@ -8925,9 +11569,8 @@ BackwardInner(target)
     DisplayMove(currentMove - 1);
     DrawPosition(full_redraw, boards[currentMove]);
     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
-    if (commentList[currentMove] != NULL) {
-       DisplayComment(currentMove - 1, commentList[currentMove]);
-    }
+    // [HGM] PV info: routine tests if comment empty
+    DisplayComment(currentMove - 1, commentList[currentMove]);
 }
 
 void
@@ -8947,7 +11590,7 @@ ToStartEvent()
     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
        /* to optimze, we temporarily turn off analysis mode while we undo
         * all the moves. Otherwise we get analysis output after each undo.
-        */ 
+        */
         if (first.analysisSupport) {
          SendToProgram("exit\nforce\n", &first);
          first.analyzing = FALSE;
@@ -8987,11 +11630,11 @@ void
 RevertEvent()
 {
     if (gameMode != IcsExamining) {
-       DisplayError("You are not examining a game", 0);
+       DisplayError(_("You are not examining a game"), 0);
        return;
     }
     if (pausing) {
-       DisplayError("You can't revert while pausing", 0);
+       DisplayError(_("You can't revert while pausing"), 0);
        return;
     }
     SendToICS(ics_prefix);
@@ -9005,7 +11648,7 @@ RetractMoveEvent()
       case MachinePlaysWhite:
       case MachinePlaysBlack:
        if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
-           DisplayError("Wait until your turn,\nor select Move Now", 0);
+           DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
            return;
        }
        if (forwardMostMove < 2) return;
@@ -9045,14 +11688,14 @@ MoveNowEvent()
     switch (gameMode) {
       case MachinePlaysWhite:
        if (!WhiteOnMove(forwardMostMove)) {
-           DisplayError("It is your turn", 0);
+           DisplayError(_("It is your turn"), 0);
            return;
        }
        cps = &first;
        break;
       case MachinePlaysBlack:
        if (WhiteOnMove(forwardMostMove)) {
-           DisplayError("It is your turn", 0);
+           DisplayError(_("It is your turn"), 0);
            return;
        }
        cps = &first;
@@ -9102,19 +11745,19 @@ HintEvent()
     switch (gameMode) {
       case MachinePlaysWhite:
        if (WhiteOnMove(forwardMostMove)) {
-           DisplayError("Wait until your turn", 0);
+           DisplayError(_("Wait until your turn"), 0);
            return;
        }
        break;
       case BeginningOfGame:
       case MachinePlaysBlack:
        if (!WhiteOnMove(forwardMostMove)) {
-           DisplayError("Wait until your turn", 0);
+           DisplayError(_("Wait until your turn"), 0);
            return;
        }
        break;
       default:
-       DisplayError("No hint available", 0);
+       DisplayError(_("No hint available"), 0);
        return;
     }
     SendToProgram("hint\n", &first);
@@ -9128,14 +11771,14 @@ BookEvent()
     switch (gameMode) {
       case MachinePlaysWhite:
        if (WhiteOnMove(forwardMostMove)) {
-           DisplayError("Wait until your turn", 0);
+           DisplayError(_("Wait until your turn"), 0);
            return;
        }
        break;
       case BeginningOfGame:
       case MachinePlaysBlack:
        if (!WhiteOnMove(forwardMostMove)) {
-           DisplayError("Wait until your turn", 0);
+           DisplayError(_("Wait until your turn"), 0);
            return;
        }
        break;
@@ -9168,12 +11811,12 @@ PrintPosition(fp, move)
      int move;
 {
     int i, j;
-    
-    for (i = BOARD_SIZE - 1; i >= 0; i--) {
-       for (j = 0; j < BOARD_SIZE; j++) {
+
+    for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
+        for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
            char c = PieceToChar(boards[move][i][j]);
            fputc(c == 'x' ? '.' : c, fp);
-           fputc(j == BOARD_SIZE - 1 ? '\n' : ' ', fp);
+            fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
        }
     }
     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
@@ -9214,6 +11857,7 @@ TidyProgramName(prog, host, buf)
     p = q;
     while (p >= prog && *p != '/' && *p != '\\') p--;
     p++;
+    if(p == prog && *p == '"') p++;
     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
     memcpy(buf, p, q - p);
     buf[q - p] = NULLCHAR;
@@ -9374,7 +12018,7 @@ AppendComment(index, text)
     int oldlen, len;
     char *old;
 
-    GetInfoFromComment( index, text );
+    text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
 
     CrushCRs(text);
     while (*text == '\n') text++;
@@ -9412,12 +12056,15 @@ static char * FindStr( char * text, char * sub_text )
 }
 
 /* [AS] Try to extract PV info from PGN comment */
-void GetInfoFromComment( int index, char * text )
+/* [HGM] PV time: and then remove it, to prevent it appearing twice */
+char *GetInfoFromComment( int index, char * text )
 {
+    char * sep = text;
+
     if( text != NULL && index > 0 ) {
         int score = 0;
         int depth = 0;
-        int time = -1;
+        int time = -1, sec = 0, deci;
         char * s_eval = FindStr( text, "[%eval " );
         char * s_emt = FindStr( text, "[%emt " );
 
@@ -9427,11 +12074,11 @@ void GetInfoFromComment( int index, char * text )
 
             if( s_eval != NULL ) {
                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
-                    return;
+                    return text;
                 }
 
                 if( delim != ']' ) {
-                    return;
+                    return text;
                 }
             }
 
@@ -9440,26 +12087,43 @@ void GetInfoFromComment( int index, char * text )
         }
         else {
             /* We expect something like: [+|-]nnn.nn/dd */
-            char * sep = strchr( text, '/' );
             int score_lo = 0;
 
+            sep = strchr( text, '/' );
             if( sep == NULL || sep < (text+4) ) {
-                return;
+                return text;
             }
 
-            if( sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
-                return;
+            time = -1; sec = -1; deci = -1;
+            if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
+               sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
+                sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
+                sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
+                return text;
             }
 
             if( score_lo < 0 || score_lo >= 100 ) {
-                return;
+                return text;
             }
 
+            if(sec >= 0) time = 600*time + 10*sec; else
+            if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
+
             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
+
+            /* [HGM] PV time: now locate end of PV info */
+            while( *++sep >= '0' && *sep <= '9'); // strip depth
+            if(time >= 0)
+            while( *++sep >= '0' && *sep <= '9'); // strip time
+            if(sec >= 0)
+            while( *++sep >= '0' && *sep <= '9'); // strip seconds
+            if(deci >= 0)
+            while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
+            while(*sep == ' ') sep++;
         }
 
         if( depth <= 0 ) {
-            return;
+            return text;
         }
 
         if( time < 0 ) {
@@ -9468,8 +12132,9 @@ void GetInfoFromComment( int index, char * text )
 
         pvInfoList[index-1].depth = depth;
         pvInfoList[index-1].score = score;
-        pvInfoList[index-1].time = time;
+        pvInfoList[index-1].time  = 10*time; // centi-sec
     }
+    return sep;
 }
 
 void
@@ -9482,20 +12147,30 @@ SendToProgram(message, cps)
 
     if (cps->pr == NULL) return;
     Attention(cps);
-    
+
     if (appData.debugMode) {
        TimeMark now;
        GetTimeMark(&now);
-       fprintf(debugFP, "%ld >%-6s: %s", 
+       fprintf(debugFP, "%ld >%-6s: %s",
                SubtractTimeMarks(&now, &programStartTime),
                cps->which, message);
     }
-    
+
     count = strlen(message);
     outCount = OutputToProcess(cps->pr, message, count, &error);
-    if (outCount < count && !exiting) {
-       sprintf(buf, "Error writing to %s chess program", cps->which);
-       DisplayFatalError(buf, error, 1);
+    if (outCount < count && !exiting
+                         && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
+       sprintf(buf, _("Error writing to %s chess program"), cps->which);
+        if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
+            if(epStatus[forwardMostMove] <= EP_DRAWS) {
+                gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
+                sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
+            } else {
+                gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
+            }
+            gameInfo.resultDetails = buf;
+        }
+        DisplayFatalError(buf, error, 1);
     }
 }
 
@@ -9515,13 +12190,22 @@ ReceiveFromProgram(isr, closure, message, count, error)
     if (count <= 0) {
        if (count == 0) {
            sprintf(buf,
-                   "Error: %s chess program (%s) exited unexpectedly",
+                   _("Error: %s chess program (%s) exited unexpectedly"),
                    cps->which, cps->program);
+        if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
+                if(epStatus[forwardMostMove] <= EP_DRAWS) {
+                    gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
+                    sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
+                } else {
+                    gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
+                }
+                gameInfo.resultDetails = buf;
+            }
            RemoveInputSource(cps->isr);
            DisplayFatalError(buf, 0, 1);
        } else {
            sprintf(buf,
-                   "Error reading from %s chess program (%s)",
+                   _("Error reading from %s chess program (%s)"),
                    cps->which, cps->program);
            RemoveInputSource(cps->isr);
 
@@ -9531,24 +12215,49 @@ ReceiveFromProgram(isr, closure, message, count, error)
                 cps->pr = NoProc;
             }
 
-           DisplayFatalError(buf, error, 1);
+            DisplayFatalError(buf, error, 1);
        }
-       GameEnds((ChessMove) 0, NULL, GE_PLAYER);
        return;
     }
-    
+
     if ((end_str = strchr(message, '\r')) != NULL)
       *end_str = NULLCHAR;
     if ((end_str = strchr(message, '\n')) != NULL)
       *end_str = NULLCHAR;
-    
+
     if (appData.debugMode) {
-       TimeMark now;
-       GetTimeMark(&now);
-       fprintf(debugFP, "%ld <%-6s: %s\n", 
-               SubtractTimeMarks(&now, &programStartTime),
-               cps->which, message);
+       TimeMark now; int print = 1;
+       char *quote = ""; char c; int i;
+
+       if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
+               char start = message[0];
+               if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
+               if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
+                  sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
+                  sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
+                  sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
+                  sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
+                  sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
+                  sscanf(message, "pong %c", &c)!=1   && start != '#')
+                       { quote = "# "; print = (appData.engineComments == 2); }
+               message[0] = start; // restore original message
+       }
+       if(print) {
+               GetTimeMark(&now);
+               fprintf(debugFP, "%ld <%-6s: %s%s\n",
+                       SubtractTimeMarks(&now, &programStartTime), cps->which,
+                       quote,
+                       message);
+       }
+    }
+
+    /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
+    if (appData.icsEngineAnalyze) {
+        if (strstr(message, "whisper") != NULL ||
+             strstr(message, "kibitz") != NULL ||
+            strstr(message, "tellics") != NULL) return;
     }
+
     HandleMachineMove(message, cps);
 }
 
@@ -9560,13 +12269,18 @@ SendTimeControl(cps, mps, tc, inc, sd, st)
      long tc;
 {
     char buf[MSG_SIZ];
-    int seconds = (tc / 1000) % 60;
+    int seconds;
 
     if( timeControl_2 > 0 ) {
         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
             tc = timeControl_2;
         }
     }
+    tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
+    inc /= cps->timeOdds;
+    st  /= cps->timeOdds;
+
+    seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
 
     if (st > 0) {
       /* Set exact time per move, normally using st command */
@@ -9605,6 +12319,23 @@ SendTimeControl(cps, mps, tc, inc, sd, st)
       }
       SendToProgram(buf, cps);
     }
+
+    if(cps->nps > 0) { /* [HGM] nps */
+       if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
+       else {
+               sprintf(buf, "nps %d\n", cps->nps);
+             SendToProgram(buf, cps);
+       }
+    }
+}
+
+ChessProgramState *WhitePlayer()
+/* [HGM] return pointer to 'first' or 'second', depending on who plays white */
+{
+    if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
+       gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
+        return &second;
+    return &first;
 }
 
 void
@@ -9626,10 +12357,19 @@ SendTimeRemaining(cps, machineWhite)
        time = blackTimeRemaining / 10;
        otime = whiteTimeRemaining / 10;
     }
+    /* [HGM] translate opponent's time by time-odds factor */
+    otime = (otime * cps->other->timeOdds) / cps->timeOdds;
+    if (appData.debugMode) {
+        fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
+    }
+
     if (time <= 0) time = 1;
     if (otime <= 0) otime = 1;
-    
-    sprintf(message, "time %ld\notim %ld\n", time, otime);
+
+    sprintf(message, "time %ld\n", time);
+    SendToProgram(message, cps);
+
+    sprintf(message, "otim %ld\n", otime);
     SendToProgram(message, cps);
 }
 
@@ -9697,6 +12437,83 @@ StringFeature(p, name, loc, cps)
   return FALSE;
 }
 
+int
+ParseOption(Option *opt, ChessProgramState *cps)
+// [HGM] options: process the string that defines an engine option, and determine
+// name, type, default value, and allowed value range
+{
+       char *p, *q, buf[MSG_SIZ];
+       int n, min = (-1)<<31, max = 1<<31, def;
+
+       if(p = strstr(opt->name, " -spin ")) {
+           if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
+           if(max < min) max = min; // enforce consistency
+           if(def < min) def = min;
+           if(def > max) def = max;
+           opt->value = def;
+           opt->min = min;
+           opt->max = max;
+           opt->type = Spin;
+       } else if((p = strstr(opt->name, " -slider "))) {
+           // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
+           if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
+           if(max < min) max = min; // enforce consistency
+           if(def < min) def = min;
+           if(def > max) def = max;
+           opt->value = def;
+           opt->min = min;
+           opt->max = max;
+           opt->type = Spin; // Slider;
+       } else if((p = strstr(opt->name, " -string "))) {
+           opt->textValue = p+9;
+           opt->type = TextBox;
+       } else if((p = strstr(opt->name, " -file "))) {
+           // for now -file is a synonym for -string, to already provide compatibility with future polyglots
+           opt->textValue = p+7;
+           opt->type = TextBox; // FileName;
+       } else if((p = strstr(opt->name, " -path "))) {
+           // for now -file is a synonym for -string, to already provide compatibility with future polyglots
+           opt->textValue = p+7;
+           opt->type = TextBox; // PathName;
+       } else if(p = strstr(opt->name, " -check ")) {
+           if(sscanf(p, " -check %d", &def) < 1) return FALSE;
+           opt->value = (def != 0);
+           opt->type = CheckBox;
+       } else if(p = strstr(opt->name, " -combo ")) {
+           opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
+           cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
+           if(*q == '*') cps->comboList[cps->comboCnt-1]++;
+           opt->value = n = 0;
+           while(q = StrStr(q, " /// ")) {
+               n++; *q = 0;    // count choices, and null-terminate each of them
+               q += 5;
+               if(*q == '*') { // remember default, which is marked with * prefix
+                   q++;
+                   opt->value = n;
+               }
+               cps->comboList[cps->comboCnt++] = q;
+           }
+           cps->comboList[cps->comboCnt++] = NULL;
+           opt->max = n + 1;
+           opt->type = ComboBox;
+       } else if(p = strstr(opt->name, " -button")) {
+           opt->type = Button;
+       } else if(p = strstr(opt->name, " -save")) {
+           opt->type = SaveButton;
+       } else return FALSE;
+       *p = 0; // terminate option name
+       // now look if the command-line options define a setting for this engine option.
+       if(cps->optionSettings && cps->optionSettings[0])
+           p = strstr(cps->optionSettings, opt->name); else p = NULL;
+       if(p && (p == cps->optionSettings || p[-1] == ',')) {
+               sprintf(buf, "option %s", p);
+               if(p = strstr(buf, ",")) *p = 0;
+               strcat(buf, "\n");
+               SendToProgram(buf, cps);
+       }
+       return TRUE;
+}
+
 void
 FeatureDone(cps, val)
      ChessProgramState* cps;
@@ -9715,7 +12532,7 @@ FeatureDone(cps, val)
 void
 ParseFeatures(args, cps)
      char* args;
-     ChessProgramState *cps;  
+     ChessProgramState *cps;
 {
   char *p = args;
   char *q;
@@ -9727,10 +12544,10 @@ ParseFeatures(args, cps)
     if (*p == NULLCHAR) return;
 
     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
-    if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
-    if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
-    if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
-    if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
+    if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
+    if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
+    if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
+    if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
     if (BoolFeature(&p, "reuse", &val, cps)) {
       /* Engine can disable reuse, but can't enable it if user said no */
       if (!val) cps->reuse = FALSE;
@@ -9763,6 +12580,29 @@ ParseFeatures(args, cps)
     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
     /* End of additions by Tord */
 
+    /* [HGM] added features: */
+    if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
+    if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
+    if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
+    if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
+    if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
+    if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
+    if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
+       if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
+           sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
+           SendToProgram(buf, cps);
+           continue;
+       }
+       if(cps->nrOptions >= MAX_OPTIONS) {
+           cps->nrOptions--;
+           sprintf(buf, "%s engine has too many options\n", cps->which);
+           DisplayError(buf, 0);
+       }
+       continue;
+    }
+    if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
+    /* End of additions by HGM */
+
     /* unknown feature: complain and skip */
     q = p;
     while (*q && *q != '=') q++;
@@ -9824,12 +12664,33 @@ PonderNextMoveEvent(newState)
 }
 
 void
-ShowThinkingEvent(newState)
-     int newState;
+NewSettingEvent(option, command, value)
+     char *command;
+     int option, value;
+{
+    char buf[MSG_SIZ];
+
+    if (gameMode == EditPosition) EditPositionDone();
+    sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
+    SendToProgram(buf, &first);
+    if (gameMode == TwoMachinesPlay) {
+       SendToProgram(buf, &second);
+    }
+}
+
+void
+ShowThinkingEvent()
+// [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
 {
-    if (newState == appData.showThinking) return;
+    static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
+    int newState = appData.showThinking
+       // [HGM] thinking: other features now need thinking output as well
+       || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
+
+    if (oldState == newState) return;
+    oldState = newState;
     if (gameMode == EditPosition) EditPositionDone();
-    if (newState) {
+    if (oldState) {
        SendToProgram("post\n", &first);
        if (gameMode == TwoMachinesPlay) {
            SendToProgram("post\n", &second);
@@ -9841,7 +12702,7 @@ ShowThinkingEvent(newState)
            SendToProgram("nopost\n", &second);
        }
     }
-    appData.showThinking = newState;
+//    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
 }
 
 void
@@ -9861,13 +12722,15 @@ DisplayMove(moveNumber)
     char res[MSG_SIZ];
     char cpThinkOutput[MSG_SIZ];
 
-    if (moveNumber == forwardMostMove - 1 || 
+    if(appData.noGUI) return; // [HGM] fast: suppress display of moves
+
+    if (moveNumber == forwardMostMove - 1 ||
        gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
 
        safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
 
         if (strchr(cpThinkOutput, '\n')) {
-         *strchr(cpThinkOutput, '\n') = NULLCHAR;
+           *strchr(cpThinkOutput, '\n') = NULLCHAR;
         }
     } else {
        *cpThinkOutput = NULLCHAR;
@@ -9898,7 +12761,7 @@ DisplayMove(moveNumber)
     } else {
        res[0] = NULLCHAR;
     }
-    
+
     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
        DisplayMessage(res, cpThinkOutput);
     } else {
@@ -9913,11 +12776,10 @@ void
 DisplayAnalysisText(text)
      char *text;
 {
-    char buf[MSG_SIZ];
-
-    if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
-       sprintf(buf, "Analysis (%s)", first.tidy);
-       AnalysisPopUp(buf, text);
+  if (gameMode == AnalyzeMode || gameMode == AnalyzeFile 
+      || appData.icsEngineAnalyze) 
+    {
+      EngineOutputPopUp();
     }
 }
 
@@ -9941,18 +12803,18 @@ DisplayAnalysis()
     double nps;
     static char *xtra[] = { "", " (--)", " (++)" };
     int h, m, s, cs;
-  
+
     if (programStats.time == 0) {
        programStats.time = 1;
     }
-  
+
     if (programStats.got_only_move) {
        safeStrCpy(buf, programStats.movelist, sizeof(buf));
     } else {
         safeStrCpy( lst, programStats.movelist, sizeof(lst));
 
-       nps = (((double)programStats.nodes) /
-              (((double)programStats.time)/100.0));
+        nps = (u64ToDouble(programStats.nodes) /
+             ((double)programStats.time /100.0));
 
        cs = programStats.time % 100;
        s = programStats.time / 100;
@@ -9963,32 +12825,32 @@ DisplayAnalysis()
 
        if (programStats.moves_left > 0 && appData.periodicUpdates) {
          if (programStats.move_name[0] != NULLCHAR) {
-           sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",
+           sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
                    programStats.depth,
                    programStats.nr_moves-programStats.moves_left,
                    programStats.nr_moves, programStats.move_name,
                    ((float)programStats.score)/100.0, lst,
                    only_one_move(lst)?
                    xtra[programStats.got_fail] : "",
-                   programStats.nodes, (int)nps, h, m, s, cs);
+                   (u64)programStats.nodes, (int)nps, h, m, s, cs);
          } else {
-           sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",
+           sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
                    programStats.depth,
                    programStats.nr_moves-programStats.moves_left,
                    programStats.nr_moves, ((float)programStats.score)/100.0,
                    lst,
                    only_one_move(lst)?
                    xtra[programStats.got_fail] : "",
-                   programStats.nodes, (int)nps, h, m, s, cs);
+                   (u64)programStats.nodes, (int)nps, h, m, s, cs);
          }
        } else {
-           sprintf(buf, "depth=%d %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",
+           sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
                    programStats.depth,
                    ((float)programStats.score)/100.0,
                    lst,
                    only_one_move(lst)?
                    xtra[programStats.got_fail] : "",
-                   programStats.nodes, (int)nps, h, m, s, cs);
+                   (u64)programStats.nodes, (int)nps, h, m, s, cs);
        }
     }
     DisplayAnalysisText(buf);
@@ -10000,18 +12862,29 @@ DisplayComment(moveNumber, text)
      char *text;
 {
     char title[MSG_SIZ];
+    char buf[8000]; // comment can be long!
+    int score, depth;
 
     if( appData.autoDisplayComment ) {
-    if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
-       strcpy(title, "Comment");
-    } else {
-       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
-               WhiteOnMove(moveNumber) ? " " : ".. ",
-               parseList[moveNumber]);
-    }
+        if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
+           strcpy(title, "Comment");
+        } else {
+           sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
+                   WhiteOnMove(moveNumber) ? " " : ".. ",
+                   parseList[moveNumber]);
+        }
+       // [HGM] PV info: display PV info together with (or as) comment
+       if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
+           if(text == NULL) text = "";
+           score = pvInfoList[moveNumber].score;
+           sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
+                              depth, (pvInfoList[moveNumber].time+50)/100, text);
+           text = buf;
+       }
+    } else title[0] = 0;
 
-    CommentPopUp(title, text);
-    }
+    if (text != NULL)
+        CommentPopUp(title, text);
 }
 
 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
@@ -10062,9 +12935,9 @@ CheckFlags()
                }
            } else {
                if (blackFlag) {
-                   DisplayTitle("Both flags fell");
+                    if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
                } else {
-                   DisplayTitle("White's flag fell");
+                    if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
                    if (appData.autoCallFlag) {
                        GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
                        return TRUE;
@@ -10084,9 +12957,9 @@ CheckFlags()
                }
            } else {
                if (whiteFlag) {
-                   DisplayTitle("Both flags fell");
+                    if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
                } else {
-                   DisplayTitle("Black's flag fell");
+                    if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
                    if (appData.autoCallFlag) {
                        GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
                        return TRUE;
@@ -10104,30 +12977,18 @@ CheckTimeControl()
     if (!appData.clockMode || appData.icsActive ||
        gameMode == PlayFromGameFile || forwardMostMove == 0) return;
 
-    if (timeIncrement >= 0) {
-       if (WhiteOnMove(forwardMostMove)) {
-           blackTimeRemaining += timeIncrement;
-       } else {
-           whiteTimeRemaining += timeIncrement;
-       }
-    }
     /*
-     * add time to clocks when time control is achieved
+     * add time to clocks when time control is achieved ([HGM] now also used for increment)
      */
-    if (movesPerSession) {
-      switch ((forwardMostMove + 1) % (movesPerSession * 2)) {
-      case 0:
+    if ( !WhiteOnMove(forwardMostMove) )
        /* White made time control */
-       whiteTimeRemaining += GetTimeControlForWhite();
-       break;
-      case 1:
+        whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
+        /* [HGM] time odds: correct new time quota for time odds! */
+                                            / WhitePlayer()->timeOdds;
+      else
        /* Black made time control */
-       blackTimeRemaining += GetTimeControlForBlack();
-       break;
-      default:
-       break;
-      }
-    }
+        blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
+                                            / WhitePlayer()->other->timeOdds;
 }
 
 void
@@ -10146,6 +13007,11 @@ DisplayBothClocks()
    you have neither ftime nor gettimeofday.
 */
 
+/* VS 2008 requires the #include outside of the function */
+#if !HAVE_GETTIMEOFDAY && HAVE_FTIME
+#include <sys/timeb.h>
+#endif
+
 /* Get the current time as a TimeMark */
 void
 GetTimeMark(tm)
@@ -10157,13 +13023,13 @@ GetTimeMark(tm)
     struct timezone timeZone;
 
     gettimeofday(&timeVal, &timeZone);
-    tm->sec = (long) timeVal.tv_sec; 
+    tm->sec = (long) timeVal.tv_sec;
     tm->ms = (int) (timeVal.tv_usec / 1000L);
 
 #else /*!HAVE_GETTIMEOFDAY*/
 #if HAVE_FTIME
 
-#include <sys/timeb.h>
+// include <sys/timeb.h> / moved to just above start of function
     struct timeb timeB;
 
     ftime(&timeB);
@@ -10196,7 +13062,7 @@ SubtractTimeMarks(tm2, tm1)
  * We give the human user a slight advantage if he is playing white---the
  * clocks don't run until he makes his first move, so it takes zero time.
  * Also, we don't account for network lag, so we could get out of sync
- * with GNU Chess's clock -- but then, referees are always right.  
+ * with GNU Chess's clock -- but then, referees are always right.
  */
 
 static TimeMark tickStartTM;
@@ -10218,16 +13084,25 @@ NextTickLength(timeRemaining)
     return nextTickLength;
 }
 
+/* Adjust clock one minute up or down */
+void
+AdjustClock(Boolean which, int dir)
+{
+    if(which) blackTimeRemaining += 60000*dir;
+    else      whiteTimeRemaining += 60000*dir;
+    DisplayBothClocks();
+}
+
 /* Stop clocks and reset to a fresh time control */
 void
-ResetClocks() 
+ResetClocks()
 {
     (void) StopClockTimer();
     if (appData.icsActive) {
        whiteTimeRemaining = blackTimeRemaining = 0;
-    } else {
-       whiteTimeRemaining = GetTimeControlForWhite();
-        blackTimeRemaining = GetTimeControlForBlack();
+    } else { /* [HGM] correct new time quote for time odds */
+        whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
+        blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
     }
     if (whiteFlag || blackFlag) {
        DisplayTitle("");
@@ -10248,7 +13123,7 @@ DecrementClocks()
 
     if (!appData.clockMode) return;
     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
-       
+
     GetTimeMark(&now);
 
     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
@@ -10258,17 +13133,19 @@ DecrementClocks()
     if (fudge < 0 || fudge > FUDGE) fudge = 0;
 
     if (WhiteOnMove(forwardMostMove)) {
+       if(whiteNPS >= 0) lastTickLength = 0;
        timeRemaining = whiteTimeRemaining -= lastTickLength;
        DisplayWhiteClock(whiteTimeRemaining - fudge,
                          WhiteOnMove(currentMove));
     } else {
+       if(blackNPS >= 0) lastTickLength = 0;
        timeRemaining = blackTimeRemaining -= lastTickLength;
        DisplayBlackClock(blackTimeRemaining - fudge,
                          !WhiteOnMove(currentMove));
     }
 
     if (CheckFlags()) return;
-       
+
     tickStartTM = now;
     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
     StartClockTimer(intendedTickLength);
@@ -10276,9 +13153,9 @@ DecrementClocks()
     /* if the time remaining has fallen below the alarm threshold, sound the
      * alarm. if the alarm has sounded and (due to a takeback or time control
      * with increment) the time remaining has increased to a level above the
-     * threshold, reset the alarm so it can sound again. 
+     * threshold, reset the alarm so it can sound again.
      */
-    
+
     if (appData.icsActive && appData.icsAlarm) {
 
        /* make sure we are dealing with the user's clock */
@@ -10288,7 +13165,7 @@ DecrementClocks()
 
        if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
            alarmSounded = FALSE;
-       } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
+       } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
            PlayAlarmSound();
            alarmSounded = TRUE;
        }
@@ -10316,9 +13193,19 @@ SwitchClocks()
     if (StopClockTimer() && appData.clockMode) {
        lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
        if (WhiteOnMove(forwardMostMove)) {
+           if(blackNPS >= 0) lastTickLength = 0;
            blackTimeRemaining -= lastTickLength;
+           /* [HGM] PGNtime: save time for PGN file if engine did not give it */
+//         if(pvInfoList[forwardMostMove-1].time == -1)
+                 pvInfoList[forwardMostMove-1].time =               // use GUI time
+                      (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
        } else {
-           whiteTimeRemaining -= lastTickLength;
+          if(whiteNPS >= 0) lastTickLength = 0;
+          whiteTimeRemaining -= lastTickLength;
+           /* [HGM] PGNtime: save time for PGN file if engine did not give it */
+//         if(pvInfoList[forwardMostMove-1].time == -1)
+                 pvInfoList[forwardMostMove-1].time =
+                      (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
        }
        flagged = CheckFlags();
     }
@@ -10347,12 +13234,12 @@ SwitchClocks()
       whiteTimeRemaining : blackTimeRemaining);
     StartClockTimer(intendedTickLength);
 }
-       
+
 
 /* Stop both clocks */
 void
 StopClocks()
-{      
+{
     long lastTickLength;
     TimeMark now;
 
@@ -10363,15 +13250,17 @@ StopClocks()
 
     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
     if (WhiteOnMove(forwardMostMove)) {
+       if(whiteNPS >= 0) lastTickLength = 0;
        whiteTimeRemaining -= lastTickLength;
        DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
     } else {
+       if(blackNPS >= 0) lastTickLength = 0;
        blackTimeRemaining -= lastTickLength;
        DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
     }
     CheckFlags();
 }
-       
+
 /* Start clock of player on move.  Time may have been reset, so
    if clock is already running, stop and restart it. */
 void
@@ -10387,6 +13276,21 @@ StartClocks()
     GetTimeMark(&tickStartTM);
     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
       whiteTimeRemaining : blackTimeRemaining);
+
+   /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
+    whiteNPS = blackNPS = -1;
+    if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
+       || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
+       whiteNPS = first.nps;
+    if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
+       || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
+       blackNPS = first.nps;
+    if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
+       whiteNPS = second.nps;
+    if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
+       blackNPS = second.nps;
+    if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
+
     StartClockTimer(intendedTickLength);
 }
 
@@ -10397,7 +13301,7 @@ TimeString(ms)
     long second, minute, hour, day;
     char *sign = "";
     static char buf[32];
-    
+
     if (ms > 0 && ms <= 9900) {
       /* convert milliseconds to tenths, rounding up */
       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
@@ -10415,14 +13319,14 @@ TimeString(ms)
        sign = "-";
        second = -second;
     }
-    
+
     day = second / (60 * 60 * 24);
     second = second % (60 * 60 * 24);
     hour = second / (60 * 60);
     second = second % (60 * 60);
     minute = second / 60;
     second = second % 60;
-    
+
     if (day > 0)
       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
              sign, day, hour, minute, second);
@@ -10430,7 +13334,7 @@ TimeString(ms)
       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
     else
       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
-    
+
     return buf;
 }
 
@@ -10443,13 +13347,13 @@ StrStr(string, match)
      char *string, *match;
 {
     int i, length;
-    
+
     length = strlen(match);
-    
+
     for (i = strlen(string) - length; i >= 0; i--, string++)
       if (!strncmp(match, string, length))
        return string;
-    
+
     return NULL;
 }
 
@@ -10458,9 +13362,9 @@ StrCaseStr(string, match)
      char *string, *match;
 {
     int i, j, length;
-    
+
     length = strlen(match);
-    
+
     for (i = strlen(string) - length; i >= 0; i--, string++) {
        for (j = 0; j < length; j++) {
            if (ToLower(match[j]) != ToLower(string[j]))
@@ -10478,7 +13382,7 @@ StrCaseCmp(s1, s2)
      char *s1, *s2;
 {
     char c1, c2;
-    
+
     for (;;) {
        c1 = ToLower(*s1++);
        c2 = ToLower(*s2++);
@@ -10546,179 +13450,236 @@ PGNDate()
 
 
 char *
-PositionToFEN(move, useFEN960)
+PositionToFEN(move, overrideCastling)
      int move;
-     int useFEN960;
+     char *overrideCastling;
 {
     int i, j, fromX, fromY, toX, toY;
     int whiteToPlay;
     char buf[128];
     char *p, *q;
     int emptycount;
+    ChessSquare piece;
 
     whiteToPlay = (gameMode == EditPosition) ?
       !blackPlaysFirst : (move % 2 == 0);
     p = buf;
 
     /* Piece placement data */
-    for (i = BOARD_SIZE - 1; i >= 0; i--) {
+    for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
        emptycount = 0;
-       for (j = 0; j < BOARD_SIZE; j++) {
+        for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
            if (boards[move][i][j] == EmptySquare) {
                emptycount++;
-           } else {
+            } else { ChessSquare piece = boards[move][i][j];
                if (emptycount > 0) {
-                   *p++ = '0' + emptycount;
+                    if(emptycount<10) /* [HGM] can be >= 10 */
+                        *p++ = '0' + emptycount;
+                    else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
                    emptycount = 0;
                }
-               *p++ = PieceToChar(boards[move][i][j]);
+                if(PieceToChar(piece) == '+') {
+                    /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
+                    *p++ = '+';
+                    piece = (ChessSquare)(DEMOTED piece);
+                }
+                *p++ = PieceToChar(piece);
+                if(p[-1] == '~') {
+                    /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
+                    p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
+                    *p++ = '~';
+                }
            }
        }
        if (emptycount > 0) {
-           *p++ = '0' + emptycount;
+            if(emptycount<10) /* [HGM] can be >= 10 */
+                *p++ = '0' + emptycount;
+            else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
            emptycount = 0;
        }
        *p++ = '/';
     }
     *(p - 1) = ' ';
 
-    /* Active color */
-    *p++ = whiteToPlay ? 'w' : 'b';
-    *p++ = ' ';
-
-    /* HACK: we don't keep track of castling availability, so fake it! */
-
-    /* PUSH Fabien & Tord */
-
-    /* Declare all potential FRC castling rights (conservative) */
-    /* outermost rook on each side of the king */
-
-    if( gameInfo.variant == VariantFischeRandom ) {
-       int fk, fr;
-
-       q = p;
-
-       /* White castling rights */
-
-       for (fk = 1; fk < 7; fk++) {
-
-          if (boards[move][0][fk] == WhiteKing) {
-
-             for (fr = 7; fr > fk; fr--) { /* H side */
-                if (boards[move][0][fr] == WhiteRook) {
-                   *p++ = useFEN960 ? 'A' + fr : 'K';
-                   break;
-                }
-             }
-
-             for (fr = 0; fr < fk; fr++) { /* A side */
-                if (boards[move][0][fr] == WhiteRook) {
-                   *p++ = useFEN960 ? 'A' + fr : 'Q';
-                   break;
-                }
-             }
-          }
-       }
-
-       /* Black castling rights */
-
-       for (fk = 1; fk < 7; fk++) {
-
-          if (boards[move][7][fk] == BlackKing) {
-
-             for (fr = 7; fr > fk; fr--) { /* H side */
-                if (boards[move][7][fr] == BlackRook) {
-                   *p++ = useFEN960 ? 'a' + fr : 'k';
-                   break;
-                }
-             }
-
-             for (fr = 0; fr < fk; fr++) { /* A side */
-                if (boards[move][7][fr] == BlackRook) {
-                   *p++ = useFEN960 ? 'a' + fr : 'q';
-                   break;
-                }
-             }
-          }
-       }
+    /* [HGM] print Crazyhouse or Shogi holdings */
+    if( gameInfo.holdingsWidth ) {
+        *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
+        q = p;
+        for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
+            piece = boards[move][i][BOARD_WIDTH-1];
+            if( piece != EmptySquare )
+              for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
+                  *p++ = PieceToChar(piece);
+        }
+        for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
+            piece = boards[move][BOARD_HEIGHT-i-1][0];
+            if( piece != EmptySquare )
+              for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
+                  *p++ = PieceToChar(piece);
+        }
 
-       if (q == p) *p++ = '-'; /* No castling rights */
-       *p++ = ' ';
+        if( q == p ) *p++ = '-';
+        *p++ = ']';
+        *p++ = ' ';
     }
-    else {
-    q = p;
-    if (boards[move][0][4] == WhiteKing) {
-       if (boards[move][0][7] == WhiteRook) *p++ = 'K';
-       if (boards[move][0][0] == WhiteRook) *p++ = 'Q';
-    }
-    if (boards[move][7][4] == BlackKing) {
-       if (boards[move][7][7] == BlackRook) *p++ = 'k';
-       if (boards[move][7][0] == BlackRook) *p++ = 'q';
-    }      
-    if (q == p) *p++ = '-';
+
+    /* Active color */
+    *p++ = whiteToPlay ? 'w' : 'b';
     *p++ = ' ';
-    }
 
-    /* POP Fabien & Tord */
+  if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
+    while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
+  } else {
+  if(nrCastlingRights) {
+     q = p;
+     if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
+       /* [HGM] write directly from rights */
+           if(castlingRights[move][2] >= 0 &&
+              castlingRights[move][0] >= 0   )
+                *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
+           if(castlingRights[move][2] >= 0 &&
+              castlingRights[move][1] >= 0   )
+                *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
+           if(castlingRights[move][5] >= 0 &&
+              castlingRights[move][3] >= 0   )
+                *p++ = castlingRights[move][3] + AAA;
+           if(castlingRights[move][5] >= 0 &&
+              castlingRights[move][4] >= 0   )
+                *p++ = castlingRights[move][4] + AAA;
+     } else {
+
+        /* [HGM] write true castling rights */
+        if( nrCastlingRights == 6 ) {
+            if(castlingRights[move][0] == BOARD_RGHT-1 &&
+               castlingRights[move][2] >= 0  ) *p++ = 'K';
+            if(castlingRights[move][1] == BOARD_LEFT &&
+               castlingRights[move][2] >= 0  ) *p++ = 'Q';
+            if(castlingRights[move][3] == BOARD_RGHT-1 &&
+               castlingRights[move][5] >= 0  ) *p++ = 'k';
+            if(castlingRights[move][4] == BOARD_LEFT &&
+               castlingRights[move][5] >= 0  ) *p++ = 'q';
+        }
+     }
+     if (q == p) *p++ = '-'; /* No castling rights */
+     *p++ = ' ';
+  }
 
+  if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
+     gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
     /* En passant target square */
     if (move > backwardMostMove) {
-       fromX = moveList[move - 1][0] - 'a';
-       fromY = moveList[move - 1][1] - '1';
-       toX = moveList[move - 1][2] - 'a';
-       toY = moveList[move - 1][3] - '1';
-       if (fromY == (whiteToPlay ? 6 : 1) &&
-           toY == (whiteToPlay ? 4 : 3) &&
+        fromX = moveList[move - 1][0] - AAA;
+        fromY = moveList[move - 1][1] - ONE;
+        toX = moveList[move - 1][2] - AAA;
+        toY = moveList[move - 1][3] - ONE;
+       if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
+           toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
            boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
            fromX == toX) {
            /* 2-square pawn move just happened */
-           *p++ = toX + 'a';
-           *p++ = whiteToPlay ? '6' : '3';
+            *p++ = toX + AAA;
+           *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
+       } else {
+           *p++ = '-';
+       }
+    } else if(move == backwardMostMove) {
+       // [HGM] perhaps we should always do it like this, and forget the above?
+       if(epStatus[move] >= 0) {
+           *p++ = epStatus[move] + AAA;
+           *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
        } else {
            *p++ = '-';
        }
     } else {
        *p++ = '-';
     }
+    *p++ = ' ';
+  }
+  }
+
+    /* [HGM] find reversible plies */
+    {   int i = 0, j=move;
 
-    /* We don't keep track of halfmove clock for 50-move rule */
-    strcpy(p, " 0 ");
-    p += 3;
+        if (appData.debugMode) { int k;
+            fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
+            for(k=backwardMostMove; k<=forwardMostMove; k++)
+                fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
 
+        }
+
+        while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
+        if( j == backwardMostMove ) i += initialRulePlies;
+        sprintf(p, "%d ", i);
+        p += i>=100 ? 4 : i >= 10 ? 3 : 2;
+    }
     /* Fullmove number */
     sprintf(p, "%d", (move / 2) + 1);
-    
+
     return StrSave(buf);
 }
 
 Boolean
 ParseFEN(board, blackPlaysFirst, fen)
-     Board board;
+    Board board;
      int *blackPlaysFirst;
      char *fen;
 {
     int i, j;
     char *p;
     int emptycount;
+    ChessSquare piece;
 
     p = fen;
 
+    /* [HGM] by default clear Crazyhouse holdings, if present */
+    if(gameInfo.holdingsWidth) {
+       for(i=0; i<BOARD_HEIGHT; i++) {
+           board[i][0]             = EmptySquare; /* black holdings */
+           board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
+           board[i][1]             = (ChessSquare) 0; /* black counts */
+           board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
+       }
+    }
+
     /* Piece placement data */
-    for (i = BOARD_SIZE - 1; i >= 0; i--) {
+    for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
        j = 0;
        for (;;) {
-           if (*p == '/' || *p == ' ') {
-               if (*p == '/') p++;
-               emptycount = BOARD_SIZE - j;
-               while (emptycount--) board[i][j++] = EmptySquare;
+            if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
+                if (*p == '/') p++;
+                emptycount = gameInfo.boardWidth - j;
+                while (emptycount--)
+                        board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
                break;
-           } else if (isdigit(*p)) {
+#if(BOARD_SIZE >= 10)
+            } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
+                p++; emptycount=10;
+                if (j + emptycount > gameInfo.boardWidth) return FALSE;
+                while (emptycount--)
+                        board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
+#endif
+            } else if (isdigit(*p)) {
                emptycount = *p++ - '0';
-               if (j + emptycount > BOARD_SIZE) return FALSE;
-               while (emptycount--) board[i][j++] = EmptySquare;
-           } else if (isalpha(*p)) {
-               if (j >= BOARD_SIZE) return FALSE;
-               board[i][j++] = CharToPiece(*p++);
+                while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
+                if (j + emptycount > gameInfo.boardWidth) return FALSE;
+                while (emptycount--)
+                        board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
+            } else if (*p == '+' || isalpha(*p)) {
+                if (j >= gameInfo.boardWidth) return FALSE;
+                if(*p=='+') {
+                    piece = CharToPiece(*++p);
+                    if(piece == EmptySquare) return FALSE; /* unknown piece */
+                    piece = (ChessSquare) (PROMOTED piece ); p++;
+                    if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
+                } else piece = CharToPiece(*p++);
+
+                if(piece==EmptySquare) return FALSE; /* unknown piece */
+                if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
+                    piece = (ChessSquare) (PROMOTED piece);
+                    if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
+                    p++;
+                }
+                board[i][(j++)+gameInfo.holdingsWidth] = piece;
            } else {
                return FALSE;
            }
@@ -10726,22 +13687,165 @@ ParseFEN(board, blackPlaysFirst, fen)
     }
     while (*p == '/' || *p == ' ') p++;
 
+    /* [HGM] look for Crazyhouse holdings here */
+    while(*p==' ') p++;
+    if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
+        if(*p == '[') p++;
+        if(*p == '-' ) *p++; /* empty holdings */ else {
+            if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
+            /* if we would allow FEN reading to set board size, we would   */
+            /* have to add holdings and shift the board read so far here   */
+            while( (piece = CharToPiece(*p) ) != EmptySquare ) {
+                *p++;
+                if((int) piece >= (int) BlackPawn ) {
+                    i = (int)piece - (int)BlackPawn;
+                   i = PieceToNumber((ChessSquare)i);
+                    if( i >= gameInfo.holdingsSize ) return FALSE;
+                    board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
+                    board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
+                } else {
+                    i = (int)piece - (int)WhitePawn;
+                   i = PieceToNumber((ChessSquare)i);
+                    if( i >= gameInfo.holdingsSize ) return FALSE;
+                    board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
+                    board[i][BOARD_WIDTH-2]++;          /* black holdings */
+                }
+            }
+        }
+        if(*p == ']') *p++;
+    }
+
+    while(*p == ' ') p++;
+
     /* Active color */
-    switch (*p) {
+    switch (*p++) {
       case 'w':
-       *blackPlaysFirst = FALSE;
+        *blackPlaysFirst = FALSE;
        break;
-      case 'b': 
+      case 'b':
        *blackPlaysFirst = TRUE;
        break;
       default:
        return FALSE;
     }
 
-    /* !!We ignore the rest of the FEN notation */
+    /* [HGM] We NO LONGER ignore the rest of the FEN notation */
+    /* return the extra info in global variiables             */
+
+    /* set defaults in case FEN is incomplete */
+    FENepStatus = EP_UNKNOWN;
+    for(i=0; i<nrCastlingRights; i++ ) {
+        FENcastlingRights[i] =
+            gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
+    }   /* assume possible unless obviously impossible */
+    if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
+    if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
+    if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
+    if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
+    if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
+    if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
+    FENrulePlies = 0;
+
+    while(*p==' ') p++;
+    if(nrCastlingRights) {
+      if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
+          /* castling indicator present, so default becomes no castlings */
+          for(i=0; i<nrCastlingRights; i++ ) {
+                 FENcastlingRights[i] = -1;
+          }
+      }
+      while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
+             (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
+             ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
+             ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
+        char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
+
+        for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
+            if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
+            if(board[0             ][i] == WhiteKing) whiteKingFile = i;
+        }
+        switch(c) {
+          case'K':
+              for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
+              FENcastlingRights[0] = i != whiteKingFile ? i : -1;
+              FENcastlingRights[2] = whiteKingFile;
+              break;
+          case'Q':
+              for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
+              FENcastlingRights[1] = i != whiteKingFile ? i : -1;
+              FENcastlingRights[2] = whiteKingFile;
+              break;
+          case'k':
+              for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
+              FENcastlingRights[3] = i != blackKingFile ? i : -1;
+              FENcastlingRights[5] = blackKingFile;
+              break;
+          case'q':
+              for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
+              FENcastlingRights[4] = i != blackKingFile ? i : -1;
+              FENcastlingRights[5] = blackKingFile;
+          case '-':
+              break;
+          default: /* FRC castlings */
+              if(c >= 'a') { /* black rights */
+                  for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
+                    if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
+                  if(i == BOARD_RGHT) break;
+                  FENcastlingRights[5] = i;
+                  c -= AAA;
+                  if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
+                     board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
+                  if(c > i)
+                      FENcastlingRights[3] = c;
+                  else
+                      FENcastlingRights[4] = c;
+              } else { /* white rights */
+                  for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
+                    if(board[0][i] == WhiteKing) break;
+                  if(i == BOARD_RGHT) break;
+                  FENcastlingRights[2] = i;
+                  c -= AAA - 'a' + 'A';
+                  if(board[0][c] >= WhiteKing) break;
+                  if(c > i)
+                      FENcastlingRights[0] = c;
+                  else
+                      FENcastlingRights[1] = c;
+              }
+        }
+      }
+    if (appData.debugMode) {
+        fprintf(debugFP, "FEN castling rights:");
+        for(i=0; i<nrCastlingRights; i++)
+        fprintf(debugFP, " %d", FENcastlingRights[i]);
+        fprintf(debugFP, "\n");
+    }
+
+      while(*p==' ') p++;
+    }
+
+    /* read e.p. field in games that know e.p. capture */
+    if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
+       gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
+      if(*p=='-') {
+        p++; FENepStatus = EP_NONE;
+      } else {
+         char c = *p++ - AAA;
+
+         if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
+         if(*p >= '0' && *p <='9') *p++;
+         FENepStatus = c;
+      }
+    }
+
+
+    if(sscanf(p, "%d", &i) == 1) {
+        FENrulePlies = i; /* 50-move ply counter */
+        /* (The move number is still ignored)    */
+    }
+
     return TRUE;
 }
-      
+
 void
 EditPositionPasteFEN(char *fen)
 {
@@ -10749,13 +13853,20 @@ EditPositionPasteFEN(char *fen)
     Board initial_position;
 
     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
-      DisplayError("Bad FEN position in clipboard", 0);
+      DisplayError(_("Bad FEN position in clipboard"), 0);
       return ;
     } else {
       int savedBlackPlaysFirst = blackPlaysFirst;
       EditPositionEvent();
       blackPlaysFirst = savedBlackPlaysFirst;
       CopyBoard(boards[0], initial_position);
+          /* [HGM] copy FEN attributes as well */
+          {   int i;
+              initialRulePlies = FENrulePlies;
+              epStatus[0] = FENepStatus;
+              for( i=0; i<nrCastlingRights; i++ )
+                  castlingRights[0][i] = FENcastlingRights[i];
+          }
       EditPositionDone();
       DisplayBothClocks();
       DrawPosition(FALSE, boards[currentMove]);