worked on premove bug
[xboard.git] / backend.c
index 99c9fd1..304acc2 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$
  *
- * 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
+#include <windows.h>
+
+#define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
+
+#else
+
+#define DoSleep( n ) if( (n) >= 0) sleep(n)
+
+#endif
 
 #include "config.h"
 
+#include <assert.h>
 #include <stdio.h>
 #include <ctype.h>
 #include <errno.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <math.h>
+#include <ctype.h>
 
 #if STDC_HEADERS
 # include <stdlib.h>
@@ -107,15 +125,15 @@ 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
+#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 */
@@ -124,23 +142,6 @@ 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 */
-  u64 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));
@@ -150,7 +151,6 @@ 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));
@@ -158,10 +158,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));
@@ -211,15 +211,40 @@ 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));
-double u64ToDouble P((u64 value));
+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);
 
 #ifdef WIN32
-       extern void ConsoleCreate();
+       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
+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 +260,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
@@ -260,6 +287,38 @@ static ChessProgramStats programStats;
 #define TN_SGA  0003
 #define TN_PORT 23
 
+/* [AS] */
+static char * safeStrCpy( char * dst, const char * src, size_t count )
+{
+    assert( dst != NULL );
+    assert( src != NULL );
+    assert( count > 0 );
+
+    strncpy( dst, src, count );
+    dst[ count-1 ] = '\0';
+    return dst;
+}
+
+#if 0
+//[HGM] for future use? Conditioned out for now to suppress warning.
+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 );
+
+    return dst;
+}
+#endif
+
 /* Some compiler can't cast u64 to double
  * This function do the job for us:
 
@@ -273,15 +332,20 @@ double
 u64ToDouble(u64 value)
 {
   double r;
-  u64 tmp = value & 0x7fffffffffffffff;
+  u64 tmp = value & u64Const(0x7fffffffffffffff);
   r = (double)(s64)tmp;
-  if (value & 0x8000000000000000)
-       r +=  9.2233720368547758080e18; /* 2^63 */
+  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)
 {
@@ -289,9 +353,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;
@@ -299,7 +365,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:
@@ -310,6 +381,15 @@ PosFlags(index)
 
 FILE *gameFileFP, *debugFP;
 
+/* 
+    [AS] Note: sometimes, the sscanf() function is used to parse the input
+    into a fixed-size buffer. Because of this, we must be prepared to
+    receive strings as long as the size of the input buffer, which is currently
+    set to 4K for Windows and 8K for the rest.
+    So, we must either allocate sufficiently large buffers here, or
+    reduce the size of the input buffer in the input reading part.
+*/
+
 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
 char thinkOutput1[MSG_SIZ*10];
@@ -348,12 +428,18 @@ InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
 GameMode gameMode = BeginningOfGame;
 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
+ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
+int hiddenThinkOutputState = 0; /* [AS] */
+int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
+int adjudicateLossPlies = 6;
 char white_holding[64], black_holding[64];
 TimeMark lastNodeCountTime;
 long lastNodeCount=0;
 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;
@@ -372,45 +458,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)
@@ -432,7 +610,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;
@@ -444,7 +622,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;
@@ -462,7 +643,7 @@ InitBackEnd1()
     if (appData.icsActive) {
        appData.matchMode = FALSE;
        appData.matchGames = 0;
-#if ZIPPY
+#if ZIPPY      
        appData.noChessProgram = !appData.zippyPlay;
 #else
        appData.zippyPlay = FALSE;
@@ -477,13 +658,24 @@ InitBackEnd1()
        appData.zippyTalk = appData.zippyPlay = FALSE;
     }
 
+    /* [AS] Initialize pv info list [HGM] and game state */
+    {
+        int i, j;
+
+        for( i=0; i<MAX_MOVES; i++ ) {
+            pvInfoList[i].depth = -1;
+            epStatus[i]=EP_NONE;
+            for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
+        }
+    }
+
     /*
      * Parse timeControl resource
      */
     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);
     }
 
@@ -498,11 +690,14 @@ 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;
+    
     first.which = "first";
     second.which = "second";
     first.maybeThinking = second.maybeThinking = FALSE;
@@ -533,6 +728,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;
@@ -554,6 +751,45 @@ InitBackEnd1()
     first.analyzing = second.analyzing = FALSE;
     first.initDone = second.initDone = FALSE;
 
+    /* New features added by Tord: */
+    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] */
+    first.isUCI = appData.firstIsUCI; /* [AS] */
+    second.isUCI = appData.secondIsUCI; /* [AS] */
+    first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
+    second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
+
     if (appData.firstProtocolVersion > PROTOVER ||
        appData.firstProtocolVersion < 1) {
       char buf[MSG_SIZ];
@@ -580,7 +816,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
@@ -590,21 +826,25 @@ 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 {
+#if 0
        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);
+       while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] backslash added */
+       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING + (q - p));
+       sprintf(programVersion, "%s + ", PACKAGE_STRING);
        strncat(programVersion, p, q - p);
+#else
+       /* [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);
+#endif
     }
 
     if (!appData.icsActive) {
@@ -617,7 +857,7 @@ InitBackEnd1()
       switch (variant) {
       case VariantBughouse:     /* need four players and two boards */
       case VariantKriegspiel:   /* need to hide pieces and move details */
-      case VariantFischeRandom: /* castling doesn't work, shuffle not done */
+      /* case VariantFischeRandom: (Fabien: moved below) */
        sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
        DisplayFatalError(buf, 0, 2);
        return;
@@ -637,11 +877,21 @@ InitBackEnd1()
        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 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,
@@ -651,10 +901,107 @@ 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 )
+{
+    int result = -1;
+    char * s = *str;
+
+    while( *s == ' ' || *s == '\t' ) {
+        s++;
+    }
+
+    *value = 0;
+
+    if( *s >= '0' && *s <= '9' ) {
+        while( *s >= '0' && *s <= '9' ) {
+            *value = *value * 10 + (*s - '0');
+            s++;
+        }
+
+        result = 0;
+    }
+
+    *str = s;
+
+    return result;
+}
+
+int NextTimeControlFromString( char ** str, long * value )
+{
+    long temp;
+    int result = NextIntegerFromString( str, &temp );
+
+    if( result == 0 ) {
+        *value = temp * 60; /* Minutes */
+        if( **str == ':' ) {
+            (*str)++;
+            result = NextIntegerFromString( str, &temp );
+            *value += temp; /* Seconds */
+        }
+    }
+
+    return result;
+}
+
+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 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(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 0; // no new time quota on this move
 }
 
 int
@@ -663,6 +1010,7 @@ ParseTimeControl(tc, ti, mps)
      int ti;
      int mps;
 {
+#if 0
     int matched, min, sec;
 
     matched = sscanf(tc, "%d:%d", &min, &sec);
@@ -673,6 +1021,51 @@ ParseTimeControl(tc, ti, mps)
     } else {
        return FALSE;
     }
+#else
+    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;
+        }
+
+        if( tc2 == 0 ) {
+            return FALSE;
+        }
+
+        timeControl_2 = tc2 * 1000;
+    }
+    else {
+        timeControl_2 = 0;
+    }
+
+    if( tc1 == 0 ) {
+        return FALSE;
+    }
+
+    timeControl = tc1 * 1000;
+#endif
 
     if (ti >= 0) {
        timeIncrement = ti * 1000;  /* convert to ms */
@@ -696,13 +1089,19 @@ InitBackEnd2()
     } 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");
+      DisplayMessage("", _("Starting chess program"));
       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
     }
 }
@@ -714,21 +1113,21 @@ InitBackEnd3 P((void))
     char buf[MSG_SIZ];
     int err;
 
-    InitChessProgram(&first);
+    InitChessProgram(&first, startedFromSetupPosition);
 
-    #ifdef WIN32
-               /* Make a console window if needed */
-               if (appData.icsActive) ConsoleCreate();
-       #endif
 
     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);
@@ -751,7 +1150,7 @@ InitBackEnd3 P((void))
        cmailISR =
          AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
     }
-
+    
     ThawUI();
     DisplayMessage("", "");
     if (StrCaseCmp(appData.initialMode, "") == 0) {
@@ -759,7 +1158,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) {
@@ -788,15 +1187,19 @@ InitBackEnd3 P((void))
        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);
                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);
                return;
@@ -822,6 +1225,19 @@ 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) {
@@ -834,7 +1250,8 @@ InitBackEnd3 P((void))
          }
          AnalyzeModeEvent();
        } else if (initialMode == AnalyzeFile) {
-         ShowThinkingEvent(TRUE);
+         appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
+         ShowThinkingEvent();
          AnalyzeFileEvent();
          AnalysisPeriodicEvent(1);
        } else if (initialMode == MachinePlaysWhite) {
@@ -905,19 +1322,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);
            }
@@ -1027,7 +1444,7 @@ 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);
@@ -1039,6 +1456,13 @@ read_from_player(isr, closure, message, count, error)
 }
 
 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);
+}
+
+void
 SendToICS(s)
      char *s;
 {
@@ -1080,7 +1504,7 @@ SendToICSDelayed(s,msdelay)
 
 
 /* Remove all highlighting escape sequences in s
-   Also deletes any suffix starting with '('
+   Also deletes any suffix starting with '(' 
    */
 char *
 StripHighlightAndTitle(s)
@@ -1150,6 +1574,12 @@ StringToVariant(e)
 
     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++ != '_');
+    }
+
     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
       if (StrCaseStr(e, variantNames[i])) {
        v = (VariantClass) i;
@@ -1160,7 +1590,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"))) {
@@ -1237,7 +1668,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;
@@ -1263,14 +1694,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;
@@ -1303,7 +1775,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;
@@ -1447,6 +1919,134 @@ 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;
+   int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
+//   Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
+
+   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;
+            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);
+
+        /* [HGM] The following should definitely be solved in a better way */
+#if 0
+        CopyBoard(board, tempBoard); /* save position in case it is board[0] */
+        for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];
+        saveEP = epStatus[0];
+#endif
+        InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
+#if 0
+        epStatus[0] = saveEP;
+        for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];
+        CopyBoard(tempBoard, board); /* restore position received from ICS   */
+#endif
+    } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
+
+    forwardMostMove = oldForwardMostMove;
+    backwardMostMove = oldBackwardMostMove;
+    currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
+}
+
 static int loggedOn = FALSE;
 
 /*-- Game start info cache: --*/
@@ -1458,6 +2058,9 @@ static int player1Rating = -1;
 static int player2Rating = -1;
 /*----------------------------*/
 
+ColorClass curColor = ColorNormal;
+int suppressKibitz = 0;
+
 void
 read_from_ics(isr, closure, data, count, error)
      InputSourceRef isr;
@@ -1475,13 +2078,12 @@ 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;
     static char buf[BUF_SIZE + 1];
     static int firstTime = TRUE, intfSet = FALSE;
-    static ColorClass curColor = ColorNormal;
     static ColorClass prevColor = ColorNormal;
     static int savingComment = FALSE;
     char str[500];
@@ -1489,8 +2091,10 @@ read_from_ics(isr, closure, data, count, error)
     int buf_len;
     int next_out;
     int tkind;
-    int backup;
+    int backup;    /* [DM] For zippy color lines */
     char *p;
+    char talker[MSG_SIZ]; // [HGM] chat
+    int channel;
 
     if (appData.debugMode) {
       if (!error) {
@@ -1500,6 +2104,10 @@ read_from_ics(isr, closure, data, count, error)
       }
     }
 
+    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. */
@@ -1513,12 +2121,17 @@ 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(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
+               buf[buf_len++] = ' '; // replace by 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
@@ -1630,12 +2243,12 @@ 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,
@@ -1662,7 +2275,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 */
@@ -1719,7 +2365,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;
            }
@@ -1743,32 +2389,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) {
-                /* Backup address for color zippy lines */
-                backup = i;
+                /* [DM] Backup address for color zippy lines */
+                backup = i;
 #if ZIPPY
-       #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;
+       #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
 #endif
-           }
-               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") ||
@@ -1950,14 +2664,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;
@@ -2040,7 +2754,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;
@@ -2050,11 +2764,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;
            }
 
@@ -2094,11 +2808,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:
@@ -2113,15 +2827,22 @@ 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 0
                                if (first.useColors) {
                                  SendToProgram("white\ngo\n", &first);
                                } else {
                                  SendToProgram("go\n", &first);
                                }
+#else
+                               if (first.useColors) {
+                                 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
+#endif
                                first.maybeThinking = TRUE;
                            } else {
                                if (first.usePlayother) {
@@ -2142,11 +2863,18 @@ read_from_ics(isr, closure, data, count, error)
                                  }
                                  SendTimeRemaining(&first, FALSE);
                                }
+#if 0
                                if (first.useColors) {
                                  SendToProgram("black\ngo\n", &first);
                                } else {
                                  SendToProgram("go\n", &first);
                                }
+#else
+                               if (first.useColors) {
+                                 SendToProgram("black\n", &first);
+                               }
+                               bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
+#endif
                                first.maybeThinking = TRUE;
                            } else {
                                if (first.usePlayother) {
@@ -2159,7 +2887,7 @@ read_from_ics(isr, closure, data, count, error)
                                  firstMove = TRUE;
                                }
                            }
-                       }
+                       }                       
                    }
 #endif
                    if (gameMode == IcsObserving && ics_gamenum == -1) {
@@ -2199,16 +2927,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
@@ -2241,7 +2980,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);
@@ -2271,25 +3010,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;
                }
            }
@@ -2334,7 +3074,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).
                */
@@ -2344,14 +3084,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. *(*)) *}*"))){
@@ -2427,6 +3167,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*/
@@ -2439,11 +3180,11 @@ read_from_ics(isr, closure, data, count, error)
                if (gameMode == IcsObserving &&
                    atoi(star_match[0]) == ics_gamenum)
                  {
-                         /* icsEngineAnalyze */
-                         if (appData.icsEngineAnalyze) {
-                            ExitAnalyzeMode();
-                                ModeHighlight();
-                         }
+                      /* icsEngineAnalyze */
+                      if (appData.icsEngineAnalyze) {
+                            ExitAnalyzeMode();
+                            ModeHighlight();
+                      }
                      StopClocks();
                      gameMode = IcsIdle;
                      ics_gamenum = -1;
@@ -2505,9 +3246,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);
                      }
                    }
 
@@ -2524,11 +3265,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) {
@@ -2543,6 +3290,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,
@@ -2560,7 +3310,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 */
@@ -2574,17 +3325,18 @@ 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);
@@ -2595,16 +3347,16 @@ read_from_ics(isr, closure, data, count, error)
 
 
 /* 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 */
@@ -2617,12 +3369,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, 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;
@@ -2631,9 +3383,11 @@ 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)
@@ -2641,15 +3395,26 @@ ParseBoard12(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;
     }
@@ -2662,7 +3427,7 @@ ParseBoard12(string)
                        0, 1);
       return;
     }
-
+    
     switch (relation) {
       case RELATION_OBSERVING_PLAYED:
       case RELATION_OBSERVING_STATIC:
@@ -2684,14 +3449,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.
        */
@@ -2724,15 +3489,15 @@ ParseBoard12(string)
        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);
+         Reset(FALSE, TRUE);
        }
        newGame = TRUE;
        if (appData.autoRaiseBoard) BoardToTop();
@@ -2746,14 +3511,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();
@@ -2772,11 +3537,19 @@ ParseBoard12(string)
        gameInfo.white = StrSave(white);
        gameInfo.black = StrSave(black);
        timeControl = basetime * 60 * 1000;
+        timeControl_2 = 0;
        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) {
@@ -2802,7 +3575,7 @@ ParseBoard12(string)
            SendToICS("set shout 0\n");
        }
     }
-
+    
     /* Deal with midgame name changes */
     if (!newGame) {
        if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
@@ -2814,7 +3587,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;
@@ -2823,7 +3596,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;
@@ -2834,36 +3607,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;
 
-       /* 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);
-                }
-       }
+    /* [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) {
@@ -2880,7 +3712,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;
@@ -2893,7 +3725,7 @@ ParseBoard12(string)
            SendToICS(str);
        }
     }
-
+    
     /* Update the clocks */
     if (strchr(elapsed_time, '.')) {
       /* Time is in ms */
@@ -2904,7 +3736,7 @@ ParseBoard12(string)
       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
     }
-
+      
 
 #if ZIPPY
     if (appData.zippyPlay && newGame &&
@@ -2912,10 +3744,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. */
@@ -2923,21 +3765,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;
            }
@@ -2946,16 +3820,12 @@ 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 (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);
+  }
 #if 0
            if (appData.testLegality && appData.debugMode) {
                sprintf(str, "Illegal move \"%s\" from ICS", move_str);
@@ -2967,11 +3837,16 @@ ParseBoard12(string)
            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)) ||
@@ -2984,8 +3859,8 @@ ParseBoard12(string)
                    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 ?
@@ -3002,6 +3877,7 @@ ParseBoard12(string)
                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);
              }
            }
@@ -3009,7 +3885,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) {
@@ -3019,7 +3895,7 @@ ParseBoard12(string)
            SetHighlights(fromX, fromY, toX, toY);
        }
     }
-
+    
     /* Start the clocks */
     whiteFlag = blackFlag = FALSE;
     appData.clockMode = !(basetime == 0 && increment == 0);
@@ -3036,39 +3912,68 @@ 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
@@ -3106,6 +4011,7 @@ SendMoveToProgram(moveNum, cps)
      ChessProgramState *cps;
 {
     char buf[MSG_SIZ];
+
     if (cps->useUsermove) {
       SendToProgram("usermove ", cps);
     }
@@ -3121,8 +4027,47 @@ SendMoveToProgram(moveNum, cps)
       }
       SendToProgram(buf, cps);
     } else {
-      SendToProgram(moveList[moveNum], cps);
+      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 || 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);
+       }
+       else SendToProgram(moveList[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
@@ -3134,7 +4079,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;
@@ -3142,12 +4087,20 @@ SendMoveToICS(moveType, fromX, fromY, toX, toY)
       case BlackKingSideCastle:
       case WhiteQueenSideCastleWild:
       case BlackQueenSideCastleWild:
+      /* PUSH Fabien */
+      case WhiteHSideCastleFR:
+      case BlackHSideCastleFR:
+      /* POP Fabien */
        sprintf(user_move, "o-o\n");
        break;
       case WhiteQueenSideCastle:
       case BlackQueenSideCastle:
       case WhiteKingSideCastleWild:
       case BlackKingSideCastleWild:
+      /* PUSH Fabien */
+      case WhiteASideCastleFR:
+      case BlackASideCastleFR:
+      /* POP Fabien */
        sprintf(user_move, "o-o-o\n");
        break;
       case WhitePromotionQueen:
@@ -3160,25 +4113,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
@@ -3189,14 +4157,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);
        }
     }
 }
@@ -3215,6 +4183,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)
@@ -3223,9 +4241,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:
@@ -3247,14 +4273,23 @@ ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
       case WhiteQueenSideCastleWild:
       case BlackKingSideCastleWild:
       case BlackQueenSideCastleWild:
+      /* Code added by Tord: */
+      case WhiteHSideCastleFR:
+      case WhiteASideCastleFR:
+      case BlackHSideCastleFR:
+      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;
        }
@@ -3268,10 +4303,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;
 
@@ -3286,6 +4321,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;
@@ -3293,35 +4331,492 @@ ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
     }
 }
 
+// [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
+
+int squaresLeft[4];
+int piecesLeft[(int)BlackPawn];
+int seed, nrOfShuffles;
+
+void GetPositionNumber()
+{      // sets global variable seed
+       int 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;
+       }
+}
+
+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;
+
+       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;
+}
+
+
+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;
+
+        i = seed % squaresLeft[shade];
+       nrOfShuffles *= squaresLeft[shade];
+       seed /= squaresLeft[shade];
+        put(board, pieceType, rank, i, shade);
+}
+
+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);
+}
+
+void SetUpShuffle(Board board, int number)
+{
+       int i, p, first=1;
+
+       GetPositionNumber(); nrOfShuffles = 1;
+
+       squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
+       squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
+       squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
+
+       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;
+           }
+
+
+       } else {
+           while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
+           while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
+       }
+
+       // 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;
+       }
+
+       if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
+}
+
+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;
+
+    if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
+                    && NrPieces >= 12 && !(NrPieces&1)) {
+        int i; /* [HGM] Accept even length from 12 to 34 */
+
+        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;
+
+    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);
-      /* !!shuffle according to FR rules */
-      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 0
+    if(gameInfo.variant == VariantFischeRandom) {
+      if( appData.defaultFrcPosition < 0 ) {
+        ShuffleFRC( initialPosition );
+      }
+      else {
+        SetupFRC( initialPosition, appData.defaultFrcPosition );
+      }
+      startedFromSetupPosition = TRUE;
+    } else 
+#else
+  if (appData.debugMode) {
+    fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
+  }
+    if(shuffleOpenings) {
+       SetUpShuffle(initialPosition, appData.defaultFrcPosition);
+       startedFromSetupPosition = TRUE;
+    }
+#endif
+    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(FALSE, boards[currentMove]);
+      DrawPosition(TRUE, boards[currentMove]);
 }
 
 void
@@ -3330,9 +4825,9 @@ SendBoard(cps, moveNum)
      int moveNum;
 {
     char message[MSG_SIZ];
-
+    
     if (cps->useSetboard) {
-      char* fen = PositionToFEN(moveNum);
+      char* fen = PositionToFEN(moveNum, cps->fenOverride);
       sprintf(message, "setboard %s\n", fen);
       SendToProgram(message, cps);
       free(fen);
@@ -3347,51 +4842,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];
@@ -3415,7 +4958,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:
@@ -3450,11 +4993,11 @@ OKToStartUserMove(x, y)
        if (!white_piece && WhiteOnMove(currentMove)) {
            DisplayMoveError(_("It is White's turn"));
            return FALSE;
-       }
+       }           
        if (white_piece && !WhiteOnMove(currentMove)) {
            DisplayMoveError(_("It is Black's turn"));
            return FALSE;
-       }
+       }           
        if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
            /* Editing correspondence game history */
            /* Could disallow this or prompt for confirmation */
@@ -3477,16 +5020,16 @@ OKToStartUserMove(x, y)
            }
        }
        break;
-
+       
       case Training:
        if (!white_piece && WhiteOnMove(currentMove)) {
            DisplayMoveError(_("It is White's turn"));
            return FALSE;
-       }
+       }           
        if (white_piece && !WhiteOnMove(currentMove)) {
            DisplayMoveError(_("It is Black's turn"));
            return FALSE;
-       }
+       }           
        break;
 
       default:
@@ -3508,17 +5051,37 @@ 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)
      int fromX, fromY, toX, toY;
      int promoChar;
 {
     ChessMove moveType;
+    ChessSquare pdown, pup;
 
-    if (fromX < 0 || fromY < 0) return;
+    if (fromX < 0 || fromY < 0) return ImpossibleMove;
     if ((fromX == toX) && (fromY == toY)) {
-       return;
-    }
+        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: this code seems to exist for filtering out some obviously illegal premoves */
+    pdown = boards[currentMove][fromY][fromX];
+    pup = boards[currentMove][toY][toX];
+    if (    gameMode != EditPosition &&
+            (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  ) 
+        )           )
+         return ImpossibleMove;
 
     /* 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
@@ -3540,13 +5103,13 @@ 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;
+            return ImpossibleMove;
        }
        break;
 
@@ -3554,7 +5117,7 @@ UserMoveEvent(fromX, fromY, toX, toY, promoChar)
        /* User is moving for White */
        if (!WhiteOnMove(currentMove)) {
            DisplayMoveError(_("It is Black's turn"));
-           return;
+            return ImpossibleMove;
        }
        break;
 
@@ -3564,17 +5127,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;
+                return ImpossibleMove;
            }
        } else {
            /* User is moving for White */
            if (!WhiteOnMove(currentMove)) {
                DisplayMoveError(_("It is Black's turn"));
-               return;
+                return ImpossibleMove;
            }
        }
        break;
@@ -3591,12 +5154,18 @@ 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);
+               if(!WhiteOnMove(currentMove) && gotPremove == 1) {
+                   // [HGM] race: we must have been hit by an opponent move from the ICS while preparing the premove
+                   if (appData.debugMode) 
+                       fprintf(debugFP, "Execute as normal move\n");
+                   gotPremove = 0; break;
+               }
            }
-           return;
+            return ImpossibleMove;
        }
        break;
 
@@ -3612,12 +5181,18 @@ 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);
+               if(WhiteOnMove(currentMove) && gotPremove == 1) {
+                   // [HGM] race: we must have been hit by an opponent move from the ICS while preparing the premove
+                   if (appData.debugMode) 
+                       fprintf(debugFP, "Execute as normal move\n");
+                   gotPremove = 0; break;
+               }
            }
-           return;
+            return ImpossibleMove;
        }
        break;
 
@@ -3625,40 +5200,109 @@ 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;
+            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 */
 
+/*    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);
@@ -3682,19 +5326,9 @@ UserMoveEvent(fromX, fromY, toX, toY, promoChar)
       } else {
        DisplayError(_("Incorrect move"), 0);
       }
-      return;
+      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) {
@@ -3719,6 +5353,7 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
     } else {
       char buf[MSG_SIZ];
       gameMode = MachinePlaysBlack;
+      StartClocks();
       SetGameInfo();
       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
       DisplayTitle(buf);
@@ -3726,10 +5361,11 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
        sprintf(buf, "name %s\n", gameInfo.white);
        SendToProgram(buf, &first);
       }
+      StartClocks();
     }
     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 ||
@@ -3743,10 +5379,11 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
                           gameMode == MachinePlaysBlack)) {
       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
     }
-    SendMoveToProgram(forwardMostMove-1, &first);
     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
-      first.maybeThinking = TRUE;
-    }
+        // [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;
     }
@@ -3757,11 +5394,12 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
   switch (gameMode) {
   case EditGame:
     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 {
@@ -3773,7 +5411,7 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
       break;
     }
     break;
-
+    
   case MachinePlaysBlack:
   case MachinePlaysWhite:
     /* disable certain menu options while machine is thinking */
@@ -3783,6 +5421,109 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
   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);
+if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
+    if(moveType == AmbiguousMove)
+       DrawPosition(FALSE, boards[currentMove]);
+    else if(moveType != ImpossibleMove)
+        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 );
+}
+
+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)
+{
+       if(savedState->lastPing != savedState->lastPong)
+                   ScheduleDelayedEvent(DeferredBookMove, 10);
+       else
+       HandleMachineMove(savedMessage, savedState);
 }
 
 void
@@ -3797,13 +5538,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) {
@@ -3828,11 +5576,9 @@ HandleMachineMove(message, cps)
     /*
      * Look for machine move.
      */
-    if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 &&
-        strcmp(buf2, "...") == 0) ||
-       (sscanf(message, "%s %s", buf1, machineMove) == 2 &&
-        strcmp(buf1, "move") == 0)) {
-
+    if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
+       (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
+    {
         /* This method is only useful on engines that support ping */
         if (cps->lastPing != cps->lastPong) {
          if (gameMode == BeginningOfGame) {
@@ -3845,7 +5591,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;
        }
@@ -3896,19 +5643,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;
@@ -3922,6 +5717,19 @@ 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 */
@@ -3929,15 +5737,412 @@ HandleMachineMove(message, cps)
        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 =  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;
+
+            while( count < adjudicateLossPlies ) {
+                int score = pvInfoList[ forwardMostMove - count - 1 ].score;
+
+                if( count & 1 ) {
+                    score = -score; /* Flip score for winning side */
+                }
+
+                if( score > adjudicateLossThreshold ) {
+                    break;
+                }
+
+                count++;
+            }
+
+            if( count >= adjudicateLossPlies ) {
+               ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
+
+                GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
+                    "Xboard adjudication", 
+                    GE_XBOARD );
+
+                return;
+            }
+        }
+
+       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;
+                    }
+               }
+
+               /* 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;
+
+
+            // 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 1
+    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]);
+
+    }
+#endif
+                /* 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 0
+    if (appData.debugMode) {
+      fprintf(debugFP, " loop\n");
+    }
+#endif
+                    if(CompareBoards(boards[k], boards[forwardMostMove])) {
+#if 0
+    if (appData.debugMode) {
+      fprintf(debugFP, "match\n");
+    }
+#endif
+                        /* 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 0
+    if (appData.debugMode) {
+      for(i=0; i<nrCastlingRights; i++)
+      fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);
+    }
+
+    if (appData.debugMode) {
+      fprintf(debugFP, " %d %d\n", rights, k);
+    }
+#endif
+                        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);
@@ -3948,15 +6153,41 @@ 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;
     }
 
@@ -3969,6 +6200,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
@@ -3984,7 +6244,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 {
@@ -3995,7 +6255,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);
        }
       }
@@ -4004,7 +6264,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 {
@@ -4023,8 +6283,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) {
@@ -4048,11 +6308,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.
@@ -4061,7 +6318,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") ||
@@ -4096,7 +6353,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;
        }
@@ -4116,7 +6373,9 @@ 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;
@@ -4125,7 +6384,7 @@ HandleMachineMove(message, cps)
           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
+              can miss some of the bad messages, but fortunately the bug 
               is fixed in current Crafty versions, so it doesn't matter. */
            return;
        }
@@ -4144,6 +6403,13 @@ HandleMachineMove(message, cps)
                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")) {
@@ -4152,7 +6418,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.
@@ -4165,14 +6431,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) {
@@ -4183,11 +6449,11 @@ 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,
+             snprintf(buf2, sizeof(buf2),
                        _("Illegal hint move \"%s\"\nfrom %s chess program"),
                        buf1, cps->which);
                DisplayError(buf2, 0);
@@ -4217,7 +6483,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 = "";
@@ -4231,10 +6497,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 = "";
@@ -4246,40 +6512,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 */
@@ -4290,17 +6561,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 */
@@ -4311,17 +6582,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 */
@@ -4330,14 +6601,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) {
@@ -4352,11 +6623,16 @@ HandleMachineMove(message, cps)
        if (gameMode == TwoMachinesPlay) {
            if (cps->other->offeredDraw) {
                GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
-           } else {
+            /* [HGM] in two-machine mode we delay relaying draw offer      */
+            /* until after we also have move, to see if it is really claim */
+           }
+#if 0
+              else {
                if (cps->other->sendDrawOffers) {
                    SendToProgram("draw\n", cps->other);
                }
            }
+#endif
        } else if (gameMode == MachinePlaysWhite ||
                   gameMode == MachinePlaysBlack) {
          if (userOfferedDraw) {
@@ -4368,14 +6644,16 @@ HandleMachineMove(message, cps)
        }
     }
 
-
+    
     /*
      * 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];
-       u64 nodes;
+       u64 nodes; // [DM]
        char plyext;
        int ignore = FALSE;
        int prefixHint = FALSE;
@@ -4392,14 +6670,12 @@ HandleMachineMove(message, cps)
            break;
          case AnalyzeMode:
          case AnalyzeFile:
-               break;
-         /* icsEngineAnalyze */
-         case IcsObserving:
-               if (!appData.icsEngineAnalyze) ignore = TRUE;
+            break;
+          case IcsObserving: /* [DM] icsEngineAnalyze */
+            if (!appData.icsEngineAnalyze) ignore = TRUE;
            break;
          case TwoMachinesPlay:
-           if ((cps->twoMachinesColor[0] == 'w') !=
-               WhiteOnMove(forwardMostMove)) {
+           if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
                ignore = TRUE;
            }
            break;
@@ -4410,18 +6686,37 @@ HandleMachineMove(message, cps)
 
        if (!ignore) {
            buf1[0] = NULLCHAR;
-           if (sscanf(message, "%d%c %d %d" u64Display "%[^\n]\n",
+           if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
                       &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
 
                if (plyext != ' ' && plyext != '\t') {
                    time *= 100;
                }
+
+                /* [AS] Negate score if machine is playing black and reporting absolute scores */
+                if( cps->scoreIsAbsolute && 
+                    ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
+                {
+                    curscore = -curscore;
+                }
+
+
                programStats.depth = plylev;
                programStats.nodes = nodes;
                programStats.time = time;
                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)
@@ -4430,10 +6725,8 @@ HandleMachineMove(message, cps)
                                "PV is too long; using the first %d bytes.\n",
                                sizeof(programStats.movelist) - 1);
                    }
-                   strncpy(programStats.movelist, buf1,
-                           sizeof(programStats.movelist));
-                   programStats.movelist[sizeof(programStats.movelist) - 1]
-                     = NULLCHAR;
+
+                    safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
                } else {
                    sprintf(programStats.movelist, " no PV\n");
                }
@@ -4450,16 +6743,35 @@ HandleMachineMove(message, cps)
                    programStats.line_is_book = 0;
                }
 
-               sprintf(thinkOutput, "[%d]%c%+.2f %s%s%s",
-                       plylev,
+                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, 
                        (gameMode == TwoMachinesPlay ?
                         ToUpper(cps->twoMachinesColor[0]) : ' '),
                        ((double) curscore) / 100.0,
                        prefixHint ? lastHint : "",
-                       prefixHint ? " " : "", buf1);
+                       prefixHint ? " " : "" );
+
+                if( buf1[0] != NULLCHAR ) {
+                    unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
+
+                    if( strlen(buf1) > max_len ) {
+                       if( appData.debugMode) {
+                           fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
+                        }
+                        buf1[max_len+1] = '\0';
+                    }
 
-               if (currentMove == forwardMostMove || gameMode == AnalyzeMode
-                       || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
+                    strcat( thinkOutput, buf1 );
+                }
+
+                if (currentMove == forwardMostMove || gameMode == AnalyzeMode
+                        || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
                    DisplayMove(currentMove - 1);
                    DisplayAnalysis();
                }
@@ -4484,13 +6796,15 @@ HandleMachineMove(message, cps)
                   isn't searching, so stats won't change) */
                programStats.line_is_book = 1;
 
-               if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
-                   gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
+                SendProgramStatsToFrontend( cps, &programStats );
+                
+               if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
+                           gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
                    DisplayMove(currentMove - 1);
                    DisplayAnalysis();
                }
                return;
-           } else if (sscanf(message,"stat01: %d" u64Display "%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
@@ -4508,6 +6822,10 @@ HandleMachineMove(message, cps)
                programStats.nr_moves = mvtot;
                strcpy(programStats.move_name, mvname);
                programStats.ok_to_send = 1;
+                programStats.movelist[0] = '\0';
+
+                SendProgramStatsToFrontend( cps, &programStats );
+
                DisplayAnalysis();
                return;
 
@@ -4523,27 +6841,75 @@ HandleMachineMove(message, cps)
 
            } else if (thinkOutput[0] != NULLCHAR &&
                       strncmp(message, "    ", 4) == 0) {
+                unsigned message_len;
+
                p = message;
                while (*p && *p == ' ') p++;
-               strcat(thinkOutput, " ");
-               strcat(thinkOutput, p);
-               strcat(programStats.movelist, " ");
-               strcat(programStats.movelist, p);
+
+                message_len = strlen( p );
+
+                /* [AS] Avoid buffer overflow */
+                if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
+                   strcat(thinkOutput, " ");
+                   strcat(thinkOutput, p);
+                }
+
+                if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
+                   strcat(programStats.movelist, " ");
+                   strcat(programStats.movelist, p);
+                }
+
                if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
-                   gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
+                           gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
                    DisplayMove(currentMove - 1);
                    DisplayAnalysis();
                }
                return;
            }
        }
+        else {
+           buf1[0] = NULLCHAR;
+
+           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;
+               }
+
+                /* [AS] Negate score if machine is playing black and reporting absolute scores */
+                if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
+                    curscore = -curscore;
+                }
+
+               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( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
+               }
+
+               cpstats.ok_to_send = 0;
+               cpstats.line_is_book = 0;
+               cpstats.nr_moves = 0;
+               cpstats.moves_left = 0;
+
+                SendProgramStatsToFrontend( cps, &cpstats );
+            }
+        }
     }
 }
 
 
 /* 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
@@ -4583,6 +6949,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:
@@ -4604,11 +6980,16 @@ ParseGameHistory(game)
          case WhiteQueenSideCastleWild:
          case BlackKingSideCastleWild:
          case BlackQueenSideCastleWild:
-         case IllegalMove:             /* maybe suicide chess, etc. */
-           fromX = currentMoveString[0] - 'a';
-           fromY = currentMoveString[1] - '1';
-           toX = currentMoveString[2] - 'a';
-           toY = currentMoveString[3] - '1';
+          /* PUSH Fabien */
+          case WhiteHSideCastleFR:
+          case WhiteASideCastleFR:
+          case BlackHSideCastleFR:
+          case BlackASideCastleFR:
+          /* POP Fabien */
+            fromX = currentMoveString[0] - AAA;
+            fromY = currentMoveString[1] - ONE;
+            toX = currentMoveString[2] - AAA;
+            toY = currentMoveString[3] - ONE;
            promoChar = currentMoveString[4];
            break;
          case WhiteDrop:
@@ -4617,18 +6998,28 @@ 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);
+  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);
+  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 */
@@ -4689,21 +7080,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;
        }
@@ -4713,75 +7108,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) {
+     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 */
-       board[toY][toX] = (ChessSquare) fromX;
-    } else if (fromX == toX && fromY == toY) {
-       return;
-    } else if (fromY == 0 && fromX == 4
-       && board[fromY][fromX] == WhiteKing
-       && toY == 0 && toX == 6) {
-       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) {
+        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. */
+    if (board[fromY][fromX] == WhiteKing &&
+            board[toY][toX] == WhiteRook) {
+      board[fromY][fromX] = EmptySquare;
+      board[toY][toX] = EmptySquare;
+      if(toX > fromX) {
+        board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
+      } else {
+        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[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
+      } else {
+        board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
+      }
+    /* End of code added by Tord */
+
+    } 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][3] = WhiteRook;
-    } else if (fromY == 0 && fromX == 3
-              && board[fromY][fromX] == WhiteKing
-              && toY == 0 && toX == 5) {
+        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][7] = EmptySquare;
-       board[toY][4] = WhiteRook;
-    } else if (fromY == 0 && fromX == 3
-              && board[fromY][fromX] == WhiteKing
-              && toY == 0 && toX == 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) {
@@ -4797,47 +7277,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;
            }
@@ -4846,6 +7390,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 */
@@ -4854,21 +7416,67 @@ MakeMove(fromX, fromY, toX, toY, promoChar)
      int fromX, fromY, toX, toY;
      int promoChar;
 {
-    forwardMostMove++;
-    if (forwardMostMove >= MAX_MOVES) {
+//    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();
-    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]);
+    timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;
+    timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;
+    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
     gameInfo.result = GameUnfinished;
     if (gameInfo.resultDetails != NULL) {
        free(gameInfo.resultDetails);
@@ -4880,19 +7488,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 */
@@ -4901,6 +7516,7 @@ ShowMove(fromX, fromY, toX, toY)
 {
     int instant = (gameMode == PlayFromGameFile) ?
        (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
+    if(appData.noGUI) return;
     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
        if (!instant) {
            if (forwardMostMove == currentMove + 1) {
@@ -4915,35 +7531,132 @@ ShowMove(fromX, fromY, toX, toY)
     }
 
     if (instant) return;
+
     DisplayMove(currentMove - 1);
     DrawPosition(FALSE, boards[currentMove]);
     DisplayBothClocks();
     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
 }
 
+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) {
+      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;
@@ -4953,7 +7666,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);
@@ -4969,7 +7685,7 @@ InitChessProgram(cps)
       SendToProgram(buf, cps);
     }
     cps->initDone = TRUE;
-}
+}   
 
 
 void
@@ -4988,26 +7704,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\ngui xboard %s\nprotover %d\n", VERSION, cps->protocolVersion);
+      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);
@@ -5019,13 +7737,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();
@@ -5035,19 +7753,69 @@ 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();
 }
 
+void UserAdjudicationEvent( int result )
+{
+    ChessMove gameResult = GameIsDrawn;
+
+    if( result > 0 ) {
+        gameResult = WhiteWins;
+    }
+    else if( result < 0 ) {
+        gameResult = BlackWins;
+    }
+
+    if( gameMode == TwoMachinesPlay ) {
+        GameEnds( gameResult, "User adjudication", GE_XBOARD );
+    }
+}
+
+
+// [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;
@@ -5056,16 +7824,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) {
@@ -5079,7 +7851,8 @@ GameEnds(result, resultDetails, whosays)
            }
         }
 #endif
-       return;
+       endingGame = 0; /* [HGM] crash */
+        return;
     }
 
     /* If we're loading the game from a file, stop */
@@ -5089,26 +7862,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();
+    
+        /* [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 (resultDetails != NULL) {
+
+        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 ||
@@ -5126,23 +8003,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) {
@@ -5184,8 +8044,8 @@ GameEnds(result, resultDetails, whosays)
                }
            }
        } else if (gameMode == EditGame ||
-                  gameMode == PlayFromGameFile ||
-                  gameMode == AnalyzeMode ||
+                  gameMode == PlayFromGameFile || 
+                  gameMode == AnalyzeMode || 
                   gameMode == AnalyzeFile) {
            nextGameMode = gameMode;
        } else {
@@ -5200,7 +8060,8 @@ GameEnds(result, resultDetails, whosays)
     if (appData.noChessProgram) {
        gameMode = nextGameMode;
        ModeHighlight();
-       return;
+       endingGame = 0; /* [HGM] crash */
+        return;
     }
 
     if (first.reuse) {
@@ -5224,10 +8085,12 @@ GameEnds(result, resultDetails, whosays)
        if (first.isr != NULL)
          RemoveInputSource(first.isr);
        first.isr = NULL;
-
+    
        if (first.pr != NoProc) {
            ExitAnalyzeMode();
+            DoSleep( appData.delayBeforeQuit );
            SendToProgram("quit\n", &first);
+            DoSleep( appData.delayAfterQuit );
            DestroyChildProcess(first.pr, first.useSigterm);
        }
        first.pr = NoProc;
@@ -5248,9 +8111,11 @@ 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);
+            DoSleep( appData.delayAfterQuit );
            DestroyChildProcess(second.pr, second.useSigterm);
        }
        second.pr = NoProc;
@@ -5277,12 +8142,17 @@ 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];
@@ -5299,24 +8169,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);
@@ -5331,9 +8213,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) {
@@ -5345,7 +8227,7 @@ ResurrectChessProgram()
     }
 
     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
-                appData.icsEngineAnalyze) && first.analysisSupport) {
+                appData.icsEngineAnalyze) && first.analysisSupport) {
       SendToProgram("analyze\n", &first);
       first.analyzing = TRUE;
     }
@@ -5364,7 +8246,6 @@ Reset(redraw, init)
        fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
                redraw, init, gameMode);
     }
-
     pausing = pauseExamInvalid = FALSE;
     startedFromSetupPosition = blackPlaysFirst = FALSE;
     firstMove = TRUE;
@@ -5373,6 +8254,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);
@@ -5382,7 +8265,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;
@@ -5391,9 +8275,24 @@ 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) {
@@ -5407,10 +8306,13 @@ Reset(redraw, init)
     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
 }
 
 void
@@ -5444,19 +8346,26 @@ AutoPlayOneMove()
     if (currentMove >= forwardMostMove) {
       gameMode = EditGame;
       ModeHighlight();
+
+      /* [AS] Clear current move marker at the end of a game */
+      /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
+
       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] */
+
        AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
 
        if (appData.highlightLastMove) {
@@ -5467,9 +8376,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;
 }
 
@@ -5483,13 +8391,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;
@@ -5498,11 +8406,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 == '(') {
@@ -5517,6 +8425,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:
@@ -5536,12 +8450,18 @@ LoadGameOneMove(readAhead)
       case WhiteQueenSideCastleWild:
       case BlackKingSideCastleWild:
       case BlackQueenSideCastleWild:
+      /* PUSH Fabien */
+      case WhiteHSideCastleFR:
+      case WhiteASideCastleFR:
+      case BlackHSideCastleFR:
+      case BlackASideCastleFR:
+      /* 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;
 
@@ -5553,8 +8473,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:
@@ -5588,11 +8508,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 {
@@ -5623,11 +8544,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 {
@@ -5662,10 +8584,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;
@@ -5683,7 +8605,7 @@ LoadGameOneMove(readAhead)
       default:
       case ImpossibleMove:
        if (appData.debugMode)
-         fprintf(debugFP, "Parsed ImpossibleMove: %s\n", yy_text);
+         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);
@@ -5696,7 +8618,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();
@@ -5707,7 +8629,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;
@@ -5732,7 +8654,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;
        }
@@ -5770,38 +8692,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);
@@ -5809,11 +8732,11 @@ MakeRegisteredMove()
                GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
            }
            break;
-
+           
          case CMAIL_ACCEPT:
            GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
            break;
-
+             
          default:
            break;
        }
@@ -5907,8 +8830,9 @@ 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 )
@@ -5926,7 +8850,7 @@ LoadGame(f, gameNumber, title, useList)
 
     if (useList) {
        lg = (ListGame *) ListElem(&gameList, gameNumber-1);
-
+       
        if (lg) {
            fseek(f, lg->offset, 0);
            GameListHighlight(gameNumber);
@@ -5956,9 +8880,8 @@ LoadGame(f, gameNumber, title, useList)
 
     yynewfile(f);
 
-
     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
-       sprintf(buf, "%s vs. %s", lg->gameInfo.white,
+      snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
                lg->gameInfo.black);
            DisplayTitle(buf);
     } else if (*title != NULLCHAR) {
@@ -5981,12 +8904,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.
@@ -6012,7 +8935,7 @@ LoadGame(f, gameNumber, title, useList)
            gn--;
            lastLoadGameStart = cm;
            break;
-
+           
          case MoveNumberOne:
            switch (lastLoadGameStart) {
              case GNUChessGame:
@@ -6080,7 +9003,7 @@ LoadGame(f, gameNumber, title, useList)
            break;
        }
     }
-
+    
     if (appData.debugMode)
       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
 
@@ -6106,15 +9029,25 @@ 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;
@@ -6138,6 +9071,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;
@@ -6149,7 +9089,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 == '(') {
@@ -6172,9 +9112,11 @@ LoadGame(f, gameNumber, title, useList)
          gameInfo.variant = StringToVariant(gameInfo.event);
        }
        if (!matchMode) {
-         tags = PGNTags(&gameInfo);
-         TagsPopUp(tags, CmailMsg());
-         free(tags);
+          if( appData.autoDisplayTags ) {
+           tags = PGNTags(&gameInfo);
+           TagsPopUp(tags, CmailMsg());
+           free(tags);
+          }
        }
     } else {
        /* Make something up, but don't display it now */
@@ -6192,8 +9134,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 '-':
@@ -6208,13 +9150,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;
@@ -6238,16 +9180,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);
-       DisplayBothClocks();
+    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 == '(') {
@@ -6279,12 +9227,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) {
@@ -6292,7 +9239,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);
@@ -6321,8 +9268,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;
 }
 
@@ -6359,7 +9308,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 {
@@ -6378,7 +9327,7 @@ LoadPosition(f, positionNumber, title)
     char *p, line[MSG_SIZ];
     Board initial_position;
     int i, j, fenMode, pn;
-
+    
     if (gameMode == Training )
        SetTrainingModeOff();
 
@@ -6394,8 +9343,8 @@ 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 */
@@ -6421,6 +9370,7 @@ LoadPosition(f, positionNumber, title)
        DisplayError(_("Position not found in file"), 0);
        return FALSE;
     }
+#if 0
     switch (line[0]) {
       case '#':  case 'x':
       default:
@@ -6429,15 +9379,22 @@ LoadPosition(f, positionNumber, title)
       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':
+      case '7':  case '8':  case '9':
+      case 'H':  case 'A':  case 'M':  case 'h':  case 'a':  case 'm':
+      case 'E':  case 'F':  case 'G':  case 'e':  case 'f':  case 'g':
+      case 'C':  case 'W':             case 'c':  case 'w': 
        fenMode = TRUE;
        break;
     }
+#else
+    // [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;
+#endif
 
     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);
@@ -6455,16 +9412,16 @@ LoadPosition(f, positionNumber, title)
     } 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);
@@ -6473,7 +9430,7 @@ LoadPosition(f, positionNumber, title)
        }
     }
     startedFromSetupPosition = TRUE;
-
+    
     SendToProgram("force\n", &first);
     CopyBoard(boards[0], initial_position);
     if (blackPlaysFirst) {
@@ -6486,7 +9443,20 @@ LoadPosition(f, positionNumber, title)
        currentMove = forwardMostMove = backwardMostMove = 0;
        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);
@@ -6500,7 +9470,7 @@ LoadPosition(f, positionNumber, title)
     timeRemaining[0][1] = whiteTimeRemaining;
     timeRemaining[1][1] = blackTimeRemaining;
     DrawPosition(FALSE, boards[currentMove]);
-
+   
     return TRUE;
 }
 
@@ -6552,7 +9522,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 {
@@ -6567,7 +9537,7 @@ SavePart(str)
 {
     static char buf[MSG_SIZ];
     char *p;
-
+    
     p = strchr(str, ' ');
     if (p == NULL) return str;
     strncpy(buf, str, p - str);
@@ -6577,6 +9547,83 @@ SavePart(str)
 
 #define PGN_MAX_LINE 75
 
+#define PGN_SIDE_WHITE  0
+#define PGN_SIDE_BLACK  1
+
+/* [AS] */
+static int FindFirstMoveOutOfBook( int side )
+{
+    int result = -1;
+
+    if( backwardMostMove == 0 && ! startedFromSetupPosition) {
+        int index = backwardMostMove;
+        int has_book_hit = 0;
+
+        if( (index % 2) != side ) {
+            index++;
+        }
+
+        while( index < forwardMostMove ) {
+            /* Check to see if engine is in book */
+            int depth = pvInfoList[index].depth;
+            int score = pvInfoList[index].score;
+            int in_book = 0;
+
+            if( depth <= 2 ) {
+                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;
+
+            if( ! in_book ) {
+                result = index;
+
+                break;
+            }
+
+            index += 2;
+        }
+    }
+
+    return result;
+}
+
+/* [AS] */
+void GetOutOfBookInfo( char * buf )
+{
+    int oob[2];
+    int i;
+    int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
+
+    oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
+    oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
+
+    *buf = '\0';
+
+    if( oob[0] >= 0 || oob[1] >= 0 ) {
+        for( i=0; i<2; i++ ) {
+            int idx = oob[i];
+
+            if( idx >= 0 ) {
+                if( i > 0 && oob[0] >= 0 ) {
+                    strcat( buf, "   " );
+                }
+
+                sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
+                sprintf( buf+strlen(buf), "%s%.2f", 
+                    pvInfoList[idx].score >= 0 ? "+" : "",
+                    pvInfoList[idx].score / 100.0 );
+            }
+        }
+    }
+}
+
 /* Save game in PGN style and close the file */
 int
 SaveGamePGN(f)
@@ -6584,27 +9631,41 @@ 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);
+        char *fen = PositionToFEN(backwardMostMove, NULL);
         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
        fprintf(f, "\n{--------------\n");
        PrintPosition(f, backwardMostMove);
        fprintf(f, "--------------}\n");
         free(fen);
-    } else {
+    }
+    else {
+        /* [AS] Out of book annotation */
+        if( appData.saveOutOfBookInfo ) {
+            char buf[64];
+
+            GetOutOfBookInfo( buf );
+
+            if( buf[0] != '\0' ) {
+                fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
+            }
+        }
+
        fprintf(f, "\n");
     }
 
     i = backwardMostMove;
-    offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
     linelen = 0;
     newblock = TRUE;
 
@@ -6645,9 +9706,19 @@ SaveGamePGN(f)
        linelen += numlen;
 
        /* Get move */
-       movetext = SavePart(parseList[i]);
-       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 */
+#if 0
+       // SavePart already does this!
+        if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
+               int p = movelen - 1;
+               if(move_buffer[p] == ' ') p--;
+               if(move_buffer[p] == ')') { // [HGM] pgn: strip off ICS time if we have extended info
+                   while(p && move_buffer[--p] != '(');
+                   if(p && move_buffer[p-1] == ' ') move_buffer[movelen=p-1] = 0;
+               }
+        }
+#endif
        /* Print move */
        blank = linelen > 0 && movelen > 0;
        if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
@@ -6659,12 +9730,61 @@ SaveGamePGN(f)
            fprintf(f, " ");
            linelen++;
        }
-       fprintf(f, movetext);
+       fprintf(f, 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 1
+            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
+#else
+            seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time
+#endif
+
+            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, move_buffer);
+           linelen += movelen;
+        }
+
        i++;
     }
-
+    
     /* Start a new line */
     if (linelen > 0) fprintf(f, "\n");
 
@@ -6683,6 +9803,7 @@ SaveGamePGN(f)
     }
 
     fclose(f);
+    lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
     return TRUE;
 }
 
@@ -6693,12 +9814,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);
@@ -6733,7 +9854,7 @@ SaveGameOldStyle(f)
            i++;
        }
     }
-
+    
     if (commentList[i] != NULL) {
        fprintf(f, "[%s]\n", commentList[i]);
     }
@@ -6759,6 +9880,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
@@ -6778,7 +9900,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 {
@@ -6797,17 +9919,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);
+       fen = PositionToFEN(currentMove, NULL);
        fprintf(f, "%s\n", fen);
        free(fen);
     }
@@ -6825,7 +9947,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) {
@@ -6853,7 +9975,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;
@@ -6861,10 +9983,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
@@ -6874,7 +9996,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);
 
@@ -6900,7 +10022,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;
@@ -6947,11 +10069,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 */
@@ -6959,10 +10081,10 @@ 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) {
@@ -7005,7 +10127,7 @@ MailMoveEvent()
 #endif
 
     if (! (cmailMailedMove || RegisterMove())) return;
-
+    
     if (   cmailMailedMove
        || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
        sprintf(string, partCommandString,
@@ -7071,7 +10193,7 @@ CmailMsg()
     char number[5];
     char string[MSG_SIZ];      /* Space for game-list */
     int  i;
-
+    
     if (!cmailMsgLoaded) return "";
 
     if (cmailMailedMove) {
@@ -7088,7 +10210,7 @@ CmailMsg()
                    sprintf(number, "%d", i + 1);
                    prependComma = 1;
                }
-
+               
                strcat(string, number);
            }
        }
@@ -7100,12 +10222,12 @@ CmailMsg()
                sprintf(cmailMsg,
                        _("Still need to make move for game\n"));
                break;
-
+               
              case 2:
                sprintf(cmailMsg,
                        _("Still need to make moves for both games\n"));
                break;
-
+               
              default:
                sprintf(cmailMsg,
                        _("Still need to make moves for all %d games\n"),
@@ -7119,7 +10241,7 @@ CmailMsg()
                        _("Still need to make a move for game %s\n"),
                        string);
                break;
-
+               
              case 0:
                if (nCmailResults == nCmailGames) {
                    sprintf(cmailMsg, _("No unfinished games\n"));
@@ -7127,7 +10249,7 @@ CmailMsg()
                    sprintf(cmailMsg, _("Ready to send mail\n"));
                }
                break;
-
+               
              default:
                sprintf(cmailMsg,
                        _("Still need to make moves for games %s\n"),
@@ -7153,8 +10275,6 @@ ResetGameEvent()
     }
 }
 
-static int exiting = 0;
-
 void
 ExitEvent(status)
      int status;
@@ -7177,8 +10297,10 @@ ExitEvent(status)
     if (icsPR != NoProc) {
       DestroyChildProcess(icsPR, TRUE);
     }
+#if 0
     /* Save game if resource set and not already saved by GameEnds() */
-    if (gameInfo.resultDetails == NULL && forwardMostMove > 0) {
+    if ((gameInfo.resultDetails == NULL || errorExitFlag )
+                             && forwardMostMove > 0) {
       if (*appData.saveGameFile != NULLCHAR) {
        SaveGameToFile(appData.saveGameFile, TRUE);
       } else if (appData.autoSaveGames) {
@@ -7189,16 +10311,32 @@ ExitEvent(status)
       }
     }
     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
+#else
+    /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
+    GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
+#endif
+    /* [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");
+    }
 
     /* Kill off chess programs */
     if (first.pr != NoProc) {
        ExitAnalyzeMode();
+        
+        DoSleep( appData.delayBeforeQuit );
        SendToProgram("quit\n", &first);
-       DestroyChildProcess(first.pr, first.useSigterm);
+        DoSleep( appData.delayAfterQuit );
+       DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
     }
     if (second.pr != NoProc) {
+        DoSleep( appData.delayBeforeQuit );
        SendToProgram("quit\n", &second);
-       DestroyChildProcess(second.pr, second.useSigterm);
+        DoSleep( appData.delayAfterQuit );
+       DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
     }
     if (first.isr != NULL) {
        RemoveInputSource(first.isr);
@@ -7226,7 +10364,7 @@ PauseEvent()
            DisplayBothClocks();
        }
        if (gameMode == PlayFromGameFile) {
-           if (appData.timeDelay >= 0)
+           if (appData.timeDelay >= 0) 
                AutoPlayGameLoop();
        } else if (gameMode == IcsExamining && pauseExamInvalid) {
            Reset(FALSE, TRUE);
@@ -7308,10 +10446,10 @@ AnalyzeModeEvent()
       return;
 
     if (gameMode != AnalyzeFile) {
-               if (!appData.icsEngineAnalyze) {
-                       EditGameEvent();
-               if (gameMode != EditGame) return;
-               }
+        if (!appData.icsEngineAnalyze) {
+               EditGameEvent();
+               if (gameMode != EditGame) return;
+        }
        ResurrectChessProgram();
        SendToProgram("analyze\n", &first);
        first.analyzing = TRUE;
@@ -7361,34 +10499,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);
        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();
@@ -7406,17 +10549,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);
     }
 }
 
@@ -7424,30 +10580,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);
        return;
     }
-
+    
     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
       ExitAnalyzeMode();
 
-    if (gameMode == EditGame || gameMode == AnalyzeMode ||
+    if (gameMode == EditGame || gameMode == AnalyzeMode || 
        gameMode == AnalyzeFile)
        TruncateGame();
 
@@ -7469,10 +10626,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();
@@ -7480,6 +10636,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);
     }
 }
 
@@ -7512,7 +10680,8 @@ TwoMachinesEvent P((void))
     int i;
     char buf[MSG_SIZ];
     ChessProgramState *onmove;
-
+    char *bookHit = NULL;
+    
     if (appData.noChessProgram) return;
 
     switch (gameMode) {
@@ -7559,10 +10728,13 @@ TwoMachinesEvent P((void))
        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);
@@ -7591,8 +10763,8 @@ TwoMachinesEvent P((void))
       SendToProgram(buf, &second);
     }
 
+    ResetClocks();
     if (!first.sendTime || !second.sendTime) {
-       ResetClocks();
        timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
        timeRemaining[1][forwardMostMove] = blackTimeRemaining;
     }
@@ -7605,11 +10777,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
@@ -7659,7 +10844,7 @@ IcsClientEvent()
       case AnalyzeFile:
        ExitAnalyzeMode();
        break;
-
+       
       default:
        EditGameEvent();
        break;
@@ -7724,7 +10909,7 @@ EditGameEvent()
       default:
        return;
     }
-
+    
     pausing = FALSE;
     StopClocks();
     first.offeredDraw = second.offeredDraw = 0;
@@ -7751,8 +10936,8 @@ EditGameEvent()
            whiteFlag = blackFlag = 0;
        }
        DisplayTitle("");
-    }
-
+    }          
+    
     gameMode = EditGame;
     ModeHighlight();
     SetGameInfo();
@@ -7766,16 +10951,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;
@@ -7786,11 +10971,12 @@ EditPositionEvent()
 void
 ExitAnalyzeMode()
 {
-       /* icsEngineAnalyze - possible call from other functions */
-       if (appData.icsEngineAnalyze) {
-           appData.icsEngineAnalyze = FALSE;
-               DisplayMessage("","Close ICS engine analyze...");
-       }
+    /* [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;
@@ -7802,24 +10988,44 @@ ExitAnalyzeMode()
 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;
     gameMode = EditGame;
     ModeHighlight();
     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
+    ClearHighlights(); /* [AS] */
 }
 
 /* Pause for `ms' milliseconds */
@@ -7847,7 +11053,7 @@ SendMultiLineToICS(buf)
     len = strlen(buf);
     if (len > MSG_SIZ)
       len = MSG_SIZ;
-
+  
     strncpy(temp, buf, len);
     temp[len] = 0;
 
@@ -7895,6 +11101,7 @@ EditPositionMenuEvent(selection, x, y)
      int x, y;
 {
     char buf[MSG_SIZ];
+    ChessSquare piece = boards[0][y][x];
 
     if (gameMode != EditPosition && gameMode != IcsExamining) return;
 
@@ -7907,16 +11114,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;
                    }
                }
            }
@@ -7936,7 +11144,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;
@@ -7944,10 +11152,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;
@@ -8005,7 +11244,7 @@ void
 AcceptEvent()
 {
     /* Accept a pending offer of any kind from opponent */
-
+    
     if (appData.icsActive) {
         SendToICS(ics_prefix);
        SendToICS("accept\n");
@@ -8030,7 +11269,7 @@ void
 DeclineEvent()
 {
     /* Decline a pending offer of any kind from opponent */
-
+    
     if (appData.icsActive) {
         SendToICS(ics_prefix);
        SendToICS("decline\n");
@@ -8102,14 +11341,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) {
@@ -8143,7 +11382,7 @@ void
 AdjournEvent()
 {
     /* Offer Adjourn or accept pending Adjourn offer from opponent */
-
+    
     if (appData.icsActive) {
         SendToICS(ics_prefix);
        SendToICS("adjourn\n");
@@ -8157,7 +11396,7 @@ void
 AbortEvent()
 {
     /* Offer Abort or accept pending Abort offer from opponent */
-
+    
     if (appData.icsActive) {
         SendToICS(ics_prefix);
        SendToICS("abort\n");
@@ -8170,7 +11409,7 @@ void
 ResignEvent()
 {
     /* Resign.  You can do this even if it's not your turn. */
-
+    
     if (appData.icsActive) {
         SendToICS(ics_prefix);
        SendToICS("resign\n");
@@ -8231,25 +11470,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);
            }
@@ -8258,8 +11497,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);
@@ -8267,7 +11506,7 @@ ForwardInner(target)
     } else {
        currentMove = target;
     }
-
+    
     if (gameMode == EditGame || gameMode == EndOfGame) {
        whiteTimeRemaining = timeRemaining[0][currentMove];
        blackTimeRemaining = timeRemaining[1][currentMove];
@@ -8276,7 +11515,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]);
     }
 }
@@ -8300,13 +11539,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");
@@ -8327,6 +11566,8 @@ void
 BackwardInner(target)
      int target;
 {
+    int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
+
     if (appData.debugMode)
        fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
                target, currentMove, forwardMostMove);
@@ -8334,23 +11575,23 @@ BackwardInner(target)
     if (gameMode == EditPosition) return;
     if (currentMove <= backwardMostMove) {
        ClearHighlights();
-       DrawPosition(FALSE, boards[currentMove]);
+       DrawPosition(full_redraw, boards[currentMove]);
        return;
     }
     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);
            }
@@ -8368,18 +11609,17 @@ BackwardInner(target)
     } else {
        currentMove = target;
     }
-
+    
     if (gameMode == EditGame || gameMode == EndOfGame) {
        whiteTimeRemaining = timeRemaining[0][currentMove];
        blackTimeRemaining = timeRemaining[1][currentMove];
     }
     DisplayBothClocks();
     DisplayMove(currentMove - 1);
-    DrawPosition(FALSE, boards[currentMove]);
+    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
@@ -8399,7 +11639,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;
@@ -8467,7 +11707,7 @@ RetractMoveEvent()
        DisplayBothClocks();
        DisplayMove(currentMove - 1);
        ClearHighlights();/*!! could figure this out*/
-       DrawPosition(FALSE, boards[currentMove]);
+       DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
        SendToProgram("remove\n", &first);
        /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
        break;
@@ -8620,12 +11860,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))
@@ -8666,6 +11906,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;
@@ -8701,7 +11942,7 @@ SetGameInfo()
 
     switch (gameMode) {
       case MachinePlaysWhite:
-       gameInfo.event = StrSave("Computer chess game");
+       gameInfo.event = StrSave( appData.pgnEventHeader );
        gameInfo.site = StrSave(HostName());
        gameInfo.date = PGNDate();
        gameInfo.round = StrSave("-");
@@ -8711,7 +11952,7 @@ SetGameInfo()
        break;
 
       case MachinePlaysBlack:
-       gameInfo.event = StrSave("Computer chess game");
+       gameInfo.event = StrSave( appData.pgnEventHeader );
        gameInfo.site = StrSave(HostName());
        gameInfo.date = PGNDate();
        gameInfo.round = StrSave("-");
@@ -8721,7 +11962,7 @@ SetGameInfo()
        break;
 
       case TwoMachinesPlay:
-       gameInfo.event = StrSave("Computer chess game");
+       gameInfo.event = StrSave( appData.pgnEventHeader );
        gameInfo.site = StrSave(HostName());
        gameInfo.date = PGNDate();
        if (matchGame > 0) {
@@ -8826,6 +12067,8 @@ AppendComment(index, text)
     int oldlen, len;
     char *old;
 
+    text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
+
     CrushCRs(text);
     while (*text == '\n') text++;
     len = strlen(text);
@@ -8850,6 +12093,99 @@ AppendComment(index, text)
     }
 }
 
+static char * FindStr( char * text, char * sub_text )
+{
+    char * result = strstr( text, sub_text );
+
+    if( result != NULL ) {
+        result += strlen( sub_text );
+    }
+
+    return result;
+}
+
+/* [AS] Try to extract PV info from PGN comment */
+/* [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, sec = 0, deci;
+        char * s_eval = FindStr( text, "[%eval " );
+        char * s_emt = FindStr( text, "[%emt " );
+
+        if( s_eval != NULL || s_emt != NULL ) {
+            /* New style */
+            char delim;
+
+            if( s_eval != NULL ) {
+                if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
+                    return text;
+                }
+
+                if( delim != ']' ) {
+                    return text;
+                }
+            }
+
+            if( s_emt != NULL ) {
+            }
+        }
+        else {
+            /* We expect something like: [+|-]nnn.nn/dd */
+            int score_lo = 0;
+
+            sep = strchr( text, '/' );
+            if( sep == NULL || sep < (text+4) ) {
+                return text;
+            }
+
+            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 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 text;
+        }
+
+        if( time < 0 ) {
+            time = -1;
+        }
+
+        pvInfoList[index-1].depth = depth;
+        pvInfoList[index-1].score = score;
+        pvInfoList[index-1].time  = 10*time; // centi-sec
+    }
+    return sep;
+}
+
 void
 SendToProgram(message, cps)
      char *message;
@@ -8860,20 +12196,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) {
+    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);
-       DisplayFatalError(buf, error, 1);
+        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);
     }
 }
 
@@ -8895,6 +12241,15 @@ ReceiveFromProgram(isr, closure, message, count, error)
            sprintf(buf,
                    _("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 {
@@ -8902,36 +12257,57 @@ ReceiveFromProgram(isr, closure, message, count, error)
                    _("Error reading from %s chess program (%s)"),
                    cps->which, cps->program);
            RemoveInputSource(cps->isr);
-           DisplayFatalError(buf, error, 1);
+
+            /* [AS] Program is misbehaving badly... kill it */
+            if( count == -2 ) {
+                DestroyChildProcess( cps->pr, 9 );
+                cps->pr = NoProc;
+            }
+
+            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);
-    }
-       /* if icsEngineAnalyze is active we block all
-          whisper and kibitz output, because nobody want
-          see this
-        */
-       if (appData.icsEngineAnalyze) {
-               if (strstr(message, "whisper") != NULL ||
-                   strstr(message, "kibitz") != NULL ||
-                       strstr(message, "tellics") != NULL) return;
-               HandleMachineMove(message, cps);
-       } else {
-               HandleMachineMove(message, cps);
+       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);
 }
 
 
@@ -8942,7 +12318,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 */
@@ -8981,6 +12368,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
@@ -9002,10 +12406,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\n", time);
+    SendToProgram(message, cps);
 
-    sprintf(message, "time %ld\notim %ld\n", time, otime);
+    sprintf(message, "otim %ld\n", otime);
     SendToProgram(message, cps);
 }
 
@@ -9073,6 +12486,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;
@@ -9091,7 +12581,7 @@ FeatureDone(cps, val)
 void
 ParseFeatures(args, cps)
      char* args;
-     ChessProgramState *cps;
+     ChessProgramState *cps;  
 {
   char *p = args;
   char *q;
@@ -9103,10 +12593,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;
@@ -9134,6 +12624,33 @@ ParseFeatures(args, cps)
       FeatureDone(cps, val);
       continue;
     }
+    /* Added by Tord: */
+    if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
+    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;
@@ -9196,12 +12713,33 @@ PonderNextMoveEvent(newState)
 }
 
 void
-ShowThinkingEvent(newState)
-     int newState;
+NewSettingEvent(option, command, value)
+     char *command;
+     int option, value;
 {
-    if (newState == appData.showThinking) return;
+    char buf[MSG_SIZ];
+
     if (gameMode == EditPosition) EditPositionDone();
-    if (newState) {
+    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
+{
+    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 (oldState) {
        SendToProgram("post\n", &first);
        if (gameMode == TwoMachinesPlay) {
            SendToProgram("post\n", &second);
@@ -9213,7 +12751,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
@@ -9233,16 +12771,34 @@ 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) {
 
-       strcpy(cpThinkOutput, thinkOutput);
-       if (strchr(cpThinkOutput, '\n'))
-         *strchr(cpThinkOutput, '\n') = NULLCHAR;
+       safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
+
+        if (strchr(cpThinkOutput, '\n')) {
+           *strchr(cpThinkOutput, '\n') = NULLCHAR;
+        }
     } else {
        *cpThinkOutput = NULLCHAR;
     }
 
+    /* [AS] Hide thinking from human user */
+    if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
+        *cpThinkOutput = NULLCHAR;
+        if( thinkOutput[0] != NULLCHAR ) {
+            int i;
+
+            for( i=0; i<=hiddenThinkOutputState; i++ ) {
+                cpThinkOutput[i] = '.';
+            }
+            cpThinkOutput[i] = NULLCHAR;
+            hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
+        }
+    }
+
     if (moveNumber == forwardMostMove - 1 &&
        gameInfo.resultDetails != NULL) {
        if (gameInfo.resultDetails[0] == NULLCHAR) {
@@ -9271,8 +12827,8 @@ DisplayAnalysisText(text)
 {
     char buf[MSG_SIZ];
 
-    if (gameMode == AnalyzeMode || gameMode == AnalyzeFile
-               || appData.icsEngineAnalyze) {
+    if (gameMode == AnalyzeMode || gameMode == AnalyzeFile 
+               || appData.icsEngineAnalyze) {
        sprintf(buf, "Analysis (%s)", first.tidy);
        AnalysisPopUp(buf, text);
     }
@@ -9294,19 +12850,22 @@ void
 DisplayAnalysis()
 {
     char buf[MSG_SIZ];
+    char lst[MSG_SIZ / 2];
     double nps;
     static char *xtra[] = { "", " (--)", " (++)" };
     int h, m, s, cs;
-
+  
     if (programStats.time == 0) {
        programStats.time = 1;
     }
-
+  
     if (programStats.got_only_move) {
-       strcpy(buf, programStats.movelist);
+       safeStrCpy(buf, programStats.movelist, sizeof(buf));
     } else {
-       nps = (u64ToDouble(programStats.nodes) /
-             ((double)programStats.time /100.0));
+        safeStrCpy( lst, programStats.movelist, sizeof(lst));
+
+        nps = (u64ToDouble(programStats.nodes) /
+             ((double)programStats.time /100.0));
 
        cs = programStats.time % 100;
        s = programStats.time / 100;
@@ -9317,30 +12876,30 @@ 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: "u64Display" 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, programStats.movelist,
-                   only_one_move(programStats.movelist)?
+                   ((float)programStats.score)/100.0, lst,
+                   only_one_move(lst)?
                    xtra[programStats.got_fail] : "",
                    (u64)programStats.nodes, (int)nps, h, m, s, cs);
          } else {
-           sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: "u64Display" 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,
-                   programStats.movelist,
-                   only_one_move(programStats.movelist)?
+                   lst,
+                   only_one_move(lst)?
                    xtra[programStats.got_fail] : "",
                    (u64)programStats.nodes, (int)nps, h, m, s, cs);
          }
        } else {
-           sprintf(buf, "depth=%d %+.2f %s%s\nNodes: "u64Display" 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,
-                   programStats.movelist,
-                   only_one_move(programStats.movelist)?
+                   lst,
+                   only_one_move(lst)?
                    xtra[programStats.got_fail] : "",
                    (u64)programStats.nodes, (int)nps, h, m, s, cs);
        }
@@ -9354,16 +12913,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]);
+        }
+       // [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;
 
-    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]);
-    }
-
-    CommentPopUp(title, text);
+    if (text != NULL)
+        CommentPopUp(title, text);
 }
 
 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
@@ -9414,9 +12986,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;
@@ -9436,9 +13008,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;
@@ -9456,30 +13028,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 += timeControl;
-       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 += timeControl;
-       break;
-      default:
-       break;
-      }
-    }
+        blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
+                                            / WhitePlayer()->other->timeOdds;
 }
 
 void
@@ -9498,6 +13058,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)
@@ -9509,13 +13074,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);
@@ -9548,7 +13113,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;
@@ -9570,15 +13135,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 = blackTimeRemaining = timeControl;
+    } else { /* [HGM] correct new time quote for time odds */
+        whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
+        blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
     }
     if (whiteFlag || blackFlag) {
        DisplayTitle("");
@@ -9599,7 +13174,7 @@ DecrementClocks()
 
     if (!appData.clockMode) return;
     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
-
+       
     GetTimeMark(&now);
 
     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
@@ -9609,17 +13184,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);
@@ -9627,9 +13204,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 */
@@ -9639,7 +13216,7 @@ DecrementClocks()
 
        if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
            alarmSounded = FALSE;
-       } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
+       } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
            PlayAlarmSound();
            alarmSounded = TRUE;
        }
@@ -9667,9 +13244,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();
     }
@@ -9698,12 +13285,12 @@ SwitchClocks()
       whiteTimeRemaining : blackTimeRemaining);
     StartClockTimer(intendedTickLength);
 }
-
+       
 
 /* Stop both clocks */
 void
 StopClocks()
-{
+{      
     long lastTickLength;
     TimeMark now;
 
@@ -9714,15 +13301,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
@@ -9738,6 +13327,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);
 }
 
@@ -9748,7 +13352,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 );
@@ -9766,14 +13370,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);
@@ -9781,7 +13385,7 @@ TimeString(ms)
       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
     else
       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
-
+    
     return buf;
 }
 
@@ -9794,13 +13398,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;
 }
 
@@ -9809,9 +13413,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]))
@@ -9829,7 +13433,7 @@ StrCaseCmp(s1, s2)
      char *s1, *s2;
 {
     char c1, c2;
-
+    
     for (;;) {
        c1 = ToLower(*s1++);
        c2 = ToLower(*s2++);
@@ -9897,116 +13501,228 @@ PGNDate()
 
 
 char *
-PositionToFEN(move)
+PositionToFEN(move, overrideCastling)
      int move;
+     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) = ' ';
 
+    /* [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++ = '-';
+        *p++ = ']';
+        *p++ = ' ';
+    }
+
     /* Active color */
     *p++ = whiteToPlay ? 'w' : 'b';
     *p++ = ' ';
 
-    /* !!We don't keep track of castling availability, so fake it */
-    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++ = '-';
-    *p++ = ' ';
+  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 {
        *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;
            }
@@ -10014,21 +13730,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)
 {
@@ -10043,6 +13903,13 @@ EditPositionPasteFEN(char *fen)
       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]);