Added internal wrapping ability.
[xboard.git] / backend.c
old mode 100644 (file)
new mode 100755 (executable)
index c7e0473..5b8dcfa
--- 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>
 # include <string.h>
+# include <stdarg.h>
 #else /* not STDC_HEADERS */
 # if HAVE_STRING_H
 #  include <string.h>
@@ -107,15 +126,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) 
+#endif 
 
 
 /* A point in time */
@@ -124,33 +143,16 @@ typedef struct {
     int ms;    /* Assuming this is >= 16 bits */
 } TimeMark;
 
-/* Search stats from chessprogram */
-typedef struct {
-  char movelist[2*MSG_SIZ]; /* Last PV we were sent */
-  int depth;              /* Current search depth */
-  int nr_moves;           /* Total nr of root moves */
-  int moves_left;         /* Moves remaining to be searched */
-  char move_name[MOVE_LEN];  /* Current move being searched, if provided */
-  unsigned long nodes;    /* # of nodes searched */
-  int time;               /* Search time (centiseconds) */
-  int score;              /* Score (centipawns) */
-  int got_only_move;      /* If last msg was "(only move)" */
-  int got_fail;           /* 0 - nothing, 1 - got "--", 2 - got "++" */
-  int ok_to_send;         /* handshaking between send & recv */
-  int line_is_book;       /* 1 if movelist is book moves */
-  int seen_stat;          /* 1 if we've seen the stat01: line */
-} ChessProgramStats;
-
 int establish P((void));
 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
                         char *buf, int count, int error));
 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
                      char *buf, int count, int error));
+void ics_printf P((char *format, ...));
 void SendToICS P((char *s));
 void SendToICSDelayed P((char *s, long msdelay));
 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
                      int toX, int toY));
-void InitPosition P((int redraw));
 void HandleMachineMove P((char *message, ChessProgramState *cps));
 int AutoPlayOneMove P((void));
 int LoadGameOneMove P((ChessMove readAhead));
@@ -158,10 +160,10 @@ int LoadGameFromFile P((char *filename, int n, char *title, int useList));
 int LoadPositionFromFile P((char *filename, int n, char *title));
 int SavePositionToFile P((char *filename));
 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
-                 Board board));
+                 Board board, char *castle, char *ep));
 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
 void ShowMove P((int fromX, int fromY, int toX, int toY));
-void FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
+int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
                   /*char*/int promoChar));
 void BackwardInner P((int target));
 void ForwardInner P((int target));
@@ -182,7 +184,6 @@ void FeedMovesToProgram P((ChessProgramState *cps, int upto));
 void ResurrectChessProgram P((void));
 void DisplayComment P((int moveNumber, char *text));
 void DisplayMove P((int moveNumber));
-void DisplayAnalysis P((void));
 
 void ParseGameHistory P((char *game));
 void ParseBoard12 P((char *string));
@@ -211,13 +212,41 @@ int string_to_rating P((char *str));
 void ParseFeatures P((char* args, ChessProgramState *cps));
 void InitBackEnd3 P((void));
 void FeatureDone P((ChessProgramState* cps, int val));
-void InitChessProgram P((ChessProgramState *cps));
-\r
-#ifdef WIN32\r
-       extern void ConsoleCreate();\r
-#endif\r
+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();
+#endif
+
+ChessProgramState *WhitePlayer();
+void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
+int VerifyDisplayMode P(());
+
+char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
+void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
+char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
+char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
+void ics_update_width P((int new_width));
+extern char installDir[MSG_SIZ];
+
 extern int tinyLayout, smallLayout;
-static ChessProgramStats programStats;
+ChessProgramStats programStats;
+static int exiting = 0; /* [HGM] moved to top */
+static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
+int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
+char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
+int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
+VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
+int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
+int opponentKibitzes;
+int lastSavedGame; /* [HGM] save: ID of game */
+char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
+extern int chatCount;
+int chattingPartner;
 
 /* States for ics_getting_history */
 #define H_FALSE 0
@@ -233,6 +262,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
@@ -258,8 +289,45 @@ 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;
+}
+
+/* Some compiler can't cast u64 to double
+ * This function do the job for us:
+
+ * We use the highest bit for cast, this only
+ * works if the highest bit is not
+ * in use (This should not happen)
+ *
+ * We used this for all compiler
+ */
+double
+u64ToDouble(u64 value)
+{
+  double r;
+  u64 tmp = value & u64Const(0x7fffffffffffffff);
+  r = (double)(s64)tmp;
+  if (value & u64Const(0x8000000000000000))
+       r +=  9.2233720368547758080e18; /* 2^63 */
+ return r;
+}
+
 /* Fake up flags for now, as we aren't keeping track of castling
-   availability yet */
+   availability yet. [HGM] Change of logic: the flag now only
+   indicates the type of castlings allowed by the rule of the game.
+   The actual rights themselves are maintained in the array
+   castlingRights, as part of the game history, and are not probed
+   by this function.
+ */
 int
 PosFlags(index)
 {
@@ -267,9 +335,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;
@@ -277,7 +347,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:
@@ -288,6 +363,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];
@@ -305,7 +389,7 @@ Boolean alarmSounded;
 /* end premove variables */
 
 char *ics_prefix = "$";
-ics_type = ICS_GENERIC;\r
+int ics_type = ICS_GENERIC;
 
 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
 int pauseExamForwardMostMove = 0;
@@ -326,12 +410,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;
@@ -350,45 +440,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)
@@ -410,7 +592,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;
@@ -422,7 +604,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;
@@ -440,7 +625,7 @@ InitBackEnd1()
     if (appData.icsActive) {
        appData.matchMode = FALSE;
        appData.matchGames = 0;
-#if ZIPPY
+#if ZIPPY      
        appData.noChessProgram = !appData.zippyPlay;
 #else
        appData.zippyPlay = FALSE;
@@ -455,13 +640,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);
     }
 
@@ -476,11 +672,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;
@@ -511,6 +710,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;
@@ -532,6 +733,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];
@@ -558,7 +798,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
@@ -568,21 +808,14 @@ InitBackEnd1()
       ZippyInit();
     }
 #endif
-
+    
     if (appData.noChessProgram) {
-       programVersion = (char*) malloc(5 + strlen(PRODUCT) + strlen(VERSION)
-                                       + strlen(PATCHLEVEL));
-       sprintf(programVersion, "%s %s.%s", PRODUCT, VERSION, PATCHLEVEL);
+       programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
+       sprintf(programVersion, "%s", PACKAGE_STRING);
     } else {
-       char *p, *q;
-       q = first.program;
-       while (*q != ' ' && *q != NULLCHAR) q++;
-       p = q;
-       while (p > first.program && *(p-1) != '/') p--;
-       programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)
-                                       + strlen(PATCHLEVEL) + (q - p));
-       sprintf(programVersion, "%s %s.%s + ", PRODUCT, VERSION, PATCHLEVEL);
-       strncat(programVersion, p, q - p);
+      /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
+      programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
+      sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
     }
 
     if (!appData.icsActive) {
@@ -595,7 +828,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;
@@ -615,11 +848,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,
@@ -629,10 +872,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
@@ -641,25 +981,58 @@ ParseTimeControl(tc, ti, mps)
      int ti;
      int mps;
 {
-    int matched, min, sec;
-
-    matched = sscanf(tc, "%d:%d", &min, &sec);
-    if (matched == 1) {
-       timeControl = min * 60 * 1000;
-    } else if (matched == 2) {
-       timeControl = (min * 60 + sec) * 1000;
-    } else {
-       return FALSE;
+  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 (ti >= 0) {
-       timeIncrement = ti * 1000;  /* convert to ms */
-       movesPerSession = 0;
-    } else {
-       timeIncrement = 0;
-       movesPerSession = mps;
+    
+    if( tc2 == 0 ) {
+      return FALSE;
     }
-    return TRUE;
+    
+    timeControl_2 = tc2 * 1000;
+  }
+  else {
+    timeControl_2 = 0;
+  }
+  
+  if( tc1 == 0 ) {
+    return FALSE;
+  }
+  
+  timeControl = tc1 * 1000;
+  
+  if (ti >= 0) {
+    timeIncrement = ti * 1000;  /* convert to ms */
+    movesPerSession = 0;
+  } else {
+    timeIncrement = 0;
+    movesPerSession = mps;
+  }
+  return TRUE;
 }
 
 void
@@ -669,18 +1042,25 @@ InitBackEnd2()
        fprintf(debugFP, "%s\n", programVersion);
     }
 
+    set_cont_sequence(appData.wrapContSeq);
     if (appData.matchGames > 0) {
        appData.matchMode = TRUE;
     } else if (appData.matchMode) {
        appData.matchGames = 1;
     }
+    if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
+       appData.matchGames = appData.sameColorGames;
+    if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
+       if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
+       if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
+    }
     Reset(TRUE, FALSE);
     if (appData.noChessProgram || first.protocolVersion == 1) {
       InitBackEnd3();
     } else {
       /* kludge: allow timeout for initial "feature" commands */
       FreezeUI();
-      DisplayMessage("", "Starting chess program");
+      DisplayMessage("", _("Starting chess program"));
       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
     }
 }
@@ -692,21 +1072,21 @@ InitBackEnd3 P((void))
     char buf[MSG_SIZ];
     int err;
 
-    InitChessProgram(&first);\r
-\r
-    #ifdef WIN32\r
-               /* Make a console window if needed */\r
-               if (appData.icsActive) ConsoleCreate();\r
-       #endif\r
+    InitChessProgram(&first, startedFromSetupPosition);
+
 
     if (appData.icsActive) {
+#ifdef WIN32
+        /* [DM] Make a console window if needed [HGM] merged ifs */
+        ConsoleCreate(); 
+#endif
        err = establish();
        if (err != 0) {
            if (*appData.icsCommPort != NULLCHAR) {
-               sprintf(buf, _("Could not open comm port %s"),
+               sprintf(buf, _("Could not open comm port %s"),  
                        appData.icsCommPort);
            } else {
-               sprintf(buf, _("Could not connect to host %s, port %s"),
+               snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
                        appData.icsHost, appData.icsPort);
            }
            DisplayFatalError(buf, err, 1);
@@ -729,7 +1109,7 @@ InitBackEnd3 P((void))
        cmailISR =
          AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
     }
-
+    
     ThawUI();
     DisplayMessage("", "");
     if (StrCaseCmp(appData.initialMode, "") == 0) {
@@ -737,7 +1117,7 @@ InitBackEnd3 P((void))
     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
       initialMode = TwoMachinesPlay;
     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
-      initialMode = AnalyzeFile;
+      initialMode = AnalyzeFile; 
     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
       initialMode = AnalyzeMode;
     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
@@ -766,15 +1146,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;
@@ -800,6 +1184,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) {
@@ -812,7 +1209,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) {
@@ -883,19 +1281,19 @@ establish()
     } else if (*appData.gateway != NULLCHAR) {
        if (*appData.remoteShell == NULLCHAR) {
            /* Use the rcmd protocol to run telnet program on a gateway host */
-           sprintf(buf, "%s %s %s",
+           snprintf(buf, sizeof(buf), "%s %s %s",
                    appData.telnetProgram, appData.icsHost, appData.icsPort);
            return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
 
        } else {
            /* Use the rsh program to run telnet program on a gateway host */
            if (*appData.remoteUser == NULLCHAR) {
-               sprintf(buf, "%s %s %s %s %s", appData.remoteShell,
+               snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
                        appData.gateway, appData.telnetProgram,
                        appData.icsHost, appData.icsPort);
            } else {
-               sprintf(buf, "%s %s -l %s %s %s %s",
-                       appData.remoteShell, appData.gateway,
+               snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
+                       appData.remoteShell, appData.gateway, 
                        appData.remoteUser, appData.telnetProgram,
                        appData.icsHost, appData.icsPort);
            }
@@ -1005,7 +1403,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);
@@ -1016,6 +1414,26 @@ 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);
+}
+
+/* added routine for printf style output to ics */
+void ics_printf(char *format, ...)
+{
+    char buffer[MSG_SIZ];
+    va_list args;
+
+    va_start(args, format);
+    vsnprintf(buffer, sizeof(buffer), format, args);
+    buffer[sizeof(buffer)-1] = '\0';
+    SendToICS(buffer);
+    va_end(args);
+}
+
 void
 SendToICS(s)
      char *s;
@@ -1058,7 +1476,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)
@@ -1128,6 +1546,16 @@ 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++ != '_');
+    }
+
+    if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
+       v = VariantNormal;
+       found = TRUE;
+    } else
     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
       if (StrCaseStr(e, variantNames[i])) {
        v = (VariantClass) i;
@@ -1138,7 +1566,8 @@ StringToVariant(e)
 
     if (!found) {
       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
-         || StrCaseStr(e, "wild/fr")) {
+         || StrCaseStr(e, "wild/fr") 
+         || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
         v = VariantFischeRandom;
       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
                 (i = 1, p = StrCaseStr(e, "w"))) {
@@ -1215,7 +1644,7 @@ StringToVariant(e)
          v = VariantShatranj;
          break;
 
-       /* Temporary names for future ICC types.  The name *will* change in
+       /* Temporary names for future ICC types.  The name *will* change in 
           the next xboard/WinBoard release after ICC defines it. */
        case 29:
          v = Variant29;
@@ -1241,14 +1670,55 @@ StringToVariant(e)
        case 36:
          v = Variant36;
          break;
-
+        case 37:
+          v = VariantShogi;
+         break;
+        case 38:
+          v = VariantXiangqi;
+         break;
+        case 39:
+          v = VariantCourier;
+         break;
+        case 40:
+          v = VariantGothic;
+         break;
+        case 41:
+          v = VariantCapablanca;
+         break;
+        case 42:
+          v = VariantKnightmate;
+         break;
+        case 43:
+          v = VariantFairy;
+          break;
+        case 44:
+          v = VariantCylinder;
+         break;
+        case 45:
+          v = VariantFalcon;
+         break;
+        case 46:
+          v = VariantCapaRandom;
+         break;
+        case 47:
+          v = VariantBerolina;
+         break;
+        case 48:
+          v = VariantJanus;
+         break;
+        case 49:
+          v = VariantSuper;
+         break;
+        case 50:
+          v = VariantGreat;
+         break;
        case -1:
          /* Found "wild" or "w" in the string but no number;
             must assume it's normal chess. */
          v = VariantNormal;
          break;
        default:
-         sprintf(buf, "Unknown wild type %d", wnum);
+         sprintf(buf, _("Unknown wild type %d"), wnum);
          DisplayError(buf, 0);
          v = VariantUnknown;
          break;
@@ -1281,7 +1751,7 @@ looking_at(buf, index, pattern)
     char *bufp = &buf[*index], *patternp = pattern;
     int star_count = 0;
     char *matchp = star_match[0];
-
+    
     for (;;) {
        if (*patternp == NULLCHAR) {
            *index = leftover_start = bufp - buf;
@@ -1425,6 +1895,128 @@ DontEcho()
     TelnetRequest(TN_DONT, TN_ECHO);
 }
 
+void
+CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
+{
+    /* put the holdings sent to us by the server on the board holdings area */
+    int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
+    char p;
+    ChessSquare piece;
+
+    if(gameInfo.holdingsWidth < 2)  return;
+
+    if( (int)lowestPiece >= BlackPawn ) {
+        holdingsColumn = 0;
+        countsColumn = 1;
+        holdingsStartRow = BOARD_HEIGHT-1;
+        direction = -1;
+    } else {
+        holdingsColumn = BOARD_WIDTH-1;
+        countsColumn = BOARD_WIDTH-2;
+        holdingsStartRow = 0;
+        direction = 1;
+    }
+
+    for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
+        board[i][holdingsColumn] = EmptySquare;
+        board[i][countsColumn]   = (ChessSquare) 0;
+    }
+    while( (p=*holdings++) != NULLCHAR ) {
+        piece = CharToPiece( ToUpper(p) );
+        if(piece == EmptySquare) continue;
+        /*j = (int) piece - (int) WhitePawn;*/
+        j = PieceToNumber(piece);
+        if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
+        if(j < 0) continue;               /* should not happen */
+        piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
+        board[holdingsStartRow+j*direction][holdingsColumn] = piece;
+        board[holdingsStartRow+j*direction][countsColumn]++;
+    }
+
+}
+
+
+void
+VariantSwitch(Board board, VariantClass newVariant)
+{
+   int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
+
+   startedFromPositionFile = FALSE;
+   if(gameInfo.variant == newVariant) return;
+
+   /* [HGM] This routine is called each time an assignment is made to
+    * gameInfo.variant during a game, to make sure the board sizes
+    * are set to match the new variant. If that means adding or deleting
+    * holdings, we shift the playing board accordingly
+    * This kludge is needed because in ICS observe mode, we get boards
+    * of an ongoing game without knowing the variant, and learn about the
+    * latter only later. This can be because of the move list we requested,
+    * in which case the game history is refilled from the beginning anyway,
+    * but also when receiving holdings of a crazyhouse game. In the latter
+    * case we want to add those holdings to the already received position.
+    */
+
+   
+   if (appData.debugMode) {
+     fprintf(debugFP, "Switch board from %s to %s\n",
+            VariantName(gameInfo.variant), VariantName(newVariant));
+     setbuf(debugFP, NULL);
+   }
+   shuffleOpenings = 0;       /* [HGM] shuffle */
+   gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
+   switch(newVariant) 
+     {
+     case VariantShogi:
+       newWidth = 9;  newHeight = 9;
+       gameInfo.holdingsSize = 7;
+     case VariantBughouse:
+     case VariantCrazyhouse:
+       newHoldingsWidth = 2; break;
+     case VariantGreat:
+       newWidth = 10;
+     case VariantSuper:
+       newHoldingsWidth = 2;
+       gameInfo.holdingsSize = 8;
+       return;
+     case VariantGothic:
+     case VariantCapablanca:
+     case VariantCapaRandom:
+       newWidth = 10;
+     default:
+       newHoldingsWidth = gameInfo.holdingsSize = 0;
+     };
+   
+   if(newWidth  != gameInfo.boardWidth  ||
+      newHeight != gameInfo.boardHeight ||
+      newHoldingsWidth != gameInfo.holdingsWidth ) {
+     
+     /* shift position to new playing area, if needed */
+     if(newHoldingsWidth > gameInfo.holdingsWidth) {
+       for(i=0; i<BOARD_HEIGHT; i++) 
+        for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
+          board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
+            board[i][j];
+       for(i=0; i<newHeight; i++) {
+        board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
+        board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
+       }
+     } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
+       for(i=0; i<BOARD_HEIGHT; i++)
+        for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
+          board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
+            board[i][j];
+     }
+     gameInfo.boardWidth  = newWidth;
+     gameInfo.boardHeight = newHeight;
+     gameInfo.holdingsWidth = newHoldingsWidth;
+     gameInfo.variant = newVariant;
+     InitDrawingSizes(-2, 0);
+     InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
+   } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
+   
+   DrawPosition(TRUE, boards[currentMove]);
+}
+
 static int loggedOn = FALSE;
 
 /*-- Game start info cache: --*/
@@ -1432,10 +2024,14 @@ int gs_gamenum;
 char gs_kind[MSG_SIZ];
 static char player1Name[128] = "";
 static char player2Name[128] = "";
+static char cont_seq[] = "\n\\   ";
 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;
@@ -1453,28 +2049,26 @@ 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;
+    static int cmatch = 0; // continuation sequence match
+    char *bp;
     char str[500];
     int i, oldi;
     int buf_len;
     int next_out;
-    int tkind;\r
-#ifdef WIN32\r
-       /* For zippy color lines of winboard\r
-        * cleanup for gcc compiler */\r
-       int backup;\r
-#endif
+    int tkind;
+    int backup;    /* [DM] For zippy color lines */
     char *p;
+    char talker[MSG_SIZ]; // [HGM] chat
+    int channel;
 
-#ifdef WIN32
     if (appData.debugMode) {
       if (!error) {
        fprintf(debugFP, "<ICS: ");
@@ -1482,8 +2076,11 @@ read_from_ics(isr, closure, data, count, error)
        fprintf(debugFP, "\n");
       }
     }
-#endif
 
+    if (appData.debugMode) { int f = forwardMostMove;
+        fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
+                castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
+    }
     if (count > 0) {
        /* If last read ended with a partial line that we couldn't parse,
           prepend it to the new read and try again. */
@@ -1492,17 +2089,71 @@ read_from_ics(isr, closure, data, count, error)
              buf[i] = buf[leftover_start + i];
        }
 
-       /* Copy in new characters, removing nulls and \r's */
-       buf_len = leftover_len;
-       for (i = 0; i < count; i++) {
-           if (data[i] != NULLCHAR && data[i] != '\r')
-             buf[buf_len++] = data[i];
-       }
+    /* copy new characters into the buffer */
+    bp = buf + leftover_len;
+    buf_len=leftover_len;
+    for (i=0; i<count; i++)
+    {
+        // ignore these
+        if (data[i] == '\r')
+            continue;
+
+        // join lines split by ICS?
+        if (!appData.noJoin)
+        {
+            /*
+                Joining just consists of finding matches against the
+                continuation sequence, and discarding that sequence
+                if found instead of copying it.  So, until a match
+                fails, there's nothing to do since it might be the
+                complete sequence, and thus, something we don't want
+                copied.
+            */
+            if (data[i] == cont_seq[cmatch])
+            {
+                cmatch++;
+                if (cmatch == strlen(cont_seq))
+                {
+                    cmatch = 0; // complete match.  just reset the counter
+
+                    /*
+                        it's possible for the ICS to not include the space
+                        at the end of the last word, making our [correct]
+                        join operation fuse two separate words.  the server
+                        does this when the space occurs at the width setting.
+                    */
+                    if (!buf_len || buf[buf_len-1] != ' ')
+                    {
+                        *bp++ = ' ';
+                        buf_len++;
+                    }
+                }
+                continue;
+            }
+            else if (cmatch)
+            {
+                /*
+                    match failed, so we have to copy what matched before
+                    falling through and copying this character.  In reality,
+                    this will only ever be just the newline character, but
+                    it doesn't hurt to be precise.
+                */
+                strncpy(bp, cont_seq, cmatch);
+                bp += cmatch;
+                buf_len += cmatch;
+                cmatch = 0;
+            }
+        }
+
+        // copy this char
+        *bp++ = data[i];
+        buf_len++;
+    }
 
        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
@@ -1614,18 +2265,17 @@ read_from_ics(isr, closure, data, count, error)
                  next_out = i;
                continue;
            }
-
+               
            /* OK, this at least will *usually* work */
            if (!loggedOn && looking_at(buf, &i, "ics%")) {
                loggedOn = TRUE;
            }
-
+           
            if (loggedOn && !intfSet) {
                if (ics_type == ICS_ICC) {
                  sprintf(str,
                          "/set-quietly interface %s\n/set-quietly style 12\n",
                          programVersion);
-
                } else if (ics_type == ICS_CHESSNET) {
                  sprintf(str, "/style 12\n");
                } else {
@@ -1638,6 +2288,7 @@ read_from_ics(isr, closure, data, count, error)
                  strcat(str, "$iset lock 1\n$style 12\n");
                }
                SendToICS(str);
+               NotifyFrontendLogin();
                intfSet = TRUE;
            }
 
@@ -1646,7 +2297,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 */
@@ -1703,7 +2387,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;
            }
@@ -1727,32 +2411,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) {
-#if ZIPPY\r
-       #ifdef WIN32\r
-               /* Backup address for color zippy lines */\r
-               backup = i;\r
-               if (loggedOn == TRUE)\r
-                       if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||\r
-                               (appData.zippyPlay && ZippyMatch(buf, &backup)));\r
-               #else
-               if (ZippyControl(buf, &i) ||
-                   ZippyConverse(buf, &i) ||
-                   (appData.zippyPlay && ZippyMatch(buf, &i))) {
-                   loggedOn = TRUE;
-                   continue;
-               }\r
-       #endif
+                /* [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;
+               }
+       #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") ||
@@ -1934,14 +2686,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;
@@ -2024,7 +2776,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;
@@ -2034,11 +2786,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;
            }
 
@@ -2078,11 +2830,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:
@@ -2097,15 +2849,14 @@ read_from_ics(isr, closure, data, count, error)
                            if (WhiteOnMove(forwardMostMove)) {
                                if (first.sendTime) {
                                  if (first.useColors) {
-                                   SendToProgram("black\n", &first);
+                                   SendToProgram("black\n", &first); 
                                  }
                                  SendTimeRemaining(&first, TRUE);
                                }
                                if (first.useColors) {
-                                 SendToProgram("white\ngo\n", &first);
-                               } else {
-                                 SendToProgram("go\n", &first);
+                                 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
                                }
+                               bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
                                first.maybeThinking = TRUE;
                            } else {
                                if (first.usePlayother) {
@@ -2127,10 +2878,9 @@ read_from_ics(isr, closure, data, count, error)
                                  SendTimeRemaining(&first, FALSE);
                                }
                                if (first.useColors) {
-                                 SendToProgram("black\ngo\n", &first);
-                               } else {
-                                 SendToProgram("go\n", &first);
+                                 SendToProgram("black\n", &first);
                                }
+                               bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
                                first.maybeThinking = TRUE;
                            } else {
                                if (first.usePlayother) {
@@ -2143,7 +2893,7 @@ read_from_ics(isr, closure, data, count, error)
                                  firstMove = TRUE;
                                }
                            }
-                       }
+                       }                       
                    }
 #endif
                    if (gameMode == IcsObserving && ics_gamenum == -1) {
@@ -2183,16 +2933,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
@@ -2225,7 +2986,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);
@@ -2255,25 +3016,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;
                }
            }
@@ -2318,7 +3080,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).
                */
@@ -2328,14 +3090,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. *(*)) *}*"))){
@@ -2411,6 +3173,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*/
@@ -2423,6 +3186,11 @@ read_from_ics(isr, closure, data, count, error)
                if (gameMode == IcsObserving &&
                    atoi(star_match[0]) == ics_gamenum)
                  {
+                      /* icsEngineAnalyze */
+                      if (appData.icsEngineAnalyze) {
+                            ExitAnalyzeMode();
+                            ModeHighlight();
+                      }
                      StopClocks();
                      gameMode = IcsIdle;
                      ics_gamenum = -1;
@@ -2484,9 +3252,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);
                      }
                    }
 
@@ -2503,11 +3271,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) {
@@ -2522,6 +3296,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,
@@ -2539,7 +3316,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 */
@@ -2553,17 +3331,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);
@@ -2574,16 +3353,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 */
@@ -2596,12 +3375,12 @@ read_from_ics(isr, closure, data, count, error)
 void
 ParseBoard12(string)
      char *string;
-{
+{ 
     GameMode newGameMode;
-    int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
-    int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
+    int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
+    int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
-    char to_play, board_chars[72];
+    char to_play, board_chars[200];
     char move_str[500], str[500], elapsed_time[500];
     char black[32], white[32];
     Board board;
@@ -2610,9 +3389,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)
@@ -2620,15 +3401,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;
     }
@@ -2641,7 +3433,7 @@ ParseBoard12(string)
                        0, 1);
       return;
     }
-
+    
     switch (relation) {
       case RELATION_OBSERVING_PLAYED:
       case RELATION_OBSERVING_STATIC:
@@ -2663,14 +3455,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.
        */
@@ -2703,12 +3495,12 @@ 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);
@@ -2725,14 +3517,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();
@@ -2751,11 +3543,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) {
@@ -2781,7 +3581,7 @@ ParseBoard12(string)
            SendToICS("set shout 0\n");
        }
     }
-
+    
     /* Deal with midgame name changes */
     if (!newGame) {
        if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
@@ -2793,7 +3593,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;
@@ -2802,7 +3602,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;
@@ -2813,26 +3613,95 @@ ParseBoard12(string)
            return;
        }
     }
-
+    
+  if (appData.debugMode) {
+    fprintf(debugFP, "load %dx%d board\n", files, ranks);
+  }
     /* Parse the board */
-    for (k = 0; k < 8; k++)
-      for (j = 0; j < 8; j++)
-       board[k][j] = CharToPiece(board_chars[(7-k)*9 + j]);
+    for (k = 0; k < ranks; k++) {
+      for (j = 0; j < files; j++)
+        board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
+      if(gameInfo.holdingsWidth > 1) {
+           board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
+           board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
+      }
+    }
     CopyBoard(boards[moveNum], board);
     if (moveNum == 0) {
        startedFromSetupPosition =
          !CompareBoards(board, initialPosition);
-    }
-
+        if(startedFromSetupPosition)
+            initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
+    }
+
+    /* [HGM] Set castling rights. Take the outermost Rooks,
+       to make it also work for FRC opening positions. Note that board12
+       is really defective for later FRC positions, as it has no way to
+       indicate which Rook can castle if they are on the same side of King.
+       For the initial position we grant rights to the outermost Rooks,
+       and remember thos rights, and we then copy them on positions
+       later in an FRC game. This means WB might not recognize castlings with
+       Rooks that have moved back to their original position as illegal,
+       but in ICS mode that is not its job anyway.
+    */
+    if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
+    { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
+
+        for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
+            if(board[0][i] == WhiteRook) j = i;
+        initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
+        for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
+            if(board[0][i] == WhiteRook) j = i;
+        initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
+        for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
+            if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
+        initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
+        for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
+            if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
+        initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
+
+       if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
+        for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
+            if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
+        for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
+            if(board[BOARD_HEIGHT-1][k] == bKing)
+                initialRights[5] = castlingRights[moveNum][5] = k;
+    } else { int r;
+        r = castlingRights[moveNum][0] = initialRights[0];
+        if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
+        r = castlingRights[moveNum][1] = initialRights[1];
+        if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
+        r = castlingRights[moveNum][3] = initialRights[3];
+        if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
+        r = castlingRights[moveNum][4] = initialRights[4];
+        if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
+        /* wildcastle kludge: always assume King has rights */
+        r = castlingRights[moveNum][2] = initialRights[2];
+        r = castlingRights[moveNum][5] = initialRights[5];
+    }
+    /* [HGM] e.p. rights. Assume that ICS sends file number here? */
+    epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
+
+    
     if (ics_getting_history == H_GOT_REQ_HEADER ||
        ics_getting_history == H_GOT_UNREQ_HEADER) {
        /* This was an initial position from a move list, not
           the current position */
        return;
     }
-
+    
     /* Update currentMove and known move number limits */
     newMove = newGame || moveNum > forwardMostMove;
+
+    /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
+    if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
+        takeback = forwardMostMove - moveNum;
+        for (i = 0; i < takeback; i++) {
+             if (appData.debugMode) fprintf(debugFP, "take back move\n");
+             SendToProgram("undo\n", &first);
+        }
+    }
+
     if (newGame) {
        forwardMostMove = backwardMostMove = currentMove = moveNum;
        if (gameMode == IcsExamining && moveNum == 0) {
@@ -2849,7 +3718,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;
@@ -2862,7 +3731,7 @@ ParseBoard12(string)
            SendToICS(str);
        }
     }
-
+    
     /* Update the clocks */
     if (strchr(elapsed_time, '.')) {
       /* Time is in ms */
@@ -2873,7 +3742,7 @@ ParseBoard12(string)
       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
     }
-
+      
 
 #if ZIPPY
     if (appData.zippyPlay && newGame &&
@@ -2881,10 +3750,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. */
@@ -2892,21 +3771,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;
            }
@@ -2915,32 +3826,27 @@ ParseBoard12(string)
            /* currentMoveString is set as a side-effect of ParseOneMove */
            strcpy(moveList[moveNum - 1], currentMoveString);
            strcat(moveList[moveNum - 1], "\n");
-       } else if (strcmp(move_str, "none") == 0) {
-           /* Again, we don't know what the board looked like;
-              this is really the start of the game. */
-           parseList[moveNum - 1][0] = NULLCHAR;
-           moveList[moveNum - 1][0] = NULLCHAR;
-           backwardMostMove = moveNum;
-           startedFromSetupPosition = TRUE;
-           fromX = fromY = toX = toY = -1;
-       } else {
+         } else {
            /* Move from ICS was illegal!?  Punt. */
-#if 0
-           if (appData.testLegality && appData.debugMode) {
-               sprintf(str, "Illegal move \"%s\" from ICS", move_str);
-               DisplayError(str, 0);
-           }
-#endif
+  if (appData.debugMode) {
+    fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
+    fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
+  }
            strcpy(parseList[moveNum - 1], move_str);
            strcat(parseList[moveNum - 1], " ");
            strcat(parseList[moveNum - 1], elapsed_time);
            moveList[moveNum - 1][0] = NULLCHAR;
            fromX = fromY = toX = toY = -1;
+         }
        }
+  if (appData.debugMode) {
+    fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
+    setbuf(debugFP, NULL);
+  }
 
 #if ZIPPY
        /* Send move to chess program (BEFORE animating it). */
-       if (appData.zippyPlay && !newGame && newMove &&
+       if (appData.zippyPlay && !newGame && newMove && 
           (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
 
            if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
@@ -2953,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 ?
@@ -2971,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);
              }
            }
@@ -2978,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) {
@@ -2988,7 +3895,7 @@ ParseBoard12(string)
            SetHighlights(fromX, fromY, toX, toY);
        }
     }
-
+    
     /* Start the clocks */
     whiteFlag = blackFlag = FALSE;
     appData.clockMode = !(basetime == 0 && increment == 0);
@@ -3005,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
@@ -3069,12 +4005,19 @@ AnalysisPeriodicEvent(force)
     programStats.ok_to_send = 0;
 }
 
+void ics_update_width(new_width)
+       int new_width;
+{
+       ics_printf("set width %d\n", new_width);
+}
+
 void
 SendMoveToProgram(moveNum, cps)
      int moveNum;
      ChessProgramState *cps;
 {
     char buf[MSG_SIZ];
+
     if (cps->useUsermove) {
       SendToProgram("usermove ", cps);
     }
@@ -3090,8 +4033,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
@@ -3103,7 +4085,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;
@@ -3111,12 +4093,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:
@@ -3129,25 +4119,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
@@ -3158,14 +4163,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);
        }
     }
 }
@@ -3184,6 +4189,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)
@@ -3192,9 +4247,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:
@@ -3216,14 +4279,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;
        }
@@ -3237,10 +4309,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;
 
@@ -3255,6 +4327,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;
@@ -3262,35 +4337,480 @@ 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 */
-      break;
-    case VariantFischeRandom:
-      CopyBoard(boards[0], initialPosition);
-      /* !!shuffle according to FR rules */
+      shuffleOpenings = 1;
       break;
     }
+
+    overrule = 0;
+    if(appData.NrFiles >= 0) {
+        if(gameInfo.boardWidth != appData.NrFiles) overrule++;
+        gameInfo.boardWidth = appData.NrFiles;
+    }
+    if(appData.NrRanks >= 0) {
+        gameInfo.boardHeight = appData.NrRanks;
+    }
+    if(appData.holdingsSize >= 0) {
+        i = appData.holdingsSize;
+        if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
+        gameInfo.holdingsSize = i;
+    }
+    if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
+    if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
+        DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
+
+    pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
+    if(pawnRow < 1) pawnRow = 1;
+
+    /* User pieceToChar list overrules defaults */
+    if(appData.pieceToCharTable != NULL)
+        SetCharTable(pieceToChar, appData.pieceToCharTable);
+
+    for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
+
+        if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
+            s = (ChessSquare) 0; /* account holding counts in guard band */
+        for( i=0; i<BOARD_HEIGHT; i++ )
+            initialPosition[i][j] = s;
+
+        if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
+        initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
+        initialPosition[pawnRow][j] = WhitePawn;
+        initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
+        if(gameInfo.variant == VariantXiangqi) {
+            if(j&1) {
+                initialPosition[pawnRow][j] = 
+                initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
+                if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
+                   initialPosition[2][j] = WhiteCannon;
+                   initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
+                }
+            }
+        }
+        initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
+    }
+    if( (gameInfo.variant == VariantShogi) && !overrule ) {
+
+            j=BOARD_LEFT+1;
+            initialPosition[1][j] = WhiteBishop;
+            initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
+            j=BOARD_RGHT-2;
+            initialPosition[1][j] = WhiteRook;
+            initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
+    }
+
+    if( nrCastlingRights == -1) {
+        /* [HGM] Build normal castling rights (must be done after board sizing!) */
+        /*       This sets default castling rights from none to normal corners   */
+        /* Variants with other castling rights must set them themselves above    */
+        nrCastlingRights = 6;
+       
+        castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
+        castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
+        castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
+        castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
+        castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
+        castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
+     }
+
+     if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
+     if(gameInfo.variant == VariantGreat) { // promotion commoners
+       initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
+       initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
+       initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
+       initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
+     }
+  if (appData.debugMode) {
+    fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
+  }
+    if(shuffleOpenings) {
+       SetUpShuffle(initialPosition, appData.defaultFrcPosition);
+       startedFromSetupPosition = TRUE;
+    }
+    if(startedFromPositionFile) {
+      /* [HGM] loadPos: use PositionFile for every new game */
+      CopyBoard(initialPosition, filePosition);
+      for(i=0; i<nrCastlingRights; i++)
+          castlingRights[0][i] = initialRights[i] = fileRights[i];
+      startedFromSetupPosition = TRUE;
+    }
+
+    CopyBoard(boards[0], initialPosition);
+
+    if(oldx != gameInfo.boardWidth ||
+       oldy != gameInfo.boardHeight ||
+       oldh != gameInfo.holdingsWidth
+#ifdef GOTHIC
+       || oldv == VariantGothic ||        // For licensing popups
+       gameInfo.variant == VariantGothic
+#endif
+#ifdef FALCON
+       || oldv == VariantFalcon ||
+       gameInfo.variant == VariantFalcon
+#endif
+                                         )
+            InitDrawingSizes(-2 ,0);
+
     if (redraw)
-      DrawPosition(FALSE, boards[currentMove]);
+      DrawPosition(TRUE, boards[currentMove]);
 }
 
 void
@@ -3299,9 +4819,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);
@@ -3316,51 +4836,159 @@ 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;
+HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
 {
-    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] rewritten IsPromotion to only flag promotions that offer a choice */
+    /* [HGM] add Shogi promotions */
+    int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
+    ChessSquare piece;
+    ChessMove moveType;
+    Boolean premove;
+
+    if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
+    if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
+
+    if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
+      !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
+       return FALSE;
+
+    piece = boards[currentMove][fromY][fromX];
+    if(gameInfo.variant == VariantShogi) {
+        promotionZoneSize = 3;
+        highestPromotingPiece = (int)WhiteFerz;
+    }
+
+    // next weed out all moves that do not touch the promotion zone at all
+    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;
+    }
+
+    if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
+
+    // weed out mandatory Shogi promotions
+    if(gameInfo.variant == VariantShogi) {
+       if(piece >= BlackPawn) {
+           if(toY == 0 && piece == BlackPawn ||
+              toY == 0 && piece == BlackQueen ||
+              toY <= 1 && piece == BlackKnight) {
+               *promoChoice = '+';
+               return FALSE;
+           }
+       } else {
+           if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
+              toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
+              toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
+               *promoChoice = '+';
+               return FALSE;
+           }
+       }
+    }
+
+    // weed out obviously illegal Pawn moves
+    if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
+       if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
+       if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
+       if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
+       if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
+       // note we are not allowed to test for valid (non-)capture, due to premove
+    }
+
+    // we either have a choice what to promote to, or (in Shogi) whether to promote
+    if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
+       *promoChoice = PieceToChar(BlackFerz);  // no choice
+       return FALSE;
+    }
+    if(appData.alwaysPromoteToQueen) { // predetermined
+       if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
+            *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
+       else *promoChoice = PieceToChar(BlackQueen);
+       return FALSE;
+    }
+
+    // suppress promotion popup on illegal moves that are not premoves
+    premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
+             gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
+    if(appData.testLegality && !premove) {
+       moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
+                       epStatus[currentMove], castlingRights[currentMove],
+                       fromY, fromX, toY, toX, NULLCHAR);
+       if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
+          moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
+           return FALSE;
+    }
+
+    return TRUE;
 }
 
+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];
@@ -3384,7 +5012,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:
@@ -3419,11 +5047,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 */
@@ -3446,16 +5074,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:
@@ -3476,18 +5104,14 @@ int lastLoadGameUseList = FALSE;
 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
 ChessMove lastLoadGameStart = (ChessMove) 0;
 
-
-void
-UserMoveEvent(fromX, fromY, toX, toY, promoChar)
+ChessMove
+UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
      int fromX, fromY, toX, toY;
      int promoChar;
+     Boolean captureOwn;
 {
     ChessMove moveType;
-
-    if (fromX < 0 || fromY < 0) return;
-    if ((fromX == toX) && (fromY == toY)) {
-       return;
-    }
+    ChessSquare pdown, pup;
 
     /* 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
@@ -3509,13 +5133,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;
 
@@ -3523,7 +5147,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;
 
@@ -3533,17 +5157,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;
@@ -3560,12 +5184,12 @@ UserMoveEvent(fromX, fromY, toX, toY, promoChar)
                premoveFromY = fromY;
                premovePromoChar = promoChar;
                gotPremove = 1;
-               if (appData.debugMode)
+               if (appData.debugMode) 
                    fprintf(debugFP, "Got premove: fromX %d,"
                            "fromY %d, toX %d, toY %d\n",
                            fromX, fromY, toX, toY);
            }
-           return;
+            return ImpossibleMove;
        }
        break;
 
@@ -3581,12 +5205,12 @@ UserMoveEvent(fromX, fromY, toX, toY, promoChar)
                premoveFromY = fromY;
                premovePromoChar = promoChar;
                gotPremove = 1;
-               if (appData.debugMode)
+               if (appData.debugMode) 
                    fprintf(debugFP, "Got premove: fromX %d,"
                            "fromY %d, toX %d, toY %d\n",
                            fromX, fromY, toX, toY);
            }
-           return;
+            return ImpossibleMove;
        }
        break;
 
@@ -3594,40 +5218,111 @@ 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;
     }
 
-    if (toX < 0 || toY < 0) return;
-    userOfferedDraw = FALSE;
+    pdown = boards[currentMove][fromY][fromX];
+    pup = boards[currentMove][toY][toX];
 
+    /* [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 */
+    }
+
+    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);
@@ -3651,19 +5346,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) {
@@ -3688,6 +5373,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);
@@ -3695,10 +5381,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 ||
@@ -3712,10 +5399,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;
     }
@@ -3726,11 +5414,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 {
@@ -3742,7 +5431,7 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
       break;
     }
     break;
-
+    
   case MachinePlaysBlack:
   case MachinePlaysWhite:
     /* disable certain menu options while machine is thinking */
@@ -3752,6 +5441,287 @@ 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, FALSE);
+if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
+    if(moveType == AmbiguousMove)
+       DrawPosition(FALSE, boards[currentMove]);
+    else if(moveType != ImpossibleMove && moveType != Comment)
+        FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
+}
+
+void LeftClick(ClickType clickType, int xPix, int yPix)
+{
+    int x, y;
+    Boolean saveAnimate;
+    static int second = 0, promotionChoice = 0;
+    char promoChoice = NULLCHAR;
+
+    if (clickType == Press) ErrorPopDown();
+
+    x = EventToSquare(xPix, BOARD_WIDTH);
+    y = EventToSquare(yPix, BOARD_HEIGHT);
+    if (!flipView && y >= 0) {
+       y = BOARD_HEIGHT - 1 - y;
+    }
+    if (flipView && x >= 0) {
+       x = BOARD_WIDTH - 1 - x;
+    }
+
+    if(promotionChoice) { // we are waiting for a click to indicate promotion piece
+       if(clickType == Release) return; // ignore upclick of click-click destination
+       promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
+       if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
+       if(gameInfo.holdingsWidth && 
+               (WhiteOnMove(currentMove) 
+                       ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
+                       : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
+           // click in right holdings, for determining promotion piece
+           ChessSquare p = boards[currentMove][y][x];
+           if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
+           if(p != EmptySquare) {
+               FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
+               fromX = fromY = -1;
+               return;
+           }
+       }
+       DrawPosition(FALSE, boards[currentMove]);
+       return;
+    }
+
+    /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
+    if(clickType == Press
+            && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
+              || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
+              || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
+       return;
+
+    if (fromX == -1) {
+       if (clickType == Press) {
+           /* First square */
+           if (OKToStartUserMove(x, y)) {
+               fromX = x;
+               fromY = y;
+               second = 0;
+               DragPieceBegin(xPix, yPix);
+               if (appData.highlightDragging) {
+                   SetHighlights(x, y, -1, -1);
+               }
+           }
+       }
+       return;
+    }
+
+    /* fromX != -1 */
+    if (clickType == Press && gameMode != EditPosition) {
+       ChessSquare fromP;
+       ChessSquare toP;
+       int frc;
+
+       // ignore off-board to clicks
+       if(y < 0 || x < 0) return;
+
+       /* Check if clicking again on the same color piece */
+       fromP = boards[currentMove][fromY][fromX];
+       toP = boards[currentMove][y][x];
+       frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
+       if ((WhitePawn <= fromP && fromP <= WhiteKing &&
+            WhitePawn <= toP && toP <= WhiteKing &&
+            !(fromP == WhiteKing && toP == WhiteRook && frc) &&
+            !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
+           (BlackPawn <= fromP && fromP <= BlackKing && 
+            BlackPawn <= toP && toP <= BlackKing &&
+            !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
+            !(fromP == BlackKing && toP == BlackRook && frc))) {
+           /* Clicked again on same color piece -- changed his mind */
+           second = (x == fromX && y == fromY);
+           if (appData.highlightDragging) {
+               SetHighlights(x, y, -1, -1);
+           } else {
+               ClearHighlights();
+           }
+           if (OKToStartUserMove(x, y)) {
+               fromX = x;
+               fromY = y;
+               DragPieceBegin(xPix, yPix);
+           }
+           return;
+       }
+       // ignore to-clicks in holdings
+       if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
+    }
+
+    if (clickType == Release && (x == fromX && y == fromY ||
+       x < BOARD_LEFT || x >= BOARD_RGHT)) {
+
+       // treat drags into holding as click on start square
+       x = fromX; y = fromY;
+
+       DragPieceEnd(xPix, yPix);
+       if (appData.animateDragging) {
+           /* Undo animation damage if any */
+           DrawPosition(FALSE, NULL);
+       }
+       if (second) {
+           /* Second up/down in same square; just abort move */
+           second = 0;
+           fromX = fromY = -1;
+           ClearHighlights();
+           gotPremove = 0;
+           ClearPremoveHighlights();
+       } else {
+           /* First upclick in same square; start click-click mode */
+           SetHighlights(x, y, -1, -1);
+       }
+       return;
+    }
+
+    /* we now have a different from- and to-square */
+    /* Completed move */
+    toX = x;
+    toY = y;
+    saveAnimate = appData.animate;
+    if (clickType == Press) {
+       /* Finish clickclick move */
+       if (appData.animate || appData.highlightLastMove) {
+           SetHighlights(fromX, fromY, toX, toY);
+       } else {
+           ClearHighlights();
+       }
+    } else {
+       /* Finish drag move */
+       if (appData.highlightLastMove) {
+           SetHighlights(fromX, fromY, toX, toY);
+       } else {
+           ClearHighlights();
+       }
+       DragPieceEnd(xPix, yPix);
+       /* Don't animate move and drag both */
+       appData.animate = FALSE;
+    }
+    if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
+       SetHighlights(fromX, fromY, toX, toY);
+       if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
+           // [HGM] super: promotion to captured piece selected from holdings
+           ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
+           promotionChoice = TRUE;
+           // kludge follows to temporarily execute move on display, without promoting yet
+           boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
+           boards[currentMove][toY][toX] = p;
+           DrawPosition(FALSE, boards[currentMove]);
+           boards[currentMove][fromY][fromX] = p; // take back, but display stays
+           boards[currentMove][toY][toX] = q;
+           DisplayMessage("Click in holdings to choose piece", "");
+           return;
+       }
+       PromotionPopUp();
+    } else {
+       UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
+       if (!appData.highlightLastMove || gotPremove) ClearHighlights();
+       if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
+       fromX = fromY = -1;
+    }
+    appData.animate = saveAnimate;
+    if (appData.animate || appData.animateDragging) {
+       /* Undo animation damage if needed */
+       DrawPosition(FALSE, NULL);
+    }
+}
+
+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
@@ -3766,12 +5736,19 @@ 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
      */
@@ -3797,11 +5774,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) {
@@ -3814,7 +5789,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;
        }
@@ -3865,19 +5841,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;
@@ -3891,6 +5915,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 */
@@ -3898,15 +5935,392 @@ 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 (appData.debugMode) { int i;
+           fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
+                   forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
+                   appData.drawRepeats);
+           for( i=forwardMostMove; i>=backwardMostMove; i-- )
+             fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
+           
+         }
+
+                /* Check for rep-draws */
+                count = 0;
+                for(k = forwardMostMove-2;
+                    k>=backwardMostMove && k>=forwardMostMove-100 &&
+                        epStatus[k] < EP_UNKNOWN &&
+                        epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
+                    k-=2)
+                {   int rights=0;
+                    if(CompareBoards(boards[k], boards[forwardMostMove])) {
+                        /* compare castling rights */
+                        if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
+                             (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
+                                rights++; /* King lost rights, while rook still had them */
+                        if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
+                            if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
+                                castlingRights[forwardMostMove][1] != castlingRights[k][1] )
+                                   rights++; /* but at least one rook lost them */
+                        }
+                        if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
+                             (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
+                                rights++; 
+                        if( castlingRights[forwardMostMove][5] >= 0 ) {
+                            if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
+                                castlingRights[forwardMostMove][4] != castlingRights[k][4] )
+                                   rights++;
+                        }
+                        if( rights == 0 && ++count > appData.drawRepeats-2
+                            && appData.drawRepeats > 1) {
+                             /* adjudicate after user-specified nr of repeats */
+                            SendToProgram("force\n", cps->other); // suppress reply
+                            SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
+                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
+                            if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
+                               // [HGM] xiangqi: check for forbidden perpetuals
+                               int m, ourPerpetual = 1, hisPerpetual = 1;
+                               for(m=forwardMostMove; m>k; m-=2) {
+                                   if(MateTest(boards[m], PosFlags(m), 
+                                                       EP_NONE, castlingRights[m]) != MT_CHECK)
+                                       ourPerpetual = 0; // the current mover did not always check
+                                   if(MateTest(boards[m-1], PosFlags(m-1), 
+                                                       EP_NONE, castlingRights[m-1]) != MT_CHECK)
+                                       hisPerpetual = 0; // the opponent did not always check
+                               }
+                               if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
+                                                                       ourPerpetual, hisPerpetual);
+                               if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
+                                   GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
+                                          "Xboard adjudication: perpetual checking", GE_XBOARD );
+                                   return;
+                               }
+                               if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
+                                   break; // (or we would have caught him before). Abort repetition-checking loop.
+                               // Now check for perpetual chases
+                               if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
+                                   hisPerpetual = PerpetualChase(k, forwardMostMove);
+                                   ourPerpetual = PerpetualChase(k+1, forwardMostMove);
+                                   if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
+                                       GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
+                                                     "Xboard adjudication: perpetual chasing", GE_XBOARD );
+                                       return;
+                                   }
+                                   if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
+                                       break; // Abort repetition-checking loop.
+                               }
+                               // if neither of us is checking or chasing all the time, or both are, it is draw
+                            }
+                             GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
+                             return;
+                        }
+                        if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
+                             epStatus[forwardMostMove] = EP_REP_DRAW;
+                    }
+                }
+
+                /* Now we test for 50-move draws. Determine ply count */
+                count = forwardMostMove;
+                /* look for last irreversble move */
+                while( epStatus[count] <= EP_NONE && count > backwardMostMove )
+                    count--;
+                /* if we hit starting position, add initial plies */
+                if( count == backwardMostMove )
+                    count -= initialRulePlies;
+                count = forwardMostMove - count; 
+                if( count >= 100)
+                         epStatus[forwardMostMove] = EP_RULE_DRAW;
+                         /* this is used to judge if draw claims are legal */
+                if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
+                        SendToProgram("force\n", cps->other); // suppress reply
+                        SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
+                         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
+                         GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
+                         return;
+                }
+
+                /* if draw offer is pending, treat it as a draw claim
+                 * when draw condition present, to allow engines a way to
+                 * claim draws before making their move to avoid a race
+                 * condition occurring after their move
+                 */
+                if( cps->other->offeredDraw || cps->offeredDraw ) {
+                         char *p = NULL;
+                         if(epStatus[forwardMostMove] == EP_RULE_DRAW)
+                             p = "Draw claim: 50-move rule";
+                         if(epStatus[forwardMostMove] == EP_REP_DRAW)
+                             p = "Draw claim: 3-fold repetition";
+                         if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
+                             p = "Draw claim: insufficient mating material";
+                         if( p != NULL ) {
+                            SendToProgram("force\n", cps->other); // suppress reply
+                            SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
+                             GameEnds( GameIsDrawn, p, GE_XBOARD );
+                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
+                             return;
+                         }
+                }
+
+
+               if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
+                   SendToProgram("force\n", cps->other); // suppress reply
+                   SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
+                   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
+
+                   GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
+
+                   return;
+               }
+        }
+
+       bookHit = NULL;
        if (gameMode == TwoMachinesPlay) {
+            /* [HGM] relaying draw offers moved to after reception of move */
+            /* and interpreting offer as claim if it brings draw condition */
+            if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
+                SendToProgram("draw\n", cps->other);
+            }
            if (cps->other->sendTime) {
                SendTimeRemaining(cps->other,
                                  cps->other->twoMachinesColor[0] == 'w');
            }
-           SendMoveToProgram(forwardMostMove-1, cps->other);
-           if (firstMove) {
+           bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
+           if (firstMove && !bookHit) {
                firstMove = FALSE;
                if (cps->other->useColors) {
                  SendToProgram(cps->other->twoMachinesColor, cps->other);
@@ -3917,15 +6331,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;
     }
 
@@ -3938,6 +6378,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
@@ -3953,7 +6422,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 {
@@ -3964,7 +6433,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);
        }
       }
@@ -3973,7 +6442,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 {
@@ -3992,8 +6461,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) {
@@ -4017,11 +6486,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.
@@ -4030,7 +6496,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") ||
@@ -4065,7 +6531,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;
        }
@@ -4085,21 +6551,36 @@ HandleMachineMove(message, cps)
                          searchTime);
          return;
        }
-       if (!StrStr(message, "llegal")) return;
+        if (!StrStr(message, "llegal")) {
+            return;
+        }
        if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
            gameMode == IcsIdle) return;
        if (forwardMostMove <= backwardMostMove) return;
-#if 0
-       /* Following removed: it caused a bug where a real illegal move
-          message in analyze mored would be ignored. */
-       if (cps == &first && programStats.ok_to_send == 0) {
-           /* Bogus message from Crafty responding to "."  This filtering
-              can miss some of the bad messages, but fortunately the bug
-              is fixed in current Crafty versions, so it doesn't matter. */
-           return;
-       }
-#endif
        if (pausing) PauseEvent();
+      if(appData.forceIllegal) {
+           // [HGM] illegal: machine refused move; force position after move into it
+          SendToProgram("force\n", cps);
+          if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
+               // we have a real problem now, as SendBoard will use the a2a3 kludge
+               // when black is to move, while there might be nothing on a2 or black
+               // might already have the move. So send the board as if white has the move.
+               // But first we must change the stm of the engine, as it refused the last move
+               SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
+               if(WhiteOnMove(forwardMostMove)) {
+                   SendToProgram("a7a6\n", cps); // for the engine black still had the move
+                   SendBoard(cps, forwardMostMove); // kludgeless board
+               } else {
+                   SendToProgram("a2a3\n", cps); // for the engine white still had the move
+                   CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
+                   SendBoard(cps, forwardMostMove+1); // kludgeless board
+               }
+          } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
+           if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
+                gameMode == TwoMachinesPlay)
+              SendToProgram("go\n", cps);
+           return;
+      } else
        if (gameMode == PlayFromGameFile) {
            /* Stop reading this game file */
            gameMode = EditGame;
@@ -4113,6 +6594,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")) {
@@ -4121,7 +6609,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.
@@ -4134,14 +6622,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) {
@@ -4152,11 +6640,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);
@@ -4186,7 +6674,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 = "";
@@ -4200,10 +6688,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 = "";
@@ -4215,40 +6703,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 */
@@ -4259,17 +6752,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 */
@@ -4280,17 +6773,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 */
@@ -4299,14 +6792,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) {
@@ -4321,10 +6814,8 @@ HandleMachineMove(message, cps)
        if (gameMode == TwoMachinesPlay) {
            if (cps->other->offeredDraw) {
                GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
-           } else {
-               if (cps->other->sendDrawOffers) {
-                   SendToProgram("draw\n", cps->other);
-               }
+            /* [HGM] in two-machine mode we delay relaying draw offer      */
+            /* until after we also have move, to see if it is really claim */
            }
        } else if (gameMode == MachinePlaysWhite ||
                   gameMode == MachinePlaysBlack) {
@@ -4337,14 +6828,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];
-       unsigned long nodes;
+       u64 nodes; // [DM]
        char plyext;
        int ignore = FALSE;
        int prefixHint = FALSE;
@@ -4361,10 +6854,12 @@ HandleMachineMove(message, cps)
            break;
          case AnalyzeMode:
          case AnalyzeFile:
+            break;
+          case IcsObserving: /* [DM] icsEngineAnalyze */
+            if (!appData.icsEngineAnalyze) ignore = TRUE;
            break;
          case TwoMachinesPlay:
-           if ((cps->twoMachinesColor[0] == 'w') !=
-               WhiteOnMove(forwardMostMove)) {
+           if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
                ignore = TRUE;
            }
            break;
@@ -4375,18 +6870,37 @@ HandleMachineMove(message, cps)
 
        if (!ignore) {
            buf1[0] = NULLCHAR;
-           if (sscanf(message, "%d%c %d %d %lu %[^\n]\n",
+           if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
                       &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
 
                if (plyext != ' ' && plyext != '\t') {
                    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)
@@ -4395,10 +6909,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");
                }
@@ -4415,18 +6927,36 @@ 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) {
+                    strcat( thinkOutput, buf1 );
+                }
+
+                if (currentMove == forwardMostMove || gameMode == AnalyzeMode
+                        || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
                    DisplayMove(currentMove - 1);
-                   DisplayAnalysis();
                }
                return;
 
@@ -4449,13 +6979,14 @@ HandleMachineMove(message, cps)
                   isn't searching, so stats won't change) */
                programStats.line_is_book = 1;
 
-               if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
-                   gameMode == AnalyzeFile) {
+                SendProgramStatsToFrontend( cps, &programStats );
+                
+               if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
+                           gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
                    DisplayMove(currentMove - 1);
-                   DisplayAnalysis();
                }
                return;
-           } else if (sscanf(message,"stat01: %d %lu %d %d %d %s",
+           } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
                              &time, &nodes, &plylev, &mvleft,
                              &mvtot, mvname) >= 5) {
                /* The stat01: line is from Crafty (9.29+) in response
@@ -4473,7 +7004,10 @@ HandleMachineMove(message, cps)
                programStats.nr_moves = mvtot;
                strcpy(programStats.move_name, mvname);
                programStats.ok_to_send = 1;
-               DisplayAnalysis();
+                programStats.movelist[0] = '\0';
+
+                SendProgramStatsToFrontend( cps, &programStats );
+
                return;
 
            } else if (strncmp(message,"++",2) == 0) {
@@ -4488,27 +7022,74 @@ 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) {
+                           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
@@ -4548,6 +7129,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:
@@ -4569,11 +7160,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:
@@ -4582,18 +7178,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 */
@@ -4654,21 +7260,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;
        }
@@ -4678,75 +7288,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) {
-       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) {
+        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][7] = EmptySquare;
-       board[toY][4] = WhiteRook;
-    } else if (fromY == 0 && fromX == 3
-              && board[fromY][fromX] == WhiteKing
-              && toY == 0 && toX == 1) {
+        board[toY][toX] = king;
+        board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
+        board[fromY][BOARD_RGHT-1] = EmptySquare;
+    } else if (board[fromY][fromX] == king
+        && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
+               && toY == fromY && toX < fromX-1) {
        board[fromY][fromX] = EmptySquare;
-       board[toY][toX] = WhiteKing;
-       board[fromY][0] = EmptySquare;
-       board[toY][2] = WhiteRook;
+        board[toY][toX] = king;
+        board[toY][toX+1] = board[fromY][BOARD_LEFT];
+        board[fromY][BOARD_LEFT] = EmptySquare;
     } else if (board[fromY][fromX] == WhitePawn
-              && toY == 7) {
+               && toY == BOARD_HEIGHT-1
+               && gameInfo.variant != VariantXiangqi
+               ) {
        /* white pawn promotion */
-       board[7][toX] = CharToPiece(ToUpper(promoChar));
-       if (board[7][toX] == EmptySquare) {
-           board[7][toX] = WhiteQueen;
+        board[toY][toX] = CharToPiece(ToUpper(promoChar));
+        if (board[toY][toX] == EmptySquare) {
+            board[toY][toX] = WhiteQueen;
        }
+        if(gameInfo.variant==VariantBughouse ||
+           gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
+            board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
        board[fromY][fromX] = EmptySquare;
-    } else if ((fromY == 4)
+    } else if ((fromY == BOARD_HEIGHT-4)
               && (toX != fromX)
+               && gameInfo.variant != VariantXiangqi
+               && gameInfo.variant != VariantBerolina
               && (board[fromY][fromX] == WhitePawn)
               && (board[toY][toX] == EmptySquare)) {
        board[fromY][fromX] = EmptySquare;
        board[toY][toX] = WhitePawn;
        captured = board[toY - 1][toX];
        board[toY - 1][toX] = EmptySquare;
-    } else if (fromY == 7 && fromX == 4
-              && board[fromY][fromX] == BlackKing
-              && toY == 7 && toX == 6) {
+    } else if ((fromY == BOARD_HEIGHT-4)
+              && (toX == fromX)
+               && gameInfo.variant == VariantBerolina
+              && (board[fromY][fromX] == WhitePawn)
+              && (board[toY][toX] == EmptySquare)) {
        board[fromY][fromX] = EmptySquare;
-       board[toY][toX] = BlackKing;
-       board[fromY][7] = EmptySquare;
-       board[toY][5] = BlackRook;
-    } else if (fromY == 7 && fromX == 4
-              && board[fromY][fromX] == BlackKing
-              && toY == 7 && toX == 2) {
+       board[toY][toX] = WhitePawn;
+       if(oldEP & EP_BEROLIN_A) {
+               captured = board[fromY][fromX-1];
+               board[fromY][fromX-1] = EmptySquare;
+       }else{  captured = board[fromY][fromX+1];
+               board[fromY][fromX+1] = EmptySquare;
+       }
+    } else if (board[fromY][fromX] == king
+        && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
+               && toY == fromY && toX > fromX+1) {
        board[fromY][fromX] = EmptySquare;
-       board[toY][toX] = BlackKing;
-       board[fromY][0] = EmptySquare;
-       board[toY][3] = BlackRook;
+        board[toY][toX] = king;
+        board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
+        board[fromY][BOARD_RGHT-1] = EmptySquare;
+    } else if (board[fromY][fromX] == king
+        && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
+               && toY == fromY && toX < fromX-1) {
+       board[fromY][fromX] = EmptySquare;
+        board[toY][toX] = king;
+        board[toY][toX+1] = board[fromY][BOARD_LEFT];
+        board[fromY][BOARD_LEFT] = EmptySquare;
     } else if (fromY == 7 && fromX == 3
               && board[fromY][fromX] == BlackKing
               && toY == 7 && toX == 5) {
@@ -4762,47 +7457,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;
            }
@@ -4811,6 +7570,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 */
@@ -4819,21 +7596,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();
+    if (commentList[forwardMostMove+1] != NULL) {
+       free(commentList[forwardMostMove+1]);
+       commentList[forwardMostMove+1] = NULL;
+    }
+    CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
+    {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
+    ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1], 
+                               castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
+    forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
+    SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
-    if (commentList[forwardMostMove] != NULL) {
-       free(commentList[forwardMostMove]);
-       commentList[forwardMostMove] = NULL;
-    }
-    CopyBoard(boards[forwardMostMove], boards[forwardMostMove - 1]);
-    ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove]);
     gameInfo.result = GameUnfinished;
     if (gameInfo.resultDetails != NULL) {
        free(gameInfo.resultDetails);
@@ -4845,19 +7668,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 */
@@ -4866,6 +7696,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) {
@@ -4880,35 +7711,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;
@@ -4918,7 +7846,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);
@@ -4934,7 +7865,7 @@ InitChessProgram(cps)
       SendToProgram(buf, cps);
     }
     cps->initDone = TRUE;
-}
+}   
 
 
 void
@@ -4953,26 +7884,28 @@ StartChessProgram(cps)
        err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
     } else {
        if (*appData.remoteUser == NULLCHAR) {
-           sprintf(buf, "%s %s %s", appData.remoteShell, cps->host,
+         snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
                    cps->program);
        } else {
-           sprintf(buf, "%s %s -l %s %s", appData.remoteShell,
+         snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
                    cps->host, appData.remoteUser, cps->program);
        }
        err = StartChildProcess(buf, "", &cps->pr);
     }
-
+    
     if (err != 0) {
-       sprintf(buf, "Startup failure on '%s'", cps->program);
+       sprintf(buf, _("Startup failure on '%s'"), cps->program);
        DisplayFatalError(buf, err, 1);
        cps->pr = NoProc;
        cps->isr = NULL;
        return;
     }
-
+    
     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
     if (cps->protocolVersion > 1) {
       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
+      cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
+      cps->comboCnt = 0;  //                and values of combo boxes
       SendToProgram(buf, cps);
     } else {
       SendToProgram("xboard\n", cps);
@@ -4984,13 +7917,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();
@@ -5000,19 +7933,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;
@@ -5021,16 +8004,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) {
@@ -5044,7 +8031,8 @@ GameEnds(result, resultDetails, whosays)
            }
         }
 #endif
-       return;
+       endingGame = 0; /* [HGM] crash */
+        return;
     }
 
     /* If we're loading the game from a file, stop */
@@ -5054,26 +8042,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
+               }
 
-       if (resultDetails != NULL) {
+               // now verify win claims, but not in drop games, as we don't understand those yet
+                if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
+                                                || gameInfo.variant == VariantGreat) &&
+                    (result == WhiteWins && claimer == 'w' ||
+                     result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
+                     if (appData.debugMode) {
+                       fprintf(debugFP, "result=%d sp=%d move=%d\n",
+                               result, epStatus[forwardMostMove], forwardMostMove);
+                     }
+                     if(result != trueResult) {
+                             sprintf(buf, "False win claim: '%s'", resultDetails);
+                             result = claimer == 'w' ? BlackWins : WhiteWins;
+                             resultDetails = buf;
+                     }
+                } else
+                if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
+                    && (forwardMostMove <= backwardMostMove ||
+                        epStatus[forwardMostMove-1] > EP_DRAWS ||
+                        (claimer=='b')==(forwardMostMove&1))
+                                                                                  ) {
+                      /* [HGM] verify: draws that were not flagged are false claims */
+                      sprintf(buf, "False draw claim: '%s'", resultDetails);
+                      result = claimer == 'w' ? BlackWins : WhiteWins;
+                      resultDetails = buf;
+                }
+                /* (Claiming a loss is accepted no questions asked!) */
+           }
+           /* [HGM] bare: don't allow bare King to win */
+           if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
+              && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
+              && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
+              && result != GameIsDrawn)
+           {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
+               for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
+                       int p = (int)boards[forwardMostMove][i][j] - color;
+                       if(p >= 0 && p <= (int)WhiteKing) k++;
+               }
+               if (appData.debugMode) {
+                    fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
+                       result, resultDetails ? resultDetails : "(null)", whosays, k, color);
+               }
+               if(k <= 1) {
+                       result = GameIsDrawn;
+                       sprintf(buf, "%s but bare king", resultDetails);
+                       resultDetails = buf;
+               }
+           }
+        }
+
+
+        if(serverMoves != NULL && !loadFlag) { char c = '=';
+            if(result==WhiteWins) c = '+';
+            if(result==BlackWins) c = '-';
+            if(resultDetails != NULL)
+                fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
+        }
+       if (resultDetails != NULL) {
            gameInfo.result = result;
            gameInfo.resultDetails = StrSave(resultDetails);
 
+           /* display last move only if game was not loaded from file */
+           if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
+               DisplayMove(currentMove - 1);
+    
+           if (forwardMostMove != 0) {
+               if (gameMode != PlayFromGameFile && gameMode != EditGame
+                   && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
+                                                               ) {
+                   if (*appData.saveGameFile != NULLCHAR) {
+                       SaveGameToFile(appData.saveGameFile, TRUE);
+                   } else if (appData.autoSaveGames) {
+                       AutoSaveGame();
+                   }
+                   if (*appData.savePositionFile != NULLCHAR) {
+                       SavePositionToFile(appData.savePositionFile);
+                   }
+               }
+           }
+
            /* Tell program how game ended in case it is learning */
+            /* [HGM] Moved this to after saving the PGN, just in case */
+            /* engine died and we got here through time loss. In that */
+            /* case we will get a fatal error writing the pipe, which */
+            /* would otherwise lose us the PGN.                       */
+            /* [HGM] crash: not needed anymore, but doesn't hurt;     */
+            /* output during GameEnds should never be fatal anymore   */
            if (gameMode == MachinePlaysWhite ||
                gameMode == MachinePlaysBlack ||
                gameMode == TwoMachinesPlay ||
@@ -5091,23 +8183,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) {
@@ -5149,8 +8224,8 @@ GameEnds(result, resultDetails, whosays)
                }
            }
        } else if (gameMode == EditGame ||
-                  gameMode == PlayFromGameFile ||
-                  gameMode == AnalyzeMode ||
+                  gameMode == PlayFromGameFile || 
+                  gameMode == AnalyzeMode || 
                   gameMode == AnalyzeFile) {
            nextGameMode = gameMode;
        } else {
@@ -5165,7 +8240,8 @@ GameEnds(result, resultDetails, whosays)
     if (appData.noChessProgram) {
        gameMode = nextGameMode;
        ModeHighlight();
-       return;
+       endingGame = 0; /* [HGM] crash */
+        return;
     }
 
     if (first.reuse) {
@@ -5189,10 +8265,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;
@@ -5213,9 +8291,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;
@@ -5242,12 +8322,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];
@@ -5264,24 +8349,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);
@@ -5296,9 +8393,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) {
@@ -5309,8 +8406,8 @@ ResurrectChessProgram()
        timeRemaining[1][currentMove] = blackTimeRemaining;
     }
 
-    if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
-       first.analysisSupport) {
+    if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
+                appData.icsEngineAnalyze) && first.analysisSupport) {
       SendToProgram("analyze\n", &first);
       first.analyzing = TRUE;
     }
@@ -5329,7 +8426,6 @@ Reset(redraw, init)
        fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
                redraw, init, gameMode);
     }
-
     pausing = pauseExamInvalid = FALSE;
     startedFromSetupPosition = blackPlaysFirst = FALSE;
     firstMove = TRUE;
@@ -5338,6 +8434,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);
@@ -5347,7 +8445,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;
@@ -5356,9 +8455,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) {
@@ -5372,10 +8486,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
@@ -5409,19 +8526,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) {
@@ -5432,9 +8556,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;
 }
 
@@ -5448,13 +8571,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;
@@ -5463,11 +8586,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 == '(') {
@@ -5482,6 +8605,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:
@@ -5501,12 +8630,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;
 
@@ -5518,8 +8653,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:
@@ -5553,11 +8688,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 {
@@ -5588,11 +8724,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 {
@@ -5627,10 +8764,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;
@@ -5648,7 +8785,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);
@@ -5661,7 +8798,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();
@@ -5672,7 +8809,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;
@@ -5697,7 +8834,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;
        }
@@ -5735,38 +8872,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);
@@ -5774,11 +8912,11 @@ MakeRegisteredMove()
                GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
            }
            break;
-
+           
          case CMAIL_ACCEPT:
            GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
            break;
-
+             
          default:
            break;
        }
@@ -5872,8 +9010,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 )
@@ -5891,7 +9030,7 @@ LoadGame(f, gameNumber, title, useList)
 
     if (useList) {
        lg = (ListGame *) ListElem(&gameList, gameNumber-1);
-
+       
        if (lg) {
            fseek(f, lg->offset, 0);
            GameListHighlight(gameNumber);
@@ -5921,9 +9060,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) {
@@ -5946,12 +9084,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.
@@ -5977,7 +9115,7 @@ LoadGame(f, gameNumber, title, useList)
            gn--;
            lastLoadGameStart = cm;
            break;
-
+           
          case MoveNumberOne:
            switch (lastLoadGameStart) {
              case GNUChessGame:
@@ -6045,7 +9183,7 @@ LoadGame(f, gameNumber, title, useList)
            break;
        }
     }
-
+    
     if (appData.debugMode)
       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
 
@@ -6071,15 +9209,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;
@@ -6103,6 +9251,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;
@@ -6114,7 +9269,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 == '(') {
@@ -6137,9 +9292,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 */
@@ -6157,8 +9314,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 '-':
@@ -6173,13 +9330,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;
@@ -6203,16 +9360,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 == '(') {
@@ -6244,12 +9407,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) {
@@ -6257,7 +9419,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);
@@ -6286,8 +9448,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;
 }
 
@@ -6324,7 +9488,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 {
@@ -6343,7 +9507,7 @@ LoadPosition(f, positionNumber, title)
     char *p, line[MSG_SIZ];
     Board initial_position;
     int i, j, fenMode, pn;
-
+