/*
* backend.c -- Common back end for X and Windows NT versions of
- * XBoard $Id$
+ * XBoard $Id: backend.c,v 2.6 2003/11/28 09:37:36 mann Exp $
*
- * Copyright 1991 by Digital Equipment Corporation, Maynard, Massachusetts.
- * Enhancements Copyright 1992-2001 Free Software Foundation, Inc.
+ * Copyright 1991 by Digital Equipment Corporation, Maynard,
+ * Massachusetts. Enhancements Copyright
+ * 1992-2001,2002,2003,2004,2005,2006,2007,2008,2009 Free Software
+ * Foundation, Inc.
*
* 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 "zippy.h"
#endif
#include "backendz.h"
-#include "gettext.h"
-
-#ifdef ENABLE_NLS
-# define _(s) gettext (s)
-# define N_(s) gettext_noop (s)
-#else
-# define _(s) (s)
-# define N_(s) s
-#endif
+#include "gettext.h"
+
+#ifdef ENABLE_NLS
+# define _(s) gettext (s)
+# define N_(s) gettext_noop (s)
+#else
+# define _(s) (s)
+# define N_(s) s
+#endif
/* A point in time */
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));
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));
void ParseFeatures P((char* args, ChessProgramState *cps));
void InitBackEnd3 P((void));
void FeatureDone P((ChessProgramState* cps, int val));
-void InitChessProgram P((ChessProgramState *cps));
+void InitChessProgram P((ChessProgramState *cps, int setup));
+void OutputKibitz(int window, char *text);
+int PerpetualChase(int first, int last);
+int EngineOutputIsUp();
+void InitDrawingSizes(int x, int y);
+
+#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
+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;
/* States for ics_getting_history */
#define H_FALSE 0
#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
#define TN_SGA 0003
#define TN_PORT 23
+/* [AS] */
+static char * safeStrCpy( char * dst, const char * src, size_t count )
+{
+ assert( dst != NULL );
+ assert( src != NULL );
+ assert( count > 0 );
+
+ strncpy( dst, src, count );
+ dst[ count-1 ] = '\0';
+ return dst;
+}
+
+#if 0
+//[HGM] for future use? Conditioned out for now to suppress warning.
+static char * safeStrCat( char * dst, const char * src, size_t count )
+{
+ size_t dst_len;
+
+ assert( dst != NULL );
+ assert( src != NULL );
+ assert( count > 0 );
+
+ dst_len = strlen(dst);
+
+ assert( count > dst_len ); /* Buffer size must be greater than current length */
+
+ safeStrCpy( dst + dst_len, src, count - dst_len );
+
+ return dst;
+}
+#endif
+
+/* Some compiler can't cast u64 to double
+ * This function do the job for us:
+
+ * 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)
{
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;
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:
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];
Boolean alarmSounded;
/* end premove variables */
-#define ICS_GENERIC 0
-#define ICS_ICC 1
-#define ICS_FICS 2
-#define ICS_CHESSNET 3 /* not really supported */
-int ics_type = ICS_GENERIC;
char *ics_prefix = "$";
+int ics_type = ICS_GENERIC;
int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
int pauseExamForwardMostMove = 0;
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;
AppData appData;
Board boards[MAX_MOVES];
-Board initialPosition = {
+/* [HGM] Following 7 needed for accurate legality tests: */
+char epStatus[MAX_MOVES];
+char castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
+char castlingRank[BOARD_SIZE]; // and corresponding ranks
+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;
+
+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)
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;
{
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;
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);
}
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.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;
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];
+ strlen(PATCHLEVEL));
sprintf(programVersion, "%s %s.%s", PRODUCT, VERSION, PATCHLEVEL);
} else {
+#if 0
char *p, *q;
q = first.program;
while (*q != ' ' && *q != NULLCHAR) q++;
p = q;
- while (p > first.program && *(p-1) != '/') p--;
+ while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] backslash added */
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);
+#else
+ /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
+ programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)
+ + strlen(PATCHLEVEL) + strlen(first.tidy));
+ sprintf(programVersion, "%s %s.%s + %s", PRODUCT, VERSION, PATCHLEVEL, first.tidy);
+#endif
}
if (!appData.icsActive) {
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;
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,
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
int ti;
int mps;
{
+#if 0
int matched, min, sec;
matched = sscanf(tc, "%d:%d", &min, &sec);
} else {
return FALSE;
}
+#else
+ long tc1;
+ long tc2;
+ char buf[MSG_SIZ];
+
+ if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
+ if(ti > 0) {
+ if(mps)
+ sprintf(buf, "+%d/%s+%d", mps, tc, ti);
+ else sprintf(buf, "+%s+%d", tc, ti);
+ } else {
+ if(mps)
+ sprintf(buf, "+%d/%s", mps, tc);
+ else sprintf(buf, "+%s", tc);
+ }
+ fullTimeControlString = StrSave(buf);
+
+ if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
+ return FALSE;
+ }
+
+ if( *tc == '/' ) {
+ /* Parse second time control */
+ tc++;
+
+ if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
+ return FALSE;
+ }
+
+ if( tc2 == 0 ) {
+ return FALSE;
+ }
+
+ timeControl_2 = tc2 * 1000;
+ }
+ else {
+ timeControl_2 = 0;
+ }
+
+ if( tc1 == 0 ) {
+ return FALSE;
+ }
+
+ timeControl = tc1 * 1000;
+#endif
if (ti >= 0) {
timeIncrement = ti * 1000; /* convert to ms */
} 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);
}
}
char buf[MSG_SIZ];
int err;
- InitChessProgram(&first);
+ InitChessProgram(&first, startedFromSetupPosition);
+
if (appData.icsActive) {
+#ifdef WIN32
+ /* [DM] Make a console window if needed [HGM] merged ifs */
+ ConsoleCreate();
+#endif
err = establish();
if (err != 0) {
if (*appData.icsCommPort != NULLCHAR) {
sprintf(buf, _("Could not open comm port %s"),
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);
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;
(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) {
}
AnalyzeModeEvent();
} else if (initialMode == AnalyzeFile) {
- ShowThinkingEvent(TRUE);
+ appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
+ ShowThinkingEvent();
AnalyzeFileEvent();
AnalysisPeriodicEvent(1);
} else if (initialMode == MachinePlaysWhite) {
} 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",
+ snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
appData.remoteShell, appData.gateway,
appData.remoteUser, appData.telnetProgram,
appData.icsHost, appData.icsPort);
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);
char buf[MSG_SIZ];
if (!e) return v;
-
+
+ /* [HGM] skip over optional board-size prefixes */
+ if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
+ sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
+ while( *e++ != '_');
+ }
+
for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
if (StrCaseStr(e, variantNames[i])) {
v = (VariantClass) i;
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"))) {
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;
TelnetRequest(TN_DONT, TN_ECHO);
}
+void
+CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
+{
+ /* put the holdings sent to us by the server on the board holdings area */
+ int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
+ char p;
+ ChessSquare piece;
+
+ if(gameInfo.holdingsWidth < 2) return;
+
+ if( (int)lowestPiece >= BlackPawn ) {
+ holdingsColumn = 0;
+ countsColumn = 1;
+ holdingsStartRow = BOARD_HEIGHT-1;
+ direction = -1;
+ } else {
+ holdingsColumn = BOARD_WIDTH-1;
+ countsColumn = BOARD_WIDTH-2;
+ holdingsStartRow = 0;
+ direction = 1;
+ }
+
+ for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
+ board[i][holdingsColumn] = EmptySquare;
+ board[i][countsColumn] = (ChessSquare) 0;
+ }
+ while( (p=*holdings++) != NULLCHAR ) {
+ piece = CharToPiece( ToUpper(p) );
+ if(piece == EmptySquare) continue;
+ /*j = (int) piece - (int) WhitePawn;*/
+ j = PieceToNumber(piece);
+ if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
+ if(j < 0) continue; /* should not happen */
+ piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
+ board[holdingsStartRow+j*direction][holdingsColumn] = piece;
+ board[holdingsStartRow+j*direction][countsColumn]++;
+ }
+
+}
+
+
+void
+VariantSwitch(Board board, VariantClass newVariant)
+{
+ int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
+ int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
+// Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
+
+ startedFromPositionFile = FALSE;
+ if(gameInfo.variant == newVariant) return;
+
+ /* [HGM] This routine is called each time an assignment is made to
+ * gameInfo.variant during a game, to make sure the board sizes
+ * are set to match the new variant. If that means adding or deleting
+ * holdings, we shift the playing board accordingly
+ * This kludge is needed because in ICS observe mode, we get boards
+ * of an ongoing game without knowing the variant, and learn about the
+ * latter only later. This can be because of the move list we requested,
+ * in which case the game history is refilled from the beginning anyway,
+ * but also when receiving holdings of a crazyhouse game. In the latter
+ * case we want to add those holdings to the already received position.
+ */
+
+
+ if (appData.debugMode) {
+ fprintf(debugFP, "Switch board from %s to %s\n",
+ VariantName(gameInfo.variant), VariantName(newVariant));
+ setbuf(debugFP, NULL);
+ }
+ shuffleOpenings = 0; /* [HGM] shuffle */
+ gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
+ switch(newVariant) {
+ case VariantShogi:
+ newWidth = 9; newHeight = 9;
+ gameInfo.holdingsSize = 7;
+ case VariantBughouse:
+ case VariantCrazyhouse:
+ newHoldingsWidth = 2; break;
+ default:
+ newHoldingsWidth = gameInfo.holdingsSize = 0;
+ }
+
+ if(newWidth != gameInfo.boardWidth ||
+ newHeight != gameInfo.boardHeight ||
+ newHoldingsWidth != gameInfo.holdingsWidth ) {
+
+ /* shift position to new playing area, if needed */
+ if(newHoldingsWidth > gameInfo.holdingsWidth) {
+ for(i=0; i<BOARD_HEIGHT; i++)
+ for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
+ board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
+ board[i][j];
+ for(i=0; i<newHeight; i++) {
+ board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
+ board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
+ }
+ } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
+ for(i=0; i<BOARD_HEIGHT; i++)
+ for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
+ board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
+ board[i][j];
+ }
+
+ gameInfo.boardWidth = newWidth;
+ gameInfo.boardHeight = newHeight;
+ gameInfo.holdingsWidth = newHoldingsWidth;
+ gameInfo.variant = newVariant;
+ InitDrawingSizes(-2, 0);
+
+ /* [HGM] The following should definitely be solved in a better way */
+#if 0
+ CopyBoard(board, tempBoard); /* save position in case it is board[0] */
+ for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];
+ saveEP = epStatus[0];
+#endif
+ InitPosition(FALSE); /* this sets up board[0], but also other stuff */
+#if 0
+ epStatus[0] = saveEP;
+ for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];
+ CopyBoard(tempBoard, board); /* restore position received from ICS */
+#endif
+ } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
+
+ forwardMostMove = oldForwardMostMove;
+ backwardMostMove = oldBackwardMostMove;
+ currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
+}
+
static int loggedOn = FALSE;
/*-- Game start info cache: --*/
static int player2Rating = -1;
/*----------------------------*/
+ColorClass curColor = ColorNormal;
+int suppressKibitz = 0;
+
void
read_from_ics(isr, closure, data, count, error)
InputSourceRef isr;
static int parse_pos = 0;
static char buf[BUF_SIZE + 1];
static int firstTime = TRUE, intfSet = FALSE;
- static ColorClass curColor = ColorNormal;
static ColorClass prevColor = ColorNormal;
static int savingComment = FALSE;
char str[500];
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;
-#ifdef WIN32
if (appData.debugMode) {
if (!error) {
fprintf(debugFP, "<ICS: ");
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. */
for (i = 0; i < count; i++) {
if (data[i] != NULLCHAR && data[i] != '\r')
buf[buf_len++] = data[i];
+ if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' &&
+ buf[buf_len-3]==' ' && buf[buf_len-2]==' ' && buf[buf_len-1]==' ')
+ buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
}
buf[buf_len] = NULLCHAR;
parse[parse_pos++] = buf[i];
if (buf[i] == '\n') {
parse[parse_pos] = NULLCHAR;
- AppendComment(forwardMostMove, StripHighlight(parse));
+ 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 */
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;
}
}
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.zippyTalk || appData.zippyPlay) {
-#if ZIPPY\r
- #ifdef WIN32\r
- /* Backup adress for color zippy lines */\r
- backup = i;\r
- if (first.initDone && 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 */
+ } // [DM] 'else { ' deleted
+ if (/* Don't color "message" or "messages" output */
(tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
looking_at(buf, &i, "*. * at *:*: ") ||
looking_at(buf, &i, "--* (*:*): ") ||
"* * 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;
}
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:
}
SendTimeRemaining(&first, TRUE);
}
+#if 0
if (first.useColors) {
SendToProgram("white\ngo\n", &first);
} else {
SendToProgram("go\n", &first);
}
+#else
+ if (first.useColors) {
+ SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
+ }
+ bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
+#endif
first.maybeThinking = TRUE;
} else {
if (first.usePlayother) {
}
SendTimeRemaining(&first, FALSE);
}
+#if 0
if (first.useColors) {
SendToProgram("black\ngo\n", &first);
} else {
SendToProgram("go\n", &first);
}
+#else
+ if (first.useColors) {
+ SendToProgram("black\n", &first);
+ }
+ bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
+#endif
first.maybeThinking = TRUE;
} else {
if (first.usePlayother) {
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;
}
}
/* 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;
}
}
/* 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*/
if (gameMode == IcsObserving &&
atoi(star_match[0]) == ics_gamenum)
{
+ /* icsEngineAnalyze */
+ if (appData.icsEngineAnalyze) {
+ ExitAnalyzeMode();
+ ModeHighlight();
+ }
StopClocks();
gameMode = IcsIdle;
ics_gamenum = -1;
ClearPremoveHighlights();
if (appData.debugMode)
fprintf(debugFP, "Sending premove:\n");
- UserMoveEvent(premoveFromX, premoveFromY,
+ UserMoveEvent(premoveFromX, premoveFromY,
premoveToX, premoveToY,
- premovePromoChar);
+ premovePromoChar);
}
}
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) {
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,
gameInfo.white, white_holding,
gameInfo.black, black_holding);
}
- DrawPosition(FALSE, NULL);
+
+ DrawPosition(FALSE, boards[currentMove]);
DisplayTitle(str);
}
/* Suppress following prompt */
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,
* 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 */
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;
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;
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;
}
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 &&
}
}
+ 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) {
/* 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) {
/* 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. */
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;
}
/* currentMoveString is set as a side-effect of ParseOneMove */
strcpy(moveList[moveNum - 1], currentMoveString);
strcat(moveList[moveNum - 1], "\n");
- } else if (strcmp(move_str, "none") == 0) {
- /* Again, we don't know what the board looked like;
- this is really the start of the game. */
- parseList[moveNum - 1][0] = NULLCHAR;
- moveList[moveNum - 1][0] = NULLCHAR;
- backwardMostMove = moveNum;
- startedFromSetupPosition = TRUE;
- fromX = fromY = toX = toY = -1;
- } else {
+ } else {
/* Move from ICS was illegal!? Punt. */
+ if (appData.debugMode) {
+ fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
+ fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
+ }
#if 0
if (appData.testLegality && appData.debugMode) {
sprintf(str, "Illegal move \"%s\" from ICS", move_str);
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 (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 ?
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);
}
}
if (gameInfo.variant != VariantBughouse &&
gameInfo.variant != VariantCrazyhouse) {
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);
+ }
}
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
ChessProgramState *cps;
{
char buf[MSG_SIZ];
+
if (cps->useUsermove) {
SendToProgram("usermove ", 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
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;
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:
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 (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);
}
}
}
}
+/* [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)
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:
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;
}
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;
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;
}
}
+// [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;
{
+ ChessSquare (* pieces)[BOARD_SIZE];
+ int i, j, pawnRow, overrule,
+ oldx = gameInfo.boardWidth,
+ oldy = gameInfo.boardHeight,
+ oldh = gameInfo.holdingsWidth,
+ oldv = gameInfo.variant;
+
currentMove = forwardMostMove = backwardMostMove = 0;
+ if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
+
+ /* [AS] Initialize pv info list [HGM] and game status */
+ {
+ for( i=0; i<MAX_MOVES; i++ ) {
+ pvInfoList[i].depth = 0;
+ epStatus[i]=EP_NONE;
+ for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
+ }
+
+ initialRulePlies = 0; /* 50-move counter start */
+
+ castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
+ castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
+ }
+
+
+ /* [HGM] logic here is completely changed. In stead of full positions */
+ /* the initialized data only consist of the two backranks. The switch */
+ /* selects which one we will use, which is than copied to the Board */
+ /* initialPosition, which for the rest is initialized by Pawns and */
+ /* empty squares. This initial position is then copied to boards[0], */
+ /* possibly after shuffling, so that it remains available. */
+
+ gameInfo.holdingsWidth = 0; /* default board sizes */
+ gameInfo.boardWidth = 8;
+ gameInfo.boardHeight = 8;
+ gameInfo.holdingsSize = 0;
+ nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
+ for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
+ SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
+
switch (gameInfo.variant) {
+ case VariantFischeRandom:
+ shuffleOpenings = TRUE;
default:
- CopyBoard(boards[0], initialPosition);
+ pieces = FIDEArray;
+ break;
+ case VariantShatranj:
+ pieces = ShatranjArray;
+ nrCastlingRights = 0;
+ SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
break;
case VariantTwoKings:
- CopyBoard(boards[0], twoKingsPosition);
+ pieces = twoKingsArray;
+ break;
+ case VariantCapaRandom:
+ shuffleOpenings = TRUE;
+ case VariantCapablanca:
+ pieces = CapablancaArray;
+ gameInfo.boardWidth = 10;
+ SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
+ break;
+ case VariantGothic:
+ pieces = GothicArray;
+ gameInfo.boardWidth = 10;
+ SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
+ break;
+ case VariantJanus:
+ pieces = JanusArray;
+ gameInfo.boardWidth = 10;
+ SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
+ nrCastlingRights = 6;
+ castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
+ castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
+ castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
+ castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
+ castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
+ castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
+ break;
+ case VariantFalcon:
+ pieces = FalconArray;
+ gameInfo.boardWidth = 10;
+ SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
+ break;
+ case VariantXiangqi:
+ pieces = XiangqiArray;
+ gameInfo.boardWidth = 9;
+ gameInfo.boardHeight = 10;
+ nrCastlingRights = 0;
+ SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
+ break;
+ case VariantShogi:
+ pieces = ShogiArray;
+ gameInfo.boardWidth = 9;
+ gameInfo.boardHeight = 9;
+ gameInfo.holdingsSize = 7;
+ nrCastlingRights = 0;
+ SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
+ break;
+ case VariantCourier:
+ pieces = CourierArray;
+ gameInfo.boardWidth = 12;
+ nrCastlingRights = 0;
+ SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
+ for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
+ break;
+ case VariantKnightmate:
+ pieces = KnightmateArray;
+ SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
+ break;
+ case VariantFairy:
+ pieces = fairyArray;
+ SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
+ break;
+ case VariantGreat:
+ pieces = GreatArray;
+ gameInfo.boardWidth = 10;
+ SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
+ gameInfo.holdingsSize = 8;
+ break;
+ case VariantSuper:
+ pieces = FIDEArray;
+ SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
+ gameInfo.holdingsSize = 8;
startedFromSetupPosition = TRUE;
break;
+ case VariantCrazyhouse:
+ case VariantBughouse:
+ pieces = FIDEArray;
+ SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
+ gameInfo.holdingsSize = 5;
+ break;
case VariantWildCastle:
- CopyBoard(boards[0], initialPosition);
+ pieces = FIDEArray;
/* !!?shuffle with kings guaranteed to be on d or e file */
+ shuffleOpenings = 1;
break;
case VariantNoCastle:
- CopyBoard(boards[0], initialPosition);
+ pieces = FIDEArray;
+ nrCastlingRights = 0;
+ for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
/* !!?unconstrained back-rank shuffle */
+ shuffleOpenings = 1;
break;
- case VariantFischeRandom:
- CopyBoard(boards[0], initialPosition);
- /* !!shuffle according to FR rules */
- break;
}
+
+ overrule = 0;
+ if(appData.NrFiles >= 0) {
+ if(gameInfo.boardWidth != appData.NrFiles) overrule++;
+ gameInfo.boardWidth = appData.NrFiles;
+ }
+ if(appData.NrRanks >= 0) {
+ gameInfo.boardHeight = appData.NrRanks;
+ }
+ if(appData.holdingsSize >= 0) {
+ i = appData.holdingsSize;
+ if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
+ gameInfo.holdingsSize = i;
+ }
+ if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
+ if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
+ DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
+
+ pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
+ if(pawnRow < 1) pawnRow = 1;
+
+ /* User pieceToChar list overrules defaults */
+ if(appData.pieceToCharTable != NULL)
+ SetCharTable(pieceToChar, appData.pieceToCharTable);
+
+ for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
+
+ if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
+ s = (ChessSquare) 0; /* account holding counts in guard band */
+ for( i=0; i<BOARD_HEIGHT; i++ )
+ initialPosition[i][j] = s;
+
+ if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
+ initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
+ initialPosition[pawnRow][j] = WhitePawn;
+ initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
+ if(gameInfo.variant == VariantXiangqi) {
+ if(j&1) {
+ initialPosition[pawnRow][j] =
+ initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
+ if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
+ initialPosition[2][j] = WhiteCannon;
+ initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
+ }
+ }
+ }
+ initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
+ }
+ if( (gameInfo.variant == VariantShogi) && !overrule ) {
+
+ j=BOARD_LEFT+1;
+ initialPosition[1][j] = WhiteBishop;
+ initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
+ j=BOARD_RGHT-2;
+ initialPosition[1][j] = WhiteRook;
+ initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
+ }
+
+ if( nrCastlingRights == -1) {
+ /* [HGM] Build normal castling rights (must be done after board sizing!) */
+ /* This sets default castling rights from none to normal corners */
+ /* Variants with other castling rights must set them themselves above */
+ nrCastlingRights = 6;
+
+ castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
+ castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
+ castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
+ castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
+ castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
+ castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
+ }
+
+ if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
+ if(gameInfo.variant == VariantGreat) { // promotion commoners
+ initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
+ initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
+ initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
+ initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
+ }
+#if 0
+ if(gameInfo.variant == VariantFischeRandom) {
+ if( appData.defaultFrcPosition < 0 ) {
+ ShuffleFRC( initialPosition );
+ }
+ else {
+ SetupFRC( initialPosition, appData.defaultFrcPosition );
+ }
+ startedFromSetupPosition = TRUE;
+ } else
+#else
+ if (appData.debugMode) {
+ fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
+ }
+ if(shuffleOpenings) {
+ SetUpShuffle(initialPosition, appData.defaultFrcPosition);
+ startedFromSetupPosition = TRUE;
+ }
+#endif
+ if(startedFromPositionFile) {
+ /* [HGM] loadPos: use PositionFile for every new game */
+ CopyBoard(initialPosition, filePosition);
+ for(i=0; i<nrCastlingRights; i++)
+ castlingRights[0][i] = initialRights[i] = fileRights[i];
+ startedFromSetupPosition = TRUE;
+ }
+
+ CopyBoard(boards[0], initialPosition);
+
+ if(oldx != gameInfo.boardWidth ||
+ oldy != gameInfo.boardHeight ||
+ oldh != gameInfo.holdingsWidth
+#ifdef GOTHIC
+ || oldv == VariantGothic || // For licensing popups
+ gameInfo.variant == VariantGothic
+#endif
+#ifdef FALCON
+ || oldv == VariantFalcon ||
+ gameInfo.variant == VariantFalcon
+#endif
+ )
+ InitDrawingSizes(-2 ,0);
+
if (redraw)
- DrawPosition(FALSE, boards[currentMove]);
+ DrawPosition(TRUE, boards[currentMove]);
}
void
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);
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);
+ AAA + j, ONE + i);
+ if(message[0] == '+' || message[0] == '~') {
+ sprintf(message, "%c%c%c+\n",
+ PieceToChar((ChessSquare)(DEMOTED *bp)),
+ AAA + j, ONE + i);
+ }
+ if(cps->alphaRank) { /* [HGM] shogi: translate coords */
+ message[1] = BOARD_RGHT - 1 - j + '1';
+ message[2] = BOARD_HEIGHT - 1 - i + 'a';
+ }
SendToProgram(message, cps);
}
}
}
SendToProgram("c\n", cps);
- for (i = BOARD_SIZE - 1; i >= 0; i--) {
- bp = &boards[moveNum][i][0];
- for (j = 0; j < BOARD_SIZE; j++, bp++) {
+ for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
+ bp = &boards[moveNum][i][BOARD_LEFT];
+ for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
if (((int) *bp != (int) EmptySquare)
&& ((int) *bp >= (int) BlackPawn)) {
sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
- 'a' + j, '1' + i);
+ AAA + j, ONE + i);
+ if(message[0] == '+' || message[0] == '~') {
+ sprintf(message, "%c%c%c+\n",
+ PieceToChar((ChessSquare)(DEMOTED *bp)),
+ AAA + j, ONE + i);
+ }
+ if(cps->alphaRank) { /* [HGM] shogi: translate coords */
+ message[1] = BOARD_RGHT - 1 - j + '1';
+ message[2] = BOARD_HEIGHT - 1 - i + 'a';
+ }
SendToProgram(message, cps);
}
}
SendToProgram(".\n", cps);
}
+ setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
}
int
IsPromotion(fromX, fromY, toX, toY)
int fromX, fromY, toX, toY;
{
- return gameMode != EditPosition &&
- fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0 &&
- ((boards[currentMove][fromY][fromX] == WhitePawn && toY == 7) ||
- (boards[currentMove][fromY][fromX] == BlackPawn && toY == 0));
+ /* [HGM] add Shogi promotions */
+ int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
+ ChessSquare piece;
+
+ if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
+ !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
+ /* [HGM] Note to self: line above also weeds out drops */
+ piece = boards[currentMove][fromY][fromX];
+ if(gameInfo.variant == VariantShogi) {
+ promotionZoneSize = 3;
+ highestPromotingPiece = (int)WhiteKing;
+ /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
+ and if in normal chess we then allow promotion to King, why not
+ allow promotion of other piece in Shogi? */
+ }
+ if((int)piece >= BlackPawn) {
+ if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
+ return FALSE;
+ highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
+ } else {
+ if( toY < BOARD_HEIGHT - promotionZoneSize &&
+ fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
+ }
+ return ( (int)piece <= highestPromotingPiece );
}
+int
+InPalace(row, column)
+ int row, column;
+{ /* [HGM] for Xiangqi */
+ if( (row < 3 || row > BOARD_HEIGHT-4) &&
+ column < (BOARD_WIDTH + 4)/2 &&
+ column > (BOARD_WIDTH - 5)/2 ) return TRUE;
+ return FALSE;
+}
int
PieceForSquare (x, y)
int x;
int y;
{
- if (x < 0 || x >= BOARD_SIZE || y < 0 || y >= BOARD_SIZE)
+ if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
return -1;
else
return boards[currentMove][y][x];
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:
ChessMove lastLoadGameStart = (ChessMove) 0;
-void
-UserMoveEvent(fromX, fromY, toX, toY, promoChar)
+ChessMove
+UserMoveTest(fromX, fromY, toX, toY, promoChar)
int fromX, fromY, toX, toY;
int promoChar;
{
ChessMove moveType;
+ ChessSquare pdown, pup;
- if (fromX < 0 || fromY < 0) return;
+ if (fromX < 0 || fromY < 0) return ImpossibleMove;
if ((fromX == toX) && (fromY == toY)) {
- return;
- }
-
+ return ImpossibleMove;
+ }
+
+ /* [HGM] suppress all moves into holdings area and guard band */
+ if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
+ return ImpossibleMove;
+
+ /* [HGM] <sameColor> moved to here from winboard.c */
+ /* note: this code seems to exist for filtering out some obviously illegal premoves */
+ pdown = boards[currentMove][fromY][fromX];
+ pup = boards[currentMove][toY][toX];
+ if ( gameMode != EditPosition &&
+ (WhitePawn <= pdown && pdown < BlackPawn &&
+ WhitePawn <= pup && pup < BlackPawn ||
+ BlackPawn <= pdown && pdown < EmptySquare &&
+ BlackPawn <= pup && pup < EmptySquare
+ ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
+ (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
+ pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 )
+ ) )
+ return ImpossibleMove;
+
/* Check if the user is playing in turn. This is complicated because we
let the user "pick up" a piece before it is his turn. So the piece he
tried to pick up may have been captured by the time he puts it down!
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;
/* User is moving for White */
if (!WhiteOnMove(currentMove)) {
DisplayMoveError(_("It is Black's turn"));
- return;
+ return ImpossibleMove;
}
break;
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;
"fromY %d, toX %d, toY %d\n",
fromX, fromY, toX, toY);
}
- return;
+ return ImpossibleMove;
}
break;
"fromY %d, toX %d, toY %d\n",
fromX, fromY, toX, toY);
}
- return;
+ return ImpossibleMove;
}
break;
break;
case EditPosition:
+ /* EditPosition, empty square, or different color piece;
+ click-click move is possible */
if (toX == -2 || toY == -2) {
boards[0][fromY][fromX] = EmptySquare;
- DrawPosition(FALSE, boards[currentMove]);
+ return AmbiguousMove;
} else if (toX >= 0 && toY >= 0) {
boards[0][toY][toX] = boards[0][fromY][fromX];
boards[0][fromY][fromX] = EmptySquare;
- DrawPosition(FALSE, boards[currentMove]);
+ return AmbiguousMove;
}
- return;
+ return ImpossibleMove;
+ }
+
+ /* [HGM] If move started in holdings, it means a drop */
+ if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
+ if( pup != EmptySquare ) return ImpossibleMove;
+ if(appData.testLegality) {
+ /* it would be more logical if LegalityTest() also figured out
+ * which drops are legal. For now we forbid pawns on back rank.
+ * Shogi is on its own here...
+ */
+ if( (pdown == WhitePawn || pdown == BlackPawn) &&
+ (toY == 0 || toY == BOARD_HEIGHT -1 ) )
+ return(ImpossibleMove); /* no pawn drops on 1st/8th */
+ }
+ return WhiteDrop; /* Not needed to specify white or black yet */
}
- if (toX < 0 || toY < 0) return;
userOfferedDraw = FALSE;
+ /* [HGM] always test for legality, to get promotion info */
+ moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
+ epStatus[currentMove], castlingRights[currentMove],
+ fromY, fromX, toY, toX, promoChar);
+
+ /* [HGM] but possibly ignore an IllegalMove result */
if (appData.testLegality) {
- moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
- EP_UNKNOWN, fromY, fromX, toY, toX, promoChar);
if (moveType == IllegalMove || moveType == ImpossibleMove) {
DisplayMoveError(_("Illegal move"));
- return;
+ return ImpossibleMove;
}
- } else {
- moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
}
+if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
+ return moveType;
+ /* [HGM] <popupFix> in stead of calling FinishMove directly, this
+ function is made into one that returns an OK move type if FinishMove
+ should be called. This to give the calling driver routine the
+ opportunity to finish the userMove input with a promotion popup,
+ without bothering the user with this for invalid or illegal moves */
+/* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
+}
+
+/* Common tail of UserMoveEvent and DropMenuEvent */
+int
+FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
+ ChessMove moveType;
+ int fromX, fromY, toX, toY;
+ /*char*/int promoChar;
+{
+ char *bookHit = 0;
+if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
+ if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
+ // [HGM] superchess: suppress promotions to non-available piece
+ int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
+ if(WhiteOnMove(currentMove)) {
+ if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
+ } else {
+ if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
+ }
+ }
+
+ /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
+ move type in caller when we know the move is a legal promotion */
+ if(moveType == NormalMove && promoChar)
+ moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
+if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
+ /* [HGM] convert drag-and-drop piece drops to standard form */
+ if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
+ moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
+ fromX = boards[currentMove][fromY][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.
* 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);
} 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) {
} else {
char buf[MSG_SIZ];
gameMode = MachinePlaysBlack;
+ StartClocks();
SetGameInfo();
sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
DisplayTitle(buf);
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 ||
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;
}
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 {
default:
break;
}
+
+ if(bookHit) { // [HGM] book: simulate book reply
+ static char bookMove[MSG_SIZ]; // a bit generous?
+
+ programStats.nodes = programStats.depth = programStats.time =
+ programStats.score = programStats.got_only_move = 0;
+ sprintf(programStats.movelist, "%s (xbook)", bookHit);
+
+ strcpy(bookMove, "move ");
+ strcat(bookMove, bookHit);
+ HandleMachineMove(bookMove, &first);
+ }
+ return 1;
+}
+
+void
+UserMoveEvent(fromX, fromY, toX, toY, promoChar)
+ int fromX, fromY, toX, toY;
+ int promoChar;
+{
+ /* [HGM] This routine was added to allow calling of its two logical
+ parts from other modules in the old way. Before, UserMoveEvent()
+ automatically called FinishMove() if the move was OK, and returned
+ otherwise. I separated the two, in order to make it possible to
+ slip a promotion popup in between. But that it always needs two
+ calls, to the first part, (now called UserMoveTest() ), and to
+ FinishMove if the first part succeeded. Calls that do not need
+ to do anything in between, can call this routine the old way.
+ */
+ ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);
+if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
+ if(moveType != ImpossibleMove)
+ FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
+}
+
+void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
+{
+// char * hint = lastHint;
+ FrontEndProgramStats stats;
+
+ stats.which = cps == &first ? 0 : 1;
+ stats.depth = cpstats->depth;
+ stats.nodes = cpstats->nodes;
+ stats.score = cpstats->score;
+ stats.time = cpstats->time;
+ stats.pv = cpstats->movelist;
+ stats.hint = lastHint;
+ stats.an_move_index = 0;
+ stats.an_move_count = 0;
+
+ if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
+ stats.hint = cpstats->move_name;
+ stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
+ stats.an_move_count = cpstats->nr_moves;
+ }
+
+ SetProgramStats( &stats );
+}
+
+char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
+{ // [HGM] book: this routine intercepts moves to simulate book replies
+ char *bookHit = NULL;
+
+ //first determine if the incoming move brings opponent into his book
+ if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
+ bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
+ if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
+ if(bookHit != NULL && !cps->bookSuspend) {
+ // make sure opponent is not going to reply after receiving move to book position
+ SendToProgram("force\n", cps);
+ cps->bookSuspend = TRUE; // flag indicating it has to be restarted
+ }
+ if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
+ // now arrange restart after book miss
+ if(bookHit) {
+ // after a book hit we never send 'go', and the code after the call to this routine
+ // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
+ char buf[MSG_SIZ];
+ if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
+ sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
+ SendToProgram(buf, cps);
+ if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
+ } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
+ SendToProgram("go\n", cps);
+ cps->bookSuspend = FALSE; // after a 'go' we are never suspended
+ } else { // 'go' might be sent based on 'firstMove' after this routine returns
+ if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
+ SendToProgram("go\n", cps);
+ cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
+ }
+ return bookHit; // notify caller of hit, so it can take action to send move to opponent
+}
+
+char *savedMessage;
+ChessProgramState *savedState;
+void DeferredBookMove(void)
+{
+ if(savedState->lastPing != savedState->lastPong)
+ ScheduleDelayedEvent(DeferredBookMove, 10);
+ else
+ HandleMachineMove(savedMessage, savedState);
}
void
char promoChar;
char *p;
int machineWhite;
+ char *bookHit;
+FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
/*
* Kludge to ignore BEL characters
*/
while (*message == '\007') message++;
/*
+ * [HGM] engine debug message: ignore lines starting with '#' character
+ */
+ if(cps->debug && *message == '#') return;
+
+ /*
* Look for book output
*/
if (cps == &first && bookRequested) {
/*
* 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) {
fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
cps->which, gameMode);
}
- SendToProgram("undo\n", cps);
+
+ SendToProgram("undo\n", cps);
}
return;
}
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;
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, %.0f nodes, %1.0f knps) PV=%s\n",
+ programStats.score / 100.,
+ programStats.depth,
+ programStats.time / 100.,
+ u64ToDouble(programStats.nodes),
+ u64ToDouble(programStats.nodes) / (10*abs(programStats.time) + 1.),
+ programStats.movelist);
+ SendToICS(buf);
+ }
}
#endif
/* currentMoveString is set as a side-effect of ParseOneMove */
strcpy(machineMove, currentMoveString);
strcat(machineMove, "\n");
strcpy(moveList[forwardMostMove], machineMove);
-
+
+ /* [AS] Save move info and clear stats for next move */
+ pvInfoList[ forwardMostMove ].score = programStats.score;
+ pvInfoList[ forwardMostMove ].depth = programStats.depth;
+ pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
+ ClearProgramStats();
+ thinkOutput[0] = NULLCHAR;
+ hiddenThinkOutputState = 0;
+
MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
-
+
+ /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
+ if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
+ int count = 0;
+
+ while( count < adjudicateLossPlies ) {
+ int score = pvInfoList[ forwardMostMove - count - 1 ].score;
+
+ if( count & 1 ) {
+ score = -score; /* Flip score for winning side */
+ }
+
+ if( score > adjudicateLossThreshold ) {
+ break;
+ }
+
+ count++;
+ }
+
+ if( count >= adjudicateLossPlies ) {
+ ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
+
+ GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
+ "Xboard adjudication",
+ GE_XBOARD );
+
+ return;
+ }
+ }
+
+ if( gameMode == TwoMachinesPlay ) {
+ // [HGM] some adjudications useful with buggy engines
+ int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
+ if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
+
+
+ if( appData.testLegality )
+ { /* [HGM] Some more adjudications for obstinate engines */
+ int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
+ NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
+ NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
+ static int moveCount = 6;
+ ChessMove result;
+ char *reason = NULL;
+
+ /* Count what is on board. */
+ for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
+ { ChessSquare p = boards[forwardMostMove][i][j];
+ int m=i;
+
+ switch((int) p)
+ { /* count B,N,R and other of each side */
+ case WhiteKing:
+ case BlackKing:
+ NrK++; break; // [HGM] atomic: count Kings
+ case WhiteKnight:
+ NrWN++; break;
+ case WhiteBishop:
+ case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
+ bishopsColor |= 1 << ((i^j)&1);
+ NrWB++; break;
+ case BlackKnight:
+ NrBN++; break;
+ case BlackBishop:
+ case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
+ bishopsColor |= 1 << ((i^j)&1);
+ NrBB++; break;
+ case WhiteRook:
+ NrWR++; break;
+ case BlackRook:
+ NrBR++; break;
+ case WhiteQueen:
+ NrWQ++; break;
+ case BlackQueen:
+ NrBQ++; break;
+ case EmptySquare:
+ break;
+ case BlackPawn:
+ m = 7-i;
+ case WhitePawn:
+ PawnAdvance += m; NrPawns++;
+ }
+ NrPieces += (p != EmptySquare);
+ NrW += ((int)p < (int)BlackPawn);
+ if(gameInfo.variant == VariantXiangqi &&
+ (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
+ NrPieces--; // [HGM] XQ: do not count purely defensive pieces
+ NrW -= ((int)p < (int)BlackPawn);
+ }
+ }
+
+ /* Some material-based adjudications that have to be made before stalemate test */
+ if(gameInfo.variant == VariantAtomic && NrK < 2) {
+ // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
+ epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
+ if(appData.checkMates) {
+ SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
+ ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
+ GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
+ "Xboard adjudication: King destroyed", GE_XBOARD );
+ return;
+ }
+ }
+
+ /* Bare King in Shatranj (loses) or Losers (wins) */
+ if( NrW == 1 || NrPieces - NrW == 1) {
+ if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
+ epStatus[forwardMostMove] = EP_WINS; // mark as win, so it becomes claimable
+ if(appData.checkMates) {
+ SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
+ ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
+ GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
+ "Xboard adjudication: Bare king", GE_XBOARD );
+ return;
+ }
+ } else
+ if( gameInfo.variant == VariantShatranj && --bare < 0)
+ { /* bare King */
+ epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
+ if(appData.checkMates) {
+ /* but only adjudicate if adjudication enabled */
+ SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
+ ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
+ GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
+ "Xboard adjudication: Bare king", GE_XBOARD );
+ return;
+ }
+ }
+ } else bare = 1;
+
+
+ // don't wait for engine to announce game end if we can judge ourselves
+ switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
+ castlingRights[forwardMostMove]) ) {
+ case MT_CHECK:
+ if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
+ int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
+ for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
+ if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
+ checkCnt++;
+ if(checkCnt >= 2) {
+ reason = "Xboard adjudication: 3rd check";
+ epStatus[forwardMostMove] = EP_CHECKMATE;
+ break;
+ }
+ }
+ }
+ case MT_NONE:
+ default:
+ break;
+ case MT_STALEMATE:
+ case MT_STAINMATE:
+ reason = "Xboard adjudication: Stalemate";
+ if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
+ epStatus[forwardMostMove] = EP_STALEMATE; // default result for stalemate is draw
+ if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
+ epStatus[forwardMostMove] = EP_WINS; // in these variants stalemated is always a win
+ else if(gameInfo.variant == VariantSuicide) // in suicide it depends
+ epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
+ ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
+ EP_CHECKMATE : EP_WINS);
+ else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
+ epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
+ }
+ break;
+ case MT_CHECKMATE:
+ reason = "Xboard adjudication: Checkmate";
+ epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
+ break;
+ }
+
+ switch(i = epStatus[forwardMostMove]) {
+ case EP_STALEMATE:
+ result = GameIsDrawn; break;
+ case EP_CHECKMATE:
+ result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
+ case EP_WINS:
+ result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
+ default:
+ result = (ChessMove) 0;
+ }
+ if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
+ SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
+ ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
+ GameEnds( result, reason, GE_XBOARD );
+ return;
+ }
+
+ /* Next absolutely insufficient mating material. */
+ if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
+ gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
+ (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
+ NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
+ { /* KBK, KNK, KK of KBKB with like Bishops */
+
+ /* always flag draws, for judging claims */
+ epStatus[forwardMostMove] = EP_INSUF_DRAW;
+
+ if(appData.materialDraws) {
+ /* but only adjudicate them if adjudication enabled */
+ SendToProgram("force\n", cps->other); // suppress reply
+ SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
+ ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
+ GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
+ return;
+ }
+ }
+
+ /* Then some trivial draws (only adjudicate, cannot be claimed) */
+ if(NrPieces == 4 &&
+ ( NrWR == 1 && NrBR == 1 /* KRKR */
+ || NrWQ==1 && NrBQ==1 /* KQKQ */
+ || NrWN==2 || NrBN==2 /* KNNK */
+ || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
+ ) ) {
+ if(--moveCount < 0 && appData.trivialDraws)
+ { /* if the first 3 moves do not show a tactical win, declare draw */
+ SendToProgram("force\n", cps->other); // suppress reply
+ SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
+ ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
+ GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
+ return;
+ }
+ } else moveCount = 6;
+ }
+ }
+#if 1
+ if (appData.debugMode) { int i;
+ fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
+ forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
+ appData.drawRepeats);
+ for( i=forwardMostMove; i>=backwardMostMove; i-- )
+ fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
+
+ }
+#endif
+ /* Check for rep-draws */
+ count = 0;
+ for(k = forwardMostMove-2;
+ k>=backwardMostMove && k>=forwardMostMove-100 &&
+ epStatus[k] < EP_UNKNOWN &&
+ epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
+ k-=2)
+ { int rights=0;
+#if 0
+ if (appData.debugMode) {
+ fprintf(debugFP, " loop\n");
+ }
+#endif
+ if(CompareBoards(boards[k], boards[forwardMostMove])) {
+#if 0
+ if (appData.debugMode) {
+ fprintf(debugFP, "match\n");
+ }
+#endif
+ /* compare castling rights */
+ if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
+ (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
+ rights++; /* King lost rights, while rook still had them */
+ if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
+ if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
+ castlingRights[forwardMostMove][1] != castlingRights[k][1] )
+ rights++; /* but at least one rook lost them */
+ }
+ if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
+ (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
+ rights++;
+ if( castlingRights[forwardMostMove][5] >= 0 ) {
+ if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
+ castlingRights[forwardMostMove][4] != castlingRights[k][4] )
+ rights++;
+ }
+#if 0
+ if (appData.debugMode) {
+ for(i=0; i<nrCastlingRights; i++)
+ fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);
+ }
+
+ if (appData.debugMode) {
+ fprintf(debugFP, " %d %d\n", rights, k);
+ }
+#endif
+ if( rights == 0 && ++count > appData.drawRepeats-2
+ && appData.drawRepeats > 1) {
+ /* adjudicate after user-specified nr of repeats */
+ SendToProgram("force\n", cps->other); // suppress reply
+ SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
+ ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
+ if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
+ // [HGM] xiangqi: check for forbidden perpetuals
+ int m, ourPerpetual = 1, hisPerpetual = 1;
+ for(m=forwardMostMove; m>k; m-=2) {
+ if(MateTest(boards[m], PosFlags(m),
+ EP_NONE, castlingRights[m]) != MT_CHECK)
+ ourPerpetual = 0; // the current mover did not always check
+ if(MateTest(boards[m-1], PosFlags(m-1),
+ EP_NONE, castlingRights[m-1]) != MT_CHECK)
+ hisPerpetual = 0; // the opponent did not always check
+ }
+ if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
+ ourPerpetual, hisPerpetual);
+ if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
+ GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
+ "Xboard adjudication: perpetual checking", GE_XBOARD );
+ return;
+ }
+ if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
+ break; // (or we would have caught him before). Abort repetition-checking loop.
+ // Now check for perpetual chases
+ if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
+ hisPerpetual = PerpetualChase(k, forwardMostMove);
+ ourPerpetual = PerpetualChase(k+1, forwardMostMove);
+ if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
+ GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
+ "Xboard adjudication: perpetual chasing", GE_XBOARD );
+ return;
+ }
+ if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
+ break; // Abort repetition-checking loop.
+ }
+ // if neither of us is checking or chasing all the time, or both are, it is draw
+ }
+ GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
+ return;
+ }
+ if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
+ epStatus[forwardMostMove] = EP_REP_DRAW;
+ }
+ }
+
+ /* Now we test for 50-move draws. Determine ply count */
+ count = forwardMostMove;
+ /* look for last irreversble move */
+ while( epStatus[count] <= EP_NONE && count > backwardMostMove )
+ count--;
+ /* if we hit starting position, add initial plies */
+ if( count == backwardMostMove )
+ count -= initialRulePlies;
+ count = forwardMostMove - count;
+ if( count >= 100)
+ epStatus[forwardMostMove] = EP_RULE_DRAW;
+ /* this is used to judge if draw claims are legal */
+ if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
+ SendToProgram("force\n", cps->other); // suppress reply
+ SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
+ ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
+ GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
+ return;
+ }
+
+ /* if draw offer is pending, treat it as a draw claim
+ * when draw condition present, to allow engines a way to
+ * claim draws before making their move to avoid a race
+ * condition occurring after their move
+ */
+ if( cps->other->offeredDraw || cps->offeredDraw ) {
+ char *p = NULL;
+ if(epStatus[forwardMostMove] == EP_RULE_DRAW)
+ p = "Draw claim: 50-move rule";
+ if(epStatus[forwardMostMove] == EP_REP_DRAW)
+ p = "Draw claim: 3-fold repetition";
+ if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
+ p = "Draw claim: insufficient mating material";
+ if( p != NULL ) {
+ SendToProgram("force\n", cps->other); // suppress reply
+ SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
+ GameEnds( GameIsDrawn, p, GE_XBOARD );
+ ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
+ return;
+ }
+ }
+
+
+ if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
+ SendToProgram("force\n", cps->other); // suppress reply
+ SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
+ ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
+
+ GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
+
+ return;
+ }
+ }
+
+ bookHit = NULL;
if (gameMode == TwoMachinesPlay) {
+ /* [HGM] relaying draw offers moved to after reception of move */
+ /* and interpreting offer as claim if it brings draw condition */
+ if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
+ SendToProgram("draw\n", cps->other);
+ }
if (cps->other->sendTime) {
SendTimeRemaining(cps->other,
cps->other->twoMachinesColor[0] == 'w');
}
- SendMoveToProgram(forwardMostMove-1, cps->other);
- if (firstMove) {
+ bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
+ if (firstMove && !bookHit) {
firstMove = FALSE;
if (cps->other->useColors) {
SendToProgram(cps->other->twoMachinesColor, cps->other);
}
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;
}
cps->useSigterm = FALSE;
}
+ /* [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
*/
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 {
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);
}
}
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 {
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.
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;
}
searchTime);
return;
}
- if (!StrStr(message, "llegal")) return;
+ if (!StrStr(message, "llegal")) {
+ return;
+ }
if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
gameMode == IcsIdle) return;
if (forwardMostMove <= backwardMostMove) return;
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")) {
|| (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);
(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);
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 = "";
}
/* 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 = "";
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 */
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 */
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 */
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) {
if (gameMode == TwoMachinesPlay) {
if (cps->other->offeredDraw) {
GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
- } else {
+ /* [HGM] in two-machine mode we delay relaying draw offer */
+ /* until after we also have move, to see if it is really claim */
+ }
+#if 0
+ else {
if (cps->other->sendDrawOffers) {
SendToProgram("draw\n", cps->other);
}
}
+#endif
} else if (gameMode == MachinePlaysWhite ||
gameMode == MachinePlaysBlack) {
if (userOfferedDraw) {
/*
* 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;
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;
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)
"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");
}
} else {
programStats.line_is_book = 0;
}
-
- sprintf(thinkOutput, "[%d]%c%+.2f %s%s%s",
+
+ 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 (currentMove == forwardMostMove ||
- gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
+ if( strlen(buf1) > max_len ) {
+ if( appData.debugMode) {
+ fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
+ }
+ buf1[max_len+1] = '\0';
+ }
+
+ strcat( thinkOutput, buf1 );
+ }
+
+ if (currentMove == forwardMostMove || gameMode == AnalyzeMode
+ || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
DisplayMove(currentMove - 1);
DisplayAnalysis();
}
mean "line isn't going to change" (Crafty
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
programStats.nr_moves = mvtot;
strcpy(programStats.move_name, mvname);
programStats.ok_to_send = 1;
+ programStats.movelist[0] = '\0';
+
+ SendProgramStatsToFrontend( cps, &programStats );
+
DisplayAnalysis();
return;
} 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 );
+ }
+ }
}
}
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:
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:
(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 */
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;
}
/* 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) {
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;
}
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 */
int fromX, fromY, toX, toY;
int promoChar;
{
- forwardMostMove++;
- if (forwardMostMove >= MAX_MOVES) {
+// forwardMostMove++; // [HGM] bare: moved downstream
+
+ if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
+ int timeLeft; static int lastLoadFlag=0; int king, piece;
+ piece = boards[forwardMostMove][fromY][fromX];
+ king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
+ if(gameInfo.variant == VariantKnightmate)
+ king += (int) WhiteUnicorn - (int) WhiteKing;
+ if(forwardMostMove == 0) {
+ if(blackPlaysFirst)
+ fprintf(serverMoves, "%s;", second.tidy);
+ fprintf(serverMoves, "%s;", first.tidy);
+ if(!blackPlaysFirst)
+ fprintf(serverMoves, "%s;", second.tidy);
+ } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
+ lastLoadFlag = loadFlag;
+ // print base move
+ fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
+ // print castling suffix
+ if( toY == fromY && piece == king ) {
+ if(toX-fromX > 1)
+ fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
+ if(fromX-toX >1)
+ fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
+ }
+ // e.p. suffix
+ if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
+ boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
+ boards[forwardMostMove][toY][toX] == EmptySquare
+ && fromX != toX )
+ fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
+ // promotion suffix
+ if(promoChar != NULLCHAR)
+ fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
+ if(!loadFlag) {
+ fprintf(serverMoves, "/%d/%d",
+ pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
+ if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
+ else timeLeft = blackTimeRemaining/1000;
+ fprintf(serverMoves, "/%d", timeLeft);
+ }
+ fflush(serverMoves);
+ }
+
+ if (forwardMostMove+1 >= MAX_MOVES) {
DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
0, 1);
return;
}
SwitchClocks();
- timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
- timeRemaining[1][forwardMostMove] = blackTimeRemaining;
- if (commentList[forwardMostMove] != NULL) {
- free(commentList[forwardMostMove]);
- commentList[forwardMostMove] = NULL;
- }
- CopyBoard(boards[forwardMostMove], boards[forwardMostMove - 1]);
- ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove]);
+ timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;
+ timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;
+ if (commentList[forwardMostMove+1] != NULL) {
+ free(commentList[forwardMostMove+1]);
+ commentList[forwardMostMove+1] = NULL;
+ }
+ CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
+ {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
+ ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1],
+ castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
+ forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
gameInfo.result = GameUnfinished;
if (gameInfo.resultDetails != NULL) {
free(gameInfo.resultDetails);
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 */
{
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) {
}
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, "egtbpath %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;
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);
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;
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);
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();
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 );
+ }
+}
+
+
void
GameEnds(result, resultDetails, whosays)
ChessMove result;
{
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.
}
}
#endif
- return;
+ endingGame = 0; /* [HGM] crash */
+ return;
}
/* If we're loading the game from a file, stop */
}
/* 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. */
if (!isIcsGame && !appData.noChessProgram)
SetUserThinkingEnables();
- if (resultDetails != NULL) {
- gameInfo.result = result;
- gameInfo.resultDetails = StrSave(resultDetails);
+ /* [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
+ }
- /* Tell program how game ended in case it is learning */
- if (gameMode == MachinePlaysWhite ||
- gameMode == MachinePlaysBlack ||
- gameMode == TwoMachinesPlay ||
- gameMode == IcsPlayingWhite ||
- gameMode == IcsPlayingBlack ||
- gameMode == BeginningOfGame) {
- char buf[MSG_SIZ];
- sprintf(buf, "result %s {%s}\n", PGNResult(result),
- resultDetails);
- if (first.pr != NoProc) {
- SendToProgram(buf, &first);
+ // 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 (second.pr != NoProc &&
- gameMode == TwoMachinesPlay) {
- SendToProgram(buf, &second);
+ 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))
}
}
}
+
+ /* 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 ||
+ gameMode == IcsPlayingWhite ||
+ gameMode == IcsPlayingBlack ||
+ gameMode == BeginningOfGame) {
+ char buf[MSG_SIZ];
+ sprintf(buf, "result %s {%s}\n", PGNResult(result),
+ resultDetails);
+ if (first.pr != NoProc) {
+ SendToProgram(buf, &first);
+ }
+ if (second.pr != NoProc &&
+ gameMode == TwoMachinesPlay) {
+ SendToProgram(buf, &second);
+ }
+ }
}
if (appData.icsActive) {
if (appData.noChessProgram) {
gameMode = nextGameMode;
ModeHighlight();
- return;
+ endingGame = 0; /* [HGM] crash */
+ return;
}
if (first.reuse) {
if (first.pr != NoProc) {
ExitAnalyzeMode();
+ DoSleep( appData.delayBeforeQuit );
SendToProgram("quit\n", &first);
+ DoSleep( appData.delayAfterQuit );
DestroyChildProcess(first.pr, first.useSigterm);
}
first.pr = NoProc;
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;
}
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];
ExitAnalyzeMode();
gameMode = nextGameMode;
ModeHighlight();
+ endingGame = 0; /* [HGM] crash */
}
/* Assumes program was just initialized (initString sent).
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);
if (appData.noChessProgram || first.pr != NoProc) return;
StartChessProgram(&first);
- InitChessProgram(&first);
+ InitChessProgram(&first, FALSE);
FeedMovesToProgram(&first, currentMove);
if (!first.sendTime) {
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;
}
fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
redraw, init, gameMode);
}
-
pausing = pauseExamInvalid = FALSE;
startedFromSetupPosition = blackPlaysFirst = FALSE;
firstMove = TRUE;
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);
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();
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;
InitPosition(redraw);
for (i = 0; i < MAX_MOVES; i++) {
if (commentList[i] != NULL) {
if (first.pr == NULL) {
StartChessProgram(&first);
}
- if (init) InitChessProgram(&first);
+ if (init) {
+ InitChessProgram(&first, startedFromSetupPosition);
+ }
DisplayTitle("");
DisplayMessage("", "");
HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
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) {
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;
}
case WhiteCapturesEnPassant:
case BlackCapturesEnPassant:
+ case WhitePromotionChancellor:
+ case BlackPromotionChancellor:
+ case WhitePromotionArchbishop:
+ case BlackPromotionArchbishop:
+ case WhitePromotionCentaur:
+ case BlackPromotionCentaur:
case WhitePromotionQueen:
case BlackPromotionQueen:
case WhitePromotionRook:
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;
(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:
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 {
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 {
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;
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);
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();
} 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;
}
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 {
int numPGNTags = 0;
int err;
GameMode oldGameMode;
+ VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
if (appData.debugMode)
fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
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) {
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;
} 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;
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 */
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 '-':
if (first.pr == NoProc) {
StartChessProgram(&first);
}
- InitChessProgram(&first);
+ InitChessProgram(&first, FALSE);
SendToProgram("force\n", &first);
if (startedFromSetupPosition) {
SendBoard(&first, forwardMostMove);
+ if (appData.debugMode) {
+ fprintf(debugFP, "Load Game\n");
+ }
DisplayBothClocks();
}
+ /* [HGM] server: flag to write setup moves in broadcast file as one */
+ loadFlag = appData.suppressLoadMoves;
+
while (cm == Comment) {
char *p;
if (appData.debugMode)
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)
DrawPosition(FALSE, boards[currentMove]);
if (appData.debugMode)
fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
+
+ loadFlag = 0; /* [HGM] true game starts */
return TRUE;
}
} 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 {
strcpy(lastLoadPositionTitle, title);
if (first.pr == NoProc) {
StartChessProgram(&first);
- InitChessProgram(&first);
+ InitChessProgram(&first, FALSE);
}
pn = positionNumber;
if (positionNumber < 0) {
DisplayError(_("Position not found in file"), 0);
return FALSE;
}
+#if 0
switch (line[0]) {
case '#': case 'x':
default:
case 'p': case 'n': case 'b': case 'r': case 'q': case 'k':
case 'P': case 'N': case 'B': case 'R': case 'Q': case 'K':
case '1': case '2': case '3': case '4': case '5': case '6':
- case '7': case '8':
+ case '7': case '8': case '9':
+ case 'H': case 'A': case 'M': case 'h': case 'a': case 'm':
+ case 'E': case 'F': case 'G': case 'e': case 'f': case 'g':
+ case 'C': case 'W': case 'c': case 'w':
fenMode = TRUE;
break;
}
+#else
+ // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
+ fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
+#endif
if (pn >= 2) {
if (fenMode || line[0] == '#') pn--;
while (pn > 0) {
- /* skip postions before number pn */
+ /* skip positions before number pn */
if (fgets(line, MSG_SIZ, f) == NULL) {
Reset(TRUE, TRUE);
DisplayError(_("Position not found in file"), 0);
(void) fgets(line, MSG_SIZ, f);
(void) fgets(line, MSG_SIZ, f);
- for (i = BOARD_SIZE - 1; i >= 0; i--) {
+ for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
(void) fgets(line, MSG_SIZ, f);
- for (p = line, j = 0; j < BOARD_SIZE; p++) {
+ for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
if (*p == ' ')
continue;
initial_position[i][j++] = CharToPiece(*p);
currentMove = forwardMostMove = backwardMostMove = 0;
DisplayMessage("", _("White to play"));
}
+ /* [HGM] copy FEN attributes as well */
+ { int i;
+ initialRulePlies = FENrulePlies;
+ epStatus[forwardMostMove] = FENepStatus;
+ for( i=0; i< nrCastlingRights; i++ )
+ castlingRights[forwardMostMove][i] = FENcastlingRights[i];
+ }
SendBoard(&first, forwardMostMove);
+ if (appData.debugMode) {
+int i, j;
+ for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
+ for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
+ fprintf(debugFP, "Load Position\n");
+ }
if (positionNumber > 1) {
sprintf(line, "%s %d", title, positionNumber);
} else {
f = fopen(filename, append ? "a" : "w");
if (f == NULL) {
- sprintf(buf, _("Can't open \"%s\""), filename);
+ snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
DisplayError(buf, errno);
return FALSE;
} else {
#define PGN_MAX_LINE 75
+#define PGN_SIDE_WHITE 0
+#define PGN_SIDE_BLACK 1
+
+/* [AS] */
+static int FindFirstMoveOutOfBook( int side )
+{
+ int result = -1;
+
+ if( backwardMostMove == 0 && ! startedFromSetupPosition) {
+ int index = backwardMostMove;
+ int has_book_hit = 0;
+
+ if( (index % 2) != side ) {
+ index++;
+ }
+
+ while( index < forwardMostMove ) {
+ /* Check to see if engine is in book */
+ int depth = pvInfoList[index].depth;
+ int score = pvInfoList[index].score;
+ int in_book = 0;
+
+ if( depth <= 2 ) {
+ in_book = 1;
+ }
+ else if( score == 0 && depth == 63 ) {
+ in_book = 1; /* Zappa */
+ }
+ else if( score == 2 && depth == 99 ) {
+ in_book = 1; /* Abrok */
+ }
+
+ has_book_hit += in_book;
+
+ if( ! in_book ) {
+ result = index;
+
+ break;
+ }
+
+ index += 2;
+ }
+ }
+
+ return result;
+}
+
+/* [AS] */
+void GetOutOfBookInfo( char * buf )
+{
+ int oob[2];
+ int i;
+ int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
+
+ oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
+ oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
+
+ *buf = '\0';
+
+ if( oob[0] >= 0 || oob[1] >= 0 ) {
+ for( i=0; i<2; i++ ) {
+ int idx = oob[i];
+
+ if( idx >= 0 ) {
+ if( i > 0 && oob[0] >= 0 ) {
+ strcat( buf, " " );
+ }
+
+ sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
+ sprintf( buf+strlen(buf), "%s%.2f",
+ pvInfoList[idx].score >= 0 ? "+" : "",
+ pvInfoList[idx].score / 100.0 );
+ }
+ }
+ }
+}
+
/* Save game in PGN style and close the file */
int
SaveGamePGN(f)
{
int i, offset, linelen, newblock;
time_t tm;
- char *movetext;
+// char *movetext;
char numtext[32];
int movelen, numlen, blank;
+ char move_buffer[100]; /* [AS] Buffer for move+PV info */
+
+ offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
tm = time((time_t *) NULL);
PrintPGNTags(f, &gameInfo);
if (backwardMostMove > 0 || startedFromSetupPosition) {
- char *fen = PositionToFEN(backwardMostMove);
+ char *fen = PositionToFEN(backwardMostMove, NULL);
fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
fprintf(f, "\n{--------------\n");
PrintPosition(f, backwardMostMove);
fprintf(f, "--------------}\n");
free(fen);
- } else {
+ }
+ else {
+ /* [AS] Out of book annotation */
+ if( appData.saveOutOfBookInfo ) {
+ char buf[64];
+
+ GetOutOfBookInfo( buf );
+
+ if( buf[0] != '\0' ) {
+ fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
+ }
+ }
+
fprintf(f, "\n");
}
i = backwardMostMove;
- offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
linelen = 0;
newblock = TRUE;
linelen += numlen;
/* Get move */
- movetext = SavePart(parseList[i]);
- movelen = strlen(movetext);
+ strcpy(move_buffer, parseList[i]); // [HGM] pgn: print move via buffer, so it can be edited
+ movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
+ if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
+ int p = movelen - 1;
+ if(move_buffer[p] == ' ') p--;
+ if(move_buffer[p] == ')') { // [HGM] pgn: strip off ICS time if we have extended info
+ while(p && move_buffer[--p] != '(');
+ if(p && move_buffer[p-1] == ' ') move_buffer[movelen=p-1] = 0;
+ }
+ }
/* Print move */
blank = linelen > 0 && movelen > 0;
fprintf(f, " ");
linelen++;
}
- fprintf(f, movetext);
+ fprintf(f, move_buffer);
linelen += movelen;
+ /* [AS] Add PV info if present */
+ if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
+ /* [HGM] add time */
+ char buf[MSG_SIZ]; int seconds = 0;
+
+#if 1
+ if(i >= backwardMostMove) {
+ if(WhiteOnMove(i))
+ seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
+ + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
+ else
+ seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
+ + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
+ }
+ seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
+#else
+ seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time
+#endif
+
+ if( seconds <= 0) buf[0] = 0; else
+ if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
+ seconds = (seconds + 4)/10; // round to full seconds
+ if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
+ sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
+ }
+
+ sprintf( move_buffer, "{%s%.2f/%d%s}",
+ pvInfoList[i].score >= 0 ? "+" : "",
+ pvInfoList[i].score / 100.0,
+ pvInfoList[i].depth,
+ buf );
+
+ movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
+
+ /* Print score/depth */
+ blank = linelen > 0 && movelen > 0;
+ if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
+ fprintf(f, "\n");
+ linelen = 0;
+ blank = 0;
+ }
+ if (blank) {
+ fprintf(f, " ");
+ linelen++;
+ }
+ fprintf(f, move_buffer);
+ linelen += movelen;
+ }
+
i++;
}
} else {
f = fopen(filename, "a");
if (f == NULL) {
- sprintf(buf, _("Can't open \"%s\""), filename);
+ snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
DisplayError(buf, errno);
return FALSE;
} else {
PrintPosition(f, currentMove);
fprintf(f, "--------------]\n");
} else {
- fen = PositionToFEN(currentMove);
+ fen = PositionToFEN(currentMove, NULL);
fprintf(f, "%s\n", fen);
free(fen);
}
|| (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
sprintf(string, partCommandString,
appData.debugMode ? " -v" : "", appData.cmailGameName);
- commandOutput = popen(string, "rb");
+ commandOutput = popen(string, "r");
if (commandOutput == NULL) {
DisplayError(_("Failed to invoke cmail"), 0);
}
}
-static int exiting = 0;
-
void
ExitEvent(status)
int status;
if (icsPR != NoProc) {
DestroyChildProcess(icsPR, TRUE);
}
+#if 0
/* Save game if resource set and not already saved by GameEnds() */
- if (gameInfo.resultDetails == NULL && forwardMostMove > 0) {
+ if ((gameInfo.resultDetails == NULL || errorExitFlag )
+ && forwardMostMove > 0) {
if (*appData.saveGameFile != NULLCHAR) {
SaveGameToFile(appData.saveGameFile, TRUE);
} else if (appData.autoSaveGames) {
}
}
GameEnds((ChessMove) 0, NULL, GE_PLAYER);
+#else
+ /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
+ GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
+#endif
+ /* [HGM] crash: the above GameEnds() is a dud if another one was running */
+ /* make sure this other one finishes before killing it! */
+ if(endingGame) { int count = 0;
+ if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
+ while(endingGame && count++ < 10) DoSleep(1);
+ if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
+ }
/* Kill off chess programs */
if (first.pr != NoProc) {
ExitAnalyzeMode();
+
+ DoSleep( appData.delayBeforeQuit );
SendToProgram("quit\n", &first);
- DestroyChildProcess(first.pr, first.useSigterm);
+ DoSleep( appData.delayAfterQuit );
+ DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
}
if (second.pr != NoProc) {
+ DoSleep( appData.delayBeforeQuit );
SendToProgram("quit\n", &second);
- DestroyChildProcess(second.pr, second.useSigterm);
+ DoSleep( appData.delayAfterQuit );
+ DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
}
if (first.isr != NULL) {
RemoveInputSource(first.isr);
return;
if (gameMode != AnalyzeFile) {
- EditGameEvent();
- if (gameMode != EditGame) return;
+ if (!appData.icsEngineAnalyze) {
+ EditGameEvent();
+ if (gameMode != EditGame) return;
+ }
ResurrectChessProgram();
SendToProgram("analyze\n", &first);
first.analyzing = TRUE;
AnalysisPopUp(_("Analysis"),
_("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
}
- gameMode = AnalyzeMode;
+ if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
pausing = FALSE;
ModeHighlight();
SetGameInfo();
MachineWhiteEvent()
{
char buf[MSG_SIZ];
+ char *bookHit = NULL;
if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
return;
TruncateGame();
ResurrectChessProgram(); /* in case it isn't running */
+ if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
+ gameMode = MachinePlaysWhite;
+ ResetClocks();
+ } else
gameMode = MachinePlaysWhite;
pausing = FALSE;
ModeHighlight();
SendTimeRemaining(&first, TRUE);
}
if (first.useColors) {
- SendToProgram("white\ngo\n", &first);
- } else {
- SendToProgram("go\n", &first);
+ SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
}
+ bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
SetMachineThinkingEnables();
first.maybeThinking = TRUE;
StartClocks();
if (appData.autoFlipView && !flipView) {
flipView = !flipView;
DrawPosition(FALSE, NULL);
+ DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
+ }
+
+ if(bookHit) { // [HGM] book: simulate book reply
+ static char bookMove[MSG_SIZ]; // a bit generous?
+
+ programStats.nodes = programStats.depth = programStats.time =
+ programStats.score = programStats.got_only_move = 0;
+ sprintf(programStats.movelist, "%s (xbook)", bookHit);
+
+ strcpy(bookMove, "move ");
+ strcat(bookMove, bookHit);
+ HandleMachineMove(bookMove, &first);
}
}
MachineBlackEvent()
{
char buf[MSG_SIZ];
+ char *bookHit = NULL;
if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
return;
SendTimeRemaining(&first, FALSE);
}
if (first.useColors) {
- SendToProgram("black\ngo\n", &first);
- } else {
- SendToProgram("go\n", &first);
+ SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
}
+ bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
SetMachineThinkingEnables();
first.maybeThinking = TRUE;
StartClocks();
if (appData.autoFlipView && flipView) {
flipView = !flipView;
DrawPosition(FALSE, NULL);
+ DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
+ }
+ if(bookHit) { // [HGM] book: simulate book reply
+ static char bookMove[MSG_SIZ]; // a bit generous?
+
+ programStats.nodes = programStats.depth = programStats.time =
+ programStats.score = programStats.got_only_move = 0;
+ sprintf(programStats.movelist, "%s (xbook)", bookHit);
+
+ strcpy(bookMove, "move ");
+ strcat(bookMove, bookHit);
+ HandleMachineMove(bookMove, &first);
}
}
int i;
char buf[MSG_SIZ];
ChessProgramState *onmove;
+ char *bookHit = NULL;
if (appData.noChessProgram) return;
return;
}
DisplayMessage("", "");
- InitChessProgram(&second);
+ InitChessProgram(&second, FALSE);
SendToProgram("force\n", &second);
if (startedFromSetupPosition) {
SendBoard(&second, backwardMostMove);
+ if (appData.debugMode) {
+ fprintf(debugFP, "Two Machines\n");
+ }
}
for (i = backwardMostMove; i < forwardMostMove; i++) {
SendMoveToProgram(i, &second);
SendToProgram(buf, &second);
}
+ ResetClocks();
if (!first.sendTime || !second.sendTime) {
- ResetClocks();
timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
timeRemaining[1][forwardMostMove] = blackTimeRemaining;
}
if (onmove->useColors) {
SendToProgram(onmove->twoMachinesColor, onmove);
}
- SendToProgram("go\n", onmove);
+ bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
+// SendToProgram("go\n", onmove);
onmove->maybeThinking = TRUE;
SetMachineThinkingEnables();
StartClocks();
+
+ if(bookHit) { // [HGM] book: simulate book reply
+ static char bookMove[MSG_SIZ]; // a bit generous?
+
+ programStats.nodes = programStats.depth = programStats.time =
+ programStats.score = programStats.got_only_move = 0;
+ sprintf(programStats.movelist, "%s (xbook)", bookHit);
+
+ strcpy(bookMove, "move ");
+ strcat(bookMove, bookHit);
+ HandleMachineMove(bookMove, &first);
+ }
}
void
void
ExitAnalyzeMode()
{
+ /* [DM] icsEngineAnalyze - possible call from other functions */
+ if (appData.icsEngineAnalyze) {
+ appData.icsEngineAnalyze = FALSE;
+
+ DisplayMessage("",_("Close ICS engine analyze..."));
+ }
if (first.analysisSupport && first.analyzing) {
SendToProgram("exit\n", &first);
first.analyzing = FALSE;
EditPositionDone()
{
startedFromSetupPosition = TRUE;
- InitChessProgram(&first);
+ InitChessProgram(&first, FALSE);
SendToProgram("force\n", &first);
if (blackPlaysFirst) {
strcpy(moveList[0], "");
strcpy(parseList[0], "");
currentMove = forwardMostMove = backwardMostMove = 1;
CopyBoard(boards[1], boards[0]);
+ /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
+ { int i;
+ epStatus[1] = epStatus[0];
+ for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
+ }
} else {
currentMove = forwardMostMove = backwardMostMove = 0;
}
SendBoard(&first, forwardMostMove);
+ if (appData.debugMode) {
+ fprintf(debugFP, "EditPosDone\n");
+ }
DisplayTitle("");
timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
timeRemaining[1][forwardMostMove] = blackTimeRemaining;
gameMode = EditGame;
ModeHighlight();
HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
+ ClearHighlights(); /* [AS] */
}
/* Pause for `ms' milliseconds */
int x, y;
{
char buf[MSG_SIZ];
+ ChessSquare piece = boards[0][y][x];
if (gameMode != EditPosition && gameMode != IcsExamining) return;
SendToICS(ics_prefix);
SendToICS("clearboard\n");
} else {
- for (x = 0; x < BOARD_SIZE; x++) {
- for (y = 0; y < BOARD_SIZE; y++) {
+ for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
+ if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
+ for (y = 0; y < BOARD_HEIGHT; y++) {
if (gameMode == IcsExamining) {
if (boards[currentMove][y][x] != EmptySquare) {
sprintf(buf, "%sx@%c%c\n", ics_prefix,
- 'a' + x, '1' + y);
+ AAA + x, ONE + y);
SendToICS(buf);
}
} else {
- boards[0][y][x] = EmptySquare;
+ boards[0][y][x] = p;
}
}
}
case EmptySquare:
if (gameMode == IcsExamining) {
- sprintf(buf, "%sx@%c%c\n", ics_prefix, 'a' + x, '1' + y);
+ sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
SendToICS(buf);
} else {
boards[0][y][x] = EmptySquare;
}
break;
+ case PromotePiece:
+ if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
+ piece >= (int)BlackPawn && piece < (int)BlackMan ) {
+ selection = (ChessSquare) (PROMOTED piece);
+ } else if(piece == EmptySquare) selection = WhiteSilver;
+ else selection = (ChessSquare)((int)piece - 1);
+ goto defaultlabel;
+
+ case DemotePiece:
+ if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
+ piece > (int)BlackMan && piece <= (int)BlackKing ) {
+ selection = (ChessSquare) (DEMOTED piece);
+ } else if(piece == EmptySquare) selection = BlackSilver;
+ else selection = (ChessSquare)((int)piece + 1);
+ goto defaultlabel;
+
+ case WhiteQueen:
+ case BlackQueen:
+ if(gameInfo.variant == VariantShatranj ||
+ gameInfo.variant == VariantXiangqi ||
+ gameInfo.variant == VariantCourier )
+ selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
+ goto defaultlabel;
+
+ case WhiteKing:
+ case BlackKing:
+ if(gameInfo.variant == VariantXiangqi)
+ selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
+ if(gameInfo.variant == VariantKnightmate)
+ selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
default:
+ defaultlabel:
if (gameMode == IcsExamining) {
sprintf(buf, "%s%c@%c%c\n", ics_prefix,
- PieceToChar(selection), 'a' + x, '1' + y);
+ PieceToChar(selection), AAA + x, ONE + y);
SendToICS(buf);
} else {
boards[0][y][x] = selection;
if (target > 0 && moveList[target - 1][0]) {
int fromX, fromY, toX, toY;
- toX = moveList[target - 1][2] - 'a';
- toY = moveList[target - 1][3] - '1';
+ toX = moveList[target - 1][2] - AAA;
+ toY = moveList[target - 1][3] - ONE;
if (moveList[target - 1][1] == '@') {
if (appData.highlightLastMove) {
SetHighlights(-1, -1, toX, toY);
}
} else {
- fromX = moveList[target - 1][0] - 'a';
- fromY = moveList[target - 1][1] - '1';
+ fromX = moveList[target - 1][0] - AAA;
+ fromY = moveList[target - 1][1] - ONE;
if (target == currentMove + 1) {
AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
}
DisplayMove(currentMove - 1);
DrawPosition(FALSE, boards[currentMove]);
HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
- if (commentList[currentMove] && !matchMode && gameMode != Training) {
+ if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
DisplayComment(currentMove - 1, commentList[currentMove]);
}
}
BackwardInner(target)
int target;
{
+ int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
+
if (appData.debugMode)
fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
target, currentMove, forwardMostMove);
if (gameMode == EditPosition) return;
if (currentMove <= backwardMostMove) {
ClearHighlights();
- DrawPosition(FALSE, boards[currentMove]);
+ DrawPosition(full_redraw, boards[currentMove]);
return;
}
if (gameMode == PlayFromGameFile && !pausing)
if (moveList[target][0]) {
int fromX, fromY, toX, toY;
- toX = moveList[target][2] - 'a';
- toY = moveList[target][3] - '1';
+ toX = moveList[target][2] - AAA;
+ toY = moveList[target][3] - ONE;
if (moveList[target][1] == '@') {
if (appData.highlightLastMove) {
SetHighlights(-1, -1, toX, toY);
}
} else {
- fromX = moveList[target][0] - 'a';
- fromY = moveList[target][1] - '1';
+ fromX = moveList[target][0] - AAA;
+ fromY = moveList[target][1] - ONE;
if (target == currentMove - 1) {
AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
}
}
DisplayBothClocks();
DisplayMove(currentMove - 1);
- DrawPosition(FALSE, boards[currentMove]);
+ DrawPosition(full_redraw, boards[currentMove]);
HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
- if (commentList[currentMove] != NULL) {
- DisplayComment(currentMove - 1, commentList[currentMove]);
- }
+ // [HGM] PV info: routine tests if comment empty
+ DisplayComment(currentMove - 1, commentList[currentMove]);
}
void
DisplayBothClocks();
DisplayMove(currentMove - 1);
ClearHighlights();/*!! could figure this out*/
- DrawPosition(FALSE, boards[currentMove]);
+ DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
SendToProgram("remove\n", &first);
/*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
break;
{
int i, j;
- for (i = BOARD_SIZE - 1; i >= 0; i--) {
- for (j = 0; j < BOARD_SIZE; j++) {
+ for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
+ for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
char c = PieceToChar(boards[move][i][j]);
fputc(c == 'x' ? '.' : c, fp);
- fputc(j == BOARD_SIZE - 1 ? '\n' : ' ', fp);
+ fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
}
}
if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
p = q;
while (p >= prog && *p != '/' && *p != '\\') p--;
p++;
+ if(p == prog && *p == '"') p++;
if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
memcpy(buf, p, q - p);
buf[q - p] = NULLCHAR;
switch (gameMode) {
case MachinePlaysWhite:
- gameInfo.event = StrSave("Computer chess game");
+ gameInfo.event = StrSave( appData.pgnEventHeader );
gameInfo.site = StrSave(HostName());
gameInfo.date = PGNDate();
gameInfo.round = StrSave("-");
break;
case MachinePlaysBlack:
- gameInfo.event = StrSave("Computer chess game");
+ gameInfo.event = StrSave( appData.pgnEventHeader );
gameInfo.site = StrSave(HostName());
gameInfo.date = PGNDate();
gameInfo.round = StrSave("-");
break;
case TwoMachinesPlay:
- gameInfo.event = StrSave("Computer chess game");
+ gameInfo.event = StrSave( appData.pgnEventHeader );
gameInfo.site = StrSave(HostName());
gameInfo.date = PGNDate();
if (matchGame > 0) {
int oldlen, len;
char *old;
+ text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
+
CrushCRs(text);
while (*text == '\n') text++;
len = strlen(text);
}
}
+static char * FindStr( char * text, char * sub_text )
+{
+ char * result = strstr( text, sub_text );
+
+ if( result != NULL ) {
+ result += strlen( sub_text );
+ }
+
+ return result;
+}
+
+/* [AS] Try to extract PV info from PGN comment */
+/* [HGM] PV time: and then remove it, to prevent it appearing twice */
+char *GetInfoFromComment( int index, char * text )
+{
+ char * sep = text;
+
+ if( text != NULL && index > 0 ) {
+ int score = 0;
+ int depth = 0;
+ int time = -1, sec = 0, deci;
+ char * s_eval = FindStr( text, "[%eval " );
+ char * s_emt = FindStr( text, "[%emt " );
+
+ if( s_eval != NULL || s_emt != NULL ) {
+ /* New style */
+ char delim;
+
+ if( s_eval != NULL ) {
+ if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
+ return text;
+ }
+
+ if( delim != ']' ) {
+ return text;
+ }
+ }
+
+ if( s_emt != NULL ) {
+ }
+ }
+ else {
+ /* We expect something like: [+|-]nnn.nn/dd */
+ int score_lo = 0;
+
+ sep = strchr( text, '/' );
+ if( sep == NULL || sep < (text+4) ) {
+ return text;
+ }
+
+ time = -1; sec = -1; deci = -1;
+ if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
+ sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
+ sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
+ sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
+ return text;
+ }
+
+ if( score_lo < 0 || score_lo >= 100 ) {
+ return text;
+ }
+
+ if(sec >= 0) time = 600*time + 10*sec; else
+ if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
+
+ score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
+
+ /* [HGM] PV time: now locate end of PV info */
+ while( *++sep >= '0' && *sep <= '9'); // strip depth
+ if(time >= 0)
+ while( *++sep >= '0' && *sep <= '9'); // strip time
+ if(sec >= 0)
+ while( *++sep >= '0' && *sep <= '9'); // strip seconds
+ if(deci >= 0)
+ while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
+ while(*sep == ' ') sep++;
+ }
+
+ if( depth <= 0 ) {
+ return text;
+ }
+
+ if( time < 0 ) {
+ time = -1;
+ }
+
+ pvInfoList[index-1].depth = depth;
+ pvInfoList[index-1].score = score;
+ pvInfoList[index-1].time = 10*time; // centi-sec
+ }
+ return sep;
+}
+
void
SendToProgram(message, cps)
char *message;
count = strlen(message);
outCount = OutputToProcess(cps->pr, message, count, &error);
- if (outCount < count && !exiting) {
+ if (outCount < count && !exiting
+ && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
sprintf(buf, _("Error writing to %s chess program"), cps->which);
- DisplayFatalError(buf, error, 1);
+ if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
+ if(epStatus[forwardMostMove] <= EP_DRAWS) {
+ gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
+ sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
+ } else {
+ gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
+ }
+ gameInfo.resultDetails = buf;
+ }
+ DisplayFatalError(buf, error, 1);
}
}
sprintf(buf,
_("Error: %s chess program (%s) exited unexpectedly"),
cps->which, cps->program);
+ if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
+ if(epStatus[forwardMostMove] <= EP_DRAWS) {
+ gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
+ sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
+ } else {
+ gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
+ }
+ gameInfo.resultDetails = buf;
+ }
RemoveInputSource(cps->isr);
DisplayFatalError(buf, 0, 1);
} else {
_("Error reading from %s chess program (%s)"),
cps->which, cps->program);
RemoveInputSource(cps->isr);
- DisplayFatalError(buf, error, 1);
+
+ /* [AS] Program is misbehaving badly... kill it */
+ if( count == -2 ) {
+ DestroyChildProcess( cps->pr, 9 );
+ cps->pr = NoProc;
+ }
+
+ DisplayFatalError(buf, error, 1);
}
- GameEnds((ChessMove) 0, NULL, GE_PLAYER);
return;
}
*end_str = NULLCHAR;
if (appData.debugMode) {
- TimeMark now;
- GetTimeMark(&now);
- fprintf(debugFP, "%ld <%-6s: %s\n",
- SubtractTimeMarks(&now, &programStartTime),
- cps->which, message);
+ TimeMark now; int print = 1;
+ char *quote = ""; char c; int i;
+
+ if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
+ char start = message[0];
+ if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
+ if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
+ sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
+ sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
+ sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
+ sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
+ sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')
+ { quote = "# "; print = (appData.engineComments == 2); }
+ message[0] = start; // restore original message
+ }
+ if(print) {
+ GetTimeMark(&now);
+ fprintf(debugFP, "%ld <%-6s: %s%s\n",
+ SubtractTimeMarks(&now, &programStartTime), cps->which,
+ quote,
+ message);
+ }
+ }
+
+ /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
+ if (appData.icsEngineAnalyze) {
+ if (strstr(message, "whisper") != NULL ||
+ strstr(message, "kibitz") != NULL ||
+ strstr(message, "tellics") != NULL) return;
}
+
HandleMachineMove(message, cps);
}
long tc;
{
char buf[MSG_SIZ];
- int seconds = (tc / 1000) % 60;
+ int seconds;
+
+ if( timeControl_2 > 0 ) {
+ if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
+ tc = timeControl_2;
+ }
+ }
+ tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
+ inc /= cps->timeOdds;
+ st /= cps->timeOdds;
+
+ seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
if (st > 0) {
/* Set exact time per move, normally using st command */
}
SendToProgram(buf, cps);
}
+
+ if(cps->nps > 0) { /* [HGM] nps */
+ if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
+ else {
+ sprintf(buf, "nps %d\n", cps->nps);
+ SendToProgram(buf, cps);
+ }
+ }
+}
+
+ChessProgramState *WhitePlayer()
+/* [HGM] return pointer to 'first' or 'second', depending on who plays white */
+{
+ if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
+ gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
+ return &second;
+ return &first;
}
void
time = blackTimeRemaining / 10;
otime = whiteTimeRemaining / 10;
}
+ /* [HGM] translate opponent's time by time-odds factor */
+ otime = (otime * cps->other->timeOdds) / cps->timeOdds;
+ if (appData.debugMode) {
+ fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
+ }
+
if (time <= 0) time = 1;
if (otime <= 0) otime = 1;
- sprintf(message, "time %ld\notim %ld\n", time, otime);
+ sprintf(message, "time %ld\n", time);
+ SendToProgram(message, cps);
+
+ sprintf(message, "otim %ld\n", otime);
SendToProgram(message, cps);
}
return FALSE;
}
+int
+ParseOption(Option *opt, ChessProgramState *cps)
+// [HGM] options: process the string that defines an engine option, and determine
+// name, type, default value, and allowed value range
+{
+ char *p, *q, buf[MSG_SIZ];
+ int n, min = (-1)<<31, max = 1<<31, def;
+
+ if(p = strstr(opt->name, " -spin ")) {
+ if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
+ if(max < min) max = min; // enforce consistency
+ if(def < min) def = min;
+ if(def > max) def = max;
+ opt->value = def;
+ opt->min = min;
+ opt->max = max;
+ opt->type = Spin;
+ } else if(p = strstr(opt->name, " -string ")) {
+ opt->textValue = p+9;
+ opt->type = TextBox;
+ } else if(p = strstr(opt->name, " -check ")) {
+ if(sscanf(p, " -check %d", &def) < 1) return FALSE;
+ opt->value = (def != 0);
+ opt->type = CheckBox;
+ } else if(p = strstr(opt->name, " -combo ")) {
+ opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
+ cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
+ opt->value = n = 0;
+ while(q = StrStr(q, " /// ")) {
+ n++; *q = 0; // count choices, and null-terminate each of them
+ q += 5;
+ if(*q == '*') { // remember default, which is marked with * prefix
+ q++;
+ opt->value = n;
+ }
+ cps->comboList[cps->comboCnt++] = q;
+ }
+ cps->comboList[cps->comboCnt++] = NULL;
+ opt->max = n + 1;
+ opt->type = ComboBox;
+ } else if(p = strstr(opt->name, " -button")) {
+ opt->type = Button;
+ } else if(p = strstr(opt->name, " -save")) {
+ opt->type = SaveButton;
+ } else return FALSE;
+ *p = 0; // terminate option name
+ // now look if the command-line options define a setting for this engine option.
+ if(cps->optionSettings && cps->optionSettings[0])
+ p = strstr(cps->optionSettings, opt->name); else p = NULL;
+ if(p && (p == cps->optionSettings || p[-1] == ',')) {
+ sprintf(buf, "option %s", p);
+ if(p = strstr(buf, ",")) *p = 0;
+ strcat(buf, "\n");
+ SendToProgram(buf, cps);
+ }
+ return TRUE;
+}
+
void
FeatureDone(cps, val)
ChessProgramState* cps;
FeatureDone(cps, val);
continue;
}
+ /* Added by Tord: */
+ if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
+ if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
+ /* End of additions by Tord */
+
+ /* [HGM] added features: */
+ if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
+ if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
+ if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
+ if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
+ if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
+ if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
+ if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
+ ParseOption(&(cps->option[cps->nrOptions++]), cps); // [HGM] options: add option feature
+ if(cps->nrOptions >= MAX_OPTIONS) {
+ cps->nrOptions--;
+ sprintf(buf, "%s engine has too many options\n", cps->which);
+ DisplayError(buf, 0);
+ }
+ continue;
+ }
+ if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
+ /* End of additions by HGM */
/* unknown feature: complain and skip */
q = p;
}
void
-ShowThinkingEvent(newState)
- int newState;
+NewSettingEvent(option, command, value)
+ char *command;
+ int option, value;
{
- if (newState == appData.showThinking) return;
+ char buf[MSG_SIZ];
+
if (gameMode == EditPosition) EditPositionDone();
- if (newState) {
+ sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
+ SendToProgram(buf, &first);
+ if (gameMode == TwoMachinesPlay) {
+ SendToProgram(buf, &second);
+ }
+}
+
+void
+ShowThinkingEvent()
+// [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
+{
+ static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
+ int newState = appData.showThinking
+ // [HGM] thinking: other features now need thinking output as well
+ || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
+
+ if (oldState == newState) return;
+ oldState = newState;
+ if (gameMode == EditPosition) EditPositionDone();
+ if (oldState) {
SendToProgram("post\n", &first);
if (gameMode == TwoMachinesPlay) {
SendToProgram("post\n", &second);
SendToProgram("nopost\n", &second);
}
}
- appData.showThinking = newState;
+// appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
}
void
char res[MSG_SIZ];
char cpThinkOutput[MSG_SIZ];
+ if(appData.noGUI) return; // [HGM] fast: suppress display of moves
+
if (moveNumber == forwardMostMove - 1 ||
gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
- strcpy(cpThinkOutput, thinkOutput);
- if (strchr(cpThinkOutput, '\n'))
- *strchr(cpThinkOutput, '\n') = NULLCHAR;
+ safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
+
+ if (strchr(cpThinkOutput, '\n')) {
+ *strchr(cpThinkOutput, '\n') = NULLCHAR;
+ }
} else {
*cpThinkOutput = NULLCHAR;
}
+ /* [AS] Hide thinking from human user */
+ if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
+ *cpThinkOutput = NULLCHAR;
+ if( thinkOutput[0] != NULLCHAR ) {
+ int i;
+
+ for( i=0; i<=hiddenThinkOutputState; i++ ) {
+ cpThinkOutput[i] = '.';
+ }
+ cpThinkOutput[i] = NULLCHAR;
+ hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
+ }
+ }
+
if (moveNumber == forwardMostMove - 1 &&
gameInfo.resultDetails != NULL) {
if (gameInfo.resultDetails[0] == NULLCHAR) {
} else {
res[0] = NULLCHAR;
}
-
+
if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
DisplayMessage(res, cpThinkOutput);
} else {
{
char buf[MSG_SIZ];
- if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
+ if (gameMode == AnalyzeMode || gameMode == AnalyzeFile
+ || appData.icsEngineAnalyze) {
sprintf(buf, "Analysis (%s)", first.tidy);
AnalysisPopUp(buf, text);
}
DisplayAnalysis()
{
char buf[MSG_SIZ];
+ char lst[MSG_SIZ / 2];
double nps;
static char *xtra[] = { "", " (--)", " (++)" };
int h, m, s, cs;
}
if (programStats.got_only_move) {
- strcpy(buf, programStats.movelist);
+ safeStrCpy(buf, programStats.movelist, sizeof(buf));
} else {
- nps = (((double)programStats.nodes) /
- (((double)programStats.time)/100.0));
+ safeStrCpy( lst, programStats.movelist, sizeof(lst));
+
+ nps = (u64ToDouble(programStats.nodes) /
+ ((double)programStats.time /100.0));
cs = programStats.time % 100;
s = programStats.time / 100;
if (programStats.moves_left > 0 && appData.periodicUpdates) {
if (programStats.move_name[0] != NULLCHAR) {
- sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",
+ sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
programStats.depth,
programStats.nr_moves-programStats.moves_left,
programStats.nr_moves, programStats.move_name,
- ((float)programStats.score)/100.0, programStats.movelist,
- only_one_move(programStats.movelist)?
+ ((float)programStats.score)/100.0, lst,
+ only_one_move(lst)?
xtra[programStats.got_fail] : "",
- programStats.nodes, (int)nps, h, m, s, cs);
+ (u64)programStats.nodes, (int)nps, h, m, s, cs);
} else {
- sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",
+ sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
programStats.depth,
programStats.nr_moves-programStats.moves_left,
programStats.nr_moves, ((float)programStats.score)/100.0,
- programStats.movelist,
- only_one_move(programStats.movelist)?
+ lst,
+ only_one_move(lst)?
xtra[programStats.got_fail] : "",
- programStats.nodes, (int)nps, h, m, s, cs);
+ (u64)programStats.nodes, (int)nps, h, m, s, cs);
}
} else {
- sprintf(buf, "depth=%d %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",
+ sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
programStats.depth,
((float)programStats.score)/100.0,
- programStats.movelist,
- only_one_move(programStats.movelist)?
+ lst,
+ only_one_move(lst)?
xtra[programStats.got_fail] : "",
- programStats.nodes, (int)nps, h, m, s, cs);
+ (u64)programStats.nodes, (int)nps, h, m, s, cs);
}
}
DisplayAnalysisText(buf);
char *text;
{
char title[MSG_SIZ];
+ char buf[8000]; // comment can be long!
+ int score, depth;
+
+ if( appData.autoDisplayComment ) {
+ if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
+ strcpy(title, "Comment");
+ } else {
+ sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
+ WhiteOnMove(moveNumber) ? " " : ".. ",
+ parseList[moveNumber]);
+ }
+ // [HGM] PV info: display PV info together with (or as) comment
+ if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
+ if(text == NULL) text = "";
+ score = pvInfoList[moveNumber].score;
+ sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
+ depth, (pvInfoList[moveNumber].time+50)/100, text);
+ text = buf;
+ }
+ } else title[0] = 0;
- if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
- strcpy(title, "Comment");
- } else {
- sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
- WhiteOnMove(moveNumber) ? " " : ".. ",
- parseList[moveNumber]);
- }
-
- CommentPopUp(title, text);
+ if (text != NULL)
+ CommentPopUp(title, text);
}
/* This routine sends a ^C interrupt to gnuchess, to awaken it if it
}
} else {
if (blackFlag) {
- DisplayTitle(_("Both flags fell"));
+ if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
} else {
- DisplayTitle(_("White's flag fell"));
+ if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
if (appData.autoCallFlag) {
GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
return TRUE;
}
} else {
if (whiteFlag) {
- DisplayTitle(_("Both flags fell"));
+ if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
} else {
- DisplayTitle(_("Black's flag fell"));
+ if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
if (appData.autoCallFlag) {
GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
return TRUE;
if (!appData.clockMode || appData.icsActive ||
gameMode == PlayFromGameFile || forwardMostMove == 0) return;
- if (timeIncrement >= 0) {
- if (WhiteOnMove(forwardMostMove)) {
- blackTimeRemaining += timeIncrement;
- } else {
- whiteTimeRemaining += timeIncrement;
- }
- }
/*
- * add time to clocks when time control is achieved
+ * add time to clocks when time control is achieved ([HGM] now also used for increment)
*/
- if (movesPerSession) {
- switch ((forwardMostMove + 1) % (movesPerSession * 2)) {
- case 0:
+ if ( !WhiteOnMove(forwardMostMove) )
/* White made time control */
- whiteTimeRemaining += timeControl;
- break;
- case 1:
+ whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
+ /* [HGM] time odds: correct new time quota for time odds! */
+ / WhitePlayer()->timeOdds;
+ else
/* Black made time control */
- blackTimeRemaining += timeControl;
- break;
- default:
- break;
- }
- }
+ blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
+ / WhitePlayer()->other->timeOdds;
}
void
you have neither ftime nor gettimeofday.
*/
+/* VS 2008 requires the #include outside of the function */
+#if !HAVE_GETTIMEOFDAY && HAVE_FTIME
+#include <sys/timeb.h>
+#endif
+
/* Get the current time as a TimeMark */
void
GetTimeMark(tm)
#else /*!HAVE_GETTIMEOFDAY*/
#if HAVE_FTIME
-#include <sys/timeb.h>
+// include <sys/timeb.h> / moved to just above start of function
struct timeb timeB;
ftime(&timeB);
return nextTickLength;
}
+/* Adjust clock one minute up or down */
+void
+AdjustClock(Boolean which, int dir)
+{
+ if(which) blackTimeRemaining += 60000*dir;
+ else whiteTimeRemaining += 60000*dir;
+ DisplayBothClocks();
+}
+
/* Stop clocks and reset to a fresh time control */
void
ResetClocks()
(void) StopClockTimer();
if (appData.icsActive) {
whiteTimeRemaining = blackTimeRemaining = 0;
- } else {
- whiteTimeRemaining = blackTimeRemaining = timeControl;
+ } else { /* [HGM] correct new time quote for time odds */
+ whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
+ blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
}
if (whiteFlag || blackFlag) {
DisplayTitle("");
if (fudge < 0 || fudge > FUDGE) fudge = 0;
if (WhiteOnMove(forwardMostMove)) {
+ if(whiteNPS >= 0) lastTickLength = 0;
timeRemaining = whiteTimeRemaining -= lastTickLength;
DisplayWhiteClock(whiteTimeRemaining - fudge,
WhiteOnMove(currentMove));
} else {
+ if(blackNPS >= 0) lastTickLength = 0;
timeRemaining = blackTimeRemaining -= lastTickLength;
DisplayBlackClock(blackTimeRemaining - fudge,
!WhiteOnMove(currentMove));
if (StopClockTimer() && appData.clockMode) {
lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
if (WhiteOnMove(forwardMostMove)) {
+ if(blackNPS >= 0) lastTickLength = 0;
blackTimeRemaining -= lastTickLength;
+ /* [HGM] PGNtime: save time for PGN file if engine did not give it */
+// if(pvInfoList[forwardMostMove-1].time == -1)
+ pvInfoList[forwardMostMove-1].time = // use GUI time
+ (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
} else {
- whiteTimeRemaining -= lastTickLength;
+ if(whiteNPS >= 0) lastTickLength = 0;
+ whiteTimeRemaining -= lastTickLength;
+ /* [HGM] PGNtime: save time for PGN file if engine did not give it */
+// if(pvInfoList[forwardMostMove-1].time == -1)
+ pvInfoList[forwardMostMove-1].time =
+ (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
}
flagged = CheckFlags();
}
lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
if (WhiteOnMove(forwardMostMove)) {
+ if(whiteNPS >= 0) lastTickLength = 0;
whiteTimeRemaining -= lastTickLength;
DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
} else {
+ if(blackNPS >= 0) lastTickLength = 0;
blackTimeRemaining -= lastTickLength;
DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
}
GetTimeMark(&tickStartTM);
intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
whiteTimeRemaining : blackTimeRemaining);
+
+ /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
+ whiteNPS = blackNPS = -1;
+ if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
+ || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
+ whiteNPS = first.nps;
+ if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
+ || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
+ blackNPS = first.nps;
+ if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
+ whiteNPS = second.nps;
+ if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
+ blackNPS = second.nps;
+ if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
+
StartClockTimer(intendedTickLength);
}
char *
-PositionToFEN(move)
+PositionToFEN(move, overrideCastling)
int move;
+ char *overrideCastling;
{
int i, j, fromX, fromY, toX, toY;
int whiteToPlay;
char buf[128];
char *p, *q;
int emptycount;
+ ChessSquare piece;
whiteToPlay = (gameMode == EditPosition) ?
!blackPlaysFirst : (move % 2 == 0);
p = buf;
/* Piece placement data */
- for (i = BOARD_SIZE - 1; i >= 0; i--) {
+ for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
emptycount = 0;
- for (j = 0; j < BOARD_SIZE; j++) {
+ for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
if (boards[move][i][j] == EmptySquare) {
emptycount++;
- } else {
+ } else { ChessSquare piece = boards[move][i][j];
if (emptycount > 0) {
- *p++ = '0' + emptycount;
+ if(emptycount<10) /* [HGM] can be >= 10 */
+ *p++ = '0' + emptycount;
+ else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
emptycount = 0;
}
- *p++ = PieceToChar(boards[move][i][j]);
+ if(PieceToChar(piece) == '+') {
+ /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
+ *p++ = '+';
+ piece = (ChessSquare)(DEMOTED piece);
+ }
+ *p++ = PieceToChar(piece);
+ if(p[-1] == '~') {
+ /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
+ p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
+ *p++ = '~';
+ }
}
}
if (emptycount > 0) {
- *p++ = '0' + emptycount;
+ if(emptycount<10) /* [HGM] can be >= 10 */
+ *p++ = '0' + emptycount;
+ else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
emptycount = 0;
}
*p++ = '/';
}
*(p - 1) = ' ';
+ /* [HGM] print Crazyhouse or Shogi holdings */
+ if( gameInfo.holdingsWidth ) {
+ *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
+ q = p;
+ for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
+ piece = boards[move][i][BOARD_WIDTH-1];
+ if( piece != EmptySquare )
+ for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
+ *p++ = PieceToChar(piece);
+ }
+ for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
+ piece = boards[move][BOARD_HEIGHT-i-1][0];
+ if( piece != EmptySquare )
+ for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
+ *p++ = PieceToChar(piece);
+ }
+
+ if( q == p ) *p++ = '-';
+ *p++ = ']';
+ *p++ = ' ';
+ }
+
/* Active color */
*p++ = whiteToPlay ? 'w' : 'b';
*p++ = ' ';
- /* !!We don't keep track of castling availability, so fake it */
- q = p;
- if (boards[move][0][4] == WhiteKing) {
- if (boards[move][0][7] == WhiteRook) *p++ = 'K';
- if (boards[move][0][0] == WhiteRook) *p++ = 'Q';
- }
- if (boards[move][7][4] == BlackKing) {
- if (boards[move][7][7] == BlackRook) *p++ = 'k';
- if (boards[move][7][0] == BlackRook) *p++ = 'q';
- }
- if (q == p) *p++ = '-';
- *p++ = ' ';
+ if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
+ while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
+ } else {
+ if(nrCastlingRights) {
+ q = p;
+ if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
+ /* [HGM] write directly from rights */
+ if(castlingRights[move][2] >= 0 &&
+ castlingRights[move][0] >= 0 )
+ *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
+ if(castlingRights[move][2] >= 0 &&
+ castlingRights[move][1] >= 0 )
+ *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
+ if(castlingRights[move][5] >= 0 &&
+ castlingRights[move][3] >= 0 )
+ *p++ = castlingRights[move][3] + AAA;
+ if(castlingRights[move][5] >= 0 &&
+ castlingRights[move][4] >= 0 )
+ *p++ = castlingRights[move][4] + AAA;
+ } else {
+
+ /* [HGM] write true castling rights */
+ if( nrCastlingRights == 6 ) {
+ if(castlingRights[move][0] == BOARD_RGHT-1 &&
+ castlingRights[move][2] >= 0 ) *p++ = 'K';
+ if(castlingRights[move][1] == BOARD_LEFT &&
+ castlingRights[move][2] >= 0 ) *p++ = 'Q';
+ if(castlingRights[move][3] == BOARD_RGHT-1 &&
+ castlingRights[move][5] >= 0 ) *p++ = 'k';
+ if(castlingRights[move][4] == BOARD_LEFT &&
+ castlingRights[move][5] >= 0 ) *p++ = 'q';
+ }
+ }
+ if (q == p) *p++ = '-'; /* No castling rights */
+ *p++ = ' ';
+ }
+ if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
+ gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
/* En passant target square */
if (move > backwardMostMove) {
- fromX = moveList[move - 1][0] - 'a';
- fromY = moveList[move - 1][1] - '1';
- toX = moveList[move - 1][2] - 'a';
- toY = moveList[move - 1][3] - '1';
- if (fromY == (whiteToPlay ? 6 : 1) &&
- toY == (whiteToPlay ? 4 : 3) &&
+ fromX = moveList[move - 1][0] - AAA;
+ fromY = moveList[move - 1][1] - ONE;
+ toX = moveList[move - 1][2] - AAA;
+ toY = moveList[move - 1][3] - ONE;
+ if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
+ toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
fromX == toX) {
/* 2-square pawn move just happened */
- *p++ = toX + 'a';
- *p++ = whiteToPlay ? '6' : '3';
+ *p++ = toX + AAA;
+ *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
} else {
*p++ = '-';
}
} else {
*p++ = '-';
}
+ *p++ = ' ';
+ }
+ }
+
+ /* [HGM] find reversible plies */
+ { int i = 0, j=move;
- /* !!We don't keep track of halfmove clock for 50-move rule */
- strcpy(p, " 0 ");
- p += 3;
+ if (appData.debugMode) { int k;
+ fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
+ for(k=backwardMostMove; k<=forwardMostMove; k++)
+ fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
+
+ }
+ while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
+ if( j == backwardMostMove ) i += initialRulePlies;
+ sprintf(p, "%d ", i);
+ p += i>=100 ? 4 : i >= 10 ? 3 : 2;
+ }
/* Fullmove number */
sprintf(p, "%d", (move / 2) + 1);
Boolean
ParseFEN(board, blackPlaysFirst, fen)
- Board board;
+ Board board;
int *blackPlaysFirst;
char *fen;
{
int i, j;
char *p;
int emptycount;
+ ChessSquare piece;
p = fen;
+ /* [HGM] by default clear Crazyhouse holdings, if present */
+ if(gameInfo.holdingsWidth) {
+ for(i=0; i<BOARD_HEIGHT; i++) {
+ board[i][0] = EmptySquare; /* black holdings */
+ board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
+ board[i][1] = (ChessSquare) 0; /* black counts */
+ board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
+ }
+ }
+
/* Piece placement data */
- for (i = BOARD_SIZE - 1; i >= 0; i--) {
+ for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
j = 0;
for (;;) {
- if (*p == '/' || *p == ' ') {
- if (*p == '/') p++;
- emptycount = BOARD_SIZE - j;
- while (emptycount--) board[i][j++] = EmptySquare;
+ if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
+ if (*p == '/') p++;
+ emptycount = gameInfo.boardWidth - j;
+ while (emptycount--)
+ board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
break;
- } else if (isdigit(*p)) {
+#if(BOARD_SIZE >= 10)
+ } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
+ p++; emptycount=10;
+ if (j + emptycount > gameInfo.boardWidth) return FALSE;
+ while (emptycount--)
+ board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
+#endif
+ } else if (isdigit(*p)) {
emptycount = *p++ - '0';
- if (j + emptycount > BOARD_SIZE) return FALSE;
- while (emptycount--) board[i][j++] = EmptySquare;
- } else if (isalpha(*p)) {
- if (j >= BOARD_SIZE) return FALSE;
- board[i][j++] = CharToPiece(*p++);
+ while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
+ if (j + emptycount > gameInfo.boardWidth) return FALSE;
+ while (emptycount--)
+ board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
+ } else if (*p == '+' || isalpha(*p)) {
+ if (j >= gameInfo.boardWidth) return FALSE;
+ if(*p=='+') {
+ piece = CharToPiece(*++p);
+ if(piece == EmptySquare) return FALSE; /* unknown piece */
+ piece = (ChessSquare) (PROMOTED piece ); p++;
+ if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
+ } else piece = CharToPiece(*p++);
+
+ if(piece==EmptySquare) return FALSE; /* unknown piece */
+ if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
+ piece = (ChessSquare) (PROMOTED piece);
+ if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
+ p++;
+ }
+ board[i][(j++)+gameInfo.holdingsWidth] = piece;
} else {
return FALSE;
}
}
while (*p == '/' || *p == ' ') p++;
+ /* [HGM] look for Crazyhouse holdings here */
+ while(*p==' ') p++;
+ if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
+ if(*p == '[') p++;
+ if(*p == '-' ) *p++; /* empty holdings */ else {
+ if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
+ /* if we would allow FEN reading to set board size, we would */
+ /* have to add holdings and shift the board read so far here */
+ while( (piece = CharToPiece(*p) ) != EmptySquare ) {
+ *p++;
+ if((int) piece >= (int) BlackPawn ) {
+ i = (int)piece - (int)BlackPawn;
+ i = PieceToNumber((ChessSquare)i);
+ if( i >= gameInfo.holdingsSize ) return FALSE;
+ board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
+ board[BOARD_HEIGHT-1-i][1]++; /* black counts */
+ } else {
+ i = (int)piece - (int)WhitePawn;
+ i = PieceToNumber((ChessSquare)i);
+ if( i >= gameInfo.holdingsSize ) return FALSE;
+ board[i][BOARD_WIDTH-1] = piece; /* white holdings */
+ board[i][BOARD_WIDTH-2]++; /* black holdings */
+ }
+ }
+ }
+ if(*p == ']') *p++;
+ }
+
+ while(*p == ' ') p++;
+
/* Active color */
- switch (*p) {
+ switch (*p++) {
case 'w':
- *blackPlaysFirst = FALSE;
+ *blackPlaysFirst = FALSE;
break;
case 'b':
*blackPlaysFirst = TRUE;
default:
return FALSE;
}
- /* !!We ignore the rest of the FEN notation */
+
+ /* [HGM] We NO LONGER ignore the rest of the FEN notation */
+ /* return the extra info in global variiables */
+
+ /* set defaults in case FEN is incomplete */
+ FENepStatus = EP_UNKNOWN;
+ for(i=0; i<nrCastlingRights; i++ ) {
+ FENcastlingRights[i] =
+ gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
+ } /* assume possible unless obviously impossible */
+ if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
+ if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
+ if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
+ if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
+ if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
+ if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
+ FENrulePlies = 0;
+
+ while(*p==' ') p++;
+ if(nrCastlingRights) {
+ if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
+ /* castling indicator present, so default becomes no castlings */
+ for(i=0; i<nrCastlingRights; i++ ) {
+ FENcastlingRights[i] = -1;
+ }
+ }
+ while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
+ (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
+ ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
+ ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
+ char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
+
+ for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
+ if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
+ if(board[0 ][i] == WhiteKing) whiteKingFile = i;
+ }
+ switch(c) {
+ case'K':
+ for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
+ FENcastlingRights[0] = i != whiteKingFile ? i : -1;
+ FENcastlingRights[2] = whiteKingFile;
+ break;
+ case'Q':
+ for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
+ FENcastlingRights[1] = i != whiteKingFile ? i : -1;
+ FENcastlingRights[2] = whiteKingFile;
+ break;
+ case'k':
+ for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
+ FENcastlingRights[3] = i != blackKingFile ? i : -1;
+ FENcastlingRights[5] = blackKingFile;
+ break;
+ case'q':
+ for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
+ FENcastlingRights[4] = i != blackKingFile ? i : -1;
+ FENcastlingRights[5] = blackKingFile;
+ case '-':
+ break;
+ default: /* FRC castlings */
+ if(c >= 'a') { /* black rights */
+ for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
+ if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
+ if(i == BOARD_RGHT) break;
+ FENcastlingRights[5] = i;
+ c -= AAA;
+ if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
+ board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
+ if(c > i)
+ FENcastlingRights[3] = c;
+ else
+ FENcastlingRights[4] = c;
+ } else { /* white rights */
+ for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
+ if(board[0][i] == WhiteKing) break;
+ if(i == BOARD_RGHT) break;
+ FENcastlingRights[2] = i;
+ c -= AAA - 'a' + 'A';
+ if(board[0][c] >= WhiteKing) break;
+ if(c > i)
+ FENcastlingRights[0] = c;
+ else
+ FENcastlingRights[1] = c;
+ }
+ }
+ }
+ if (appData.debugMode) {
+ fprintf(debugFP, "FEN castling rights:");
+ for(i=0; i<nrCastlingRights; i++)
+ fprintf(debugFP, " %d", FENcastlingRights[i]);
+ fprintf(debugFP, "\n");
+ }
+
+ while(*p==' ') p++;
+ }
+
+ /* read e.p. field in games that know e.p. capture */
+ if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
+ gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
+ if(*p=='-') {
+ p++; FENepStatus = EP_NONE;
+ } else {
+ char c = *p++ - AAA;
+
+ if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
+ if(*p >= '0' && *p <='9') *p++;
+ FENepStatus = c;
+ }
+ }
+
+
+ if(sscanf(p, "%d", &i) == 1) {
+ FENrulePlies = i; /* 50-move ply counter */
+ /* (The move number is still ignored) */
+ }
+
return TRUE;
}
EditPositionEvent();
blackPlaysFirst = savedBlackPlaysFirst;
CopyBoard(boards[0], initial_position);
+ /* [HGM] copy FEN attributes as well */
+ { int i;
+ initialRulePlies = FENrulePlies;
+ epStatus[0] = FENepStatus;
+ for( i=0; i<nrCastlingRights; i++ )
+ castlingRights[0][i] = FENcastlingRights[i];
+ }
EditPositionDone();
DisplayBothClocks();
DrawPosition(FALSE, boards[currentMove]);