/*
* backend.c -- Common back end for X and Windows NT versions of
- * XBoard $Id$
*
- * Copyright 1991 by Digital Equipment Corporation, Maynard, Massachusetts.
- * Enhancements Copyright 1992-2001 Free Software Foundation, Inc.
+ * Copyright 1991 by Digital Equipment Corporation, Maynard,
+ * Massachusetts.
+ *
+ * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
+ * 2007, 2008, 2009 Free Software Foundation, Inc.
+ *
+ * Enhancements Copyright 2005 Alessandro Scotti
*
* The following terms apply to Digital Equipment Corporation's copyright
* interest in XBoard:
* SOFTWARE.
* ------------------------------------------------------------------------
*
- * The following terms apply to the enhanced version of XBoard distributed
- * by the Free Software Foundation:
+ * The following terms apply to the enhanced version of XBoard
+ * distributed by the Free Software Foundation:
* ------------------------------------------------------------------------
- * This program is free software; you can redistribute it and/or modify
+ *
+ * GNU XBoard is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
*
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
+ * GNU XBoard is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
*
* You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- * ------------------------------------------------------------------------
+ * along with this program. If not, see http://www.gnu.org/licenses/. *
*
- * See the file ChangeLog for a revision history. */
+ *------------------------------------------------------------------------
+ ** See the file ChangeLog for a revision history. */
+
+/* [AS] Also useful here for debugging */
+#ifdef WIN32
+#include <windows.h>
+
+#define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
+
+#else
+
+#define DoSleep( n ) if( (n) >= 0) sleep(n)
+
+#endif
#include "config.h"
+#include <assert.h>
#include <stdio.h>
#include <ctype.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <math.h>
+#include <ctype.h>
#if STDC_HEADERS
# include <stdlib.h>
# include <string.h>
+# include <stdarg.h>
#else /* not STDC_HEADERS */
# if HAVE_STRING_H
# include <string.h>
# 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 */
- u64 nodes; /* # of nodes searched */
- int time; /* Search time (centiseconds) */
- int score; /* Score (centipawns) */
- int got_only_move; /* If last msg was "(only move)" */
- int got_fail; /* 0 - nothing, 1 - got "--", 2 - got "++" */
- int ok_to_send; /* handshaking between send & recv */
- int line_is_book; /* 1 if movelist is book moves */
- int seen_stat; /* 1 if we've seen the stat01: line */
-} ChessProgramStats;
-
int establish P((void));
void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
char *buf, int count, int error));
void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
char *buf, int count, int error));
+void ics_printf P((char *format, ...));
void SendToICS P((char *s));
void SendToICSDelayed P((char *s, long msdelay));
void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
int toX, int toY));
-void InitPosition P((int redraw));
void HandleMachineMove P((char *message, ChessProgramState *cps));
int AutoPlayOneMove P((void));
int LoadGameOneMove P((ChessMove readAhead));
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));
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 GameEnds P((ChessMove result, char *resultDetails, int whosays));
-void EditPositionDone P((void));
+void EditPositionDone P((Boolean fakeRights));
void PrintOpponents P((FILE *fp));
void PrintPosition P((FILE *fp, int move));
void StartChessProgram P((ChessProgramState *cps));
void ResurrectChessProgram P((void));
void DisplayComment P((int moveNumber, char *text));
void DisplayMove P((int moveNumber));
-void DisplayAnalysis P((void));
void ParseGameHistory P((char *game));
void ParseBoard12 P((char *string));
void ParseFeatures P((char* args, ChessProgramState *cps));
void InitBackEnd3 P((void));
void FeatureDone P((ChessProgramState* cps, int val));
-void InitChessProgram P((ChessProgramState *cps));
-double u64ToDouble P((u64 value));
+void InitChessProgram P((ChessProgramState *cps, int setup));
+void OutputKibitz(int window, char *text);
+int PerpetualChase(int first, int last);
+int EngineOutputIsUp();
+void InitDrawingSizes(int x, int y);
#ifdef WIN32
- extern void ConsoleCreate();
+ extern void ConsoleCreate();
#endif
+ChessProgramState *WhitePlayer();
+void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
+int VerifyDisplayMode P(());
+
+char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
+void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
+char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
+char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
+void ics_update_width P((int new_width));
+extern char installDir[MSG_SIZ];
+
extern int tinyLayout, smallLayout;
-static ChessProgramStats programStats;
+ChessProgramStats programStats;
+static int exiting = 0; /* [HGM] moved to top */
+static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
+int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
+char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
+int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
+VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
+int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
+int opponentKibitzes;
+int lastSavedGame; /* [HGM] save: ID of game */
+char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
+extern int chatCount;
+int chattingPartner;
/* States for ics_getting_history */
#define H_FALSE 0
#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;
+}
+
/* Some compiler can't cast u64 to double
* This function do the job for us:
u64 tmp = value & u64Const(0x7fffffffffffffff);
r = (double)(s64)tmp;
if (value & u64Const(0x8000000000000000))
- r += 9.2233720368547758080e18; /* 2^63 */
+ 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];
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: */
+signed char castlingRank[BOARD_FILES]; // and corresponding ranks
+signed char initialRights[BOARD_FILES];
+int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
+int initialRulePlies, FENrulePlies;
+FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
+int loadFlag = 0;
+int shuffleOpenings;
+int mute; // mute all sounds
+
+// [HGM] vari: next 12 to save and restore variations
+#define MAX_VARIATIONS 10
+int framePtr = MAX_MOVES-1; // points to free stack entry
+int storedGames = 0;
+int savedFirst[MAX_VARIATIONS];
+int savedLast[MAX_VARIATIONS];
+int savedFramePtr[MAX_VARIATIONS];
+char *savedDetails[MAX_VARIATIONS];
+ChessMove savedResult[MAX_VARIATIONS];
+
+void PushTail P((int firstMove, int lastMove));
+Boolean PopTail P((Boolean annotate));
+void CleanupTail P((void));
+
+ChessSquare FIDEArray[2][BOARD_FILES] = {
{ 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_FILES] = {
{ 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_FILES] = {
+ { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
+ WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
+ { BlackRook, BlackMan, BlackBishop, BlackQueen,
+ BlackUnicorn, BlackBishop, BlackMan, BlackRook }
+};
+
+ChessSquare fairyArray[2][BOARD_FILES] = { /* [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_FILES] = { /* [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_FILES>=10)
+ChessSquare ShogiArray[2][BOARD_FILES] = {
+ { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
+ WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
+ { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
+ BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
+};
+
+ChessSquare XiangqiArray[2][BOARD_FILES] = {
+ { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
+ WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
+ { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
+ BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
};
+ChessSquare CapablancaArray[2][BOARD_FILES] = {
+ { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
+ WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
+ { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
+ BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
+};
+
+ChessSquare GreatArray[2][BOARD_FILES] = {
+ { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
+ WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
+ { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
+ BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
+};
+
+ChessSquare JanusArray[2][BOARD_FILES] = {
+ { 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_FILES] = {
+ { 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_FILES] = {
+ { 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_FILES>=10)
+#define XiangqiPosition FIDEArray
+#define CapablancaArray FIDEArray
+#define GothicArray FIDEArray
+#define GreatArray FIDEArray
+#endif // !(BOARD_FILES>=10)
+
+#if (BOARD_FILES>=12)
+ChessSquare CourierArray[2][BOARD_FILES] = {
+ { 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_FILES>=12)
+#define CourierArray CapablancaArray
+#endif // !(BOARD_FILES>=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);
+ srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
ClearProgramStats();
programStats.ok_to_send = 1;
if (appData.icsActive) {
appData.matchMode = FALSE;
appData.matchGames = 0;
-#if ZIPPY
+#if ZIPPY
appData.noChessProgram = !appData.zippyPlay;
#else
appData.zippyPlay = FALSE;
appData.zippyTalk = appData.zippyPlay = FALSE;
}
+ /* [AS] Initialize pv info list [HGM] and game state */
+ {
+ int i, j;
+
+ for( i=0; i<=framePtr; i++ ) {
+ pvInfoList[i].depth = -1;
+ boards[i][EP_STATUS] = EP_NONE;
+ for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
+ }
+ }
+
/*
* 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.maybeThinking = second.maybeThinking = FALSE;
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];
if (appData.icsActive) {
appData.clockMode = TRUE; /* changes dynamically in ICS mode */
- } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
+// } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
+ } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
appData.clockMode = FALSE;
first.sendTime = second.sendTime = 0;
}
-
+
#if ZIPPY
/* Override some settings from environment variables, for backward
compatibility. Unfortunately it's not feasible to have the env
ZippyInit();
}
#endif
-
+
if (appData.noChessProgram) {
- programVersion = (char*) malloc(5 + strlen(PRODUCT) + strlen(VERSION)
- + strlen(PATCHLEVEL));
- sprintf(programVersion, "%s %s.%s", PRODUCT, VERSION, PATCHLEVEL);
+ programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
+ sprintf(programVersion, "%s", PACKAGE_STRING);
} else {
- char *p, *q;
- q = first.program;
- while (*q != ' ' && *q != NULLCHAR) q++;
- p = q;
- while (p > first.program && *(p-1) != '/') p--;
- programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)
- + strlen(PATCHLEVEL) + (q - p));
- sprintf(programVersion, "%s %s.%s + ", PRODUCT, VERSION, PATCHLEVEL);
- strncat(programVersion, p, q - p);
+ /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
+ programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
+ sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
}
if (!appData.icsActive) {
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;
{
- int matched, min, sec;
-
- matched = sscanf(tc, "%d:%d", &min, &sec);
- if (matched == 1) {
- timeControl = min * 60 * 1000;
- } else if (matched == 2) {
- timeControl = (min * 60 + sec) * 1000;
- } else {
- return FALSE;
+ long tc1;
+ long tc2;
+ char buf[MSG_SIZ];
+
+ if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
+ if(ti > 0) {
+ if(mps)
+ sprintf(buf, "+%d/%s+%d", mps, tc, ti);
+ else sprintf(buf, "+%s+%d", tc, ti);
+ } else {
+ if(mps)
+ sprintf(buf, "+%d/%s", mps, tc);
+ else sprintf(buf, "+%s", tc);
+ }
+ fullTimeControlString = StrSave(buf);
+
+ if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
+ return FALSE;
+ }
+
+ if( *tc == '/' ) {
+ /* Parse second time control */
+ tc++;
+
+ if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
+ return FALSE;
}
-
- if (ti >= 0) {
- timeIncrement = ti * 1000; /* convert to ms */
- movesPerSession = 0;
- } else {
- timeIncrement = 0;
- movesPerSession = mps;
+
+ if( tc2 == 0 ) {
+ return FALSE;
}
- return TRUE;
+
+ timeControl_2 = tc2 * 1000;
+ }
+ else {
+ timeControl_2 = 0;
+ }
+
+ if( tc1 == 0 ) {
+ return FALSE;
+ }
+
+ timeControl = tc1 * 1000;
+
+ if (ti >= 0) {
+ timeIncrement = ti * 1000; /* convert to ms */
+ movesPerSession = 0;
+ } else {
+ timeIncrement = 0;
+ movesPerSession = mps;
+ }
+ return TRUE;
}
void
fprintf(debugFP, "%s\n", programVersion);
}
+ set_cont_sequence(appData.wrapContSeq);
if (appData.matchGames > 0) {
appData.matchMode = TRUE;
} else if (appData.matchMode) {
appData.matchGames = 1;
}
+ if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
+ appData.matchGames = appData.sameColorGames;
+ if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
+ if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
+ if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
+ }
Reset(TRUE, FALSE);
if (appData.noChessProgram || first.protocolVersion == 1) {
InitBackEnd3();
} else {
/* kludge: allow timeout for initial "feature" commands */
FreezeUI();
- DisplayMessage("", "Starting chess program");
+ DisplayMessage("", _("Starting chess program"));
ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
}
}
char buf[MSG_SIZ];
int err;
- InitChessProgram(&first);
+ InitChessProgram(&first, startedFromSetupPosition);
- #ifdef WIN32
- /* Make a console window if needed */
- if (appData.icsActive) ConsoleCreate();
- #endif
if (appData.icsActive) {
+#ifdef WIN32
+ /* [DM] Make a console window if needed [HGM] merged ifs */
+ ConsoleCreate();
+#endif
err = establish();
if (err != 0) {
if (*appData.icsCommPort != NULLCHAR) {
- sprintf(buf, _("Could not open comm port %s"),
+ sprintf(buf, _("Could not open comm port %s"),
appData.icsCommPort);
} else {
- sprintf(buf, _("Could not connect to host %s, port %s"),
+ snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
appData.icsHost, appData.icsPort);
}
DisplayFatalError(buf, err, 1);
cmailISR =
AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
}
-
+
ThawUI();
DisplayMessage("", "");
if (StrCaseCmp(appData.initialMode, "") == 0) {
} else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
initialMode = TwoMachinesPlay;
} else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
- initialMode = AnalyzeFile;
+ initialMode = AnalyzeFile;
} else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
initialMode = AnalyzeMode;
} else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
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) {
+ startedFromPositionFile = TRUE;
+ CopyBoard(filePosition, boards[0]);
+ }
}
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",
- appData.remoteShell, appData.gateway,
+ snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
+ appData.remoteShell, appData.gateway,
appData.remoteUser, appData.telnetProgram,
appData.icsHost, appData.icsPort);
}
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);
}
void
+KeepAlive()
+{ // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
+ SendToICS("date\n");
+ if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
+}
+
+/* added routine for printf style output to ics */
+void ics_printf(char *format, ...)
+{
+ char buffer[MSG_SIZ];
+ va_list args;
+
+ va_start(args, format);
+ vsnprintf(buffer, sizeof(buffer), format, args);
+ buffer[sizeof(buffer)-1] = '\0';
+ SendToICS(buffer);
+ va_end(args);
+}
+
+void
SendToICS(s)
char *s;
{
/* Remove all highlighting escape sequences in s
- Also deletes any suffix starting with '('
+ Also deletes any suffix starting with '('
*/
char *
StripHighlightAndTitle(s)
if (!e) return v;
+ /* [HGM] skip over optional board-size prefixes */
+ if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
+ sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
+ while( *e++ != '_');
+ }
+
+ if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
+ v = VariantNormal;
+ found = TRUE;
+ } else
for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
if (StrCaseStr(e, variantNames[i])) {
v = (VariantClass) i;
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"))) {
v = VariantShatranj;
break;
- /* Temporary names for future ICC types. The name *will* change in
+ /* Temporary names for future ICC types. The name *will* change in
the next xboard/WinBoard release after ICC defines it. */
case 29:
v = Variant29;
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;
char *bufp = &buf[*index], *patternp = pattern;
int star_count = 0;
char *matchp = star_match[0];
-
+
for (;;) {
if (*patternp == NULLCHAR) {
*index = leftover_start = bufp - buf;
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(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
+ return; // prevent overwriting by pre-board holdings
+
+ 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;
+ Board oldBoard;
+
+ startedFromPositionFile = FALSE;
+ if(gameInfo.variant == newVariant) return;
+
+ /* [HGM] This routine is called each time an assignment is made to
+ * gameInfo.variant during a game, to make sure the board sizes
+ * are set to match the new variant. If that means adding or deleting
+ * holdings, we shift the playing board accordingly
+ * This kludge is needed because in ICS observe mode, we get boards
+ * of an ongoing game without knowing the variant, and learn about the
+ * latter only later. This can be because of the move list we requested,
+ * in which case the game history is refilled from the beginning anyway,
+ * but also when receiving holdings of a crazyhouse game. In the latter
+ * case we want to add those holdings to the already received position.
+ */
+
+
+ if (appData.debugMode) {
+ fprintf(debugFP, "Switch board from %s to %s\n",
+ VariantName(gameInfo.variant), VariantName(newVariant));
+ setbuf(debugFP, NULL);
+ }
+ shuffleOpenings = 0; /* [HGM] shuffle */
+ gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
+ switch(newVariant)
+ {
+ case VariantShogi:
+ newWidth = 9; newHeight = 9;
+ gameInfo.holdingsSize = 7;
+ case VariantBughouse:
+ case VariantCrazyhouse:
+ newHoldingsWidth = 2; break;
+ case VariantGreat:
+ newWidth = 10;
+ case VariantSuper:
+ newHoldingsWidth = 2;
+ gameInfo.holdingsSize = 8;
+ break;
+ case VariantGothic:
+ case VariantCapablanca:
+ case VariantCapaRandom:
+ newWidth = 10;
+ default:
+ newHoldingsWidth = gameInfo.holdingsSize = 0;
+ };
+
+ if(newWidth != gameInfo.boardWidth ||
+ newHeight != gameInfo.boardHeight ||
+ newHoldingsWidth != gameInfo.holdingsWidth ) {
+
+ /* shift position to new playing area, if needed */
+ if(newHoldingsWidth > gameInfo.holdingsWidth) {
+ for(i=0; i<BOARD_HEIGHT; i++)
+ for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
+ board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
+ board[i][j];
+ for(i=0; i<newHeight; i++) {
+ board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
+ board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
+ }
+ } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
+ for(i=0; i<BOARD_HEIGHT; i++)
+ for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
+ board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
+ board[i][j];
+ }
+ gameInfo.boardWidth = newWidth;
+ gameInfo.boardHeight = newHeight;
+ gameInfo.holdingsWidth = newHoldingsWidth;
+ gameInfo.variant = newVariant;
+ InitDrawingSizes(-2, 0);
+ } else gameInfo.variant = newVariant;
+ CopyBoard(oldBoard, board); // remember correctly formatted board
+ InitPosition(FALSE); /* this sets up board[0], but also other stuff */
+ DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
+}
+
static int loggedOn = FALSE;
/*-- Game start info cache: --*/
char gs_kind[MSG_SIZ];
static char player1Name[128] = "";
static char player2Name[128] = "";
+static char cont_seq[] = "\n\\ ";
static int player1Rating = -1;
static int player2Rating = -1;
/*----------------------------*/
+ColorClass curColor = ColorNormal;
+int suppressKibitz = 0;
+
void
read_from_ics(isr, closure, data, count, error)
InputSourceRef isr;
#define STARTED_CHATTER 5
#define STARTED_COMMENT 6
#define STARTED_MOVES_NOHIDE 7
-
+
static int started = STARTED_NONE;
static char parse[20000];
static int parse_pos = 0;
static char buf[BUF_SIZE + 1];
static int firstTime = TRUE, intfSet = FALSE;
- static ColorClass curColor = ColorNormal;
static ColorClass prevColor = ColorNormal;
static int savingComment = FALSE;
+ static int cmatch = 0; // continuation sequence match
+ char *bp;
char str[500];
int i, oldi;
int buf_len;
int next_out;
int tkind;
- int backup;
+ int backup; /* [DM] For zippy color lines */
char *p;
+ char talker[MSG_SIZ]; // [HGM] chat
+ int channel;
if (appData.debugMode) {
if (!error) {
}
}
+ if (appData.debugMode) { int f = forwardMostMove;
+ fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
+ boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
+ boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][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. */
buf[i] = buf[leftover_start + i];
}
- /* Copy in new characters, removing nulls and \r's */
- buf_len = leftover_len;
- for (i = 0; i < count; i++) {
- if (data[i] != NULLCHAR && data[i] != '\r')
- buf[buf_len++] = data[i];
- }
+ /* copy new characters into the buffer */
+ bp = buf + leftover_len;
+ buf_len=leftover_len;
+ for (i=0; i<count; i++)
+ {
+ // ignore these
+ if (data[i] == '\r')
+ continue;
+
+ // join lines split by ICS?
+ if (!appData.noJoin)
+ {
+ /*
+ Joining just consists of finding matches against the
+ continuation sequence, and discarding that sequence
+ if found instead of copying it. So, until a match
+ fails, there's nothing to do since it might be the
+ complete sequence, and thus, something we don't want
+ copied.
+ */
+ if (data[i] == cont_seq[cmatch])
+ {
+ cmatch++;
+ if (cmatch == strlen(cont_seq))
+ {
+ cmatch = 0; // complete match. just reset the counter
+
+ /*
+ it's possible for the ICS to not include the space
+ at the end of the last word, making our [correct]
+ join operation fuse two separate words. the server
+ does this when the space occurs at the width setting.
+ */
+ if (!buf_len || buf[buf_len-1] != ' ')
+ {
+ *bp++ = ' ';
+ buf_len++;
+ }
+ }
+ continue;
+ }
+ else if (cmatch)
+ {
+ /*
+ match failed, so we have to copy what matched before
+ falling through and copying this character. In reality,
+ this will only ever be just the newline character, but
+ it doesn't hurt to be precise.
+ */
+ strncpy(bp, cont_seq, cmatch);
+ bp += cmatch;
+ buf_len += cmatch;
+ cmatch = 0;
+ }
+ }
+
+ // copy this char
+ *bp++ = data[i];
+ buf_len++;
+ }
buf[buf_len] = NULLCHAR;
next_out = leftover_len;
leftover_start = 0;
-
+
i = 0;
while (i < buf_len) {
/* Deal with part of the TELNET option negotiation
next_out = i;
continue;
}
-
+
/* OK, this at least will *usually* work */
if (!loggedOn && looking_at(buf, &i, "ics%")) {
loggedOn = TRUE;
}
-
+
if (loggedOn && !intfSet) {
if (ics_type == ICS_ICC) {
sprintf(str,
"/set-quietly interface %s\n/set-quietly style 12\n",
programVersion);
-
} else if (ics_type == ICS_CHESSNET) {
sprintf(str, "/style 12\n");
} else {
strcat(str, "$iset lock 1\n$style 12\n");
}
SendToICS(str);
+ NotifyFrontendLogin();
intfSet = TRUE;
}
parse[parse_pos++] = buf[i];
if (buf[i] == '\n') {
parse[parse_pos] = NULLCHAR;
- AppendComment(forwardMostMove, StripHighlight(parse));
+ if(chattingPartner>=0) {
+ char mess[MSG_SIZ];
+ sprintf(mess, "%s%s", talker, parse);
+ OutputChatMessage(chattingPartner, mess);
+ chattingPartner = -1;
+ } else
+ if(!suppressKibitz) // [HGM] kibitz
+ AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
+ 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.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
+
+ // [HGM] chat: intercept tells by users for which we have an open chat window
+ channel = -1;
+ if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
+ looking_at(buf, &i, "* whispers:") ||
+ looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
+ looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
+ int p;
+ sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
+ chattingPartner = -1;
+
+ if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
+ for(p=0; p<MAX_CHAT; p++) {
+ if(channel == atoi(chatPartner[p])) {
+ talker[0] = '['; strcat(talker, "]");
+ chattingPartner = p; break;
+ }
+ } else
+ if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
+ for(p=0; p<MAX_CHAT; p++) {
+ if(!strcmp("WHISPER", chatPartner[p])) {
+ talker[0] = '['; strcat(talker, "]");
+ chattingPartner = p; break;
+ }
+ }
+ if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
+ for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
+ talker[0] = 0;
+ chattingPartner = p; break;
+ }
+ if(chattingPartner<0) i = oldi; else {
+ started = STARTED_COMMENT;
+ parse_pos = 0; parse[0] = NULLCHAR;
+ savingComment = TRUE;
+ suppressKibitz = TRUE;
+ }
+ } // [HGM] chat: end of patch
+
if (appData.zippyTalk || appData.zippyPlay) {
- /* Backup address for color zippy lines */
- backup = i;
+ /* [DM] Backup address for color zippy lines */
+ backup = i;
#if ZIPPY
- #ifdef WIN32
- if (loggedOn == TRUE)
- if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
- (appData.zippyPlay && ZippyMatch(buf, &backup)));
- #else
- if (ZippyControl(buf, &i) ||
- ZippyConverse(buf, &i) ||
- (appData.zippyPlay && ZippyMatch(buf, &i))) {
- loggedOn = TRUE;
- if (!appData.colorize) continue;
+ #ifdef WIN32
+ if (loggedOn == TRUE)
+ if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
+ (appData.zippyPlay && ZippyMatch(buf, &backup)));
+ #else
+ if (ZippyControl(buf, &i) ||
+ ZippyConverse(buf, &i) ||
+ (appData.zippyPlay && ZippyMatch(buf, &i))) {
+ loggedOn = TRUE;
+ if (!appData.colorize) continue;
}
- #endif
+ #endif
#endif
- }
- if (/* Don't color "message" or "messages" output */
- (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
- looking_at(buf, &i, "*. * at *:*: ") ||
- looking_at(buf, &i, "--* (*:*): ") ||
+ } // [DM] 'else { ' deleted
+ if (
/* Regular tells and says */
(tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
looking_at(buf, &i, "* (your partner) tells you: ") ||
looking_at(buf, &i, "* says: ") ||
+ /* Don't color "message" or "messages" output */
+ (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
+ looking_at(buf, &i, "*. * at *:*: ") ||
+ looking_at(buf, &i, "--* (*:*): ") ||
/* Message notifications (same color as tells) */
looking_at(buf, &i, "* has left a message ") ||
looking_at(buf, &i, "* just sent you a message:\n") ||
SendToICS("refresh\n");
continue;
}
-
+
if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
ICSInitScript();
have_sent_ICS_logon = 1;
continue;
}
-
- if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
+
+ if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
(looking_at(buf, &i, "\n<12> ") ||
looking_at(buf, &i, "<12> "))) {
loggedOn = TRUE;
gameInfo.whiteRating = string_to_rating(star_match[1]);
gameInfo.blackRating = string_to_rating(star_match[3]);
if (appData.debugMode)
- fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
+ fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
gameInfo.whiteRating, gameInfo.blackRating);
}
continue;
"* * 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;
}
break;
}
continue;
- }
-
+ }
+
if (looking_at(buf, &i, "% ") ||
((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
- && looking_at(buf, &i, "}*"))) {
+ && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
savingComment = FALSE;
switch (started) {
case STARTED_MOVES:
if (WhiteOnMove(forwardMostMove)) {
if (first.sendTime) {
if (first.useColors) {
- SendToProgram("black\n", &first);
+ SendToProgram("black\n", &first);
}
SendTimeRemaining(&first, TRUE);
}
if (first.useColors) {
- SendToProgram("white\ngo\n", &first);
- } else {
- SendToProgram("go\n", &first);
+ SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
}
+ bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
first.maybeThinking = TRUE;
} else {
if (first.usePlayother) {
SendTimeRemaining(&first, FALSE);
}
if (first.useColors) {
- SendToProgram("black\ngo\n", &first);
- } else {
- SendToProgram("go\n", &first);
+ SendToProgram("black\n", &first);
}
+ bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
first.maybeThinking = TRUE;
} else {
if (first.usePlayother) {
firstMove = TRUE;
}
}
- }
+ }
}
#endif
if (gameMode == IcsObserving && ics_gamenum == -1) {
currentMove = forwardMostMove;
ClearHighlights();/*!!could figure this out*/
flipView = appData.flipView;
- DrawPosition(FALSE, boards[currentMove]);
+ DrawPosition(TRUE, boards[currentMove]);
DisplayBothClocks();
sprintf(str, "%s vs. %s",
gameInfo.white, gameInfo.black);
default:
break;
}
+ if(bookHit) { // [HGM] book: simulate book reply
+ static char bookMove[MSG_SIZ]; // a bit generous?
+
+ programStats.nodes = programStats.depth = programStats.time =
+ programStats.score = programStats.got_only_move = 0;
+ sprintf(programStats.movelist, "%s (xbook)", bookHit);
+
+ strcpy(bookMove, "move ");
+ strcat(bookMove, bookHit);
+ HandleMachineMove(bookMove, &first);
+ }
continue;
}
-
+
if ((started == STARTED_MOVES || started == STARTED_BOARD ||
started == STARTED_HOLDINGS ||
started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
/* Accumulate characters in move list or board */
parse[parse_pos++] = buf[i];
}
-
+
/* Start of game messages. Mostly we detect start of game
when the first board image arrives. On some versions
of the ICS, though, we need to do a "refresh" after starting
player2Rating = string_to_rating(star_match[3]);
if (appData.debugMode)
- fprintf(debugFP,
+ fprintf(debugFP,
"Ratings from 'Game notification:' %s %d, %s %d\n",
player1Name, player1Rating,
player2Name, player2Rating);
SendToICS("refresh\n");
}
continue;
- }
-
+ }
+
/* Error messages */
- if (ics_user_moved) {
+// if (ics_user_moved) {
+ if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
if (looking_at(buf, &i, "Illegal move") ||
looking_at(buf, &i, "Not a legal move") ||
looking_at(buf, &i, "Your king is in check") ||
looking_at(buf, &i, "It isn't your turn") ||
looking_at(buf, &i, "It is not your move")) {
/* Illegal move */
- ics_user_moved = 0;
- if (forwardMostMove > backwardMostMove) {
+ if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
currentMove = --forwardMostMove;
DisplayMove(currentMove - 1); /* before DMError */
- DisplayMoveError("Illegal move (rejected by ICS)");
DrawPosition(FALSE, boards[currentMove]);
SwitchClocks();
DisplayBothClocks();
}
+ DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
+ ics_user_moved = 0;
continue;
}
}
2 empty, white, or black (IGNORED)
3 player 2 name (not necessarily black)
4 player 2 rating
-
+
The names/ratings are sorted out when the game
actually starts (below).
*/
player2Rating = string_to_rating(star_match[4]);
if (appData.debugMode)
- fprintf(debugFP,
+ fprintf(debugFP,
"Ratings from 'Creating:' %s %d, %s %d\n",
player1Name, player1Rating,
player2Name, player2Rating);
continue;
}
-
+
/* Improved generic start/end-of-game messages */
if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
(tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
/* 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();
- }
+ /* icsEngineAnalyze */
+ if (appData.icsEngineAnalyze) {
+ ExitAnalyzeMode();
+ ModeHighlight();
+ }
StopClocks();
gameMode = IcsIdle;
ics_gamenum = -1;
if (currentMove == 0 &&
gameMode == IcsPlayingWhite &&
appData.premoveWhite) {
- sprintf(str, "%s%s\n", ics_prefix,
- appData.premoveWhiteText);
+ sprintf(str, "%s\n", appData.premoveWhiteText);
if (appData.debugMode)
fprintf(debugFP, "Sending premove:\n");
SendToICS(str);
} else if (currentMove == 1 &&
gameMode == IcsPlayingBlack &&
appData.premoveBlack) {
- sprintf(str, "%s%s\n", ics_prefix,
- appData.premoveBlackText);
+ sprintf(str, "%s\n", appData.premoveBlackText);
if (appData.debugMode)
fprintf(debugFP, "Sending premove:\n");
SendToICS(str);
ClearPremoveHighlights();
if (appData.debugMode)
fprintf(debugFP, "Sending premove:\n");
- UserMoveEvent(premoveFromX, premoveFromY,
- premoveToX, premoveToY,
- premovePromoChar);
+ UserMoveEvent(premoveFromX, premoveFromY,
+ premoveToX, premoveToY,
+ premovePromoChar);
}
}
/* Usually suppress following prompt */
if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
+ while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
if (looking_at(buf, &i, "*% ")) {
savingComment = FALSE;
}
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!
+ */
+ VariantClass newVariant;
+ switch(gameInfo.boardWidth) { // base guess on board width
+ case 9: newVariant = VariantShogi; break;
+ case 10: newVariant = VariantGreat; break;
+ default: newVariant = VariantCrazyhouse; break;
+ }
+ VariantSwitch(boards[currentMove], newVariant); /* 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[forwardMostMove], white_holding, WhitePawn);
+ CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
+ boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
#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 */
if (looking_at(buf, &i, "*% ")) {
+ if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
savingComment = FALSE;
}
next_out = i;
i++; /* skip unparsed character and loop back */
}
-
- if (started != STARTED_MOVES && started != STARTED_BOARD &&
+
+ if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
started != STARTED_HOLDINGS && i > next_out) {
SendToPlayer(&buf[next_out], i - next_out);
next_out = i;
}
-
+ suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
+
leftover_len = buf_len - leftover_start;
/* if buffer ends with something we couldn't parse,
reparse it after appending the next read */
-
+
} else if (count == 0) {
RemoveInputSource(isr);
DisplayFatalError(_("Connection closed by ICS"), 0, 0);
/* Board style 12 looks like this:
-
+
<12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
-
+
* The "<12> " is stripped before it gets to this routine. The two
* trailing 0's (flip state and clock ticking) are later addition, and
* some chess servers may not have them, or may have only the first.
- * Additional trailing fields may be added in the future.
+ * Additional trailing fields may be added in the future.
*/
-#define PATTERN "%72c%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
+#define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
#define RELATION_OBSERVING_PLAYED 0
#define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
void
ParseBoard12(string)
char *string;
-{
+{
GameMode newGameMode;
int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
- char to_play, board_chars[72];
+ char to_play, board_chars[200];
char move_str[500], str[500], elapsed_time[500];
char black[32], white[32];
Board board;
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
+ Boolean weird = FALSE, reqFlag = FALSE;
fromX = fromY = toX = toY = -1;
-
+
newGame = FALSE;
if (appData.debugMode)
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++;
+ if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
+ 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;
}
/* Convert the move number to internal form */
moveNum = (moveNum - 1) * 2;
if (to_play == 'B') moveNum++;
- if (moveNum >= MAX_MOVES) {
+ if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
0, 1);
return;
}
-
+
switch (relation) {
case RELATION_OBSERVING_PLAYED:
case RELATION_OBSERVING_STATIC:
case RELATION_ISOLATED_BOARD:
default:
/* Just display this board. If user was doing something else,
- we will forget about it until the next board comes. */
+ we will forget about it until the next board comes. */
newGameMode = IcsIdle;
break;
case RELATION_STARTING_POSITION:
newGameMode = gameMode;
break;
}
-
+
/* Modify behavior for initial board display on move listing
of wild games.
*/
return;
}
+ if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
+ weird && (int)gameInfo.variant <= (int)VariantShogi) {
+ /* [HGM] We seem to have switched variant unexpectedly
+ * Try to guess new variant from board size
+ */
+ VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
+ if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
+ if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
+ if(ranks == 8 && files == 12) newVariant = VariantCourier; else
+ if(ranks == 9 && files == 9) newVariant = VariantShogi; else
+ if(!weird) newVariant = VariantNormal;
+ VariantSwitch(boards[currentMove], newVariant); /* 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) {
+ ics_getting_history = H_REQUESTED; reqFlag = TRUE;
+ sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
+ SendToICS(str);
+ }
+ }
+
/* Take action if this is the first board of a new game, or of a
different game than is currently being displayed. */
if (gamenum != ics_gamenum || newGameMode != gameMode ||
relation == RELATION_ISOLATED_BOARD) {
-
+
/* Forget the old game and get the history (if any) of the new one */
if (gameMode != BeginningOfGame) {
- Reset(FALSE, TRUE);
+ Reset(TRUE, TRUE);
}
newGame = TRUE;
if (appData.autoRaiseBoard) BoardToTop();
prevMove = -3;
if (gamenum == -1) {
newGameMode = IcsIdle;
- } else if (moveNum > 0 && newGameMode != IcsIdle &&
- appData.getMoveList) {
+ } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
+ appData.getMoveList && !reqFlag) {
/* Need to get game history */
ics_getting_history = H_REQUESTED;
sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
SendToICS(str);
}
-
+
/* Initially flip the board to have black on the bottom if playing
black or if the ICS flip flag is set, but let the user change
it with the Flip View button. */
- flipView = appData.autoFlipView ?
+ flipView = appData.autoFlipView ?
(newGameMode == IcsPlayingBlack) || ics_flip :
appData.flipView;
-
+
/* Done with values from previous mode; copy in new ones */
gameMode = newGameMode;
ModeHighlight();
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(boards[currentMove], StringToVariant(gameInfo.event) );
+ if (appData.debugMode) {
+ fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
+ fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
+ setbuf(debugFP, NULL);
+ }
+ gameInfo.outOfBook = NULL;
+
/* Do we have the ratings? */
if (strcmp(player1Name, white) == 0 &&
strcmp(player2Name, black) == 0) {
SendToICS("set shout 0\n");
}
}
-
+
/* Deal with midgame name changes */
if (!newGame) {
if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
gameInfo.black = StrSave(black);
}
}
-
+
/* Throw away game result if anything actually changes in examine mode */
if (gameMode == IcsExamining && !newGame) {
gameInfo.result = GameUnfinished;
gameInfo.resultDetails = NULL;
}
}
-
+
/* In pausing && IcsExamining mode, we ignore boards coming
in if they are in a different variation than we are. */
if (pauseExamInvalid) return;
return;
}
}
-
+
+ if (appData.debugMode) {
+ fprintf(debugFP, "load %dx%d board\n", files, ranks);
+ }
/* Parse the board */
- for (k = 0; k < 8; k++)
- for (j = 0; j < 8; j++)
- board[k][j] = CharToPiece(board_chars[(7-k)*9 + j]);
+ for (k = 0; k < ranks; k++) {
+ for (j = 0; j < files; j++)
+ board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
+ if(gameInfo.holdingsWidth > 1) {
+ board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
+ board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
+ }
+ }
CopyBoard(boards[moveNum], board);
+ boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
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] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
+ for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
+ if(board[0][i] == WhiteRook) j = i;
+ initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
+ for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
+ if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
+ initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
+ for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
+ if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
+ initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
+
+ if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
+ for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
+ if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
+ for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
+ if(board[BOARD_HEIGHT-1][k] == bKing)
+ initialRights[5] = boards[moveNum][CASTLING][5] = k;
+ } else { int r;
+ r = boards[moveNum][CASTLING][0] = initialRights[0];
+ if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
+ r = boards[moveNum][CASTLING][1] = initialRights[1];
+ if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
+ r = boards[moveNum][CASTLING][3] = initialRights[3];
+ if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
+ r = boards[moveNum][CASTLING][4] = initialRights[4];
+ if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
+ /* wildcastle kludge: always assume King has rights */
+ r = boards[moveNum][CASTLING][2] = initialRights[2];
+ r = boards[moveNum][CASTLING][5] = initialRights[5];
+ }
+ /* [HGM] e.p. rights. Assume that ICS sends file number here? */
+ boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
+
+
if (ics_getting_history == H_GOT_REQ_HEADER ||
ics_getting_history == H_GOT_UNREQ_HEADER) {
/* This was an initial position from a move list, not
the current position */
return;
}
-
+
/* Update currentMove and known move number limits */
newMove = newGame || moveNum > forwardMostMove;
- /* If we found takebacks during icsEngineAnalyze
- try send to engine */
- if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
- takeback = forwardMostMove - moveNum;
- for (i = 0; i < takeback; i++) {
- if (appData.debugMode) fprintf(debugFP, "take back move\n");
- SendToProgram("undo\n", &first);
- }
- }
if (newGame) {
forwardMostMove = backwardMostMove = currentMove = moveNum;
if (gameMode == IcsExamining && moveNum == 0) {
}
} else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
|| (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
+#if ZIPPY
+ /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
+ /* [HGM] applied this also to an engine that is silently watching */
+ if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
+ (gameMode == IcsObserving || gameMode == IcsExamining) &&
+ gameInfo.variant == currentlyInitializedVariant) {
+ takeback = forwardMostMove - moveNum;
+ for (i = 0; i < takeback; i++) {
+ if (appData.debugMode) fprintf(debugFP, "take back move\n");
+ SendToProgram("undo\n", &first);
+ }
+ }
+#endif
+
forwardMostMove = moveNum;
if (!pausing || currentMove > forwardMostMove)
currentMove = forwardMostMove;
} else {
- /* New part of history that is not contiguous with old part */
+ /* New part of history that is not contiguous with old part */
if (pausing && gameMode == IcsExamining) {
pauseExamInvalid = TRUE;
forwardMostMove = pauseExamForwardMostMove;
return;
}
- forwardMostMove = backwardMostMove = currentMove = moveNum;
if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
+#if ZIPPY
+ if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
+ // [HGM] when we will receive the move list we now request, it will be
+ // fed to the engine from the first move on. So if the engine is not
+ // in the initial position now, bring it there.
+ InitChessProgram(&first, 0);
+ }
+#endif
ics_getting_history = H_REQUESTED;
sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
SendToICS(str);
}
+ forwardMostMove = backwardMostMove = currentMove = moveNum;
}
-
+
/* Update the clocks */
if (strchr(elapsed_time, '.')) {
/* Time is in ms */
timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
}
-
+
#if ZIPPY
if (appData.zippyPlay && newGame &&
gameMode != IcsExamining)
ZippyFirstBoard(moveNum, basetime, increment);
#endif
-
+
/* Put the move on the move list, first converting
to canonical algebraic form. */
if (moveNum > 0) {
+ if (appData.debugMode) {
+ if (appData.debugMode) { int f = forwardMostMove;
+ fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
+ boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
+ boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][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,
+ PosFlags(moveNum - 1),
fromY, fromX, toY, toX, promoChar,
parseList[moveNum-1]);
- switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN)){
+ switch (MateTest(boards[moveNum], PosFlags(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 0
- if (appData.testLegality && appData.debugMode) {
- sprintf(str, "Illegal move \"%s\" from ICS", move_str);
- DisplayError(str, 0);
- }
-#endif
+ if (appData.debugMode) {
+ fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
+ fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
+ }
strcpy(parseList[moveNum - 1], move_str);
strcat(parseList[moveNum - 1], " ");
strcat(parseList[moveNum - 1], elapsed_time);
moveList[moveNum - 1][0] = NULLCHAR;
fromX = fromY = toX = toY = -1;
+ }
}
+ if (appData.debugMode) {
+ fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
+ setbuf(debugFP, NULL);
+ }
#if ZIPPY
/* Send move to chess program (BEFORE animating it). */
- if (appData.zippyPlay && !newGame && newMove &&
+ if (appData.zippyPlay && !newGame && newMove &&
(!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
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);
}
}
#endif
}
- if (moveNum > 0 && !gotPremove) {
+ if (moveNum > 0 && !gotPremove && !appData.noGUI) {
/* If move comes from a remote source, animate it. If it
isn't remote, it will have already been animated. */
if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
SetHighlights(fromX, fromY, toX, toY);
}
}
-
+
/* Start the clocks */
whiteFlag = blackFlag = FALSE;
appData.clockMode = !(basetime == 0 && increment == 0);
DisplayBothClocks();
else
StartClocks();
-
+
/* Display opponents and material strengths */
if (gameInfo.variant != VariantBughouse &&
- gameInfo.variant != VariantCrazyhouse) {
+ gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
if (tinyLayout || smallLayout) {
- sprintf(str, "%s(%d) %s(%d) {%d %d}",
+ if(gameInfo.variant == VariantNormal)
+ sprintf(str, "%s(%d) %s(%d) {%d %d}",
gameInfo.white, white_stren, gameInfo.black, black_stren,
basetime, increment);
+ else
+ sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
+ gameInfo.white, white_stren, gameInfo.black, black_stren,
+ basetime, increment, (int) gameInfo.variant);
} else {
- sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
+ if(gameInfo.variant == VariantNormal)
+ sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
gameInfo.white, white_stren, gameInfo.black, black_stren,
basetime, increment);
+ else
+ sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
+ gameInfo.white, white_stren, gameInfo.black, black_stren,
+ basetime, increment, VariantName(gameInfo.variant));
}
DisplayTitle(str);
+ if (appData.debugMode) {
+ fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
+ }
}
-
+
/* Display the board */
- if (!pausing) {
-
+ if (!pausing && !appData.noGUI) {
+
if (appData.premove)
- if (!gotPremove ||
+ if (!gotPremove ||
((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
ClearPremoveHighlights();
DrawPosition(FALSE, boards[currentMove]);
DisplayMove(moveNum - 1);
- if (appData.ringBellAfterMoves && !ics_user_moved)
- RingBell();
+ if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
+ !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
+ (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
+ if(newMove) RingBell(); else PlayIcsUnfinishedSound();
+ }
}
HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
+#if ZIPPY
+ if(bookHit) { // [HGM] book: simulate book reply
+ static char bookMove[MSG_SIZ]; // a bit generous?
+
+ programStats.nodes = programStats.depth = programStats.time =
+ programStats.score = programStats.got_only_move = 0;
+ sprintf(programStats.movelist, "%s (xbook)", bookHit);
+
+ strcpy(bookMove, "move ");
+ strcat(bookMove, bookHit);
+ HandleMachineMove(bookMove, &first);
+ }
+#endif
}
void
programStats.ok_to_send = 0;
}
+void ics_update_width(new_width)
+ int new_width;
+{
+ ics_printf("set width %d\n", new_width);
+}
+
void
SendMoveToProgram(moveNum, cps)
int moveNum;
ChessProgramState *cps;
{
char buf[MSG_SIZ];
+
if (cps->useUsermove) {
SendToProgram("usermove ", cps);
}
}
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(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
+ ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
}
void
{
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)
ChessMove *moveType;
int *fromX, *fromY, *toX, *toY;
char *promoChar;
-{
+{
+ if (appData.debugMode) {
+ fprintf(debugFP, "move to parse: %s\n", move);
+ }
*moveType = yylexstr(moveNum, move);
+
switch (*moveType) {
+ case WhitePromotionChancellor:
+ case BlackPromotionChancellor:
+ case WhitePromotionArchbishop:
+ case BlackPromotionArchbishop:
case WhitePromotionQueen:
case BlackPromotionQueen:
case WhitePromotionRook:
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] = boards[0][CASTLING][2] = boards[0][CASTLING][5] = i;
+ }
+
+ while(piecesLeft[(int)WhiteKing]) {
+ i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
+ initialRights[2] = initialRights[5] = boards[0][CASTLING][2] = boards[0][CASTLING][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] = boards[0][CASTLING][1] = boards[0][CASTLING][4] = i;
+ }
+ initialRights[0] = initialRights[3] = boards[0][CASTLING][0] = boards[0][CASTLING][3] = i;
+ }
+ }
+ for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
+ board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
+ }
+
+ if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
+}
+
+int SetCharTable( char *table, const char * map )
+/* [HGM] moved here from winboard.c because of its general usefulness */
+/* Basically a safe strcpy that uses the last character as King */
+{
+ int result = FALSE; int NrPieces;
+
+ if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
+ && NrPieces >= 12 && !(NrPieces&1)) {
+ int i; /* [HGM] Accept even length from 12 to 34 */
+
+ for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
+ for( i=0; i<NrPieces/2-1; i++ ) {
+ table[i] = map[i];
+ table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
+ }
+ table[(int) WhiteKing] = map[NrPieces/2-1];
+ table[(int) BlackKing] = map[NrPieces-1];
+
+ result = TRUE;
+ }
+
+ return result;
+}
+
+void Prelude(Board board)
+{ // [HGM] superchess: random selection of exo-pieces
+ int i, j, k; ChessSquare p;
+ static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
+
+ GetPositionNumber(); // use FRC position number
+
+ if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
+ SetCharTable(pieceToChar, appData.pieceToCharTable);
+ for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
+ if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
+ }
+
+ j = seed%4; seed /= 4;
+ p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
+ board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
+ board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
+ j = seed%3 + (seed%3 >= j); seed /= 3;
+ p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
+ board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
+ board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
+ j = seed%3; seed /= 3;
+ p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
+ board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
+ board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
+ j = seed%2 + (seed%2 >= j); seed /= 2;
+ p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
+ board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
+ board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
+ j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
+ j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
+ j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
+ put(board, exoPieces[0], 0, 0, ANY);
+ for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
+}
void
InitPosition(redraw)
int redraw;
{
- currentMove = forwardMostMove = backwardMostMove = 0;
+ ChessSquare (* pieces)[BOARD_FILES];
+ int i, j, pawnRow, overrule,
+ oldx = gameInfo.boardWidth,
+ oldy = gameInfo.boardHeight,
+ oldh = gameInfo.holdingsWidth,
+ oldv = gameInfo.variant;
+
+ if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
+
+ /* [AS] Initialize pv info list [HGM] and game status */
+ {
+ for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
+ pvInfoList[i].depth = 0;
+ boards[i][EP_STATUS] = EP_NONE;
+ for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
+ }
+
+ 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_FILES-2; i++)
+ initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
+ initialPosition[EP_STATUS] = EP_NONE;
+ 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;
+ initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
+ initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
+ initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
+ initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
+ initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
+ initialPosition[CASTLING][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");
+ 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;
/* !!?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_RANKS || BOARD_WIDTH > BOARD_FILES)
+ DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 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;
+
+ initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
+ initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
+ initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
+ initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
+ initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
+ initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
+ }
+
+ if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
+ if(gameInfo.variant == VariantGreat) { // promotion commoners
+ initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
+ initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
+ initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
+ initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
+ }
+ if (appData.debugMode) {
+ fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
+ }
+ if(shuffleOpenings) {
+ SetUpShuffle(initialPosition, appData.defaultFrcPosition);
+ startedFromSetupPosition = TRUE;
+ }
+ if(startedFromPositionFile) {
+ /* [HGM] loadPos: use PositionFile for every new game */
+ CopyBoard(initialPosition, filePosition);
+ for(i=0; i<nrCastlingRights; i++)
+ initialRights[i] = filePosition[CASTLING][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
int moveNum;
{
char message[MSG_SIZ];
-
+
if (cps->useSetboard) {
- char* fen = PositionToFEN(moveNum);
+ char* fen = PositionToFEN(moveNum, cps->fenOverride);
sprintf(message, "setboard %s\n", fen);
SendToProgram(message, cps);
free(fen);
SendToProgram("edit\n", cps);
SendToProgram("#\n", cps);
- for (i = BOARD_SIZE - 1; i >= 0; i--) {
- bp = &boards[moveNum][i][0];
- for (j = 0; j < BOARD_SIZE; j++, bp++) {
+ for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
+ bp = &boards[moveNum][i][BOARD_LEFT];
+ for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
if ((int) *bp < (int) BlackPawn) {
- sprintf(message, "%c%c%c\n", PieceToChar(*bp),
- 'a' + j, '1' + i);
+ sprintf(message, "%c%c%c\n", PieceToChar(*bp),
+ AAA + j, ONE + i);
+ if(message[0] == '+' || message[0] == '~') {
+ sprintf(message, "%c%c%c+\n",
+ PieceToChar((ChessSquare)(DEMOTED *bp)),
+ AAA + j, ONE + i);
+ }
+ if(cps->alphaRank) { /* [HGM] shogi: translate coords */
+ message[1] = BOARD_RGHT - 1 - j + '1';
+ message[2] = BOARD_HEIGHT - 1 - i + 'a';
+ }
SendToProgram(message, cps);
}
}
}
-
+
SendToProgram("c\n", cps);
- for (i = BOARD_SIZE - 1; i >= 0; i--) {
- bp = &boards[moveNum][i][0];
- for (j = 0; j < BOARD_SIZE; j++, bp++) {
+ for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
+ bp = &boards[moveNum][i][BOARD_LEFT];
+ for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
if (((int) *bp != (int) EmptySquare)
&& ((int) *bp >= (int) BlackPawn)) {
sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
- 'a' + j, '1' + i);
+ AAA + j, ONE + i);
+ if(message[0] == '+' || message[0] == '~') {
+ sprintf(message, "%c%c%c+\n",
+ PieceToChar((ChessSquare)(DEMOTED *bp)),
+ AAA + j, ONE + i);
+ }
+ if(cps->alphaRank) { /* [HGM] shogi: translate coords */
+ message[1] = BOARD_RGHT - 1 - j + '1';
+ message[2] = BOARD_HEIGHT - 1 - i + 'a';
+ }
SendToProgram(message, cps);
}
}
}
-
+
SendToProgram(".\n", cps);
}
+ setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
}
int
-IsPromotion(fromX, fromY, toX, toY)
- int fromX, fromY, toX, toY;
+HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
{
- return gameMode != EditPosition &&
- fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0 &&
- ((boards[currentMove][fromY][fromX] == WhitePawn && toY == 7) ||
- (boards[currentMove][fromY][fromX] == BlackPawn && toY == 0));
+ /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
+ /* [HGM] add Shogi promotions */
+ int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
+ ChessSquare piece;
+ ChessMove moveType;
+ Boolean premove;
+
+ if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
+ if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
+
+ if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
+ !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
+ return FALSE;
+
+ piece = boards[currentMove][fromY][fromX];
+ if(gameInfo.variant == VariantShogi) {
+ promotionZoneSize = 3;
+ highestPromotingPiece = (int)WhiteFerz;
+ }
+
+ // next weed out all moves that do not touch the promotion zone at all
+ if((int)piece >= BlackPawn) {
+ if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
+ return FALSE;
+ highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
+ } else {
+ if( toY < BOARD_HEIGHT - promotionZoneSize &&
+ fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
+ }
+
+ if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
+
+ // weed out mandatory Shogi promotions
+ if(gameInfo.variant == VariantShogi) {
+ if(piece >= BlackPawn) {
+ if(toY == 0 && piece == BlackPawn ||
+ toY == 0 && piece == BlackQueen ||
+ toY <= 1 && piece == BlackKnight) {
+ *promoChoice = '+';
+ return FALSE;
+ }
+ } else {
+ if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
+ toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
+ toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
+ *promoChoice = '+';
+ return FALSE;
+ }
+ }
+ }
+
+ // weed out obviously illegal Pawn moves
+ if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
+ if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
+ if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
+ if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
+ if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
+ // note we are not allowed to test for valid (non-)capture, due to premove
+ }
+
+ // we either have a choice what to promote to, or (in Shogi) whether to promote
+ if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
+ *promoChoice = PieceToChar(BlackFerz); // no choice
+ return FALSE;
+ }
+ if(appData.alwaysPromoteToQueen) { // predetermined
+ if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
+ *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
+ else *promoChoice = PieceToChar(BlackQueen);
+ return FALSE;
+ }
+
+ // suppress promotion popup on illegal moves that are not premoves
+ premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
+ gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
+ if(appData.testLegality && !premove) {
+ moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
+ fromY, fromX, toY, toX, NULLCHAR);
+ if(moveType != WhitePromotionQueen && moveType != BlackPromotionQueen &&
+ moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
+ return FALSE;
+ }
+
+ return TRUE;
}
+int
+InPalace(row, column)
+ int row, column;
+{ /* [HGM] for Xiangqi */
+ if( (row < 3 || row > BOARD_HEIGHT-4) &&
+ column < (BOARD_WIDTH + 4)/2 &&
+ column > (BOARD_WIDTH - 5)/2 ) return TRUE;
+ return FALSE;
+}
int
PieceForSquare (x, y)
int x;
int y;
{
- if (x < 0 || x >= BOARD_SIZE || y < 0 || y >= BOARD_SIZE)
+ if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
return -1;
else
return boards[currentMove][y][x];
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:
if (!white_piece && WhiteOnMove(currentMove)) {
DisplayMoveError(_("It is White's turn"));
return FALSE;
- }
+ }
if (white_piece && !WhiteOnMove(currentMove)) {
DisplayMoveError(_("It is Black's turn"));
return FALSE;
- }
+ }
if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
/* Editing correspondence game history */
/* Could disallow this or prompt for confirmation */
cmailOldMove = -1;
}
- if (currentMove < forwardMostMove) {
- /* Discarding moves */
- /* Could prompt for confirmation here,
- but I don't think that's such a good idea */
- forwardMostMove = currentMove;
- }
break;
case BeginningOfGame:
}
}
break;
-
+
case Training:
if (!white_piece && WhiteOnMove(currentMove)) {
DisplayMoveError(_("It is White's turn"));
return FALSE;
- }
+ }
if (white_piece && !WhiteOnMove(currentMove)) {
DisplayMoveError(_("It is Black's turn"));
return FALSE;
- }
+ }
break;
default:
break;
}
if (currentMove != forwardMostMove && gameMode != AnalyzeMode
+ && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
&& gameMode != AnalyzeFile && gameMode != Training) {
DisplayMoveError(_("Displayed position is not current"));
return FALSE;
char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
ChessMove lastLoadGameStart = (ChessMove) 0;
-
-void
-UserMoveEvent(fromX, fromY, toX, toY, promoChar)
+ChessMove
+UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
int fromX, fromY, toX, toY;
int promoChar;
+ Boolean captureOwn;
{
ChessMove moveType;
-
- if (fromX < 0 || fromY < 0) return;
- if ((fromX == toX) && (fromY == toY)) {
- return;
- }
+ ChessSquare pdown, pup;
/* Check if the user is playing in turn. This is complicated because we
let the user "pick up" a piece before it is his turn. So the piece he
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;
premoveFromY = fromY;
premovePromoChar = promoChar;
gotPremove = 1;
- if (appData.debugMode)
+ if (appData.debugMode)
fprintf(debugFP, "Got premove: fromX %d,"
"fromY %d, toX %d, toY %d\n",
fromX, fromY, toX, toY);
}
- return;
+ return ImpossibleMove;
}
break;
premoveFromY = fromY;
premovePromoChar = promoChar;
gotPremove = 1;
- if (appData.debugMode)
+ if (appData.debugMode)
fprintf(debugFP, "Got premove: fromX %d,"
"fromY %d, toX %d, toY %d\n",
fromX, fromY, toX, toY);
}
- return;
+ return ImpossibleMove;
}
break;
break;
case EditPosition:
+ /* EditPosition, empty square, or different color piece;
+ click-click move is possible */
if (toX == -2 || toY == -2) {
boards[0][fromY][fromX] = EmptySquare;
- DrawPosition(FALSE, boards[currentMove]);
+ return AmbiguousMove;
} else if (toX >= 0 && toY >= 0) {
boards[0][toY][toX] = boards[0][fromY][fromX];
boards[0][fromY][fromX] = EmptySquare;
- DrawPosition(FALSE, boards[currentMove]);
+ return AmbiguousMove;
}
- return;
+ return ImpossibleMove;
}
- if (toX < 0 || toY < 0) return;
- userOfferedDraw = FALSE;
+ if(toX < 0 || toY < 0) return ImpossibleMove;
+ pdown = boards[currentMove][fromY][fromX];
+ pup = boards[currentMove][toY][toX];
+
+ /* [HGM] If move started in holdings, it means a drop */
+ if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
+ if( pup != EmptySquare ) return ImpossibleMove;
+ if(appData.testLegality) {
+ /* it would be more logical if LegalityTest() also figured out
+ * which drops are legal. For now we forbid pawns on back rank.
+ * Shogi is on its own here...
+ */
+ if( (pdown == WhitePawn || pdown == BlackPawn) &&
+ (toY == 0 || toY == BOARD_HEIGHT -1 ) )
+ return(ImpossibleMove); /* no pawn drops on 1st/8th */
+ }
+ return WhiteDrop; /* Not needed to specify white or black yet */
+ }
+ userOfferedDraw = FALSE;
+
+ /* [HGM] always test for legality, to get promotion info */
+ moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
+ 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);
}
+ 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((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);
+
+ /* [HGM] convert drag-and-drop piece drops to standard form */
+ if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
+ moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
+ if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
+ moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
+ // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
+ if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
+ fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
+ while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
+ fromY = DROP_RANK;
+ }
+
+ /* [HGM] <popupFix> The following if has been moved here from
+ UserMoveEvent(). Because it seemed to belong here (why not allow
+ piece drops in training games?), and because it can only be
+ performed after it is known to what we promote. */
if (gameMode == Training) {
/* compare the move played on the board to the next move in the
- * game. If they match, display the move and the opponent's response.
+ * game. If they match, display the move and the opponent's response.
* If they don't match, display an error message.
*/
int saveAnimate;
} 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) {
- forwardMostMove = currentMove;
+ if ((gameMode == AnalyzeMode || gameMode == EditGame)
+ && currentMove < forwardMostMove) {
+ PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
}
/* If we need the chess program but it's dead, restart it */
} 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();
}
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)) {
+ switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
case MT_NONE:
case MT_CHECK:
break;
case MT_CHECKMATE:
+ case MT_STAINMATE:
if (WhiteOnMove(currentMove)) {
GameEnds(BlackWins, "Black mates", GE_PLAYER);
} else {
break;
}
break;
-
+
case MachinePlaysBlack:
case MachinePlaysWhite:
/* disable certain menu options while machine is thinking */
default:
break;
}
+
+ if(bookHit) { // [HGM] book: simulate book reply
+ static char bookMove[MSG_SIZ]; // a bit generous?
+
+ programStats.nodes = programStats.depth = programStats.time =
+ programStats.score = programStats.got_only_move = 0;
+ sprintf(programStats.movelist, "%s (xbook)", bookHit);
+
+ strcpy(bookMove, "move ");
+ strcat(bookMove, bookHit);
+ HandleMachineMove(bookMove, &first);
+ }
+ return 1;
+}
+
+void
+UserMoveEvent(fromX, fromY, toX, toY, promoChar)
+ int fromX, fromY, toX, toY;
+ int promoChar;
+{
+ /* [HGM] This routine was added to allow calling of its two logical
+ parts from other modules in the old way. Before, UserMoveEvent()
+ automatically called FinishMove() if the move was OK, and returned
+ otherwise. I separated the two, in order to make it possible to
+ slip a promotion popup in between. But that it always needs two
+ calls, to the first part, (now called UserMoveTest() ), and to
+ FinishMove if the first part succeeded. Calls that do not need
+ to do anything in between, can call this routine the old way.
+ */
+ ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
+if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
+ if(moveType == AmbiguousMove)
+ DrawPosition(FALSE, boards[currentMove]);
+ else if(moveType != ImpossibleMove && moveType != Comment)
+ FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
+}
+
+void LeftClick(ClickType clickType, int xPix, int yPix)
+{
+ int x, y;
+ Boolean saveAnimate;
+ static int second = 0, promotionChoice = 0;
+ char promoChoice = NULLCHAR;
+
+ if (clickType == Press) ErrorPopDown();
+
+ x = EventToSquare(xPix, BOARD_WIDTH);
+ y = EventToSquare(yPix, BOARD_HEIGHT);
+ if (!flipView && y >= 0) {
+ y = BOARD_HEIGHT - 1 - y;
+ }
+ if (flipView && x >= 0) {
+ x = BOARD_WIDTH - 1 - x;
+ }
+
+ if(promotionChoice) { // we are waiting for a click to indicate promotion piece
+ if(clickType == Release) return; // ignore upclick of click-click destination
+ promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
+ if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
+ if(gameInfo.holdingsWidth &&
+ (WhiteOnMove(currentMove)
+ ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
+ : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
+ // click in right holdings, for determining promotion piece
+ ChessSquare p = boards[currentMove][y][x];
+ if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
+ if(p != EmptySquare) {
+ FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
+ fromX = fromY = -1;
+ return;
+ }
+ }
+ DrawPosition(FALSE, boards[currentMove]);
+ return;
+ }
+
+ /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
+ if(clickType == Press
+ && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
+ || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
+ || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
+ return;
+
+ if (fromX == -1) {
+ if (clickType == Press) {
+ /* First square */
+ if (OKToStartUserMove(x, y)) {
+ fromX = x;
+ fromY = y;
+ second = 0;
+ DragPieceBegin(xPix, yPix);
+ if (appData.highlightDragging) {
+ SetHighlights(x, y, -1, -1);
+ }
+ }
+ }
+ return;
+ }
+
+ /* fromX != -1 */
+ if (clickType == Press && gameMode != EditPosition) {
+ ChessSquare fromP;
+ ChessSquare toP;
+ int frc;
+
+ // ignore off-board to clicks
+ if(y < 0 || x < 0) return;
+
+ /* Check if clicking again on the same color piece */
+ fromP = boards[currentMove][fromY][fromX];
+ toP = boards[currentMove][y][x];
+ frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
+ if ((WhitePawn <= fromP && fromP <= WhiteKing &&
+ WhitePawn <= toP && toP <= WhiteKing &&
+ !(fromP == WhiteKing && toP == WhiteRook && frc) &&
+ !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
+ (BlackPawn <= fromP && fromP <= BlackKing &&
+ BlackPawn <= toP && toP <= BlackKing &&
+ !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
+ !(fromP == BlackKing && toP == BlackRook && frc))) {
+ /* Clicked again on same color piece -- changed his mind */
+ second = (x == fromX && y == fromY);
+ if (appData.highlightDragging) {
+ SetHighlights(x, y, -1, -1);
+ } else {
+ ClearHighlights();
+ }
+ if (OKToStartUserMove(x, y)) {
+ fromX = x;
+ fromY = y;
+ DragPieceBegin(xPix, yPix);
+ }
+ return;
+ }
+ // ignore clicks on holdings
+ if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
+ }
+
+ if (clickType == Release && x == fromX && y == fromY) {
+ DragPieceEnd(xPix, yPix);
+ if (appData.animateDragging) {
+ /* Undo animation damage if any */
+ DrawPosition(FALSE, NULL);
+ }
+ if (second) {
+ /* Second up/down in same square; just abort move */
+ second = 0;
+ fromX = fromY = -1;
+ ClearHighlights();
+ gotPremove = 0;
+ ClearPremoveHighlights();
+ } else {
+ /* First upclick in same square; start click-click mode */
+ SetHighlights(x, y, -1, -1);
+ }
+ return;
+ }
+
+ /* we now have a different from- and (possibly off-board) to-square */
+ /* Completed move */
+ toX = x;
+ toY = y;
+ saveAnimate = appData.animate;
+ if (clickType == Press) {
+ /* Finish clickclick move */
+ if (appData.animate || appData.highlightLastMove) {
+ SetHighlights(fromX, fromY, toX, toY);
+ } else {
+ ClearHighlights();
+ }
+ } else {
+ /* Finish drag move */
+ if (appData.highlightLastMove) {
+ SetHighlights(fromX, fromY, toX, toY);
+ } else {
+ ClearHighlights();
+ }
+ DragPieceEnd(xPix, yPix);
+ /* Don't animate move and drag both */
+ appData.animate = FALSE;
+ }
+
+ // moves into holding are invalid for now (later perhaps allow in EditPosition)
+ if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
+ ClearHighlights();
+ fromX = fromY = -1;
+ DrawPosition(TRUE, NULL);
+ return;
+ }
+
+ // off-board moves should not be highlighted
+ if(x < 0 || x < 0) ClearHighlights();
+
+ if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
+ SetHighlights(fromX, fromY, toX, toY);
+ if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
+ // [HGM] super: promotion to captured piece selected from holdings
+ ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
+ promotionChoice = TRUE;
+ // kludge follows to temporarily execute move on display, without promoting yet
+ boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
+ boards[currentMove][toY][toX] = p;
+ DrawPosition(FALSE, boards[currentMove]);
+ boards[currentMove][fromY][fromX] = p; // take back, but display stays
+ boards[currentMove][toY][toX] = q;
+ DisplayMessage("Click in holdings to choose piece", "");
+ return;
+ }
+ PromotionPopUp();
+ } else {
+ UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
+ if (!appData.highlightLastMove || gotPremove) ClearHighlights();
+ if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
+ fromX = fromY = -1;
+ }
+ appData.animate = saveAnimate;
+ if (appData.animate || appData.animateDragging) {
+ /* Undo animation damage if needed */
+ DrawPosition(FALSE, NULL);
+ }
+}
+
+void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
+{
+// char * hint = lastHint;
+ FrontEndProgramStats stats;
+
+ stats.which = cps == &first ? 0 : 1;
+ stats.depth = cpstats->depth;
+ stats.nodes = cpstats->nodes;
+ stats.score = cpstats->score;
+ stats.time = cpstats->time;
+ stats.pv = cpstats->movelist;
+ stats.hint = lastHint;
+ stats.an_move_index = 0;
+ stats.an_move_count = 0;
+
+ if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
+ stats.hint = cpstats->move_name;
+ stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
+ stats.an_move_count = cpstats->nr_moves;
+ }
+
+ SetProgramStats( &stats );
+}
+
+char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
+{ // [HGM] book: this routine intercepts moves to simulate book replies
+ char *bookHit = NULL;
+
+ //first determine if the incoming move brings opponent into his book
+ if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
+ bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
+ if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
+ if(bookHit != NULL && !cps->bookSuspend) {
+ // make sure opponent is not going to reply after receiving move to book position
+ SendToProgram("force\n", cps);
+ cps->bookSuspend = TRUE; // flag indicating it has to be restarted
+ }
+ if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
+ // now arrange restart after book miss
+ if(bookHit) {
+ // after a book hit we never send 'go', and the code after the call to this routine
+ // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
+ char buf[MSG_SIZ];
+ if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
+ sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
+ SendToProgram(buf, cps);
+ if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
+ } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
+ SendToProgram("go\n", cps);
+ cps->bookSuspend = FALSE; // after a 'go' we are never suspended
+ } else { // 'go' might be sent based on 'firstMove' after this routine returns
+ if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
+ SendToProgram("go\n", cps);
+ cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
+ }
+ return bookHit; // notify caller of hit, so it can take action to send move to opponent
+}
+
+char *savedMessage;
+ChessProgramState *savedState;
+void DeferredBookMove(void)
+{
+ if(savedState->lastPing != savedState->lastPong)
+ ScheduleDelayedEvent(DeferredBookMove, 10);
+ else
+ HandleMachineMove(savedMessage, savedState);
}
void
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,
+ boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
+ boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][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),
+ fromY, fromX, toY, toX, promoChar);
+ if (appData.debugMode) {
+ int i;
+ for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
+ boards[forwardMostMove][CASTLING][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, %u nodes, %.0f knps) PV=%s\n",
+ programStats.score / 100.,
+ programStats.depth,
+ programStats.time / 100.,
+ (unsigned int)programStats.nodes,
+ (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
+ programStats.movelist);
+ SendToICS(buf);
+if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
+ }
}
#endif
/* currentMoveString is set as a side-effect of ParseOneMove */
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; 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
+ boards[forwardMostMove][EP_STATUS] = 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)
+ boards[forwardMostMove][EP_STATUS] = 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 */
+ boards[forwardMostMove][EP_STATUS] = 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)) ) {
+ 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)) == MT_CHECK)
+ checkCnt++;
+ if(checkCnt >= 2) {
+ reason = "Xboard adjudication: 3rd check";
+ boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
+ break;
+ }
+ }
+ }
+ case MT_NONE:
+ default:
+ break;
+ case MT_STALEMATE:
+ case MT_STAINMATE:
+ reason = "Xboard adjudication: Stalemate";
+ if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
+ boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
+ if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
+ boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
+ else if(gameInfo.variant == VariantSuicide) // in suicide it depends
+ boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
+ ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
+ EP_CHECKMATE : EP_WINS);
+ else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
+ boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
+ }
+ break;
+ case MT_CHECKMATE:
+ reason = "Xboard adjudication: Checkmate";
+ boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
+ break;
+ }
+
+ switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
+ 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 */
+ boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
+
+ if(appData.materialDraws) {
+ /* but only adjudicate them if adjudication enabled */
+ SendToProgram("force\n", cps->other); // suppress reply
+ SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
+ ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
+ GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
+ return;
+ }
+ }
+
+ /* Then some trivial draws (only adjudicate, cannot be claimed) */
+ if(NrPieces == 4 &&
+ ( NrWR == 1 && NrBR == 1 /* KRKR */
+ || NrWQ==1 && NrBQ==1 /* KQKQ */
+ || NrWN==2 || NrBN==2 /* KNNK */
+ || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
+ ) ) {
+ if(--moveCount < 0 && appData.trivialDraws)
+ { /* if the first 3 moves do not show a tactical win, declare draw */
+ SendToProgram("force\n", cps->other); // suppress reply
+ SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
+ ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
+ GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
+ return;
+ }
+ } else moveCount = 6;
+ }
+ }
+
+ if (appData.debugMode) { int i;
+ fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
+ forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
+ appData.drawRepeats);
+ for( i=forwardMostMove; i>=backwardMostMove; i-- )
+ fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
+
+ }
+
+ /* Check for rep-draws */
+ count = 0;
+ for(k = forwardMostMove-2;
+ k>=backwardMostMove && k>=forwardMostMove-100 &&
+ (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
+ (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
+ k-=2)
+ { int rights=0;
+ if(CompareBoards(boards[k], boards[forwardMostMove])) {
+ /* compare castling rights */
+ if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
+ (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
+ rights++; /* King lost rights, while rook still had them */
+ if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
+ if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
+ boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
+ rights++; /* but at least one rook lost them */
+ }
+ if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
+ (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
+ rights++;
+ if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
+ if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
+ boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
+ rights++;
+ }
+ if( rights == 0 && ++count > appData.drawRepeats-2
+ && appData.drawRepeats > 1) {
+ /* adjudicate after user-specified nr of repeats */
+ SendToProgram("force\n", cps->other); // suppress reply
+ SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
+ ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
+ if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
+ // [HGM] xiangqi: check for forbidden perpetuals
+ int m, ourPerpetual = 1, hisPerpetual = 1;
+ for(m=forwardMostMove; m>k; m-=2) {
+ if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
+ ourPerpetual = 0; // the current mover did not always check
+ if(MateTest(boards[m-1], PosFlags(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 */
+ boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
+ }
+ }
+
+ /* Now we test for 50-move draws. Determine ply count */
+ count = forwardMostMove;
+ /* look for last irreversble move */
+ while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
+ count--;
+ /* if we hit starting position, add initial plies */
+ if( count == backwardMostMove )
+ count -= initialRulePlies;
+ count = forwardMostMove - count;
+ if( count >= 100)
+ boards[forwardMostMove][EP_STATUS] = 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((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
+ p = "Draw claim: 50-move rule";
+ if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
+ p = "Draw claim: 3-fold repetition";
+ if((signed char)boards[forwardMostMove][EP_STATUS] == 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->useSigint = FALSE;
cps->useSigterm = FALSE;
}
+ if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
+ ParseFeatures(message+8, cps);
+ return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
+ }
+
+ /* [HGM] Allow engine to set up a position. Don't ask me why one would
+ * want this, I was asked to put it in, and obliged.
+ */
+ if (!strncmp(message, "setboard ", 9)) {
+ Board initial_position;
+
+ GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
+
+ if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
+ DisplayError(_("Bad FEN received from engine"), 0);
+ return ;
+ } else {
+ Reset(TRUE, FALSE);
+ CopyBoard(boards[0], initial_position);
+ initialRulePlies = FENrulePlies;
+ 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 {
AskQuestion(realname, buf2, buf1, cps->pr);
return;
}
- /* Commands from the engine directly to ICS. We don't allow these to be
- * sent until we are logged on. Crafty kibitzes have been known to
+ /* Commands from the engine directly to ICS. We don't allow these to be
+ * sent until we are logged on. Crafty kibitzes have been known to
* interfere with the login process.
*/
if (loggedOn) {
return;
}
}
- if (strncmp(message, "feature ", 8) == 0) {
- ParseFeatures(message+8, cps);
- }
if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
- return;
+ return;
}
/*
* If the move is illegal, cancel it and redraw the board.
*/
if (strncmp(message + 1, "llegal move", 11) == 0 ||
strncmp(message, "Error", 5) == 0) {
- if (StrStr(message, "name") ||
+ if (StrStr(message, "name") ||
StrStr(message, "rating") || StrStr(message, "?") ||
StrStr(message, "result") || StrStr(message, "board") ||
StrStr(message, "bk") || StrStr(message, "computer") ||
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;
-#if 0
- /* Following removed: it caused a bug where a real illegal move
- message in analyze mored would be ignored. */
- if (cps == &first && programStats.ok_to_send == 0) {
- /* Bogus message from Crafty responding to "." This filtering
- can miss some of the bad messages, but fortunately the bug
- is fixed in current Crafty versions, so it doesn't matter. */
- return;
- }
-#endif
if (pausing) PauseEvent();
+ if(appData.forceIllegal) {
+ // [HGM] illegal: machine refused move; force position after move into it
+ SendToProgram("force\n", cps);
+ if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
+ // we have a real problem now, as SendBoard will use the a2a3 kludge
+ // when black is to move, while there might be nothing on a2 or black
+ // might already have the move. So send the board as if white has the move.
+ // But first we must change the stm of the engine, as it refused the last move
+ SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
+ if(WhiteOnMove(forwardMostMove)) {
+ SendToProgram("a7a6\n", cps); // for the engine black still had the move
+ SendBoard(cps, forwardMostMove); // kludgeless board
+ } else {
+ SendToProgram("a2a3\n", cps); // for the engine white still had the move
+ CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
+ SendBoard(cps, forwardMostMove+1); // kludgeless board
+ }
+ } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
+ if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
+ gameMode == TwoMachinesPlay)
+ SendToProgram("go\n", cps);
+ return;
+ } else
if (gameMode == PlayFromGameFile) {
/* Stop reading this game file */
gameMode = EditGame;
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")) {
Don't use it. */
cps->sendTime = 0;
}
-
+
/*
* If chess program startup fails, exit with an error message.
* Attempts to recover here are futile.
|| (StrStr(message, "Permission denied") != NULL)) {
cps->maybeThinking = FALSE;
- sprintf(buf1, _("Failed to start %s chess program %s on %s: %s\n"),
+ snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
cps->which, cps->program, cps->host, message);
RemoveInputSource(cps->isr);
DisplayFatalError(buf1, 0, 1);
return;
}
-
- /*
+
+ /*
* Look for hint output
*/
if (sscanf(message, "Hint: %s", buf1) == 1) {
if (ParseOneMove(buf1, forwardMostMove, &moveType,
&fromX, &fromY, &toX, &toY, &promoChar)) {
(void) CoordsToAlgebraic(boards[forwardMostMove],
- PosFlags(forwardMostMove), EP_UNKNOWN,
+ PosFlags(forwardMostMove),
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 {
- if (cps->other->sendDrawOffers) {
- SendToProgram("draw\n", cps->other);
- }
+ /* [HGM] in two-machine mode we delay relaying draw offer */
+ /* until after we also have move, to see if it is really claim */
}
} else if (gameMode == MachinePlaysWhite ||
gameMode == MachinePlaysBlack) {
}
}
-
+
/*
* Look for thinking output
*/
- if (appData.showThinking) {
+ if ( appData.showThinking // [HGM] thinking: test all options that cause this output
+ || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
+ ) {
int plylev, mvleft, mvtot, curscore, time;
char mvname[MOVE_LEN];
- u64 nodes;
+ u64 nodes; // [DM]
char plyext;
int ignore = FALSE;
int prefixHint = FALSE;
break;
case AnalyzeMode:
case AnalyzeFile:
- break;
- /* icsEngineAnalyze */
- case IcsObserving:
- if (!appData.icsEngineAnalyze) ignore = TRUE;
+ break;
+ case IcsObserving: /* [DM] icsEngineAnalyze */
+ if (!appData.icsEngineAnalyze) ignore = TRUE;
break;
case TwoMachinesPlay:
- if ((cps->twoMachinesColor[0] == 'w') !=
- WhiteOnMove(forwardMostMove)) {
+ if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
ignore = TRUE;
}
break;
if (!ignore) {
buf1[0] = NULLCHAR;
- if (sscanf(message, "%d%c %d %d" u64Display "%[^\n]\n",
+ if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
&plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
if (plyext != ' ' && plyext != '\t') {
time *= 100;
}
+
+ /* [AS] Negate score if machine is playing black and reporting absolute scores */
+ if( cps->scoreIsAbsolute &&
+ ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
+ {
+ curscore = -curscore;
+ }
+
+
programStats.depth = plylev;
programStats.nodes = nodes;
programStats.time = time;
programStats.score = curscore;
programStats.got_only_move = 0;
+ if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
+ int ticklen;
+
+ if(cps->nps == 0) ticklen = 10*time; // use engine reported time
+ else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
+ if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
+ gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
+ whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
+ if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
+ gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
+ blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
+ }
+
/* Buffer overflow protection */
if (buf1[0] != NULLCHAR) {
if (strlen(buf1) >= sizeof(programStats.movelist)
&& appData.debugMode) {
fprintf(debugFP,
- "PV is too long; using the first %d bytes.\n",
- sizeof(programStats.movelist) - 1);
+ "PV is too long; using the first %u bytes.\n",
+ (unsigned) 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");
}
programStats.line_is_book = 0;
}
- sprintf(thinkOutput, "[%d]%c%+.2f %s%s%s",
- plylev,
+ SendProgramStatsToFrontend( cps, &programStats );
+
+ /*
+ [AS] Protect the thinkOutput buffer from overflow... this
+ is only useful if buf1 hasn't overflowed first!
+ */
+ sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
+ plylev,
(gameMode == TwoMachinesPlay ?
ToUpper(cps->twoMachinesColor[0]) : ' '),
((double) curscore) / 100.0,
prefixHint ? lastHint : "",
- prefixHint ? " " : "", programStats.movelist);
+ prefixHint ? " " : "" );
+
+ if( buf1[0] != NULLCHAR ) {
+ unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
- if (currentMove == forwardMostMove || gameMode == AnalyzeMode
- || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
+ 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();
}
return;
isn't searching, so stats won't change) */
programStats.line_is_book = 1;
- if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
- gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
+ SendProgramStatsToFrontend( cps, &programStats );
+
+ if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
+ gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
DisplayMove(currentMove - 1);
- DisplayAnalysis();
}
return;
- } else if (sscanf(message,"stat01: %d" u64Display "%d %d %d %s",
+ } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
&time, &nodes, &plylev, &mvleft,
&mvtot, mvname) >= 5) {
/* The stat01: line is from Crafty (9.29+) in response
programStats.nr_moves = mvtot;
strcpy(programStats.move_name, mvname);
programStats.ok_to_send = 1;
- DisplayAnalysis();
+ programStats.movelist[0] = '\0';
+
+ SendProgramStatsToFrontend( cps, &programStats );
+
return;
} else if (strncmp(message,"++",2) == 0) {
} else if (thinkOutput[0] != NULLCHAR &&
strncmp(message, " ", 4) == 0) {
+ unsigned message_len;
+
p = message;
while (*p && *p == ' ') p++;
- strcat(thinkOutput, " ");
- strcat(thinkOutput, p);
- strcat(programStats.movelist, " ");
- strcat(programStats.movelist, p);
+
+ message_len = strlen( p );
+
+ /* [AS] Avoid buffer overflow */
+ if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
+ strcat(thinkOutput, " ");
+ strcat(thinkOutput, p);
+ }
+
+ if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
+ strcat(programStats.movelist, " ");
+ strcat(programStats.movelist, p);
+ }
+
if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
- gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
+ gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
DisplayMove(currentMove - 1);
- DisplayAnalysis();
}
return;
}
}
+ else {
+ buf1[0] = NULLCHAR;
+
+ if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
+ &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
+ {
+ ChessProgramStats cpstats;
+
+ if (plyext != ' ' && plyext != '\t') {
+ time *= 100;
+ }
+
+ /* [AS] Negate score if machine is playing black and reporting absolute scores */
+ if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
+ curscore = -curscore;
+ }
+
+ cpstats.depth = plylev;
+ cpstats.nodes = nodes;
+ cpstats.time = time;
+ cpstats.score = curscore;
+ cpstats.got_only_move = 0;
+ cpstats.movelist[0] = '\0';
+
+ if (buf1[0] != NULLCHAR) {
+ safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
+ }
+
+ cpstats.ok_to_send = 0;
+ cpstats.line_is_book = 0;
+ cpstats.nr_moves = 0;
+ cpstats.moves_left = 0;
+
+ SendProgramStatsToFrontend( cps, &cpstats );
+ }
+ }
}
}
/* Parse a game score from the character string "game", and
record it as the history of the current game. The game
- score is NOT assumed to start from the standard position.
+ score is NOT assumed to start from the standard position.
The display is not updated in any way.
*/
void
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 */
return;
}
(void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
- EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
+ fromY, fromX, toY, toX, promoChar,
parseList[boardIndex]);
CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
/* currentMoveString is set as a side-effect of yylex */
strcat(moveList[boardIndex], "\n");
boardIndex++;
ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
- switch (MateTest(boards[boardIndex],
- PosFlags(boardIndex), EP_UNKNOWN)) {
+ switch (MateTest(boards[boardIndex], PosFlags(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;
}
int promoChar;
Board board;
{
- ChessSquare captured = board[toY][toX];
- if (fromY == DROP_RANK) {
+ 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 = (signed char)board[EP_STATUS];
+ board[EP_STATUS] = EP_NONE;
+
+ if( board[toY][toX] != EmptySquare )
+ board[EP_STATUS] = EP_CAPTURE;
+
+ if( board[fromY][fromX] == WhitePawn ) {
+ if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
+ board[EP_STATUS] = EP_PAWN_MOVE;
+ if( toY-fromY==2) {
+ if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
+ gameInfo.variant != VariantBerolina || toX < fromX)
+ board[EP_STATUS] = toX | berolina;
+ if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
+ gameInfo.variant != VariantBerolina || toX > fromX)
+ board[EP_STATUS] = toX;
+ }
+ } else
+ if( board[fromY][fromX] == BlackPawn ) {
+ if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
+ board[EP_STATUS] = EP_PAWN_MOVE;
+ if( toY-fromY== -2) {
+ if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
+ gameInfo.variant != VariantBerolina || toX < fromX)
+ board[EP_STATUS] = toX | berolina;
+ if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
+ gameInfo.variant != VariantBerolina || toX > fromX)
+ board[EP_STATUS] = toX;
+ }
+ }
+
+ for(i=0; i<nrCastlingRights; i++) {
+ if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
+ board[CASTLING][i] == toX && castlingRank[i] == toY
+ ) board[CASTLING][i] = NoRights; // 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;
+ p = PieceToNumber((ChessSquare)p);
+ if(p >= gameInfo.holdingsSize) p = 0;
+ if(--board[p][BOARD_WIDTH-2] <= 0)
+ board[p][BOARD_WIDTH-1] = EmptySquare;
+ if((int)board[p][BOARD_WIDTH-2] < 0)
+ board[p][BOARD_WIDTH-2] = 0;
+ } else { /* black drop */
+ p -= (int)BlackPawn;
+ p = PieceToNumber((ChessSquare)p);
+ if(p >= gameInfo.holdingsSize) p = 0;
+ if(--board[BOARD_HEIGHT-1-p][1] <= 0)
+ board[BOARD_HEIGHT-1-p][0] = EmptySquare;
+ if((int)board[BOARD_HEIGHT-1-p][1] < 0)
+ board[BOARD_HEIGHT-1-p][1] = 0;
+ }
}
- 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 > framePtr) { // [HGM] vari: do not run into saved variations
DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
0, 1);
return;
}
- SwitchClocks();
+ if (commentList[forwardMostMove+1] != NULL) {
+ free(commentList[forwardMostMove+1]);
+ commentList[forwardMostMove+1] = NULL;
+ }
+ CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
+ ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
+ forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
+ SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
timeRemaining[1][forwardMostMove] = blackTimeRemaining;
- if (commentList[forwardMostMove] != NULL) {
- free(commentList[forwardMostMove]);
- commentList[forwardMostMove] = NULL;
- }
- CopyBoard(boards[forwardMostMove], boards[forwardMostMove - 1]);
- ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove]);
gameInfo.result = GameUnfinished;
if (gameInfo.resultDetails != NULL) {
free(gameInfo.resultDetails);
CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
moveList[forwardMostMove - 1]);
(void) CoordsToAlgebraic(boards[forwardMostMove - 1],
- PosFlags(forwardMostMove - 1), EP_UNKNOWN,
+ PosFlags(forwardMostMove - 1),
fromY, fromX, toY, toX, promoChar,
parseList[forwardMostMove - 1]);
- switch (MateTest(boards[forwardMostMove],
- PosFlags(forwardMostMove), EP_UNKNOWN)){
+ switch (MateTest(boards[forwardMostMove], PosFlags(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, "egtpath %s %s\n", name+1, s);
+ *r = c;
+ SendToProgram(buf,cps); // send egtbpath command for this format
+ }
+ if(*p == ',') p++; // read away comma to position for next format name
+ }
+}
void
-InitChessProgram(cps)
+InitChessProgram(cps, setup)
ChessProgramState *cps;
+ int setup; /* [HGM] needed to setup FRC opening position */
{
- char buf[MSG_SIZ];
+ char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
if (appData.noChessProgram) return;
hintRequested = FALSE;
bookRequested = FALSE;
+
+ /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
+ /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
+ if(cps->memSize) { /* [HGM] memory */
+ sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
+ SendToProgram(buf, cps);
+ }
+ SendEgtPath(cps); /* [HGM] EGT */
+ if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
+ sprintf(buf, "cores %d\n", appData.smpCores);
+ SendToProgram(buf, cps);
+ }
+
SendToProgram(cps->initString, cps);
if (gameInfo.variant != VariantNormal &&
- gameInfo.variant != VariantLoadable) {
+ gameInfo.variant != VariantLoadable
+ /* [HGM] also send variant if board size non-standard */
+ || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
+ ) {
char *v = VariantName(gameInfo.variant);
- if (StrStr(cps->variants, v) == NULL) {
+ if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
+ /* [HGM] in protocol 1 we have to assume all variants valid */
sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
DisplayFatalError(buf, 0, 1);
return;
}
- sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
+
+ /* [HGM] make prefix for non-standard board size. Awkward testing... */
+ overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
+ if( gameInfo.variant == VariantXiangqi )
+ overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
+ if( gameInfo.variant == VariantShogi )
+ overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
+ if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
+ overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
+ if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
+ gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
+ overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
+ if( gameInfo.variant == VariantCourier )
+ overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
+ if( gameInfo.variant == VariantSuper )
+ overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
+ if( gameInfo.variant == VariantGreat )
+ overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
+
+ if(overruled) {
+ sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
+ gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
+ /* [HGM] varsize: try first if this defiant size variant is specifically known */
+ if(StrStr(cps->variants, b) == NULL) {
+ // specific sized variant not known, check if general sizing allowed
+ if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
+ if(StrStr(cps->variants, "boardsize") == NULL) {
+ sprintf(buf, "Board size %dx%d+%d not supported by %s",
+ gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
+ DisplayFatalError(buf, 0, 1);
+ return;
+ }
+ /* [HGM] here we really should compare with the maximum supported board size */
+ }
+ }
+ } else sprintf(b, "%s", VariantName(gameInfo.variant));
+ sprintf(buf, "variant %s\n", b);
SendToProgram(buf, cps);
}
+ currentlyInitializedVariant = gameInfo.variant;
+
+ /* [HGM] send opening position in FRC to first engine */
+ if(setup) {
+ SendToProgram("force\n", cps);
+ SendBoard(cps, 0);
+ /* engine is now in force mode! Set flag to wake it up after first move. */
+ setboardSpoiledMachineBlack = 1;
+ }
+
if (cps->sendICS) {
- sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-");
+ snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
SendToProgram(buf, cps);
}
cps->maybeThinking = FALSE;
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);
SendToProgram(buf, cps);
}
cps->initDone = TRUE;
-}
+}
void
err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
} else {
if (*appData.remoteUser == NULLCHAR) {
- sprintf(buf, "%s %s %s", appData.remoteShell, cps->host,
+ snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
cps->program);
} else {
- sprintf(buf, "%s %s -l %s %s", appData.remoteShell,
+ snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
cps->host, appData.remoteUser, cps->program);
}
err = StartChildProcess(buf, "", &cps->pr);
}
-
+
if (err != 0) {
- sprintf(buf, "Startup failure on '%s'", cps->program);
+ sprintf(buf, _("Startup failure on '%s'"), cps->program);
DisplayFatalError(buf, err, 1);
cps->pr = NoProc;
cps->isr = NULL;
return;
}
-
+
cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
if (cps->protocolVersion > 1) {
sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
+ cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
+ cps->comboCnt = 0; // and values of combo boxes
SendToProgram(buf, cps);
} else {
SendToProgram("xboard\n", cps);
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 load index during match */
Reset(FALSE, TRUE);
if (*appData.loadGameFile != NULLCHAR) {
+ index = appData.loadGameIndex;
+ if(index < 0) { // [HGM] autoinc
+ lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
+ if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
+ }
LoadGameFromFile(appData.loadGameFile,
- appData.loadGameIndex,
+ index,
appData.loadGameFile, FALSE);
} else if (*appData.loadPositionFile != NULLCHAR) {
+ index = appData.loadPositionIndex;
+ if(index < 0) { // [HGM] autoinc
+ lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
+ if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
+ }
LoadPositionFromFile(appData.loadPositionFile,
- appData.loadPositionIndex,
+ index,
appData.loadPositionFile);
}
TwoMachinesEventIfReady();
}
+void UserAdjudicationEvent( int result )
+{
+ ChessMove gameResult = GameIsDrawn;
+
+ if( result > 0 ) {
+ gameResult = WhiteWins;
+ }
+ else if( result < 0 ) {
+ gameResult = BlackWins;
+ }
+
+ if( gameMode == TwoMachinesPlay ) {
+ GameEnds( gameResult, "User adjudication", GE_XBOARD );
+ }
+}
+
+
+// [HGM] save: calculate checksum of game to make games easily identifiable
+int StringCheckSum(char *s)
+{
+ int i = 0;
+ if(s==NULL) return 0;
+ while(*s) i = i*259 + *s++;
+ return i;
+}
+
+int GameCheckSum()
+{
+ int i, sum=0;
+ for(i=backwardMostMove; i<forwardMostMove; i++) {
+ sum += pvInfoList[i].depth;
+ sum += StringCheckSum(parseList[i]);
+ sum += StringCheckSum(commentList[i]);
+ sum *= 261;
+ }
+ if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
+ return sum + StringCheckSum(commentList[i]);
+} // end of save patch
+
void
GameEnds(result, resultDetails, whosays)
ChessMove result;
{
GameMode nextGameMode;
int isIcsGame;
+ char buf[MSG_SIZ];
+
+ if(endingGame) return; /* [HGM] crash: forbid recursion */
+ endingGame = 1;
if (appData.debugMode) {
fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
result, resultDetails ? resultDetails : "(null)", whosays);
}
- if (appData.icsActive && whosays == GE_ENGINE) {
+ if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
/* If we are playing on ICS, the server decides when the
- game is over, but the engine can offer to draw, claim
- a draw, or resign.
+ game is over, but the engine can offer to draw, claim
+ a draw, or resign.
*/
#if ZIPPY
if (appData.zippyPlay && first.initDone) {
}
}
#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. */
- isIcsGame = (gameMode == IcsPlayingWhite ||
- gameMode == IcsPlayingBlack ||
- gameMode == IcsObserving ||
+ isIcsGame = (gameMode == IcsPlayingWhite ||
+ gameMode == IcsPlayingBlack ||
+ gameMode == IcsObserving ||
gameMode == IcsExamining);
if (!isIcsGame || whosays == GE_ICS) {
/* OK -- not an ICS game, or ICS said it was done */
StopClocks();
- if (!isIcsGame && !appData.noChessProgram)
+ if (!isIcsGame && !appData.noChessProgram)
SetUserThinkingEnables();
+
+ /* [HGM] if a machine claims the game end we verify this claim */
+ if(gameMode == TwoMachinesPlay && appData.testClaims) {
+ if(appData.testLegality && whosays >= GE_ENGINE1 ) {
+ char claimer;
+ ChessMove trueResult = (ChessMove) -1;
+
+ claimer = whosays == GE_ENGINE1 ? /* color of claimer */
+ first.twoMachinesColor[0] :
+ second.twoMachinesColor[0] ;
+
+ // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
+ if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
+ /* [HGM] verify: engine mate claims accepted if they were flagged */
+ trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
+ } else
+ if((signed char)boards[forwardMostMove][EP_STATUS] == 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((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
+ trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
+ }
+
+ // now verify win claims, but not in drop games, as we don't understand those yet
+ if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
+ || gameInfo.variant == VariantGreat) &&
+ (result == WhiteWins && claimer == 'w' ||
+ result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
+ if (appData.debugMode) {
+ fprintf(debugFP, "result=%d sp=%d move=%d\n",
+ result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
+ }
+ if(result != trueResult) {
+ sprintf(buf, "False win claim: '%s'", resultDetails);
+ result = claimer == 'w' ? BlackWins : WhiteWins;
+ resultDetails = buf;
+ }
+ } else
+ if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
+ && (forwardMostMove <= backwardMostMove ||
+ (signed char)boards[forwardMostMove-1][EP_STATUS] > 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 = (signed char)boards[forwardMostMove][i][j] - color;
+ if(p >= 0 && p <= (int)WhiteKing) k++;
+ }
+ if (appData.debugMode) {
+ fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
+ result, resultDetails ? resultDetails : "(null)", whosays, k, color);
+ }
+ if(k <= 1) {
+ result = GameIsDrawn;
+ sprintf(buf, "%s but bare king", resultDetails);
+ resultDetails = buf;
+ }
+ }
+ }
+
- if (resultDetails != NULL) {
+ if(serverMoves != NULL && !loadFlag) { char c = '=';
+ if(result==WhiteWins) c = '+';
+ if(result==BlackWins) c = '-';
+ if(resultDetails != NULL)
+ fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
+ }
+ if (resultDetails != NULL) {
gameInfo.result = result;
gameInfo.resultDetails = StrSave(resultDetails);
+ /* display last move only if game was not loaded from file */
+ if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
+ DisplayMove(currentMove - 1);
+
+ if (forwardMostMove != 0) {
+ if (gameMode != PlayFromGameFile && gameMode != EditGame
+ && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
+ ) {
+ if (*appData.saveGameFile != NULLCHAR) {
+ SaveGameToFile(appData.saveGameFile, TRUE);
+ } else if (appData.autoSaveGames) {
+ AutoSaveGame();
+ }
+ if (*appData.savePositionFile != NULLCHAR) {
+ SavePositionToFile(appData.savePositionFile);
+ }
+ }
+ }
+
/* Tell program how game ended in case it is learning */
+ /* [HGM] Moved this to after saving the PGN, just in case */
+ /* engine died and we got here through time loss. In that */
+ /* case we will get a fatal error writing the pipe, which */
+ /* would otherwise lose us the PGN. */
+ /* [HGM] crash: not needed anymore, but doesn't hurt; */
+ /* output during GameEnds should never be fatal anymore */
if (gameMode == MachinePlaysWhite ||
gameMode == MachinePlaysBlack ||
gameMode == TwoMachinesPlay ||
SendToProgram(buf, &second);
}
}
-
- /* display last move only if game was not loaded from file */
- if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
- DisplayMove(currentMove - 1);
-
- if (forwardMostMove != 0) {
- if (gameMode != PlayFromGameFile && gameMode != EditGame) {
- if (*appData.saveGameFile != NULLCHAR) {
- SaveGameToFile(appData.saveGameFile, TRUE);
- } else if (appData.autoSaveGames) {
- AutoSaveGame();
- }
- if (*appData.savePositionFile != NULLCHAR) {
- SavePositionToFile(appData.savePositionFile);
- }
- }
- }
}
if (appData.icsActive) {
}
}
} else if (gameMode == EditGame ||
- gameMode == PlayFromGameFile ||
- gameMode == AnalyzeMode ||
+ gameMode == PlayFromGameFile ||
+ gameMode == AnalyzeMode ||
gameMode == AnalyzeFile) {
nextGameMode = gameMode;
} else {
if (appData.noChessProgram) {
gameMode = nextGameMode;
ModeHighlight();
- return;
+ endingGame = 0; /* [HGM] crash */
+ return;
}
if (first.reuse) {
if (first.isr != NULL)
RemoveInputSource(first.isr);
first.isr = NULL;
-
+
if (first.pr != NoProc) {
ExitAnalyzeMode();
+ DoSleep( appData.delayBeforeQuit );
SendToProgram("quit\n", &first);
+ DoSleep( appData.delayAfterQuit );
DestroyChildProcess(first.pr, first.useSigterm);
}
first.pr = NoProc;
if (second.isr != NULL)
RemoveInputSource(second.isr);
second.isr = NULL;
-
+
if (second.pr != NoProc) {
+ DoSleep( appData.delayBeforeQuit );
SendToProgram("quit\n", &second);
+ DoSleep( appData.delayAfterQuit );
DestroyChildProcess(second.pr, second.useSigterm);
}
second.pr = NoProc;
}
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).
Leaves program in force mode. */
void
-FeedMovesToProgram(cps, upto)
+FeedMovesToProgram(cps, upto)
ChessProgramState *cps;
int upto;
{
int i;
-
+
if (appData.debugMode)
fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
startedFromSetupPosition ? "position and " : "",
backwardMostMove, upto, cps->which);
+ if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
+ // [HGM] variantswitch: make engine aware of new variant
+ if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
+ return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
+ sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
+ SendToProgram(buf, cps);
+ currentlyInitializedVariant = gameInfo.variant;
+ }
SendToProgram("force\n", cps);
if (startedFromSetupPosition) {
SendBoard(cps, backwardMostMove);
+ if (appData.debugMode) {
+ fprintf(debugFP, "feedMoves\n");
+ }
}
for (i = backwardMostMove; i < upto; i++) {
SendMoveToProgram(i, cps);
If so, restart it and feed it all the moves made so far. */
if (appData.noChessProgram || first.pr != NoProc) return;
-
+
StartChessProgram(&first);
- InitChessProgram(&first);
+ InitChessProgram(&first, FALSE);
FeedMovesToProgram(&first, currentMove);
if (!first.sendTime) {
}
if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
- appData.icsEngineAnalyze) && first.analysisSupport) {
+ appData.icsEngineAnalyze) && first.analysisSupport) {
SendToProgram("analyze\n", &first);
first.analyzing = TRUE;
}
fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
redraw, init, gameMode);
}
-
+ CleanupTail(); // [HGM] vari: delete any stored variations
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();
flipView = appData.flipView;
alarmSounded = FALSE;
GameEnds((ChessMove) 0, NULL, GE_PLAYER);
+ if(appData.serverMovesName != NULL) {
+ /* [HGM] prepare to make moves file for broadcasting */
+ clock_t t = clock();
+ if(serverMoves != NULL) fclose(serverMoves);
+ serverMoves = fopen(appData.serverMovesName, "r");
+ if(serverMoves != NULL) {
+ fclose(serverMoves);
+ /* delay 15 sec before overwriting, so all clients can see end */
+ while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
+ }
+ serverMoves = fopen(appData.serverMovesName, "w");
+ }
+
ExitAnalyzeMode();
gameMode = BeginningOfGame;
ModeHighlight();
+ if(appData.icsActive) gameInfo.variant = VariantNormal;
+ currentMove = forwardMostMove = backwardMostMove = 0;
InitPosition(redraw);
for (i = 0; i < MAX_MOVES; i++) {
if (commentList[i] != NULL) {
if (first.pr == NULL) {
StartChessProgram(&first);
}
- if (init) InitChessProgram(&first);
+ if (init) {
+ InitChessProgram(&first, startedFromSetupPosition);
+ }
DisplayTitle("");
DisplayMessage("", "");
HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
+ lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
}
void
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;
}
ChessMove moveType;
char move[MSG_SIZ];
char *p, *q;
-
- if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
+
+ if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
gameMode != AnalyzeMode && gameMode != Training) {
gameFileFP = NULL;
return FALSE;
}
-
+
yyboardindex = forwardMostMove;
if (readAhead != (ChessMove)0) {
moveType = readAhead;
return FALSE;
moveType = (ChessMove) yylex();
}
-
+
done = FALSE;
switch (moveType) {
case Comment:
- if (appData.debugMode)
+ if (appData.debugMode)
fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
p = yy_text;
- if (*p == '{' || *p == '[' || *p == '(') {
- p[strlen(p) - 1] = NULLCHAR;
- p++;
- }
/* append the comment but don't display it */
- while (*p == '\n') p++;
- AppendComment(currentMove, p);
+ AppendComment(currentMove, p, FALSE);
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:
case (ChessMove) 0: /* end of file */
if (appData.debugMode)
fprintf(debugFP, "Parser hit end of file\n");
- switch (MateTest(boards[currentMove], PosFlags(currentMove),
- EP_UNKNOWN)) {
+ switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
case MT_NONE:
case MT_CHECK:
break;
case MT_CHECKMATE:
+ case MT_STAINMATE:
if (WhiteOnMove(currentMove)) {
GameEnds(BlackWins, "Black mates", GE_FILE);
} else {
/* Reached start of next game in file */
if (appData.debugMode)
fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
- switch (MateTest(boards[currentMove], PosFlags(currentMove),
- EP_UNKNOWN)) {
+ switch (MateTest(boards[currentMove], PosFlags(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();
/* currentMoveString is set as a side-effect of yylex */
strcat(currentMoveString, "\n");
strcpy(moveList[forwardMostMove], currentMoveString);
-
+
thinkOutput[0] = NULLCHAR;
MakeMove(fromX, fromY, toX, toY, promoChar);
currentMove = forwardMostMove;
} 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;
}
if (appData.debugMode)
fprintf(debugFP, "Restoring %s for game %d\n",
cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
-
+
thinkOutput[0] = NULLCHAR;
strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
- fromX = cmailMove[lastLoadGameNumber - 1][0] - 'a';
- fromY = cmailMove[lastLoadGameNumber - 1][1] - '1';
- toX = cmailMove[lastLoadGameNumber - 1][2] - 'a';
- toY = cmailMove[lastLoadGameNumber - 1][3] - '1';
+ fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
+ fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
+ toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
+ toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
promoChar = cmailMove[lastLoadGameNumber - 1][4];
MakeMove(fromX, fromY, toX, toY, promoChar);
ShowMove(fromX, fromY, toX, toY);
-
- switch (MateTest(boards[currentMove], PosFlags(currentMove),
- EP_UNKNOWN)) {
+
+ switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
case MT_NONE:
case MT_CHECK:
break;
-
+
case MT_CHECKMATE:
+ case MT_STAINMATE:
if (WhiteOnMove(currentMove)) {
GameEnds(BlackWins, "Black mates", GE_PLAYER);
} else {
GameEnds(WhiteWins, "White mates", GE_PLAYER);
}
break;
-
+
case MT_STALEMATE:
GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
break;
}
break;
-
+
case CMAIL_RESIGN:
if (WhiteOnMove(currentMove)) {
GameEnds(BlackWins, "White resigns", GE_PLAYER);
GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
}
break;
-
+
case CMAIL_ACCEPT:
GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
break;
-
+
default:
break;
}
int numPGNTags = 0;
int err;
GameMode oldGameMode;
+ VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
- if (appData.debugMode)
+ if (appData.debugMode)
fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
if (gameMode == Training )
if (useList) {
lg = (ListGame *) ListElem(&gameList, gameNumber-1);
-
+
if (lg) {
fseek(f, lg->offset, 0);
GameListHighlight(gameNumber);
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) {
/*
* Skip the first gn-1 games in the file.
- * Also skip over anything that precedes an identifiable
- * start of game marker, to avoid being confused by
- * garbage at the start of the file. Currently
+ * Also skip over anything that precedes an identifiable
+ * start of game marker, to avoid being confused by
+ * garbage at the start of the file. Currently
* recognized start of game markers are the move number "1",
* the pattern "gnuchess .* game", the pattern
- * "^[#;%] [^ ]* game file", and a PGN tag block.
+ * "^[#;%] [^ ]* game file", and a PGN tag block.
* A game that starts with one of the latter two patterns
* will also have a move number 1, possibly
* following a position diagram.
gn--;
lastLoadGameStart = cm;
break;
-
+
case MoveNumberOne:
switch (lastLoadGameStart) {
case GNUChessGame:
break;
}
}
-
+
if (appData.debugMode)
fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
free(gameInfo.event);
}
gameInfo.event = StrSave(yy_text);
- }
+ }
startedFromSetupPosition = FALSE;
while (cm == PGNTag) {
- if (appData.debugMode)
+ if (appData.debugMode)
fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
err = ParsePGNTag(yy_text, &gameInfo);
if (!err) numPGNTags++;
+ /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
+ if(gameInfo.variant != oldVariant) {
+ startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
+ InitPosition(TRUE);
+ oldVariant = gameInfo.variant;
+ if (appData.debugMode)
+ fprintf(debugFP, "New variant %d\n", (int) oldVariant);
+ }
+
+
if (gameInfo.fen != NULL) {
Board initial_position;
startedFromSetupPosition = TRUE;
} 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;
+ for( i=0; i< nrCastlingRights; i++ )
+ initialRights[i] = initial_position[CASTLING][i];
+ }
yyboardindex = forwardMostMove;
free(gameInfo.fen);
gameInfo.fen = NULL;
/* Handle comments interspersed among the tags */
while (cm == Comment) {
char *p;
- if (appData.debugMode)
+ if (appData.debugMode)
fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
p = yy_text;
- if (*p == '{' || *p == '[' || *p == '(') {
- p[strlen(p) - 1] = NULLCHAR;
- p++;
- }
- while (*p == '\n') p++;
- AppendComment(currentMove, p);
+ AppendComment(currentMove, p, FALSE);
yyboardindex = forwardMostMove;
cm = (ChessMove) yylex();
}
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 '-':
}
while (*p == ' ' || *p == '\t' ||
*p == '\n' || *p == '\r') p++;
-
+
if (strncmp(p, "black", strlen("black"))==0)
blackPlaysFirst = TRUE;
else
blackPlaysFirst = FALSE;
startedFromSetupPosition = TRUE;
-
+
CopyBoard(boards[0], initial_position);
if (blackPlaysFirst) {
currentMove = forwardMostMove = backwardMostMove = 1;
if (first.pr == NoProc) {
StartChessProgram(&first);
}
- InitChessProgram(&first);
+ InitChessProgram(&first, FALSE);
SendToProgram("force\n", &first);
if (startedFromSetupPosition) {
SendBoard(&first, forwardMostMove);
- DisplayBothClocks();
+ if (appData.debugMode) {
+ fprintf(debugFP, "Load Game\n");
}
+ DisplayBothClocks();
+ }
+
+ /* [HGM] server: flag to write setup moves in broadcast file as one */
+ loadFlag = appData.suppressLoadMoves;
while (cm == Comment) {
char *p;
- if (appData.debugMode)
+ if (appData.debugMode)
fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
p = yy_text;
- if (*p == '{' || *p == '[' || *p == '(') {
- p[strlen(p) - 1] = NULLCHAR;
- p++;
- }
- while (*p == '\n') p++;
- AppendComment(currentMove, p);
+ AppendComment(currentMove, p, FALSE);
yyboardindex = forwardMostMove;
cm = (ChessMove) yylex();
}
return TRUE;
}
- if (commentList[currentMove] != NULL) {
- if (!matchMode && (pausing || appData.timeDelay != 0)) {
+ // [HGM] PV info: routine tests if comment empty
+ if (!matchMode && (pausing || appData.timeDelay != 0)) {
DisplayComment(currentMove - 1, commentList[currentMove]);
- }
}
- if (!matchMode && appData.timeDelay != 0)
+ if (!matchMode && appData.timeDelay != 0)
DrawPosition(FALSE, boards[currentMove]);
if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
}
/* if the first token after the PGN tags is a move
- * and not move number 1, retrieve it from the parser
+ * and not move number 1, retrieve it from the parser
*/
if (cm != MoveNumberOne)
LoadGameOneMove(cm);
AutoPlayGameLoop();
}
- if (appData.debugMode)
+ 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 {
char *p, line[MSG_SIZ];
Board initial_position;
int i, j, fenMode, pn;
-
+
if (gameMode == Training )
SetTrainingModeOff();
strcpy(lastLoadPositionTitle, title);
if (first.pr == NoProc) {
StartChessProgram(&first);
- InitChessProgram(&first);
- }
+ InitChessProgram(&first, FALSE);
+ }
pn = positionNumber;
if (positionNumber < 0) {
/* Negative position number means to seek to that byte offset */
DisplayError(_("Position not found in file"), 0);
return FALSE;
}
- switch (line[0]) {
- case '#': case 'x':
- default:
- fenMode = FALSE;
- break;
- case 'p': case 'n': case 'b': case 'r': case 'q': case 'k':
- case 'P': case 'N': case 'B': case 'R': case 'Q': case 'K':
- case '1': case '2': case '3': case '4': case '5': case '6':
- case '7': case '8':
- fenMode = TRUE;
- break;
- }
+ // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
+ fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
if (pn >= 2) {
if (fenMode || line[0] == '#') pn--;
while (pn > 0) {
- /* skip postions before number pn */
+ /* skip positions before number pn */
if (fgets(line, MSG_SIZ, f) == NULL) {
Reset(TRUE, TRUE);
DisplayError(_("Position not found in file"), 0);
} else {
(void) fgets(line, MSG_SIZ, f);
(void) fgets(line, MSG_SIZ, f);
-
- for (i = BOARD_SIZE - 1; i >= 0; i--) {
+
+ for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
(void) fgets(line, MSG_SIZ, f);
- for (p = line, j = 0; j < BOARD_SIZE; p++) {
+ for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
if (*p == ' ')
continue;
initial_position[i][j++] = CharToPiece(*p);
}
}
-
+
blackPlaysFirst = FALSE;
if (!feof(f)) {
(void) fgets(line, MSG_SIZ, f);
}
}
startedFromSetupPosition = TRUE;
-
+
SendToProgram("force\n", &first);
CopyBoard(boards[0], initial_position);
if (blackPlaysFirst) {
currentMove = forwardMostMove = backwardMostMove = 0;
DisplayMessage("", _("White to play"));
}
+ initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
SendBoard(&first, forwardMostMove);
+ if (appData.debugMode) {
+int i, j;
+ for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][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);
timeRemaining[0][1] = whiteTimeRemaining;
timeRemaining[1][1] = blackTimeRemaining;
DrawPosition(FALSE, boards[currentMove]);
-
+
return TRUE;
}
} 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 {
{
static char buf[MSG_SIZ];
char *p;
-
+
p = strchr(str, ' ');
if (p == NULL) return str;
strncpy(buf, str, p - str);
#define PGN_MAX_LINE 75
+#define PGN_SIDE_WHITE 0
+#define PGN_SIDE_BLACK 1
+
+/* [AS] */
+static int FindFirstMoveOutOfBook( int side )
+{
+ int result = -1;
+
+ if( backwardMostMove == 0 && ! startedFromSetupPosition) {
+ int index = backwardMostMove;
+ int has_book_hit = 0;
+
+ if( (index % 2) != side ) {
+ index++;
+ }
+
+ while( index < forwardMostMove ) {
+ /* Check to see if engine is in book */
+ int depth = pvInfoList[index].depth;
+ int score = pvInfoList[index].score;
+ int in_book = 0;
+
+ if( depth <= 2 ) {
+ in_book = 1;
+ }
+ else if( score == 0 && depth == 63 ) {
+ in_book = 1; /* Zappa */
+ }
+ else if( score == 2 && depth == 99 ) {
+ in_book = 1; /* Abrok */
+ }
+
+ has_book_hit += in_book;
+
+ if( ! in_book ) {
+ result = index;
+
+ break;
+ }
+
+ index += 2;
+ }
+ }
+
+ return result;
+}
+
+/* [AS] */
+void GetOutOfBookInfo( char * buf )
+{
+ int oob[2];
+ int i;
+ int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
+
+ oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
+ oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
+
+ *buf = '\0';
+
+ if( oob[0] >= 0 || oob[1] >= 0 ) {
+ for( i=0; i<2; i++ ) {
+ int idx = oob[i];
+
+ if( idx >= 0 ) {
+ if( i > 0 && oob[0] >= 0 ) {
+ strcat( buf, " " );
+ }
+
+ sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
+ sprintf( buf+strlen(buf), "%s%.2f",
+ pvInfoList[idx].score >= 0 ? "+" : "",
+ pvInfoList[idx].score / 100.0 );
+ }
+ }
+ }
+}
+
/* Save game in PGN style and close the file */
int
SaveGamePGN(f)
{
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;
/* Print comments preceding this move */
if (commentList[i] != NULL) {
if (linelen > 0) fprintf(f, "\n");
- fprintf(f, "{\n%s}\n", commentList[i]);
+ fprintf(f, "%s", commentList[i]);
linelen = 0;
newblock = TRUE;
}
fprintf(f, " ");
linelen++;
}
- fprintf(f, numtext);
+ fprintf(f, "%s", numtext);
linelen += numlen;
/* Get move */
- movetext = SavePart(parseList[i]);
- movelen = strlen(movetext);
+ strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
+ movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
/* Print move */
blank = linelen > 0 && movelen > 0;
fprintf(f, " ");
linelen++;
}
- fprintf(f, movetext);
+ fprintf(f, "%s", move_buffer);
linelen += movelen;
+ /* [AS] Add PV info if present */
+ if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
+ /* [HGM] add time */
+ char buf[MSG_SIZ]; int seconds;
+
+ seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
+
+ if( seconds <= 0) buf[0] = 0; else
+ if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
+ seconds = (seconds + 4)/10; // round to full seconds
+ if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
+ sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
+ }
+
+ sprintf( move_buffer, "{%s%.2f/%d%s}",
+ pvInfoList[i].score >= 0 ? "+" : "",
+ pvInfoList[i].score / 100.0,
+ pvInfoList[i].depth,
+ buf );
+
+ movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
+
+ /* Print score/depth */
+ blank = linelen > 0 && movelen > 0;
+ if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
+ fprintf(f, "\n");
+ linelen = 0;
+ blank = 0;
+ }
+ if (blank) {
+ fprintf(f, " ");
+ linelen++;
+ }
+ fprintf(f, "%s", move_buffer);
+ linelen += movelen;
+ }
+
i++;
}
-
+
/* Start a new line */
if (linelen > 0) fprintf(f, "\n");
/* Print comments after last move */
if (commentList[i] != NULL) {
- fprintf(f, "{\n%s}\n", commentList[i]);
+ fprintf(f, "%s\n", commentList[i]);
}
/* Print result */
}
fclose(f);
+ lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
return TRUE;
}
{
int i, offset;
time_t tm;
-
+
tm = time((time_t *) NULL);
-
+
fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
PrintOpponents(f);
-
+
if (backwardMostMove > 0 || startedFromSetupPosition) {
fprintf(f, "\n[--------------\n");
PrintPosition(f, backwardMostMove);
i++;
}
}
-
+
if (commentList[i] != NULL) {
fprintf(f, "[%s]\n", commentList[i]);
}
int dummy;
char *dummy2;
{
- if (gameMode == EditPosition) EditPositionDone();
+ if (gameMode == EditPosition) EditPositionDone(TRUE);
+ lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
if (appData.oldSaveStyle)
return SaveGameOldStyle(f);
else
} 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 {
{
time_t tm;
char *fen;
-
+
+ if (gameMode == EditPosition) EditPositionDone(TRUE);
if (appData.oldSaveStyle) {
tm = time((time_t *) NULL);
-
+
fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
PrintOpponents(f);
fprintf(f, "[--------------\n");
PrintPosition(f, currentMove);
fprintf(f, "--------------]\n");
} else {
- fen = PositionToFEN(currentMove);
+ fen = PositionToFEN(currentMove, NULL);
fprintf(f, "%s\n", fen);
free(fen);
}
int i;
struct stat inbuf, outbuf;
int status;
-
+
/* Any registered moves are unregistered if unregister is set, */
/* i.e. invoked by the signal handler */
if (unregister) {
outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
sprintf(outFilename, "%s.out", appData.cmailGameName);
}
-
+
status = stat(outFilename, &outbuf);
if (status < 0) {
cmailMailedMove = FALSE;
status = stat(inFilename, &inbuf);
cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
}
-
+
/* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
counts the games, notes how each one terminated, etc.
-
+
It would be nice to remove this kludge and instead gather all
the information while building the game list. (And to keep it
in the game list nodes instead of having a bunch of fixed-size
*/
cmailMsgLoaded = TRUE;
LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
-
+
/* Load first game in the file or popup game menu */
LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
nCmailMovesRegistered --;
- if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
+ if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
{
free(cmailCommentList[lastLoadGameNumber - 1]);
cmailCommentList[lastLoadGameNumber - 1] = NULL;
sprintf(string,
"%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
-
+
f = fopen(string, "w");
if (appData.oldSaveStyle) {
SaveGameOldStyle(f); /* also closes the file */
-
+
sprintf(string, "%s.pos.out", appData.cmailGameName);
f = fopen(string, "w");
SavePosition(f, 0, NULL); /* also closes the file */
fprintf(f, "{--------------\n");
PrintPosition(f, currentMove);
fprintf(f, "--------------}\n\n");
-
+
SaveGame(f, 0, NULL); /* also closes the file*/
}
-
+
cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
nCmailMovesRegistered ++;
} else if (nCmailGames == 1) {
#endif
if (! (cmailMailedMove || RegisterMove())) return;
-
+
if ( cmailMailedMove
|| (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
sprintf(string, partCommandString,
char number[5];
char string[MSG_SIZ]; /* Space for game-list */
int i;
-
+
if (!cmailMsgLoaded) return "";
if (cmailMailedMove) {
sprintf(number, "%d", i + 1);
prependComma = 1;
}
-
+
strcat(string, number);
}
}
sprintf(cmailMsg,
_("Still need to make move for game\n"));
break;
-
+
case 2:
sprintf(cmailMsg,
_("Still need to make moves for both games\n"));
break;
-
+
default:
sprintf(cmailMsg,
_("Still need to make moves for all %d games\n"),
_("Still need to make a move for game %s\n"),
string);
break;
-
+
case 0:
if (nCmailResults == nCmailGames) {
sprintf(cmailMsg, _("No unfinished games\n"));
sprintf(cmailMsg, _("Ready to send mail\n"));
}
break;
-
+
default:
sprintf(cmailMsg,
_("Still need to make moves for games %s\n"),
}
}
-static int exiting = 0;
-
void
ExitEvent(status)
int status;
if (icsPR != NoProc) {
DestroyChildProcess(icsPR, TRUE);
}
- /* Save game if resource set and not already saved by GameEnds() */
- if (gameInfo.resultDetails == NULL && forwardMostMove > 0) {
- if (*appData.saveGameFile != NULLCHAR) {
- SaveGameToFile(appData.saveGameFile, TRUE);
- } else if (appData.autoSaveGames) {
- AutoSaveGame();
- }
- if (*appData.savePositionFile != NULLCHAR) {
- SavePositionToFile(appData.savePositionFile);
- }
+
+ /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
+ GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
+
+ /* [HGM] crash: the above GameEnds() is a dud if another one was running */
+ /* make sure this other one finishes before killing it! */
+ if(endingGame) { int count = 0;
+ if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
+ while(endingGame && count++ < 10) DoSleep(1);
+ if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
}
- GameEnds((ChessMove) 0, NULL, GE_PLAYER);
/* Kill off chess programs */
if (first.pr != NoProc) {
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);
DisplayBothClocks();
}
if (gameMode == PlayFromGameFile) {
- if (appData.timeDelay >= 0)
+ if (appData.timeDelay >= 0)
AutoPlayGameLoop();
} else if (gameMode == IcsExamining && pauseExamInvalid) {
Reset(FALSE, TRUE);
return;
if (gameMode != AnalyzeFile) {
- if (!appData.icsEngineAnalyze) {
- EditGameEvent();
- if (gameMode != EditGame) return;
- }
+ if (!appData.icsEngineAnalyze) {
+ EditGameEvent();
+ if (gameMode != EditGame) return;
+ }
ResurrectChessProgram();
SendToProgram("analyze\n", &first);
first.analyzing = TRUE;
/*first.maybeThinking = TRUE;*/
first.maybeThinking = FALSE; /* avoid killing GNU Chess */
- AnalysisPopUp(_("Analysis"),
- _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
+ EngineOutputPopUp();
}
if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
pausing = FALSE;
first.analyzing = TRUE;
/*first.maybeThinking = TRUE;*/
first.maybeThinking = FALSE; /* avoid killing GNU Chess */
- AnalysisPopUp(_("Analysis"),
- _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
+ EngineOutputPopUp();
}
gameMode = AnalyzeFile;
pausing = FALSE;
MachineWhiteEvent()
{
char buf[MSG_SIZ];
+ char *bookHit = NULL;
if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
return;
- if (gameMode == PlayFromGameFile ||
- gameMode == TwoMachinesPlay ||
- gameMode == Training ||
- gameMode == AnalyzeMode ||
+ if (gameMode == PlayFromGameFile ||
+ gameMode == TwoMachinesPlay ||
+ gameMode == Training ||
+ gameMode == AnalyzeMode ||
gameMode == EndOfGame)
EditGameEvent();
- if (gameMode == EditPosition)
- EditPositionDone();
+ if (gameMode == EditPosition)
+ EditPositionDone(TRUE);
if (!WhiteOnMove(currentMove)) {
DisplayError(_("It is not White's turn"), 0);
return;
}
-
+
if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
ExitAnalyzeMode();
- if (gameMode == EditGame || gameMode == AnalyzeMode ||
+ if (gameMode == EditGame || gameMode == AnalyzeMode ||
gameMode == AnalyzeFile)
TruncateGame();
ResurrectChessProgram(); /* in case it isn't running */
+ if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
+ gameMode = MachinePlaysWhite;
+ ResetClocks();
+ } else
gameMode = MachinePlaysWhite;
pausing = FALSE;
ModeHighlight();
SendTimeRemaining(&first, TRUE);
}
if (first.useColors) {
- SendToProgram("white\ngo\n", &first);
- } else {
- SendToProgram("go\n", &first);
+ SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
}
+ bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
SetMachineThinkingEnables();
first.maybeThinking = TRUE;
StartClocks();
+ firstMove = FALSE;
if (appData.autoFlipView && !flipView) {
flipView = !flipView;
DrawPosition(FALSE, NULL);
+ DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
+ }
+
+ if(bookHit) { // [HGM] book: simulate book reply
+ static char bookMove[MSG_SIZ]; // a bit generous?
+
+ programStats.nodes = programStats.depth = programStats.time =
+ programStats.score = programStats.got_only_move = 0;
+ sprintf(programStats.movelist, "%s (xbook)", bookHit);
+
+ strcpy(bookMove, "move ");
+ strcat(bookMove, bookHit);
+ HandleMachineMove(bookMove, &first);
}
}
MachineBlackEvent()
{
char buf[MSG_SIZ];
+ char *bookHit = NULL;
if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
return;
- if (gameMode == PlayFromGameFile ||
- gameMode == TwoMachinesPlay ||
- gameMode == Training ||
- gameMode == AnalyzeMode ||
+ if (gameMode == PlayFromGameFile ||
+ gameMode == TwoMachinesPlay ||
+ gameMode == Training ||
+ gameMode == AnalyzeMode ||
gameMode == EndOfGame)
EditGameEvent();
- if (gameMode == EditPosition)
- EditPositionDone();
+ if (gameMode == EditPosition)
+ EditPositionDone(TRUE);
if (WhiteOnMove(currentMove)) {
DisplayError(_("It is not Black's turn"), 0);
return;
}
-
+
if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
ExitAnalyzeMode();
- if (gameMode == EditGame || gameMode == AnalyzeMode ||
+ if (gameMode == EditGame || gameMode == AnalyzeMode ||
gameMode == AnalyzeFile)
TruncateGame();
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;
switch (gameMode) {
if (gameMode != EditGame) return;
break;
case EditPosition:
- EditPositionDone();
+ EditPositionDone(TRUE);
break;
case AnalyzeMode:
case AnalyzeFile:
break;
}
- forwardMostMove = currentMove;
+// forwardMostMove = currentMove;
+ TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
ResurrectChessProgram(); /* in case first program isn't running */
if (second.pr == NULL) {
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);
+ savedMessage = bookMove; // args for deferred call
+ savedState = onmove;
+ ScheduleDelayedEvent(DeferredBookMove, 1);
+ }
}
void
break;
case EditPosition:
- EditPositionDone();
+ EditPositionDone(TRUE);
break;
case AnalyzeMode:
case AnalyzeFile:
ExitAnalyzeMode();
break;
-
+
default:
EditGameEvent();
break;
}
break;
case EditPosition:
- EditPositionDone();
+ EditPositionDone(TRUE);
break;
case AnalyzeMode:
case AnalyzeFile:
default:
return;
}
-
+
pausing = FALSE;
StopClocks();
first.offeredDraw = second.offeredDraw = 0;
whiteFlag = blackFlag = 0;
}
DisplayTitle("");
- }
-
+ }
+
gameMode = EditGame;
ModeHighlight();
SetGameInfo();
EditGameEvent();
return;
}
-
+
EditGameEvent();
if (gameMode != EditGame) return;
-
+
gameMode = EditPosition;
ModeHighlight();
SetGameInfo();
if (currentMove > 0)
CopyBoard(boards[0], boards[currentMove]);
-
+
blackPlaysFirst = !WhiteOnMove(currentMove);
ResetClocks();
currentMove = forwardMostMove = backwardMostMove = 0;
void
ExitAnalyzeMode()
{
- /* icsEngineAnalyze - possible call from other functions */
- if (appData.icsEngineAnalyze) {
- appData.icsEngineAnalyze = FALSE;
- DisplayMessage("","Close ICS engine analyze...");
- }
+ /* [DM] icsEngineAnalyze - possible call from other functions */
+ if (appData.icsEngineAnalyze) {
+ appData.icsEngineAnalyze = FALSE;
+
+ DisplayMessage("",_("Close ICS engine analyze..."));
+ }
if (first.analysisSupport && first.analyzing) {
SendToProgram("exit\n", &first);
first.analyzing = FALSE;
}
- AnalysisPopDown();
thinkOutput[0] = NULLCHAR;
}
void
-EditPositionDone()
+EditPositionDone(Boolean fakeRights)
{
+ int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
+
startedFromSetupPosition = TRUE;
- InitChessProgram(&first);
+ InitChessProgram(&first, FALSE);
+ if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
+ boards[0][EP_STATUS] = EP_NONE;
+ boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
+ if(boards[0][0][BOARD_WIDTH>>1] == king) {
+ boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
+ boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
+ } else boards[0][CASTLING][2] = NoRights;
+ if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
+ boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
+ boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
+ } else boards[0][CASTLING][5] = NoRights;
+ }
SendToProgram("force\n", &first);
if (blackPlaysFirst) {
strcpy(moveList[0], "");
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 */
len = strlen(buf);
if (len > MSG_SIZ)
len = MSG_SIZ;
-
+
strncpy(temp, buf, len);
temp[len] = 0;
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;
AcceptEvent()
{
/* Accept a pending offer of any kind from opponent */
-
+
if (appData.icsActive) {
SendToICS(ics_prefix);
SendToICS("accept\n");
DeclineEvent()
{
/* Decline a pending offer of any kind from opponent */
-
+
if (appData.icsActive) {
SendToICS(ics_prefix);
SendToICS("decline\n");
StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
"Black offers a draw" : "White offers a draw")) {
#ifdef NOTDEF
- AppendComment(cmailOldMove, "Draw declined");
+ AppendComment(cmailOldMove, "Draw declined", TRUE);
DisplayComment(cmailOldMove - 1, "Draw declined");
#endif /*NOTDEF*/
} else {
DrawEvent()
{
/* Offer draw or accept pending draw offer from opponent */
-
+
if (appData.icsActive) {
/* Note: tournament rules require draw offers to be
made after you make your move but before you punch
your clock. Currently ICS doesn't let you do that;
instead, you immediately punch your clock after making
a move, but you can offer a draw at any time. */
-
+
SendToICS(ics_prefix);
SendToICS("draw\n");
} else if (cmailMsgLoaded) {
} else if (currentMove == cmailOldMove + 1) {
char *offer = WhiteOnMove(cmailOldMove) ?
"White offers a draw" : "Black offers a draw";
- AppendComment(currentMove, offer);
+ AppendComment(currentMove, offer, TRUE);
DisplayComment(currentMove - 1, offer);
cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
} else {
AdjournEvent()
{
/* Offer Adjourn or accept pending Adjourn offer from opponent */
-
+
if (appData.icsActive) {
SendToICS(ics_prefix);
SendToICS("adjourn\n");
AbortEvent()
{
/* Offer Abort or accept pending Abort offer from opponent */
-
+
if (appData.icsActive) {
SendToICS(ics_prefix);
SendToICS("abort\n");
ResignEvent()
{
/* Resign. You can do this even if it's not your turn. */
-
+
if (appData.icsActive) {
SendToICS(ics_prefix);
SendToICS("resign\n");
if (gameMode == PlayFromGameFile && !pausing)
PauseEvent();
-
+
if (gameMode == IcsExamining && pausing)
limit = pauseExamForwardMostMove;
else
limit = forwardMostMove;
-
+
if (target > limit) target = limit;
if (target > 0 && moveList[target - 1][0]) {
int fromX, fromY, toX, toY;
- toX = moveList[target - 1][2] - 'a';
- toY = moveList[target - 1][3] - '1';
+ toX = moveList[target - 1][2] - AAA;
+ toY = moveList[target - 1][3] - ONE;
if (moveList[target - 1][1] == '@') {
if (appData.highlightLastMove) {
SetHighlights(-1, -1, toX, toY);
}
} else {
- fromX = moveList[target - 1][0] - 'a';
- fromY = moveList[target - 1][1] - '1';
+ fromX = moveList[target - 1][0] - AAA;
+ fromY = moveList[target - 1][1] - ONE;
if (target == currentMove + 1) {
AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
}
}
}
}
- if (gameMode == EditGame || gameMode == AnalyzeMode ||
- gameMode == Training || gameMode == PlayFromGameFile ||
+ if (gameMode == EditGame || gameMode == AnalyzeMode ||
+ gameMode == Training || gameMode == PlayFromGameFile ||
gameMode == AnalyzeFile) {
while (currentMove < target) {
SendMoveToProgram(currentMove++, &first);
} else {
currentMove = target;
}
-
+
if (gameMode == EditGame || gameMode == EndOfGame) {
whiteTimeRemaining = timeRemaining[0][currentMove];
blackTimeRemaining = timeRemaining[1][currentMove];
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]);
}
}
/* to optimze, we temporarily turn off analysis mode while we feed
* the remaining moves to the engine. Otherwise we get analysis output
* after each move.
- */
+ */
if (first.analysisSupport) {
SendToProgram("exit\nforce\n", &first);
first.analyzing = FALSE;
}
}
-
+
if (gameMode == IcsExamining && !pausing) {
SendToICS(ics_prefix);
SendToICS("forward 999999\n");
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)
PauseEvent();
-
+
if (moveList[target][0]) {
int fromX, fromY, toX, toY;
- toX = moveList[target][2] - 'a';
- toY = moveList[target][3] - '1';
+ toX = moveList[target][2] - AAA;
+ toY = moveList[target][3] - ONE;
if (moveList[target][1] == '@') {
if (appData.highlightLastMove) {
SetHighlights(-1, -1, toX, toY);
}
} else {
- fromX = moveList[target][0] - 'a';
- fromY = moveList[target][1] - '1';
+ fromX = moveList[target][0] - AAA;
+ fromY = moveList[target][1] - ONE;
if (target == currentMove - 1) {
AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
}
} else {
currentMove = target;
}
-
+
if (gameMode == EditGame || gameMode == EndOfGame) {
whiteTimeRemaining = timeRemaining[0][currentMove];
blackTimeRemaining = timeRemaining[1][currentMove];
}
DisplayBothClocks();
DisplayMove(currentMove - 1);
- DrawPosition(FALSE, boards[currentMove]);
+ DrawPosition(full_redraw, boards[currentMove]);
HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
- if (commentList[currentMove] != NULL) {
- DisplayComment(currentMove - 1, commentList[currentMove]);
- }
+ // [HGM] PV info: routine tests if comment empty
+ DisplayComment(currentMove - 1, commentList[currentMove]);
}
void
ToStartEvent()
{
if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
- /* to optimze, we temporarily turn off analysis mode while we undo
+ /* to optimize, we temporarily turn off analysis mode while we undo
* all the moves. Otherwise we get analysis output after each undo.
- */
+ */
if (first.analysisSupport) {
SendToProgram("exit\nforce\n", &first);
first.analyzing = FALSE;
void
RevertEvent()
{
+ if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
+ return;
+ }
if (gameMode != IcsExamining) {
DisplayError(_("You are not examining a game"), 0);
return;
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;
void
TruncateGame()
{
+ CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
if (forwardMostMove > currentMove) {
if (gameInfo.resultDetails != NULL) {
free(gameInfo.resultDetails);
}
break;
case EditPosition:
- EditPositionDone();
+ EditPositionDone(TRUE);
break;
case TwoMachinesPlay:
return;
int move;
{
int i, j;
-
- for (i = BOARD_SIZE - 1; i >= 0; i--) {
- for (j = 0; j < BOARD_SIZE; j++) {
+
+ for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
+ for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
char c = PieceToChar(boards[move][i][j]);
fputc(c == 'x' ? '.' : c, fp);
- fputc(j == BOARD_SIZE - 1 ? '\n' : ' ', fp);
+ fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
}
}
if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
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;
{
/* This routine is used only for certain modes */
VariantClass v = gameInfo.variant;
+ ChessMove r = GameUnfinished;
+ char *p = NULL;
+
+ if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
+ r = gameInfo.result;
+ p = gameInfo.resultDetails;
+ gameInfo.resultDetails = NULL;
+ }
ClearGameInfo(&gameInfo);
gameInfo.variant = v;
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) {
gameInfo.round = StrSave("-");
gameInfo.white = StrSave("-");
gameInfo.black = StrSave("-");
+ gameInfo.result = r;
+ gameInfo.resultDetails = p;
break;
case EditPosition:
commentList[index] = NULL;
return;
}
+ if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
+ *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
+ *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
commentList[index] = (char *) malloc(len + 2);
strncpy(commentList[index], text, len);
commentList[index][len] = '\n';
commentList[index][len + 1] = NULLCHAR;
+ } else {
+ // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
+ char *p;
+ commentList[index] = (char *) malloc(len + 6);
+ strcpy(commentList[index], "{\n");
+ strncpy(commentList[index]+2, text, len);
+ commentList[index][len+2] = NULLCHAR;
+ while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
+ strcat(commentList[index], "\n}\n");
+ }
}
void
}
void
-AppendComment(index, text)
+AppendComment(index, text, addBraces)
int index;
char *text;
+ Boolean addBraces; // [HGM] braces: tells if we should add {}
{
int oldlen, len;
char *old;
+if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
+ text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
+
CrushCRs(text);
while (*text == '\n') text++;
len = strlen(text);
if (commentList[index] != NULL) {
old = commentList[index];
oldlen = strlen(old);
- commentList[index] = (char *) malloc(oldlen + len + 2);
+ while(commentList[index][oldlen-1] == '\n')
+ commentList[index][--oldlen] = NULLCHAR;
+ commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
strcpy(commentList[index], old);
free(old);
- strncpy(&commentList[index][oldlen], text, len);
- commentList[index][oldlen + len] = '\n';
- commentList[index][oldlen + len + 1] = NULLCHAR;
+ // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
+ if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
+ if(addBraces) addBraces = FALSE; else { text++; len--; }
+ while (*text == '\n') { text++; len--; }
+ commentList[index][--oldlen] = NULLCHAR;
+ }
+ if(addBraces) strcat(commentList[index], "\n{\n");
+ else strcat(commentList[index], "\n");
+ strcat(commentList[index], text);
+ if(addBraces) strcat(commentList[index], "\n}\n");
+ else strcat(commentList[index], "\n");
} else {
- commentList[index] = (char *) malloc(len + 2);
- strncpy(commentList[index], text, len);
- commentList[index][len] = '\n';
- commentList[index][len + 1] = NULLCHAR;
+ commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
+ if(addBraces)
+ strcpy(commentList[index], "{\n");
+ else commentList[index][0] = NULLCHAR;
+ strcat(commentList[index], text);
+ strcat(commentList[index], "\n");
+ if(addBraces) strcat(commentList[index], "}\n");
+ }
+}
+
+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 ) {
+ }
+ return text;
+ }
+ else {
+ /* We expect something like: [+|-]nnn.nn/dd */
+ int score_lo = 0;
+
+ if(*text != '{') return text; // [HGM] braces: must be normal comment
+
+ sep = strchr( text, '/' );
+ if( sep == NULL || sep < (text+4) ) {
+ return text;
+ }
+
+ time = -1; sec = -1; deci = -1;
+ if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
+ sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
+ sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
+ sscanf( text+1, "%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
+ if(*sep == '}') *sep = 0; else *--sep = '{';
}
+ return sep;
}
void
if (cps->pr == NULL) return;
Attention(cps);
-
+
if (appData.debugMode) {
TimeMark now;
GetTimeMark(&now);
- fprintf(debugFP, "%ld >%-6s: %s",
+ fprintf(debugFP, "%ld >%-6s: %s",
SubtractTimeMarks(&now, &programStartTime),
cps->which, message);
}
-
+
count = strlen(message);
outCount = OutputToProcess(cps->pr, message, count, &error);
- if (outCount < count && !exiting) {
+ if (outCount < count && !exiting
+ && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
sprintf(buf, _("Error writing to %s chess program"), cps->which);
- DisplayFatalError(buf, error, 1);
+ if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
+ if((signed char)boards[forwardMostMove][EP_STATUS] <= 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 = StrSave(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((signed char)boards[forwardMostMove][EP_STATUS] <= 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 = StrSave(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;
}
-
+
if ((end_str = strchr(message, '\r')) != NULL)
*end_str = NULLCHAR;
if ((end_str = strchr(message, '\n')) != NULL)
*end_str = NULLCHAR;
-
+
if (appData.debugMode) {
- TimeMark now;
- GetTimeMark(&now);
- fprintf(debugFP, "%ld <%-6s: %s\n",
- SubtractTimeMarks(&now, &programStartTime),
- cps->which, message);
- }
- /* if icsEngineAnalyze is active we block all
- whisper and kibitz output, because nobody want
- see this
- */
- if (appData.icsEngineAnalyze) {
- if (strstr(message, "whisper") != NULL ||
- strstr(message, "kibitz") != NULL ||
- strstr(message, "tellics") != NULL) return;
- HandleMachineMove(message, cps);
- } else {
- HandleMachineMove(message, cps);
+ TimeMark now; int print = 1;
+ char *quote = ""; char c; int i;
+
+ if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
+ char start = message[0];
+ if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
+ if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
+ sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
+ sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
+ sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
+ sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
+ sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
+ sscanf(message, "pong %c", &c)!=1 && start != '#')
+ { quote = "# "; print = (appData.engineComments == 2); }
+ message[0] = start; // restore original message
}
+ if(print) {
+ GetTimeMark(&now);
+ fprintf(debugFP, "%ld <%-6s: %s%s\n",
+ SubtractTimeMarks(&now, &programStartTime), cps->which,
+ quote,
+ message);
+ }
+ }
+
+ /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
+ if (appData.icsEngineAnalyze) {
+ if (strstr(message, "whisper") != NULL ||
+ strstr(message, "kibitz") != NULL ||
+ strstr(message, "tellics") != NULL) return;
+ }
+
+ HandleMachineMove(message, cps);
}
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\n", time);
+ SendToProgram(message, cps);
- sprintf(message, "time %ld\notim %ld\n", time, otime);
+ 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, " -slider "))) {
+ // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
+ if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
+ if(max < min) max = min; // enforce consistency
+ if(def < min) def = min;
+ if(def > max) def = max;
+ opt->value = def;
+ opt->min = min;
+ opt->max = max;
+ opt->type = Spin; // Slider;
+ } else if((p = strstr(opt->name, " -string "))) {
+ opt->textValue = p+9;
+ opt->type = TextBox;
+ } else if((p = strstr(opt->name, " -file "))) {
+ // for now -file is a synonym for -string, to already provide compatibility with future polyglots
+ opt->textValue = p+7;
+ opt->type = TextBox; // FileName;
+ } else if((p = strstr(opt->name, " -path "))) {
+ // for now -file is a synonym for -string, to already provide compatibility with future polyglots
+ opt->textValue = p+7;
+ opt->type = TextBox; // PathName;
+ } else if(p = strstr(opt->name, " -check ")) {
+ if(sscanf(p, " -check %d", &def) < 1) return FALSE;
+ opt->value = (def != 0);
+ opt->type = CheckBox;
+ } else if(p = strstr(opt->name, " -combo ")) {
+ opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
+ cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
+ if(*q == '*') cps->comboList[cps->comboCnt-1]++;
+ opt->value = n = 0;
+ while(q = StrStr(q, " /// ")) {
+ n++; *q = 0; // count choices, and null-terminate each of them
+ q += 5;
+ if(*q == '*') { // remember default, which is marked with * prefix
+ q++;
+ opt->value = n;
+ }
+ cps->comboList[cps->comboCnt++] = q;
+ }
+ cps->comboList[cps->comboCnt++] = NULL;
+ opt->max = n + 1;
+ opt->type = ComboBox;
+ } else if(p = strstr(opt->name, " -button")) {
+ opt->type = Button;
+ } else if(p = strstr(opt->name, " -save")) {
+ opt->type = SaveButton;
+ } else return FALSE;
+ *p = 0; // terminate option name
+ // now look if the command-line options define a setting for this engine option.
+ if(cps->optionSettings && cps->optionSettings[0])
+ p = strstr(cps->optionSettings, opt->name); else p = NULL;
+ if(p && (p == cps->optionSettings || p[-1] == ',')) {
+ sprintf(buf, "option %s", p);
+ if(p = strstr(buf, ",")) *p = 0;
+ strcat(buf, "\n");
+ SendToProgram(buf, cps);
+ }
+ return TRUE;
+}
+
void
FeatureDone(cps, val)
ChessProgramState* cps;
void
ParseFeatures(args, cps)
char* args;
- ChessProgramState *cps;
+ ChessProgramState *cps;
{
char *p = args;
char *q;
if (*p == NULLCHAR) return;
if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
- if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
- if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
- if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
- if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
+ if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
+ if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
+ if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
+ if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
if (BoolFeature(&p, "reuse", &val, cps)) {
/* Engine can disable reuse, but can't enable it if user said no */
if (!val) cps->reuse = FALSE;
FeatureDone(cps, val);
continue;
}
+ /* Added by Tord: */
+ if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
+ if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
+ /* End of additions by Tord */
+
+ /* [HGM] added features: */
+ if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
+ if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
+ if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
+ if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
+ if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
+ if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
+ if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
+ if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
+ sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
+ SendToProgram(buf, cps);
+ continue;
+ }
+ if(cps->nrOptions >= MAX_OPTIONS) {
+ cps->nrOptions--;
+ sprintf(buf, "%s engine has too many options\n", cps->which);
+ DisplayError(buf, 0);
+ }
+ continue;
+ }
+ if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
+ /* End of additions by HGM */
/* unknown feature: complain and skip */
q = p;
while (*q && *q != '=') q++;
- sprintf(buf, "rejected %.*s\n", q-p, p);
+ sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
SendToProgram(buf, cps);
p = q;
if (*p == '=') {
appData.periodicUpdates=newState;
/* Display type changes, so update it now */
- DisplayAnalysis();
+// DisplayAnalysis();
/* Get the ball rolling again... */
if (newState) {
int newState;
{
if (newState == appData.ponderNextMove) return;
- if (gameMode == EditPosition) EditPositionDone();
+ if (gameMode == EditPosition) EditPositionDone(TRUE);
if (newState) {
SendToProgram("hard\n", &first);
if (gameMode == TwoMachinesPlay) {
}
void
-ShowThinkingEvent(newState)
- int newState;
+NewSettingEvent(option, command, value)
+ char *command;
+ int option, value;
{
- if (newState == appData.showThinking) return;
- if (gameMode == EditPosition) EditPositionDone();
- if (newState) {
+ char buf[MSG_SIZ];
+
+ if (gameMode == EditPosition) EditPositionDone(TRUE);
+ 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(TRUE);
+ 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 (moveNumber == forwardMostMove - 1 ||
+ if(appData.noGUI) return; // [HGM] fast: suppress display of moves
+
+ if (moveNumber == forwardMostMove - 1 ||
gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
- strcpy(cpThinkOutput, thinkOutput);
- if (strchr(cpThinkOutput, '\n'))
- *strchr(cpThinkOutput, '\n') = NULLCHAR;
+ safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
+
+ if (strchr(cpThinkOutput, '\n')) {
+ *strchr(cpThinkOutput, '\n') = NULLCHAR;
+ }
} else {
*cpThinkOutput = NULLCHAR;
}
+ /* [AS] Hide thinking from human user */
+ if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
+ *cpThinkOutput = NULLCHAR;
+ if( thinkOutput[0] != NULLCHAR ) {
+ int i;
+
+ for( i=0; i<=hiddenThinkOutputState; i++ ) {
+ cpThinkOutput[i] = '.';
+ }
+ cpThinkOutput[i] = NULLCHAR;
+ hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
+ }
+ }
+
if (moveNumber == forwardMostMove - 1 &&
gameInfo.resultDetails != NULL) {
if (gameInfo.resultDetails[0] == NULLCHAR) {
}
void
-DisplayAnalysisText(text)
- char *text;
-{
- char buf[MSG_SIZ];
-
- if (gameMode == AnalyzeMode || gameMode == AnalyzeFile
- || appData.icsEngineAnalyze) {
- sprintf(buf, "Analysis (%s)", first.tidy);
- AnalysisPopUp(buf, text);
- }
-}
-
-static int
-only_one_move(str)
- char *str;
-{
- while (*str && isspace(*str)) ++str;
- while (*str && !isspace(*str)) ++str;
- if (!*str) return 1;
- while (*str && isspace(*str)) ++str;
- if (!*str) return 1;
- return 0;
-}
-
-void
-DisplayAnalysis()
-{
- char buf[MSG_SIZ];
- double nps;
- static char *xtra[] = { "", " (--)", " (++)" };
- int h, m, s, cs;
-
- if (programStats.time == 0) {
- programStats.time = 1;
- }
-
- if (programStats.got_only_move) {
- strcpy(buf, programStats.movelist);
- } else {
- nps = (u64ToDouble(programStats.nodes) /
- ((double)programStats.time /100.0));
-
- cs = programStats.time % 100;
- s = programStats.time / 100;
- h = (s / (60*60));
- s = s - h*60*60;
- m = (s/60);
- s = s - m*60;
-
- if (programStats.moves_left > 0 && appData.periodicUpdates) {
- if (programStats.move_name[0] != NULLCHAR) {
- sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: "u64Display" NPS: %d\nTime: %02d:%02d:%02d.%02d",
- 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)?
- xtra[programStats.got_fail] : "",
- (u64)programStats.nodes, (int)nps, h, m, s, cs);
- } else {
- sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: "u64Display" NPS: %d\nTime: %02d:%02d:%02d.%02d",
- programStats.depth,
- programStats.nr_moves-programStats.moves_left,
- programStats.nr_moves, ((float)programStats.score)/100.0,
- programStats.movelist,
- only_one_move(programStats.movelist)?
- xtra[programStats.got_fail] : "",
- (u64)programStats.nodes, (int)nps, h, m, s, cs);
- }
- } else {
- sprintf(buf, "depth=%d %+.2f %s%s\nNodes: "u64Display" NPS: %d\nTime: %02d:%02d:%02d.%02d",
- programStats.depth,
- ((float)programStats.score)/100.0,
- programStats.movelist,
- only_one_move(programStats.movelist)?
- xtra[programStats.got_fail] : "",
- (u64)programStats.nodes, (int)nps, h, m, s, cs);
- }
- }
- DisplayAnalysisText(buf);
-}
-
-void
DisplayComment(moveNumber, text)
int moveNumber;
char *text;
{
char title[MSG_SIZ];
-
+ char buf[8000]; // comment can be long!
+ int score, depth;
+
if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
- strcpy(title, "Comment");
+ strcpy(title, "Comment");
} else {
- sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
- WhiteOnMove(moveNumber) ? " " : ".. ",
- parseList[moveNumber]);
+ sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
+ WhiteOnMove(moveNumber) ? " " : ".. ",
+ parseList[moveNumber]);
}
-
- CommentPopUp(title, text);
+ // [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;
+ }
+ if (text != NULL && (appData.autoDisplayComment || commentUp))
+ 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;
void
CheckTimeControl()
{
- if (!appData.clockMode || appData.icsActive ||
+ if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
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)
struct timezone timeZone;
gettimeofday(&timeVal, &timeZone);
- tm->sec = (long) timeVal.tv_sec;
+ tm->sec = (long) timeVal.tv_sec;
tm->ms = (int) (timeVal.tv_usec / 1000L);
#else /*!HAVE_GETTIMEOFDAY*/
#if HAVE_FTIME
-#include <sys/timeb.h>
+// include <sys/timeb.h> / moved to just above start of function
struct timeb timeB;
ftime(&timeB);
* We give the human user a slight advantage if he is playing white---the
* clocks don't run until he makes his first move, so it takes zero time.
* Also, we don't account for network lag, so we could get out of sync
- * with GNU Chess's clock -- but then, referees are always right.
+ * with GNU Chess's clock -- but then, referees are always right.
*/
static TimeMark tickStartTM;
return nextTickLength;
}
+/* Adjust clock one minute up or down */
+void
+AdjustClock(Boolean which, int dir)
+{
+ if(which) blackTimeRemaining += 60000*dir;
+ else whiteTimeRemaining += 60000*dir;
+ DisplayBothClocks();
+}
+
/* Stop clocks and reset to a fresh time control */
void
-ResetClocks()
+ResetClocks()
{
(void) StopClockTimer();
if (appData.icsActive) {
whiteTimeRemaining = blackTimeRemaining = 0;
- } else {
- whiteTimeRemaining = blackTimeRemaining = timeControl;
+ } else if (searchTime) {
+ whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
+ blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
+ } 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 (!appData.clockMode) return;
if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
-
+
GetTimeMark(&now);
lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
if (fudge < 0 || fudge > FUDGE) fudge = 0;
if (WhiteOnMove(forwardMostMove)) {
+ if(whiteNPS >= 0) lastTickLength = 0;
timeRemaining = whiteTimeRemaining -= lastTickLength;
DisplayWhiteClock(whiteTimeRemaining - fudge,
WhiteOnMove(currentMove));
} else {
+ if(blackNPS >= 0) lastTickLength = 0;
timeRemaining = blackTimeRemaining -= lastTickLength;
DisplayBlackClock(blackTimeRemaining - fudge,
!WhiteOnMove(currentMove));
}
if (CheckFlags()) return;
-
+
tickStartTM = now;
intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
StartClockTimer(intendedTickLength);
/* if the time remaining has fallen below the alarm threshold, sound the
* alarm. if the alarm has sounded and (due to a takeback or time control
* with increment) the time remaining has increased to a level above the
- * threshold, reset the alarm so it can sound again.
+ * threshold, reset the alarm so it can sound again.
*/
-
+
if (appData.icsActive && appData.icsAlarm) {
/* make sure we are dealing with the user's clock */
if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
alarmSounded = FALSE;
- } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
+ } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
PlayAlarmSound();
alarmSounded = TRUE;
}
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();
}
break;
}
+ if (searchTime) { // [HGM] st: set clock of player that has to move to max time
+ if(WhiteOnMove(forwardMostMove))
+ whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
+ else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
+ }
+
tickStartTM = now;
intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
whiteTimeRemaining : blackTimeRemaining);
StartClockTimer(intendedTickLength);
}
-
+
/* Stop both clocks */
void
StopClocks()
-{
+{
long lastTickLength;
TimeMark now;
lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
if (WhiteOnMove(forwardMostMove)) {
+ if(whiteNPS >= 0) lastTickLength = 0;
whiteTimeRemaining -= lastTickLength;
DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
} else {
+ if(blackNPS >= 0) lastTickLength = 0;
blackTimeRemaining -= lastTickLength;
DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
}
CheckFlags();
}
-
+
/* Start clock of player on move. Time may have been reset, so
if clock is already running, stop and restart it. */
void
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);
}
long second, minute, hour, day;
char *sign = "";
static char buf[32];
-
+
if (ms > 0 && ms <= 9900) {
/* convert milliseconds to tenths, rounding up */
double tenths = floor( ((double)(ms + 99L)) / 100.00 );
sign = "-";
second = -second;
}
-
+
day = second / (60 * 60 * 24);
second = second % (60 * 60 * 24);
hour = second / (60 * 60);
second = second % (60 * 60);
minute = second / 60;
second = second % 60;
-
+
if (day > 0)
sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
sign, day, hour, minute, second);
sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
else
sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
-
+
return buf;
}
char *string, *match;
{
int i, length;
-
+
length = strlen(match);
-
+
for (i = strlen(string) - length; i >= 0; i--, string++)
if (!strncmp(match, string, length))
return string;
-
+
return NULL;
}
char *string, *match;
{
int i, j, length;
-
+
length = strlen(match);
-
+
for (i = strlen(string) - length; i >= 0; i--, string++) {
for (j = 0; j < length; j++) {
if (ToLower(match[j]) != ToLower(string[j]))
char *s1, *s2;
{
char c1, c2;
-
+
for (;;) {
c1 = ToLower(*s1++);
c2 = ToLower(*s2++);
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(boards[move][CASTLING][2] != NoRights &&
+ boards[move][CASTLING][0] != NoRights )
+ *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
+ if(boards[move][CASTLING][2] != NoRights &&
+ boards[move][CASTLING][1] != NoRights )
+ *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
+ if(boards[move][CASTLING][5] != NoRights &&
+ boards[move][CASTLING][3] != NoRights )
+ *p++ = boards[move][CASTLING][3] + AAA;
+ if(boards[move][CASTLING][5] != NoRights &&
+ boards[move][CASTLING][4] != NoRights )
+ *p++ = boards[move][CASTLING][4] + AAA;
+ } else {
+
+ /* [HGM] write true castling rights */
+ if( nrCastlingRights == 6 ) {
+ if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
+ boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
+ if(boards[move][CASTLING][1] == BOARD_LEFT &&
+ boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
+ if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
+ boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
+ if(boards[move][CASTLING][4] == BOARD_LEFT &&
+ boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
+ }
+ }
+ if (q == p) *p++ = '-'; /* No castling rights */
+ *p++ = ' ';
+ }
+ if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
+ gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
/* En passant target square */
if (move > backwardMostMove) {
- fromX = moveList[move - 1][0] - 'a';
- fromY = moveList[move - 1][1] - '1';
- toX = moveList[move - 1][2] - 'a';
- toY = moveList[move - 1][3] - '1';
- if (fromY == (whiteToPlay ? 6 : 1) &&
- toY == (whiteToPlay ? 4 : 3) &&
+ fromX = moveList[move - 1][0] - AAA;
+ fromY = moveList[move - 1][1] - ONE;
+ toX = moveList[move - 1][2] - AAA;
+ toY = moveList[move - 1][3] - ONE;
+ if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
+ toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
fromX == toX) {
/* 2-square pawn move just happened */
- *p++ = toX + 'a';
- *p++ = whiteToPlay ? '6' : '3';
+ *p++ = toX + AAA;
+ *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
+ } else {
+ *p++ = '-';
+ }
+ } else if(move == backwardMostMove) {
+ // [HGM] perhaps we should always do it like this, and forget the above?
+ if((signed char)boards[move][EP_STATUS] >= 0) {
+ *p++ = boards[move][EP_STATUS] + 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, (signed char)boards[k][EP_STATUS]);
+ }
+
+ while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
+ if( j == backwardMostMove ) i += initialRulePlies;
+ sprintf(p, "%d ", i);
+ p += i>=100 ? 4 : i >= 10 ? 3 : 2;
+ }
/* Fullmove number */
sprintf(p, "%d", (move / 2) + 1);
-
+
return StrSave(buf);
}
Boolean
ParseFEN(board, blackPlaysFirst, fen)
- Board board;
+ Board board;
int *blackPlaysFirst;
char *fen;
{
int i, j;
char *p;
int emptycount;
+ ChessSquare piece;
p = fen;
+ /* [HGM] by default clear Crazyhouse holdings, if present */
+ if(gameInfo.holdingsWidth) {
+ for(i=0; i<BOARD_HEIGHT; i++) {
+ board[i][0] = EmptySquare; /* black holdings */
+ board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
+ board[i][1] = (ChessSquare) 0; /* black counts */
+ board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
+ }
+ }
+
/* Piece placement data */
- for (i = BOARD_SIZE - 1; i >= 0; i--) {
+ for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
j = 0;
for (;;) {
- if (*p == '/' || *p == ' ') {
- if (*p == '/') p++;
- emptycount = BOARD_SIZE - j;
- while (emptycount--) board[i][j++] = EmptySquare;
+ if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
+ if (*p == '/') p++;
+ emptycount = gameInfo.boardWidth - j;
+ while (emptycount--)
+ board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
break;
- } else if (isdigit(*p)) {
+#if(BOARD_FILES >= 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':
+ case 'b':
*blackPlaysFirst = TRUE;
break;
default:
return FALSE;
}
- /* !!We ignore the rest of the FEN notation */
+
+ /* [HGM] We NO LONGER ignore the rest of the FEN notation */
+ /* return the extra info in global variiables */
+
+ /* set defaults in case FEN is incomplete */
+ board[EP_STATUS] = EP_UNKNOWN;
+ for(i=0; i<nrCastlingRights; i++ ) {
+ board[CASTLING][i] =
+ gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
+ } /* assume possible unless obviously impossible */
+ if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
+ if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
+ if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
+ if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
+ if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
+ if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
+ 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++ ) {
+ board[CASTLING][i] = NoRights;
+ }
+ }
+ 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--);
+ board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
+ board[CASTLING][2] = whiteKingFile;
+ break;
+ case'Q':
+ for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
+ board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
+ board[CASTLING][2] = whiteKingFile;
+ break;
+ case'k':
+ for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
+ board[CASTLING][3] = i != blackKingFile ? i : NoRights;
+ board[CASTLING][5] = blackKingFile;
+ break;
+ case'q':
+ for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
+ board[CASTLING][4] = i != blackKingFile ? i : NoRights;
+ board[CASTLING][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;
+ board[CASTLING][5] = i;
+ c -= AAA;
+ if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
+ board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
+ if(c > i)
+ board[CASTLING][3] = c;
+ else
+ board[CASTLING][4] = c;
+ } else { /* white rights */
+ for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
+ if(board[0][i] == WhiteKing) break;
+ if(i == BOARD_RGHT) break;
+ board[CASTLING][2] = i;
+ c -= AAA - 'a' + 'A';
+ if(board[0][c] >= WhiteKing) break;
+ if(c > i)
+ board[CASTLING][0] = c;
+ else
+ board[CASTLING][1] = c;
+ }
+ }
+ }
+ if (appData.debugMode) {
+ fprintf(debugFP, "FEN castling rights:");
+ for(i=0; i<nrCastlingRights; i++)
+ fprintf(debugFP, " %d", board[CASTLING][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++; board[EP_STATUS] = EP_NONE;
+ } else {
+ char c = *p++ - AAA;
+
+ if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
+ if(*p >= '0' && *p <='9') *p++;
+ board[EP_STATUS] = c;
+ }
+ }
+
+
+ if(sscanf(p, "%d", &i) == 1) {
+ FENrulePlies = i; /* 50-move ply counter */
+ /* (The move number is still ignored) */
+ }
+
return TRUE;
}
-
+
void
EditPositionPasteFEN(char *fen)
{
EditPositionEvent();
blackPlaysFirst = savedBlackPlaysFirst;
CopyBoard(boards[0], initial_position);
- EditPositionDone();
+ initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
+ EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
DisplayBothClocks();
DrawPosition(FALSE, boards[currentMove]);
}
}
}
+
+static char cseq[12] = "\\ ";
+
+Boolean set_cont_sequence(char *new_seq)
+{
+ int len;
+ Boolean ret;
+
+ // handle bad attempts to set the sequence
+ if (!new_seq)
+ return 0; // acceptable error - no debug
+
+ len = strlen(new_seq);
+ ret = (len > 0) && (len < sizeof(cseq));
+ if (ret)
+ strcpy(cseq, new_seq);
+ else if (appData.debugMode)
+ fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
+ return ret;
+}
+
+/*
+ reformat a source message so words don't cross the width boundary. internal
+ newlines are not removed. returns the wrapped size (no null character unless
+ included in source message). If dest is NULL, only calculate the size required
+ for the dest buffer. lp argument indicats line position upon entry, and it's
+ passed back upon exit.
+*/
+int wrap(char *dest, char *src, int count, int width, int *lp)
+{
+ int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
+
+ cseq_len = strlen(cseq);
+ old_line = line = *lp;
+ ansi = len = clen = 0;
+
+ for (i=0; i < count; i++)
+ {
+ if (src[i] == '\033')
+ ansi = 1;
+
+ // if we hit the width, back up
+ if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
+ {
+ // store i & len in case the word is too long
+ old_i = i, old_len = len;
+
+ // find the end of the last word
+ while (i && src[i] != ' ' && src[i] != '\n')
+ {
+ i--;
+ len--;
+ }
+
+ // word too long? restore i & len before splitting it
+ if ((old_i-i+clen) >= width)
+ {
+ i = old_i;
+ len = old_len;
+ }
+
+ // extra space?
+ if (i && src[i-1] == ' ')
+ len--;
+
+ if (src[i] != ' ' && src[i] != '\n')
+ {
+ i--;
+ if (len)
+ len--;
+ }
+
+ // now append the newline and continuation sequence
+ if (dest)
+ dest[len] = '\n';
+ len++;
+ if (dest)
+ strncpy(dest+len, cseq, cseq_len);
+ len += cseq_len;
+ line = cseq_len;
+ clen = cseq_len;
+ continue;
+ }
+
+ if (dest)
+ dest[len] = src[i];
+ len++;
+ if (!ansi)
+ line++;
+ if (src[i] == '\n')
+ line = 0;
+ if (src[i] == 'm')
+ ansi = 0;
+ }
+ if (dest && appData.debugMode)
+ {
+ fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
+ count, width, line, len, *lp);
+ show_bytes(debugFP, src, count);
+ fprintf(debugFP, "\ndest: ");
+ show_bytes(debugFP, dest, len);
+ fprintf(debugFP, "\n");
+ }
+ *lp = dest ? line : old_line;
+
+ return len;
+}
+
+// [HGM] vari: routines for shelving variations
+
+void
+PushTail(int firstMove, int lastMove)
+{
+ int i, j, nrMoves = lastMove - firstMove;
+
+ if(appData.icsActive) { // only in local mode
+ forwardMostMove = currentMove; // mimic old ICS behavior
+ return;
+ }
+ if(storedGames >= MAX_VARIATIONS-1) return;
+
+ // push current tail of game on stack
+ savedResult[storedGames] = gameInfo.result;
+ savedDetails[storedGames] = gameInfo.resultDetails;
+ gameInfo.resultDetails = NULL;
+ savedFirst[storedGames] = firstMove;
+ savedLast [storedGames] = lastMove;
+ savedFramePtr[storedGames] = framePtr;
+ framePtr -= nrMoves; // reserve space for the boards
+ for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
+ CopyBoard(boards[framePtr+i], boards[firstMove+i]);
+ for(j=0; j<MOVE_LEN; j++)
+ moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
+ for(j=0; j<2*MOVE_LEN; j++)
+ parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
+ timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
+ timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
+ pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
+ pvInfoList[firstMove+i-1].depth = 0;
+ commentList[framePtr+i] = commentList[firstMove+i];
+ commentList[firstMove+i] = NULL;
+ }
+
+ storedGames++;
+ forwardMostMove = currentMove; // truncte game so we can start variation
+ if(storedGames == 1) GreyRevert(FALSE);
+}
+
+Boolean
+PopTail(Boolean annotate)
+{
+ int i, j, nrMoves;
+ char buf[8000], moveBuf[20];
+
+ if(appData.icsActive) return FALSE; // only in local mode
+ if(!storedGames) return FALSE; // sanity
+
+ storedGames--;
+ ToNrEvent(savedFirst[storedGames]); // sets currentMove
+ nrMoves = savedLast[storedGames] - currentMove;
+ if(annotate) {
+ int cnt = 10;
+ if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
+ else strcpy(buf, "(");
+ for(i=currentMove; i<forwardMostMove; i++) {
+ if(WhiteOnMove(i))
+ sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
+ else sprintf(moveBuf, " %s", SavePart(parseList[i]));
+ strcat(buf, moveBuf);
+ if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
+ }
+ strcat(buf, ")");
+ }
+ for(i=1; i<nrMoves; i++) { // copy last variation back
+ CopyBoard(boards[currentMove+i], boards[framePtr+i]);
+ for(j=0; j<MOVE_LEN; j++)
+ moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
+ for(j=0; j<2*MOVE_LEN; j++)
+ parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
+ timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
+ timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
+ pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
+ if(commentList[currentMove+i]) free(commentList[currentMove+i]);
+ commentList[currentMove+i] = commentList[framePtr+i];
+ commentList[framePtr+i] = NULL;
+ }
+ if(annotate) AppendComment(currentMove+1, buf, FALSE);
+ framePtr = savedFramePtr[storedGames];
+ gameInfo.result = savedResult[storedGames];
+ if(gameInfo.resultDetails != NULL) {
+ free(gameInfo.resultDetails);
+ }
+ gameInfo.resultDetails = savedDetails[storedGames];
+ forwardMostMove = currentMove + nrMoves;
+ if(storedGames == 0) GreyRevert(TRUE);
+ return TRUE;
+}
+
+void
+CleanupTail()
+{ // remove all shelved variations
+ int i;
+ for(i=0; i<storedGames; i++) {
+ if(savedDetails[i])
+ free(savedDetails[i]);
+ savedDetails[i] = NULL;
+ }
+ for(i=framePtr; i<MAX_MOVES; i++) {
+ if(commentList[i]) free(commentList[i]);
+ commentList[i] = NULL;
+ }
+ framePtr = MAX_MOVES-1;
+ storedGames = 0;
+}