* Massachusetts.
*
* Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
- * 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
+ * 2007, 2008, 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
*
* Enhancements Copyright 2005 Alessandro Scotti
*
#ifdef WIN32
#include <windows.h>
-#define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
+int flock(int f, int code);
+#define LOCK_EX 2
+#define SLASH '\\'
#else
-#define DoSleep( n ) if( (n) >= 0) sleep(n)
+#include <sys/file.h>
+#define SLASH '/'
#endif
# include "zippy.h"
#endif
#include "backendz.h"
-#include "gettext.h"
-
-#ifdef ENABLE_NLS
-# define _(s) gettext (s)
-# define N_(s) gettext_noop (s)
-#else
-# define _(s) (s)
-# define N_(s) s
-#endif
-
-
-/* A point in time */
-typedef struct {
- long sec; /* Assuming this is >= 32 bits */
- int ms; /* Assuming this is >= 16 bits */
-} TimeMark;
+#include "gettext.h"
+
+#ifdef ENABLE_NLS
+# define _(s) gettext (s)
+# define N_(s) gettext_noop (s)
+# define T_(s) gettext(s)
+#else
+# ifdef WIN32
+# define _(s) T_(s)
+# define N_(s) s
+# else
+# define _(s) (s)
+# define N_(s) s
+# define T_(s) s
+# endif
+#endif
+
int establish P((void));
void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
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 SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
void HandleMachineMove P((char *message, ChessProgramState *cps));
int AutoPlayOneMove P((void));
int LoadGameOneMove P((ChessMove readAhead));
int LoadGameFromFile P((char *filename, int n, char *title, int useList));
int LoadPositionFromFile P((char *filename, int n, char *title));
int SavePositionToFile P((char *filename));
-void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
- Board board));
void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
void ShowMove P((int fromX, int fromY, int toX, int toY));
int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
char *TimeControlTagValue P((void));
void Attention P((ChessProgramState *cps));
void FeedMovesToProgram P((ChessProgramState *cps, int upto));
-void ResurrectChessProgram P((void));
+int ResurrectChessProgram P((void));
void DisplayComment P((int moveNumber, char *text));
void DisplayMove P((int moveNumber));
void ResetClocks P((void));
char *PGNDate P((void));
void SetGameInfo P((void));
-Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
int RegisterMove P((void));
void MakeRegisteredMove P((void));
void TruncateGame P((void));
char *SavePart P((char *));
int SaveGameOldStyle P((FILE *));
int SaveGamePGN P((FILE *));
-void GetTimeMark P((TimeMark *));
-long SubtractTimeMarks P((TimeMark *, TimeMark *));
int CheckFlags P((void));
long NextTickLength P((long));
void CheckTimeControl P((void));
int PerpetualChase(int first, int last);
int EngineOutputIsUp();
void InitDrawingSizes(int x, int y);
+void NextMatchGame P((void));
+int NextTourneyGame P((int nr, int *swap));
+int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
+FILE *WriteTourneyFile P((char *results, FILE *f));
+void DisplayTwoMachinesTitle P(());
+static void ExcludeClick P((int index));
#ifdef WIN32
extern void ConsoleCreate();
void ics_update_width P((int new_width));
extern char installDir[MSG_SIZ];
VariantClass startVariant; /* [HGM] nicks: initial variant */
+Boolean abortMatch;
extern int tinyLayout, smallLayout;
ChessProgramStats programStats;
extern int chatCount;
int chattingPartner;
char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
+char lastMsg[MSG_SIZ];
+ChessSquare pieceSweep = EmptySquare;
+ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
+int promoDefaultAltered;
/* States for ics_getting_history */
#define H_FALSE 0
#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 );
+char*
+safeStrCpy (char *dst, const char *src, size_t count)
+{ // [HGM] made safe
+ int i;
+ assert( dst != NULL );
+ assert( src != NULL );
+ assert( count > 0 );
- strncpy( dst, src, count );
- dst[ count-1 ] = '\0';
- return dst;
+ for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
+ if( i == count && dst[count-1] != NULLCHAR)
+ {
+ dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
+ if(appData.debugMode)
+ fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
+ }
+
+ return dst;
}
/* Some compiler can't cast u64 to double
* We used this for all compiler
*/
double
-u64ToDouble(u64 value)
+u64ToDouble (u64 value)
{
double r;
u64 tmp = value & u64Const(0x7fffffffffffffff);
by this function.
*/
int
-PosFlags(index)
+PosFlags (index)
{
int flags = F_ALL_CASTLE_OK;
if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
case VariantKriegspiel:
flags |= F_KRIEGSPIEL_CAPTURE;
break;
- case VariantCapaRandom:
+ case VariantCapaRandom:
case VariantFischeRandom:
flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
case VariantNoCastle:
case VariantShatranj:
case VariantCourier:
case VariantMakruk:
+ case VariantGrand:
flags &= ~F_ALL_CASTLE_OK;
break;
default:
return flags;
}
-FILE *gameFileFP, *debugFP;
+FILE *gameFileFP, *debugFP, *serverFP;
+char *currentDebugFile; // [HGM] debug split: to remember name
-/*
+/*
[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
char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
char thinkOutput1[MSG_SIZ*10];
-ChessProgramState first, second;
+ChessProgramState first, second, pairing;
/* premove variables */
int premoveToX = 0;
char white_holding[64], black_holding[64];
TimeMark lastNodeCountTime;
long lastNodeCount=0;
+int shiftKey, controlKey; // [HGM] set by mouse handler
+
int have_sent_ICS_logon = 0;
int movesPerSession;
-long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
+int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
+long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
+Boolean adjustedClock;
long timeControl_2; /* [AS] Allow separate time controls */
-char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
+char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
long timeRemaining[2][MAX_MOVES];
-int matchGame = 0;
-TimeMark programStartTime;
+int matchGame = 0, nextGame = 0, roundNr = 0;
+Boolean waitingForGame = FALSE;
+TimeMark programStartTime, pauseStart;
char ics_handle[MSG_SIZ];
int have_set_title = 0;
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 loadFlag = 0;
+Boolean shuffleOpenings;
int mute; // mute all sounds
// [HGM] vari: next 12 to save and restore variations
void PushTail P((int firstMove, int lastMove));
Boolean PopTail P((Boolean annotate));
+void PushInner P((int firstMove, int lastMove));
+void PopInner P((Boolean annotate));
void CleanupTail P((void));
ChessSquare FIDEArray[2][BOARD_FILES] = {
BlackUnicorn, BlackBishop, BlackMan, BlackRook }
};
+ChessSquare SpartanArray[2][BOARD_FILES] = {
+ { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
+ WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
+ { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
+ BlackDragon, BlackKing, BlackAngel, BlackAlfil }
+};
+
ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
{ WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
- { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
- BlackKing, BlackMarshall, BlackAlfil, BlackLance }
+ { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
+ BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
};
ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
};
ChessSquare CapablancaArray[2][BOARD_FILES] = {
- { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
+ { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
- { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
+ { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
};
ChessSquare GreatArray[2][BOARD_FILES] = {
- { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
+ { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
- { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
+ { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
};
ChessSquare JanusArray[2][BOARD_FILES] = {
- { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
+ { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
- { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
+ { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
};
+ChessSquare GrandArray[2][BOARD_FILES] = {
+ { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
+ WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
+ { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
+ BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
+};
+
#ifdef GOTHIC
ChessSquare GothicArray[2][BOARD_FILES] = {
- { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
+ { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
- { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
+ { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
};
#else // !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 }
+ { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
+ WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
+ { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
+ BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
};
#else // !FALCON
#define FalconArray CapablancaArray
"++++", etc. Also strips ()'s */
int
-string_to_rating(str)
- char *str;
+string_to_rating (char *str)
{
while(*str && !isdigit(*str)) ++str;
if (!*str)
}
void
-ClearProgramStats()
+ClearProgramStats ()
{
/* Init programStats */
programStats.movelist[0] = 0;
}
void
-InitBackEnd1()
+CommonEngineInit ()
+{ // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
+ if (appData.firstPlaysBlack) {
+ first.twoMachinesColor = "black\n";
+ second.twoMachinesColor = "white\n";
+ } else {
+ first.twoMachinesColor = "white\n";
+ second.twoMachinesColor = "black\n";
+ }
+
+ first.other = &second;
+ second.other = &first;
+
+ { float norm = 1;
+ if(appData.timeOddsMode) {
+ norm = appData.timeOdds[0];
+ if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
+ }
+ first.timeOdds = appData.timeOdds[0]/norm;
+ second.timeOdds = appData.timeOdds[1]/norm;
+ }
+
+ if(programVersion) free(programVersion);
+ if (appData.noChessProgram) {
+ programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
+ sprintf(programVersion, "%s", PACKAGE_STRING);
+ } else {
+ /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
+ programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
+ sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
+ }
+}
+
+void
+UnloadEngine (ChessProgramState *cps)
+{
+ /* Kill off first chess program */
+ if (cps->isr != NULL)
+ RemoveInputSource(cps->isr);
+ cps->isr = NULL;
+
+ if (cps->pr != NoProc) {
+ ExitAnalyzeMode();
+ DoSleep( appData.delayBeforeQuit );
+ SendToProgram("quit\n", cps);
+ DoSleep( appData.delayAfterQuit );
+ DestroyChildProcess(cps->pr, cps->useSigterm);
+ }
+ cps->pr = NoProc;
+ if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
+}
+
+void
+ClearOptions (ChessProgramState *cps)
+{
+ int i;
+ cps->nrOptions = cps->comboCnt = 0;
+ for(i=0; i<MAX_OPTIONS; i++) {
+ cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
+ cps->option[i].textValue = 0;
+ }
+}
+
+char *engineNames[] = {
+ /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
+ such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
+N_("first"),
+ /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
+ such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
+N_("second")
+};
+
+void
+InitEngine (ChessProgramState *cps, int n)
+{ // [HGM] all engine initialiation put in a function that does one engine
+
+ ClearOptions(cps);
+
+ cps->which = engineNames[n];
+ cps->maybeThinking = FALSE;
+ cps->pr = NoProc;
+ cps->isr = NULL;
+ cps->sendTime = 2;
+ cps->sendDrawOffers = 1;
+
+ cps->program = appData.chessProgram[n];
+ cps->host = appData.host[n];
+ cps->dir = appData.directory[n];
+ cps->initString = appData.engInitString[n];
+ cps->computerString = appData.computerString[n];
+ cps->useSigint = TRUE;
+ cps->useSigterm = TRUE;
+ cps->reuse = appData.reuse[n];
+ cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
+ cps->useSetboard = FALSE;
+ cps->useSAN = FALSE;
+ cps->usePing = FALSE;
+ cps->lastPing = 0;
+ cps->lastPong = 0;
+ cps->usePlayother = FALSE;
+ cps->useColors = TRUE;
+ cps->useUsermove = FALSE;
+ cps->sendICS = FALSE;
+ cps->sendName = appData.icsActive;
+ cps->sdKludge = FALSE;
+ cps->stKludge = FALSE;
+ TidyProgramName(cps->program, cps->host, cps->tidy);
+ cps->matchWins = 0;
+ safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
+ cps->analysisSupport = 2; /* detect */
+ cps->analyzing = FALSE;
+ cps->initDone = FALSE;
+
+ /* New features added by Tord: */
+ cps->useFEN960 = FALSE;
+ cps->useOOCastle = TRUE;
+ /* End of new features added by Tord. */
+ cps->fenOverride = appData.fenOverride[n];
+
+ /* [HGM] time odds: set factor for each machine */
+ cps->timeOdds = appData.timeOdds[n];
+
+ /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
+ cps->accumulateTC = appData.accumulateTC[n];
+ cps->maxNrOfSessions = 1;
+
+ /* [HGM] debug */
+ cps->debug = FALSE;
+
+ cps->supportsNPS = UNKNOWN;
+ cps->memSize = FALSE;
+ cps->maxCores = FALSE;
+ cps->egtFormats[0] = NULLCHAR;
+
+ /* [HGM] options */
+ cps->optionSettings = appData.engOptions[n];
+
+ cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
+ cps->isUCI = appData.isUCI[n]; /* [AS] */
+ cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
+
+ if (appData.protocolVersion[n] > PROTOVER
+ || appData.protocolVersion[n] < 1)
+ {
+ char buf[MSG_SIZ];
+ int len;
+
+ len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
+ appData.protocolVersion[n]);
+ if( (len >= MSG_SIZ) && appData.debugMode )
+ fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
+
+ DisplayFatalError(buf, 0, 2);
+ }
+ else
+ {
+ cps->protocolVersion = appData.protocolVersion[n];
+ }
+
+ InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
+ ParseFeatures(appData.featureDefaults, cps);
+}
+
+ChessProgramState *savCps;
+
+void
+LoadEngine ()
+{
+ int i;
+ if(WaitForEngine(savCps, LoadEngine)) return;
+ CommonEngineInit(); // recalculate time odds
+ if(gameInfo.variant != StringToVariant(appData.variant)) {
+ // we changed variant when loading the engine; this forces us to reset
+ Reset(TRUE, savCps != &first);
+ EditGameEvent(); // for consistency with other path, as Reset changes mode
+ }
+ InitChessProgram(savCps, FALSE);
+ SendToProgram("force\n", savCps);
+ DisplayMessage("", "");
+ if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
+ for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
+ ThawUI();
+ SetGNUMode();
+}
+
+void
+ReplaceEngine (ChessProgramState *cps, int n)
+{
+ EditGameEvent();
+ UnloadEngine(cps);
+ appData.noChessProgram = FALSE;
+ appData.clockMode = TRUE;
+ InitEngine(cps, n);
+ UpdateLogos(TRUE);
+ if(n) return; // only startup first engine immediately; second can wait
+ savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
+ LoadEngine();
+}
+
+extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
+extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
+
+static char resetOptions[] =
+ "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
+ "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
+ "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
+ "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
+
+void
+FloatToFront(char **list, char *engineLine)
+{
+ char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
+ int i=0;
+ if(appData.recentEngines <= 0) return;
+ TidyProgramName(engineLine, "localhost", tidy+1);
+ tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
+ strncpy(buf+1, *list, MSG_SIZ-50);
+ if(p = strstr(buf, tidy)) { // tidy name appears in list
+ q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
+ while(*p++ = *++q); // squeeze out
+ }
+ strcat(tidy, buf+1); // put list behind tidy name
+ p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
+ if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
+ ASSIGN(*list, tidy+1);
+}
+
+char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
+
+void
+Load (ChessProgramState *cps, int i)
+{
+ char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
+ if(engineLine && engineLine[0]) { // an engine was selected from the combo box
+ snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
+ SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
+ ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
+ FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
+ appData.firstProtocolVersion = PROTOVER;
+ ParseArgsFromString(buf);
+ SwapEngines(i);
+ ReplaceEngine(cps, i);
+ FloatToFront(&appData.recentEngineList, engineLine);
+ return;
+ }
+ p = engineName;
+ while(q = strchr(p, SLASH)) p = q+1;
+ if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
+ if(engineDir[0] != NULLCHAR) {
+ ASSIGN(appData.directory[i], engineDir); p = engineName;
+ } else if(p != engineName) { // derive directory from engine path, when not given
+ p[-1] = 0;
+ ASSIGN(appData.directory[i], engineName);
+ p[-1] = SLASH;
+ if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
+ } else { ASSIGN(appData.directory[i], "."); }
+ if(params[0]) {
+ if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
+ snprintf(command, MSG_SIZ, "%s %s", p, params);
+ p = command;
+ }
+ ASSIGN(appData.chessProgram[i], p);
+ appData.isUCI[i] = isUCI;
+ appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
+ appData.hasOwnBookUCI[i] = hasBook;
+ if(!nickName[0]) useNick = FALSE;
+ if(useNick) ASSIGN(appData.pgnName[i], nickName);
+ if(addToList) {
+ int len;
+ char quote;
+ q = firstChessProgramNames;
+ if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
+ quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
+ snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
+ quote, p, quote, appData.directory[i],
+ useNick ? " -fn \"" : "",
+ useNick ? nickName : "",
+ useNick ? "\"" : "",
+ v1 ? " -firstProtocolVersion 1" : "",
+ hasBook ? "" : " -fNoOwnBookUCI",
+ isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
+ storeVariant ? " -variant " : "",
+ storeVariant ? VariantName(gameInfo.variant) : "");
+ if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
+ firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
+ if(insert != q) insert[-1] = NULLCHAR;
+ snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
+ if(q) free(q);
+ FloatToFront(&appData.recentEngineList, buf);
+ }
+ ReplaceEngine(cps, i);
+}
+
+void
+InitTimeControls ()
{
int matched, min, sec;
+ /*
+ * Parse timeControl resource
+ */
+ if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
+ appData.movesPerSession)) {
+ char buf[MSG_SIZ];
+ snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
+ DisplayFatalError(buf, 0, 2);
+ }
+
+ /*
+ * Parse searchTime resource
+ */
+ if (*appData.searchTime != NULLCHAR) {
+ matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
+ if (matched == 1) {
+ searchTime = min * 60;
+ } else if (matched == 2) {
+ searchTime = min * 60 + sec;
+ } else {
+ char buf[MSG_SIZ];
+ snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
+ DisplayFatalError(buf, 0, 2);
+ }
+ }
+}
+
+void
+InitBackEnd1 ()
+{
ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
GetTimeMark(&programStartTime);
srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
+ appData.seedBase = random() + (random()<<15);
+ pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
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;
}
}
- /*
- * Parse timeControl resource
- */
- if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
- appData.movesPerSession)) {
- char buf[MSG_SIZ];
- snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
- DisplayFatalError(buf, 0, 2);
- }
-
- /*
- * Parse searchTime resource
- */
- if (*appData.searchTime != NULLCHAR) {
- matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
- if (matched == 1) {
- searchTime = min * 60;
- } else if (matched == 2) {
- searchTime = min * 60 + sec;
- } else {
- char buf[MSG_SIZ];
- snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
- DisplayFatalError(buf, 0, 2);
- }
- }
+ InitTimeControls();
/* [AS] Adjudication threshold */
adjudicateLossThreshold = appData.adjudicateLossThreshold;
-
- first.which = "first";
- second.which = "second";
- first.maybeThinking = second.maybeThinking = FALSE;
- first.pr = second.pr = NoProc;
- first.isr = second.isr = NULL;
- first.sendTime = second.sendTime = 2;
- first.sendDrawOffers = 1;
- if (appData.firstPlaysBlack) {
- first.twoMachinesColor = "black\n";
- second.twoMachinesColor = "white\n";
- } else {
- first.twoMachinesColor = "white\n";
- second.twoMachinesColor = "black\n";
- }
- first.program = appData.firstChessProgram;
- second.program = appData.secondChessProgram;
- first.host = appData.firstHost;
- second.host = appData.secondHost;
- first.dir = appData.firstDirectory;
- second.dir = appData.secondDirectory;
- first.other = &second;
- second.other = &first;
- first.initString = appData.initString;
- second.initString = appData.secondInitString;
- first.computerString = appData.firstComputerString;
- second.computerString = appData.secondComputerString;
- first.useSigint = second.useSigint = TRUE;
- 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.lastPing = second.lastPing = 0;
- first.lastPong = second.lastPong = 0;
- first.usePlayother = second.usePlayother = FALSE;
- first.useColors = second.useColors = TRUE;
- first.useUsermove = second.useUsermove = FALSE;
- first.sendICS = second.sendICS = FALSE;
- first.sendName = second.sendName = appData.icsActive;
- first.sdKludge = second.sdKludge = FALSE;
- first.stKludge = second.stKludge = FALSE;
- TidyProgramName(first.program, first.host, first.tidy);
- TidyProgramName(second.program, second.host, second.tidy);
- first.matchWins = second.matchWins = 0;
- strcpy(first.variants, appData.variant);
- strcpy(second.variants, appData.variant);
- first.analysisSupport = second.analysisSupport = 2; /* detect */
- 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;
+ InitEngine(&first, 0);
+ InitEngine(&second, 1);
+ CommonEngineInit();
- /* [HGM] time odds: set factor for each machine */
- first.timeOdds = appData.firstTimeOdds;
- second.timeOdds = appData.secondTimeOdds;
- { float 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];
- sprintf(buf, _("protocol version %d not supported"),
- appData.firstProtocolVersion);
- DisplayFatalError(buf, 0, 2);
- } else {
- first.protocolVersion = appData.firstProtocolVersion;
- }
-
- if (appData.secondProtocolVersion > PROTOVER ||
- appData.secondProtocolVersion < 1) {
- char buf[MSG_SIZ];
- sprintf(buf, _("protocol version %d not supported"),
- appData.secondProtocolVersion);
- DisplayFatalError(buf, 0, 2);
- } else {
- second.protocolVersion = appData.secondProtocolVersion;
- }
+ pairing.which = "pairing"; // pairing engine
+ pairing.pr = NoProc;
+ pairing.isr = NULL;
+ pairing.program = appData.pairingEngine;
+ pairing.host = "localhost";
+ pairing.dir = ".";
if (appData.icsActive) {
appData.clockMode = TRUE; /* changes dynamically in ICS mode */
-// } 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(PACKAGE_STRING));
- sprintf(programVersion, "%s", PACKAGE_STRING);
- } else {
- /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
- programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
- sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
- }
if (!appData.icsActive) {
char buf[MSG_SIZ];
+ int len;
+
/* Check for variants that are supported only in ICS mode,
or not at all. Some that are accepted here nevertheless
have bugs; see comments below.
switch (variant) {
case VariantBughouse: /* need four players and two boards */
case VariantKriegspiel: /* need to hide pieces and move details */
- /* case VariantFischeRandom: (Fabien: moved below) */
- sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
+ /* case VariantFischeRandom: (Fabien: moved below) */
+ len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
+ if( (len >= MSG_SIZ) && appData.debugMode )
+ fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
+
DisplayFatalError(buf, 0, 2);
return;
case Variant35:
case Variant36:
default:
- sprintf(buf, _("Unknown variant name %s"), appData.variant);
+ len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
+ if( (len >= MSG_SIZ) && appData.debugMode )
+ fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
+
DisplayFatalError(buf, 0, 2);
return;
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 VariantShogi: /* [HGM] could still mate with pawn drop */
case VariantKnightmate: /* [HGM] should work */
case VariantCylinder: /* [HGM] untested */
case VariantFalcon: /* [HGM] untested */
case VariantAtomic: /* should work except for win condition */
case Variant3Check: /* should work except for win condition */
case VariantShatranj: /* should work except for all win conditions */
- case VariantMakruk: /* should work except for daw countdown */
+ case VariantMakruk: /* should work except for draw countdown */
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 */
+ case VariantSChess: /* S-Chess, should work */
+ case VariantGrand: /* should work */
+ case VariantSpartan: /* should work */
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
+NextIntegerFromString (char ** str, long * value)
{
int result = -1;
char * s = *str;
return result;
}
-int NextTimeControlFromString( char ** str, long * value )
+int
+NextTimeControlFromString (char ** str, long * value)
{
long temp;
int result = NextIntegerFromString( str, &temp );
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;
+int
+NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
+{ /* [HGM] routine added to read '+moves/time' for secondary time control. */
+ int result = -1, type = 0; long temp, temp2;
- if(**str != '+') return -1; // old params remain in force!
+ if(**str != ':') return -1; // old params remain in force!
(*str)++;
- if( NextTimeControlFromString( str, &temp ) ) return -1;
+ if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
+ if( NextIntegerFromString( str, &temp ) ) return -1;
+ if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
if(**str != '/') {
/* time only: incremental or sudden-death time control */
if(**str == '+') { /* increment follows; read it */
(*str)++;
+ if(**str == '!') type = *(*str)++; // Bronstein TC
if(result = NextIntegerFromString( str, &temp2)) return -1;
*inc = temp2 * 1000;
+ if(**str == '.') { // read fraction of increment
+ char *start = ++(*str);
+ if(result = NextIntegerFromString( str, &temp2)) return -1;
+ temp2 *= 1000;
+ while(start++ < *str) temp2 /= 10;
+ *inc += temp2;
+ }
} else *inc = 0;
- *moves = 0; *tc = temp * 1000;
+ *moves = 0; *tc = temp * 1000; *incType = type;
return 0;
- } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
+ }
(*str)++; /* classical time control */
- result = NextTimeControlFromString( str, &temp2);
+ result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
+
if(result == 0) {
- *moves = temp/60;
+ *moves = temp;
*tc = temp2 * 1000;
*inc = 0;
+ *incType = type;
}
return result;
}
-int GetTimeQuota(int movenr)
+int
+GetTimeQuota (int movenr, int lastUsed, char *tcString)
{ /* [HGM] get time to add from the multi-session time-control string */
- int moves=1; /* kludge to force reading of first session */
+ int incType, moves=1; /* kludge to force reading of first session */
long time, increment;
- char *s = fullTimeControlString;
+ char *s = tcString;
- if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
+ if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
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(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
+ nextSession = s; suddenDeath = moves == 0 && increment == 0;
if(movenr == -1) return time; /* last move before new session */
+ if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
+ if(incType == '!' && lastUsed < increment) increment = lastUsed;
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 */
}
int
-ParseTimeControl(tc, ti, mps)
- char *tc;
- int ti;
- int mps;
+ParseTimeControl (char *tc, float ti, int mps)
{
long tc1;
long tc2;
- char buf[MSG_SIZ];
-
+ char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
+ int min, sec=0;
+
if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
+ if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
+ sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
if(ti > 0) {
+
if(mps)
- sprintf(buf, "+%d/%s+%d", mps, tc, ti);
- else sprintf(buf, "+%s+%d", tc, ti);
+ snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
+ else
+ snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
} else {
if(mps)
- sprintf(buf, "+%d/%s", mps, tc);
- else sprintf(buf, "+%s", tc);
+ snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
+ else
+ snprintf(buf, MSG_SIZ, ":%s", mytc);
}
- fullTimeControlString = StrSave(buf);
+ fullTimeControlString = StrSave(buf); // this should now be in PGN format
if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
return FALSE;
}
-
+
if( *tc == '/' ) {
/* Parse second time control */
tc++;
-
+
if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
return FALSE;
}
-
+
if( tc2 == 0 ) {
return FALSE;
}
-
+
timeControl_2 = tc2 * 1000;
}
else {
timeControl_2 = 0;
}
-
+
if( tc1 == 0 ) {
return FALSE;
}
-
+
timeControl = tc1 * 1000;
-
+
if (ti >= 0) {
timeIncrement = ti * 1000; /* convert to ms */
movesPerSession = 0;
}
void
-InitBackEnd2()
+InitBackEnd2 ()
{
if (appData.debugMode) {
fprintf(debugFP, "%s\n", programVersion);
}
+ ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
set_cont_sequence(appData.wrapContSeq);
if (appData.matchGames > 0) {
}
}
+int
+CalculateIndex (int index, int gameNr)
+{ // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
+ int res;
+ if(index > 0) return index; // fixed nmber
+ if(index == 0) return 1;
+ res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
+ if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
+ return res;
+}
+
+int
+LoadGameOrPosition (int gameNr)
+{ // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
+ if (*appData.loadGameFile != NULLCHAR) {
+ if (!LoadGameFromFile(appData.loadGameFile,
+ CalculateIndex(appData.loadGameIndex, gameNr),
+ appData.loadGameFile, FALSE)) {
+ DisplayFatalError(_("Bad game file"), 0, 1);
+ return 0;
+ }
+ } else if (*appData.loadPositionFile != NULLCHAR) {
+ if (!LoadPositionFromFile(appData.loadPositionFile,
+ CalculateIndex(appData.loadPositionIndex, gameNr),
+ appData.loadPositionFile)) {
+ DisplayFatalError(_("Bad position file"), 0, 1);
+ return 0;
+ }
+ }
+ return 1;
+}
+
+void
+ReserveGame (int gameNr, char resChar)
+{
+ FILE *tf = fopen(appData.tourneyFile, "r+");
+ char *p, *q, c, buf[MSG_SIZ];
+ if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
+ safeStrCpy(buf, lastMsg, MSG_SIZ);
+ DisplayMessage(_("Pick new game"), "");
+ flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
+ ParseArgsFromFile(tf);
+ p = q = appData.results;
+ if(appData.debugMode) {
+ char *r = appData.participants;
+ fprintf(debugFP, "results = '%s'\n", p);
+ while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
+ fprintf(debugFP, "\n");
+ }
+ while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
+ nextGame = q - p;
+ q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
+ safeStrCpy(q, p, strlen(p) + 2);
+ if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
+ if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
+ if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
+ if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
+ q[nextGame] = '*';
+ }
+ fseek(tf, -(strlen(p)+4), SEEK_END);
+ c = fgetc(tf);
+ if(c != '"') // depending on DOS or Unix line endings we can be one off
+ fseek(tf, -(strlen(p)+2), SEEK_END);
+ else fseek(tf, -(strlen(p)+3), SEEK_END);
+ fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
+ DisplayMessage(buf, "");
+ free(p); appData.results = q;
+ if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
+ (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
+ int round = appData.defaultMatchGames * appData.tourneyType;
+ if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
+ appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
+ UnloadEngine(&first); // next game belongs to other pairing;
+ UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
+ }
+ if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
+}
+
+void
+MatchEvent (int mode)
+{ // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
+ int dummy;
+ if(matchMode) { // already in match mode: switch it off
+ abortMatch = TRUE;
+ if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
+ return;
+ }
+// if(gameMode != BeginningOfGame) {
+// DisplayError(_("You can only start a match from the initial position."), 0);
+// return;
+// }
+ abortMatch = FALSE;
+ if(mode == 2) appData.matchGames = appData.defaultMatchGames;
+ /* Set up machine vs. machine match */
+ nextGame = 0;
+ NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
+ if(appData.tourneyFile[0]) {
+ ReserveGame(-1, 0);
+ if(nextGame > appData.matchGames) {
+ char buf[MSG_SIZ];
+ if(strchr(appData.results, '*') == NULL) {
+ FILE *f;
+ appData.tourneyCycles++;
+ if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
+ fclose(f);
+ NextTourneyGame(-1, &dummy);
+ ReserveGame(-1, 0);
+ if(nextGame <= appData.matchGames) {
+ DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
+ matchMode = mode;
+ ScheduleDelayedEvent(NextMatchGame, 10000);
+ return;
+ }
+ }
+ }
+ snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
+ DisplayError(buf, 0);
+ appData.tourneyFile[0] = 0;
+ return;
+ }
+ } else
+ if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
+ DisplayFatalError(_("Can't have a match with no chess programs"),
+ 0, 2);
+ return;
+ }
+ matchMode = mode;
+ matchGame = roundNr = 1;
+ first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
+ NextMatchGame();
+}
+
+char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
+
void
InitBackEnd3 P((void))
{
GameMode initialMode;
char buf[MSG_SIZ];
- int err;
+ int err, len;
InitChessProgram(&first, startedFromSetupPosition);
free(programVersion);
programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
+ FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
}
if (appData.icsActive) {
#ifdef WIN32
/* [DM] Make a console window if needed [HGM] merged ifs */
- ConsoleCreate();
+ ConsoleCreate();
#endif
err = establish();
- if (err != 0) {
- if (*appData.icsCommPort != NULLCHAR) {
- sprintf(buf, _("Could not open comm port %s"),
- appData.icsCommPort);
- } else {
- snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
+ if (err != 0)
+ {
+ if (*appData.icsCommPort != NULLCHAR)
+ len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
+ appData.icsCommPort);
+ else
+ len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
appData.icsHost, appData.icsPort);
- }
+
+ if( (len >= MSG_SIZ) && appData.debugMode )
+ fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
+
DisplayFatalError(buf, err, 1);
return;
}
cmailISR =
AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
}
-
+
ThawUI();
DisplayMessage("", "");
if (StrCaseCmp(appData.initialMode, "") == 0) {
initialMode = BeginningOfGame;
+ if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
+ gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
+ ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
+ gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
+ ModeHighlight();
+ }
} 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) {
} else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
initialMode = Training;
} else {
- sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
+ len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
+ if( (len >= MSG_SIZ) && appData.debugMode )
+ fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
+
DisplayFatalError(buf, 0, 2);
return;
}
if (appData.matchMode) {
- /* Set up machine vs. machine match */
- if (appData.noChessProgram) {
- DisplayFatalError(_("Can't have a match with no chess programs"),
- 0, 2);
- return;
- }
- matchMode = TRUE;
- matchGame = 1;
- if (*appData.loadGameFile != NULLCHAR) {
- int index = appData.loadGameIndex; // [HGM] autoinc
- if(index<0) lastIndex = index = 1;
- if (!LoadGameFromFile(appData.loadGameFile,
- 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,
- index,
- appData.loadPositionFile)) {
- DisplayFatalError(_("Bad position file"), 0, 1);
- return;
- }
- }
- TwoMachinesEvent();
+ if(appData.tourneyFile[0]) { // start tourney from command line
+ FILE *f;
+ if(f = fopen(appData.tourneyFile, "r")) {
+ ParseArgsFromFile(f); // make sure tourney parmeters re known
+ fclose(f);
+ appData.clockMode = TRUE;
+ SetGNUMode();
+ } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
+ }
+ MatchEvent(TRUE);
} else if (*appData.cmailGameName != NULLCHAR) {
/* Set up cmail mode */
ReloadCmailMsgEvent(TRUE);
}
}
+void
+HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
+{
+ DisplayBook(current+1);
+
+ MoveHistorySet( movelist, first, last, current, pvInfoList );
+
+ EvalGraphSet( first, last, current, pvInfoList );
+
+ MakeEngineOutputTitle();
+}
+
/*
* Establish will establish a contact to a remote host.port.
* Sets icsPR to a ProcRef for a process (or pseudo-process)
* Returns 0 if okay, error code if not.
*/
int
-establish()
+establish ()
{
char buf[MSG_SIZ];
appData.icsHost, appData.icsPort);
} else {
snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
- appData.remoteShell, appData.gateway,
+ appData.remoteShell, appData.gateway,
appData.remoteUser, appData.telnetProgram,
appData.icsHost, appData.icsPort);
}
}
}
-void EscapeExpand(char *p, char *q)
+void
+EscapeExpand (char *p, char *q)
{ // [HGM] initstring: routine to shape up string arguments
while(*p++ = *q++) if(p[-1] == '\\')
switch(*q++) {
}
void
-show_bytes(fp, buf, count)
- FILE *fp;
- char *buf;
- int count;
+show_bytes (FILE *fp, char *buf, int count)
{
while (count--) {
if (*buf < 040 || *(unsigned char *) buf > 0177) {
/* Returns an errno value */
int
-OutputMaybeTelnet(pr, message, count, outError)
- ProcRef pr;
- char *message;
- int count;
- int *outError;
+OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
{
char buf[8192], *p, *q, *buflim;
int left, newcount, outcount;
}
void
-read_from_player(isr, closure, message, count, error)
- InputSourceRef isr;
- VOIDSTAR closure;
- char *message;
- int count;
- int error;
+read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
{
int outError, outCount;
static int gotEof = 0;
}
void
-KeepAlive()
+KeepAlive ()
{ // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
connectionAlive = FALSE; // only sticks if no response to 'date' command.
}
/* added routine for printf style output to ics */
-void ics_printf(char *format, ...)
+void
+ics_printf (char *format, ...)
{
char buffer[MSG_SIZ];
va_list args;
}
void
-SendToICS(s)
- char *s;
+SendToICS (char *s)
{
int count, outCount, outError;
- if (icsPR == NULL) return;
+ if (icsPR == NoProc) return;
count = strlen(s);
outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
without a delay causes problems when using timestamp on ICC
(at least on my machine). */
void
-SendToICSDelayed(s,msdelay)
- char *s;
- long msdelay;
+SendToICSDelayed (char *s, long msdelay)
{
int count, outCount, outError;
- if (icsPR == NULL) return;
+ if (icsPR == NoProc) return;
count = strlen(s);
if (appData.debugMode) {
/* Remove all highlighting escape sequences in s
- Also deletes any suffix starting with '('
+ Also deletes any suffix starting with '('
*/
char *
-StripHighlightAndTitle(s)
- char *s;
+StripHighlightAndTitle (char *s)
{
static char retbuf[MSG_SIZ];
char *p = retbuf;
/* Remove all highlighting escape sequences in s */
char *
-StripHighlight(s)
- char *s;
+StripHighlight (char *s)
{
static char retbuf[MSG_SIZ];
char *p = retbuf;
char *variantNames[] = VARIANT_NAMES;
char *
-VariantName(v)
- VariantClass v;
+VariantName (VariantClass v)
{
return variantNames[v];
}
/* Identify a variant from the strings the chess servers use or the
PGN Variant tag names we use. */
VariantClass
-StringToVariant(e)
- char *e;
+StringToVariant (char *e)
{
char *p;
int wnum = -1;
VariantClass v = VariantNormal;
int i, found = FALSE;
char buf[MSG_SIZ];
+ int len;
if (!e) return v;
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")) ||
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;
v = VariantNormal;
break;
default:
- sprintf(buf, _("Unknown wild type %d"), wnum);
+ len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
+ if( (len >= MSG_SIZ) && appData.debugMode )
+ fprintf(debugFP, "StringToVariant: buffer truncated.\n");
+
DisplayError(buf, 0);
v = VariantUnknown;
break;
copied into star_match.
*/
int
-looking_at(buf, index, pattern)
- char *buf;
- int *index;
- char *pattern;
+looking_at ( char *buf, int *index, char *pattern)
{
char *bufp = &buf[*index], *patternp = pattern;
int star_count = 0;
char *matchp = star_match[0];
-
+
for (;;) {
if (*patternp == NULLCHAR) {
*index = leftover_start = bufp - buf;
}
void
-SendToPlayer(data, length)
- char *data;
- int length;
+SendToPlayer (char *data, int length)
{
int error, outCount;
outCount = OutputToProcess(NoProc, data, length, &error);
}
void
-PackHolding(packed, holding)
- char packed[];
- char *holding;
+PackHolding (char packed[], char *holding)
{
char *p = holding;
char *q = packed;
/* Telnet protocol requests from the front end */
void
-TelnetRequest(ddww, option)
- unsigned char ddww, option;
+TelnetRequest (unsigned char ddww, unsigned char option)
{
unsigned char msg[3];
int outCount, outError;
break;
default:
ddwwStr = buf1;
- sprintf(buf1, "%d", ddww);
+ snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
break;
}
switch (option) {
break;
default:
optionStr = buf2;
- sprintf(buf2, "%d", option);
+ snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
break;
}
fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
}
void
-DoEcho()
+DoEcho ()
{
if (!appData.icsActive) return;
TelnetRequest(TN_DO, TN_ECHO);
}
void
-DontEcho()
+DontEcho ()
{
if (!appData.icsActive) return;
TelnetRequest(TN_DONT, TN_ECHO);
}
void
-CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
+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;
void
-VariantSwitch(Board board, VariantClass newVariant)
+VariantSwitch (Board board, VariantClass newVariant)
{
int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
static Board oldBoard;
* 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));
}
shuffleOpenings = 0; /* [HGM] shuffle */
gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
- switch(newVariant)
+ switch(newVariant)
{
case VariantShogi:
newWidth = 9; newHeight = 9;
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(i=0; i<BOARD_HEIGHT; i++)
for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
board[i][j];
extern int squareSize, lineGap;
void
-PlotSeekAd(int i)
+PlotSeekAd (int i)
{
int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
}
void
-AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
+AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
{
char buf[MSG_SIZ], *ext = "";
VariantClass v = StringToVariant(type);
if(v == VariantLoadable) type = "setup"; else
type = VariantName(v);
}
- sprintf(buf, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
+ snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
if(nrOfSeekAds < MAX_SEEK_ADS-1) {
if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
}
void
-EraseSeekDot(int i)
+EraseSeekDot (int i)
{
int x = xList[i], y = yList[i], d=squareSize/4, k;
DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
}
void
-RemoveSeekAd(int nr)
+RemoveSeekAd (int nr)
{
int i;
for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
}
Boolean
-MatchSoughtLine(char *line)
+MatchSoughtLine (char *line)
{
char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
int nr, base, inc, u=0; char dummy;
}
int
-DrawSeekGraph()
+DrawSeekGraph ()
{
int i;
if(!seekGraphUp) return FALSE;
DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
if(i%500 == 0) {
char buf[MSG_SIZ];
- sprintf(buf, "%d", i);
+ snprintf(buf, MSG_SIZ, "%d", i);
DrawSeekText(buf, hMargin+squareSize/8+7, yy);
}
}
DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
char buf[MSG_SIZ];
- sprintf(buf, "%d", i);
+ snprintf(buf, MSG_SIZ, "%d", i);
DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
}
}
return TRUE;
}
-int SeekGraphClick(ClickType click, int x, int y, int moving)
+int
+SeekGraphClick (ClickType click, int x, int y, int moving)
{
static int lastDown = 0, displayed = 0, lastSecond;
+ if(y < 0) return FALSE;
+ if(!(appData.seekGraph && appData.icsActive && loggedOn &&
+ (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
+ if(!seekGraphUp) return FALSE;
+ seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
+ DrawPosition(TRUE, NULL);
+ return TRUE;
+ }
if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
if(click == Release || moving) return FALSE;
nrOfSeekAds = 0;
return TRUE;
} // on press 'hit', only show info
if(moving == 2) return TRUE; // ignore right up-clicks on dot
- sprintf(buf, "play %d\n", seekNrList[closest]);
+ snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
SendToICS(ics_prefix);
SendToICS(buf);
return TRUE; // let incoming board of started game pop down the graph
}
void
-read_from_ics(isr, closure, data, count, error)
- InputSourceRef isr;
- VOIDSTAR closure;
- char *data;
- int count;
- int error;
+read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
{
#define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
#define STARTED_NONE 0
#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 int savingComment = FALSE;
static int cmatch = 0; // continuation sequence match
char *bp;
- char str[500];
+ char str[MSG_SIZ];
int i, oldi;
int buf_len;
int next_out;
// next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
next_out = 0;
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,
+ snprintf(str, MSG_SIZ,
"/set-quietly interface %s\n/set-quietly style 12\n",
programVersion);
if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
strcat(str, "/set-2 51 1\n/set seek 1\n");
} else if (ics_type == ICS_CHESSNET) {
- sprintf(str, "/style 12\n");
+ snprintf(str, MSG_SIZ, "/style 12\n");
} else {
- strcpy(str, "alias $ @\n$set interface ");
+ safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
strcat(str, programVersion);
strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
parse[parse_pos] = NULLCHAR;
if(chattingPartner>=0) {
char mess[MSG_SIZ];
- sprintf(mess, "%s%s", talker, parse);
+ snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
OutputChatMessage(chattingPartner, mess);
chattingPartner = -1;
next_out = i+1; // [HGM] suppress printing in ICS window
OutputKibitz(suppressKibitz, parse);
} else {
char tmp[MSG_SIZ];
- sprintf(tmp, _("your opponent kibitzes: %s"), parse);
+ if(gameMode == IcsObserving) // restore original ICS messages
+ snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
+ else
+ snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
SendToPlayer(tmp, strlen(tmp));
}
next_out = i+1; // [HGM] suppress printing in ICS window
(looking_at(buf, &i, "\"*\" is *a registered name") ||
looking_at(buf, &i, "Logging you in as \"*\"") ||
looking_at(buf, &i, "will be \"*\""))) {
- strcpy(ics_handle, star_match[0]);
+ safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
continue;
}
if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
int s = (ics_type == ICS_ICC); // ICC format differs
if(seekGraphUp)
- AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
+ AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
looking_at(buf, &i, "*% "); // eat prompt
if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
}
// [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
- if (appData.autoKibitz && started == STARTED_NONE &&
+ if (appData.autoKibitz && started == STARTED_NONE &&
!appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
(gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
- if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
- (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
+ if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
+ looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
+ (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 (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
savingComment = TRUE;
suppressKibitz = gameMode != IcsObserving ? 2 :
(StrStr(star_match[0], gameInfo.white) == NULL) + 1;
- }
+ }
continue;
} else
if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
}
} // [HGM] kibitz: end of patch
+ if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
+
// [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:") ||
+ if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
looking_at(buf, &i, "* whispers:") ||
looking_at(buf, &i, "* kibitzes:") ||
looking_at(buf, &i, "* shouts:") ||
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])) {
+ if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
talker[0] = '['; strcat(talker, "] ");
Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
chattingPartner = p; break;
}
} // [HGM] chat: end of patch
+ backup = i;
if (appData.zippyTalk || appData.zippyPlay) {
/* [DM] Backup address for color zippy lines */
- backup = i;
#if ZIPPY
if (loggedOn == TRUE)
if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
continue;
}
+ if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
+
if (looking_at(buf, &i, "\\ ")) {
if (prevColor != ColorNormal) {
if (oldi > next_out) {
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;
/* 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]);
+ snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
gameInfo.event = StrSave(str);
/* [HGM] we switched variant. Translate boards if needed. */
VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
break;
}
continue;
- }
-
+ }
+
if (looking_at(buf, &i, "% ") ||
((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
&& looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
- if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
+ if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
soughtPending = FALSE;
seekGraphUp = TRUE;
DrawSeekGraph();
if (WhiteOnMove(forwardMostMove)) {
if (first.sendTime) {
if (first.useColors) {
- SendToProgram("black\n", &first);
+ SendToProgram("black\n", &first);
}
SendTimeRemaining(&first, TRUE);
}
firstMove = TRUE;
}
}
- }
+ }
}
#endif
if (gameMode == IcsObserving && ics_gamenum == -1) {
flipView = appData.flipView;
DrawPosition(TRUE, boards[currentMove]);
DisplayBothClocks();
- sprintf(str, "%s vs. %s",
- gameInfo.white, gameInfo.black);
+ snprintf(str, MSG_SIZ, "%s %s %s",
+ gameInfo.white, _("vs."), gameInfo.black);
DisplayTitle(str);
gameMode = IcsIdle;
} else {
if(bookHit) { // [HGM] book: simulate book reply
static char bookMove[MSG_SIZ]; // a bit generous?
- programStats.nodes = programStats.depth = programStats.time =
+ programStats.nodes = programStats.depth = programStats.time =
programStats.score = programStats.got_only_move = 0;
sprintf(programStats.movelist, "%s (xbook)", bookHit);
- strcpy(bookMove, "move ");
+ safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
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
} else {
player = star_match[2];
}
- sprintf(str, "%sobserve %s\n",
+ snprintf(str, MSG_SIZ, "%sobserve %s\n",
ics_prefix, StripHighlightAndTitle(player));
SendToICS(str);
/* Save ratings from notify string */
- strcpy(player1Name, star_match[0]);
+ safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
player1Rating = string_to_rating(star_match[1]);
- strcpy(player2Name, star_match[2]);
+ safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
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 (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
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).
*/
- strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
- player1Rating = string_to_rating(star_match[1]);
- strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
- player2Rating = string_to_rating(star_match[4]);
+ safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
+ player1Rating = string_to_rating(star_match[1]);
+ safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
+ 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. *(*)) *}*"))){
/* [4] is " *" or empty (don't care). */
int gamenum = atoi(star_match[0]);
char *whitename, *blackname, *why, *endtoken;
- ChessMove endtype = (ChessMove) 0;
+ ChessMove endtype = EndOfFile;
if (tkind == 0) {
whitename = star_match[1];
if (strncmp(why, "Creating ", 9) == 0 ||
strncmp(why, "Continuing ", 11) == 0) {
gs_gamenum = gamenum;
- strcpy(gs_kind, strchr(why, ' ') + 1);
+ safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
+ if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
#if ZIPPY
if (appData.zippyPlay) {
#if ZIPPY
if (appData.zippyPlay && first.initDone) {
ZippyGameEnd(endtype, why);
- if (first.pr == NULL) {
+ if (first.pr == NoProc) {
/* Start the next process early so that we'll
be ready for the next challenge */
StartChessProgram(&first);
if (currentMove == 0 &&
gameMode == IcsPlayingWhite &&
appData.premoveWhite) {
- sprintf(str, "%s\n", appData.premoveWhiteText);
+ snprintf(str, MSG_SIZ, "%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\n", appData.premoveBlackText);
+ snprintf(str, MSG_SIZ, "%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,
+ UserMoveEvent(premoveFromX, premoveFromY,
+ premoveToX, premoveToY,
premovePromoChar);
}
}
will tell us whether this is really bug or zh */
if (ics_getting_history == H_FALSE) {
ics_getting_history = H_REQUESTED;
- sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
+ snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
SendToICS(str);
}
}
char wh[16], bh[16];
PackHolding(wh, white_holding);
PackHolding(bh, black_holding);
- sprintf(str, "[%s-%s] %s-%s", wh, bh,
+ snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
gameInfo.white, gameInfo.black);
} else {
- sprintf(str, "%s [%s] vs. %s [%s]",
- gameInfo.white, white_holding,
+ snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
+ gameInfo.white, white_holding, _("vs."),
gameInfo.black, black_holding);
}
if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
i++; /* skip unparsed character and loop back */
}
-
+
if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
// started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
// SendToPlayer(&buf[next_out], i - next_out);
SendToPlayer(&buf[next_out], leftover_start - next_out);
next_out = i;
}
-
+
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 "%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_STARTING_POSITION -4 /* FICS only */
void
-ParseBoard12(string)
- char *string;
-{
+ParseBoard12 (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[200];
- char move_str[500], str[500], elapsed_time[500];
+ char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
char black[32], white[32];
Board board;
int prevMove = currentMove;
Boolean weird = FALSE, reqFlag = FALSE;
fromX = fromY = toX = toY = -1;
-
+
newGame = FALSE;
if (appData.debugMode)
&ticking);
if (n < 21) {
- snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
+ snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
DisplayError(str, 0);
return;
}
0, 1);
return;
}
-
+
switch (relation) {
case RELATION_OBSERVING_PLAYED:
case RELATION_OBSERVING_STATIC:
newGameMode =
((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
IcsPlayingWhite : IcsPlayingBlack;
+ soughtPending =FALSE; // [HGM] seekgraph: solve race condition
break;
case RELATION_EXAMINING:
newGameMode = IcsExamining;
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;
}
-
+
if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
&& newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
// [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
+ int fac = strchr(elapsed_time, '.') ? 1 : 1000;
char *toSqr;
for (k = 0; k < ranks; k++) {
for (j = 0; j < files; j++)
if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
if(partnerUp) DrawPosition(FALSE, partnerBoard);
- if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
- sprintf(partnerStatus, "W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
- (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
+ if(twoBoards) {
+ DisplayWhiteClock(white_time*fac, to_play == 'W');
+ DisplayBlackClock(black_time*fac, to_play != 'W');
+ activePartner = to_play;
+ activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
+ partnerUp = 0; flipView = !flipView; } // [HGM] dual
+ snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
+ (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
DisplayMessage(partnerStatus, "");
partnerBoardValid = TRUE;
return;
return;
}
- if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
- weird && (int)gameInfo.variant <= (int)VariantShogi) {
+ 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
*/
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);
+ snprintf(str, MSG_SIZ, "%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(TRUE, TRUE);
appData.getMoveList && !reqFlag) {
/* Need to get game history */
ics_getting_history = H_REQUESTED;
- sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
+ snprintf(str, MSG_SIZ, "%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();
if (gamenum == gs_gamenum) {
int klen = strlen(gs_kind);
if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
- sprintf(str, "ICS %s", gs_kind);
+ snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
gameInfo.event = StrSave(str);
} else {
gameInfo.event = StrSave("ICS game");
}
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);
}
board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
}
}
+ if(moveNum==0 && gameInfo.variant == VariantSChess) {
+ board[5][BOARD_RGHT+1] = WhiteAngel;
+ board[6][BOARD_RGHT+1] = WhiteMarshall;
+ board[1][0] = BlackMarshall;
+ board[2][0] = BlackAngel;
+ board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
+ }
CopyBoard(boards[moveNum], board);
boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
if (moveNum == 0) {
if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
+ boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
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;
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;
+ boards[moveNum][EP_STATUS] = EP_NONE;
+ if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
+ if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
+ if(double_push != -1) boards[moveNum][EP_STATUS] = 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;
type when starting to examine a game. But if we ask for
the move list, the move list header will tell us */
ics_getting_history = H_REQUESTED;
- sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
+ snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
SendToICS(str);
}
} else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
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;
}
#endif
ics_getting_history = H_REQUESTED;
- sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
+ snprintf(str, MSG_SIZ, "%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 (moveNum <= backwardMostMove) {
/* We don't know what the board looked like before
this move. Punt. */
- strcpy(parseList[moveNum - 1], move_str);
+ safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
strcat(parseList[moveNum - 1], " ");
strcat(parseList[moveNum - 1], elapsed_time);
moveList[moveNum - 1][0] = NULLCHAR;
startedFromSetupPosition = TRUE;
fromX = fromY = toX = toY = -1;
} else {
- // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
+ // [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;
+ if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
+ strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
// 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, "="))
+ if(str[1] == '/')
+ snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
+ else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // 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)
+ if(appData.debugMode)
fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
- strcpy(move_str, buf);
+ safeStrCpy(move_str, buf, MSG_SIZ);
}
valid = ParseOneMove(move_str, moveNum - 1, &moveType,
&fromX, &fromY, &toX, &toY, &promoChar)
strcat(parseList[moveNum - 1], " ");
strcat(parseList[moveNum - 1], elapsed_time);
/* currentMoveString is set as a side-effect of ParseOneMove */
- strcpy(moveList[moveNum - 1], currentMoveString);
+ if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
+ safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
strcat(moveList[moveNum - 1], "\n");
+
+ if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
+ && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
+ for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
+ ChessSquare old, new = boards[moveNum][k][j];
+ if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
+ old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
+ if(old == new) continue;
+ if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
+ else if(new == WhiteWazir || new == BlackWazir) {
+ if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
+ boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
+ else boards[moveNum][k][j] = old; // preserve type of Gold
+ } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
+ boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
+ }
} else {
/* Move from ICS was illegal!? Punt. */
- if (appData.debugMode) {
- fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
- fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
- }
- strcpy(parseList[moveNum - 1], move_str);
+ 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);
+ }
+ safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
strcat(parseList[moveNum - 1], " ");
strcat(parseList[moveNum - 1], elapsed_time);
moveList[moveNum - 1][0] = NULLCHAR;
#if ZIPPY
/* Send move to chess program (BEFORE animating it). */
- if (appData.zippyPlay && !newGame && newMove &&
+ if (appData.zippyPlay && !newGame && newMove &&
(!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
(gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
if (moveList[moveNum - 1][0] == NULLCHAR) {
- sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
+ snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
move_str);
DisplayError(str, 0);
} else {
}
} else if (gameMode == IcsObserving || gameMode == IcsExamining) {
if (moveList[moveNum - 1][0] == NULLCHAR) {
- sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
+ snprintf(str, MSG_SIZ, _("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!
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 && !appData.noGUI) {
if (tinyLayout || smallLayout) {
if(gameInfo.variant == VariantNormal)
- sprintf(str, "%s(%d) %s(%d) {%d %d}",
+ snprintf(str, MSG_SIZ, "%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}",
+ snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
gameInfo.white, white_stren, gameInfo.black, black_stren,
basetime, increment, (int) gameInfo.variant);
} else {
if(gameInfo.variant == VariantNormal)
- sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
- gameInfo.white, white_stren, gameInfo.black, black_stren,
+ snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
+ gameInfo.white, white_stren, _("vs."), 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,
+ snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
+ gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
basetime, increment, VariantName(gameInfo.variant));
}
DisplayTitle(str);
/* Display the board */
if (!pausing && !appData.noGUI) {
-
+
if (appData.premove)
- if (!gotPremove ||
+ if (!gotPremove ||
((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
ClearPremoveHighlights();
if(bookHit) { // [HGM] book: simulate book reply
static char bookMove[MSG_SIZ]; // a bit generous?
- programStats.nodes = programStats.depth = programStats.time =
+ programStats.nodes = programStats.depth = programStats.time =
programStats.score = programStats.got_only_move = 0;
sprintf(programStats.movelist, "%s (xbook)", bookHit);
- strcpy(bookMove, "move ");
+ safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
strcat(bookMove, bookHit);
HandleMachineMove(bookMove, &first);
}
}
void
-GetMoveListEvent()
+GetMoveListEvent ()
{
char buf[MSG_SIZ];
if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
ics_getting_history = H_REQUESTED;
- sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
+ snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
SendToICS(buf);
}
}
void
-AnalysisPeriodicEvent(force)
- int force;
+AnalysisPeriodicEvent (int force)
{
if (((programStats.ok_to_send == 0 || programStats.line_is_book)
&& !force) || !appData.periodicUpdates)
programStats.ok_to_send = 0;
}
-void ics_update_width(new_width)
- int new_width;
+void
+ics_update_width (int new_width)
{
ics_printf("set width %d\n", new_width);
}
void
-SendMoveToProgram(moveNum, cps)
- int moveNum;
- ChessProgramState *cps;
+SendMoveToProgram (int moveNum, ChessProgramState *cps)
{
char buf[MSG_SIZ];
+ if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
+ // null move in variant where engine does not understand it (for analysis purposes)
+ SendBoard(cps, moveNum + 1); // send position after move in stead.
+ return;
+ }
if (cps->useUsermove) {
SendToProgram("usermove ", cps);
}
buf[len++] = '\n';
buf[len] = NULLCHAR;
} else {
- sprintf(buf, "%s\n", parseList[moveNum]);
+ snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
}
SendToProgram(buf, cps);
} else {
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
+ * 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 fromX = moveList[moveNum][0] - AAA;
int fromY = moveList[moveNum][1] - ONE;
- int toX = moveList[moveNum][2] - AAA;
+ int toX = moveList[moveNum][2] - AAA;
int toY = moveList[moveNum][3] - ONE;
- if((boards[moveNum][fromY][fromX] == WhiteKing
+ if((boards[moveNum][fromY][fromX] == WhiteKing
&& boards[moveNum][toY][toX] == WhiteRook)
- || (boards[moveNum][fromY][fromX] == BlackKing
+ || (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
+ if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
+ if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
+ if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
+ snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
+ moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
+ } else
+ snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
+ moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
+ SendToProgram(buf, cps);
}
else SendToProgram(moveList[moveNum], cps);
/* End of additions by Tord */
}
void
-SendMoveToICS(moveType, fromX, fromY, toX, toY)
- ChessMove moveType;
- int fromX, fromY, toX, toY;
+SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
{
char user_move[MSG_SIZ];
+ char suffix[4];
+
+ if(gameInfo.variant == VariantSChess && promoChar) {
+ snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
+ if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
+ } else suffix[0] = NULLCHAR;
switch (moveType) {
default:
- sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
+ snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
(int)moveType, fromX, fromY, toX, toY);
DisplayError(user_move + strlen("say "), 0);
break;
case WhiteHSideCastleFR:
case BlackHSideCastleFR:
/* POP Fabien */
- sprintf(user_move, "o-o\n");
+ snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
break;
case WhiteQueenSideCastle:
case BlackQueenSideCastle:
case WhiteASideCastleFR:
case BlackASideCastleFR:
/* POP Fabien */
- sprintf(user_move, "o-o-o\n");
+ snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
break;
- case WhitePromotionQueen:
- case BlackPromotionQueen:
- case WhitePromotionRook:
- case BlackPromotionRook:
- case WhitePromotionBishop:
- case BlackPromotionBishop:
- case WhitePromotionKnight:
- case BlackPromotionKnight:
- case WhitePromotionKing:
- case BlackPromotionKing:
- case WhitePromotionChancellor:
- case BlackPromotionChancellor:
- case WhitePromotionArchbishop:
- case BlackPromotionArchbishop:
+ case WhiteNonPromotion:
+ case BlackNonPromotion:
+ sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
+ break;
+ case WhitePromotion:
+ case BlackPromotion:
if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
- sprintf(user_move, "%c%c%c%c=%c\n",
+ snprintf(user_move, MSG_SIZ, "%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",
+ snprintf(user_move, MSG_SIZ,"%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",
+ snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
- PieceToChar(PromoPiece(moveType)));
+ promoChar);
break;
case WhiteDrop:
case BlackDrop:
- sprintf(user_move, "%c@%c%c\n",
- ToUpper(PieceToChar((ChessSquare) fromX)),
- AAA + toX, ONE + toY);
+ drop:
+ snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
+ ToUpper(PieceToChar((ChessSquare) fromX)),
+ AAA + toX, ONE + toY);
break;
+ case IllegalMove: /* could be a variant we don't quite understand */
+ if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
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",
+ snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
break;
}
}
void
-UploadGameEvent()
+UploadGameEvent ()
{ // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
- DisplayError("You cannot do this while you are playing or observing", 0);
- return;
+ DisplayError(_("You cannot do this while you are playing or observing"), 0);
+ return;
}
if(gameMode != IcsExamining) { // is this ever not the case?
char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
- sprintf(command, "match %s", ics_handle);
+ snprintf(command,MSG_SIZ, "match %s", ics_handle);
} else { // on FICS we must first go to general examine mode
- strcpy(command, "examine\nbsetup"); // and specify variant within it with bsetups
+ safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
}
if(gameInfo.variant != VariantNormal) {
// try figure out wild number, as xboard names are not always valid on ICS
for(i=1; i<=36; i++) {
- sprintf(buf, "wild/%d", i);
+ snprintf(buf, MSG_SIZ, "wild/%d", i);
if(StringToVariant(buf) == gameInfo.variant) break;
}
- if(i<=36 && ics_type == ICS_ICC) sprintf(buf, "%s w%d\n", command, i);
- else if(i == 22) sprintf(buf, "%s fr\n", command);
- else sprintf(buf, "%s %s\n", command, VariantName(gameInfo.variant));
- } else sprintf(buf, "%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
+ if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
+ else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
+ else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
+ } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
SendToICS(ics_prefix);
SendToICS(buf);
if(startedFromSetupPosition || backwardMostMove != 0) {
fen = PositionToFEN(backwardMostMove, NULL);
if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
- sprintf(buf, "loadfen %s\n", fen);
+ snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
SendToICS(buf);
} else { // FICS: everything has to set by separate bsetup commands
p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
- sprintf(buf, "bsetup fen %s\n", fen);
+ snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
SendToICS(buf);
if(!WhiteOnMove(backwardMostMove)) {
SendToICS("bsetup tomove black\n");
}
i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
- sprintf(buf, "bsetup wcastle %s\n", castlingStrings[i]);
+ snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
SendToICS(buf);
i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
- sprintf(buf, "bsetup bcastle %s\n", castlingStrings[i]);
+ snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
SendToICS(buf);
i = boards[backwardMostMove][EP_STATUS];
if(i >= 0) { // set e.p.
- sprintf(buf, "bsetup eppos %c\n", i+AAA);
+ snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
SendToICS(buf);
}
bsetup++;
}
for(i = backwardMostMove; i<last; i++) {
char buf[20];
- sprintf(buf, "%s\n", parseList[i]);
+ snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
SendToICS(buf);
}
SendToICS(ics_prefix);
}
void
-CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
- int rf, ff, rt, ft;
- char promoChar;
- char move[7];
+CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
{
if (rf == DROP_RANK) {
- sprintf(move, "%c@%c%c\n",
+ if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
+ sprintf(move, "%c@%c%c\n",
ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
} else {
if (promoChar == 'x' || promoChar == NULLCHAR) {
- sprintf(move, "%c%c%c%c\n",
+ sprintf(move, "%c%c%c%c\n",
AAA + ff, ONE + rf, AAA + ft, ONE + rt);
} else {
sprintf(move, "%c%c%c%c%c\n",
}
void
-ProcessICSInitScript(f)
- FILE *f;
+ProcessICSInitScript (FILE *f)
{
char buf[MSG_SIZ];
}
+static int lastX, lastY, selectFlag, dragging;
+
+void
+Sweep (int step)
+{
+ ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
+ if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
+ if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
+ if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
+ if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
+ if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
+ do {
+ promoSweep -= step;
+ if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
+ else if((int)promoSweep == -1) promoSweep = WhiteKing;
+ else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
+ else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
+ if(!step) step = -1;
+ } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
+ appData.testLegality && (promoSweep == king ||
+ gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
+ ChangeDragPiece(promoSweep);
+}
+
+int
+PromoScroll (int x, int y)
+{
+ int step = 0;
+
+ if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
+ if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
+ if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
+ if(!step) return FALSE;
+ lastX = x; lastY = y;
+ if((promoSweep < BlackPawn) == flipView) step = -step;
+ if(step > 0) selectFlag = 1;
+ if(!selectFlag) Sweep(step);
+ return FALSE;
+}
+
+void
+NextPiece (int step)
+{
+ ChessSquare piece = boards[currentMove][toY][toX];
+ do {
+ pieceSweep -= step;
+ if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
+ if((int)pieceSweep == -1) pieceSweep = BlackKing;
+ if(!step) step = -1;
+ } while(PieceToChar(pieceSweep) == '.');
+ boards[currentMove][toY][toX] = pieceSweep;
+ DrawPosition(FALSE, boards[currentMove]);
+ boards[currentMove][toY][toX] = piece;
+}
/* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
void
-AlphaRank(char *move, int n)
+AlphaRank (char *move, int n)
{
// char *p = move, c; int x, y;
fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
}
- if(move[1]=='*' &&
+ if(move[1]=='*' &&
move[2]>='0' && move[2]<='9' &&
move[3]>='a' && move[3]<='x' ) {
move[1] = '@';
/* Parser for moves from gnuchess, ICS, or user typein box */
Boolean
-ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
- char *move;
- int moveNum;
- ChessMove *moveType;
- int *fromX, *fromY, *toX, *toY;
- char *promoChar;
-{
- if (appData.debugMode) {
- fprintf(debugFP, "move to parse: %s\n", move);
- }
+ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
+{
*moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
switch (*moveType) {
- case WhitePromotionChancellor:
- case BlackPromotionChancellor:
- case WhitePromotionArchbishop:
- case BlackPromotionArchbishop:
- case WhitePromotionQueen:
- case BlackPromotionQueen:
- case WhitePromotionRook:
- case BlackPromotionRook:
- case WhitePromotionBishop:
- case BlackPromotionBishop:
- case WhitePromotionKnight:
- case BlackPromotionKnight:
- case WhitePromotionKing:
- case BlackPromotionKing:
+ case WhitePromotion:
+ case BlackPromotion:
+ case WhiteNonPromotion:
+ case BlackNonPromotion:
case NormalMove:
case WhiteCapturesEnPassant:
case BlackCapturesEnPassant:
if (appData.testLegality) {
return (*moveType != IllegalMove);
} else {
- return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
+ return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
}
case AmbiguousMove:
case ImpossibleMove:
- case (ChessMove) 0: /* end of file */
+ case EndOfFile:
case ElapsedTime:
case Comment:
case PGNTag:
}
}
+Boolean pushed = FALSE;
+char *lastParseAttempt;
void
-ParsePV(char *pv, Boolean storeComments)
+ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
{ // Parse a string of PV moves, and append to current game, behind forwardMostMove
int fromX, fromY, toX, toY; char promoChar;
ChessMove moveType;
Boolean valid;
int nr = 0;
+ if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
+ PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
+ pushed = TRUE;
+ }
endPV = forwardMostMove;
do {
while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
+ lastParseAttempt = pv;
valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
-if(appData.debugMode){
-fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, yy_textstr, pv);
-}
if(!valid && nr == 0 &&
- ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
+ ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
// Hande case where played move is different from leading PV move
CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
moveList[endPV-1][2] = toX + AAA;
moveList[endPV-1][3] = toY + ONE;
parseList[endPV-1][0] = NULLCHAR;
- strcpy(moveList[endPV-2], "_0_0"); // suppress premove highlight on takeback move
+ safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
}
}
pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
endPV++;
CopyBoard(boards[endPV], boards[endPV-1]);
ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
- moveList[endPV-1][0] = fromX + AAA;
- moveList[endPV-1][1] = fromY + ONE;
- moveList[endPV-1][2] = toX + AAA;
- moveList[endPV-1][3] = toY + ONE;
- if(storeComments)
- CoordsToAlgebraic(boards[endPV - 1],
+ CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
+ strncat(moveList[endPV-1], "\n", MOVE_LEN);
+ CoordsToAlgebraic(boards[endPV - 1],
PosFlags(endPV - 1),
fromY, fromX, toY, toX, promoChar,
parseList[endPV - 1]);
- else
- parseList[endPV-1][0] = NULLCHAR;
} while(valid);
- currentMove = endPV;
+ if(atEnd == 2) return; // used hidden, for PV conversion
+ currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
DrawPosition(TRUE, boards[currentMove]);
}
-static int lastX, lastY;
+int
+MultiPV (ChessProgramState *cps)
+{ // check if engine supports MultiPV, and if so, return the number of the option that sets it
+ int i;
+ for(i=0; i<cps->nrOptions; i++)
+ if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
+ return i;
+ return -1;
+}
Boolean
-LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
+LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end)
{
- int startPV;
- char *p;
+ int startPV, multi, lineStart, origIndex = index;
+ char *p, buf2[MSG_SIZ];
if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
lastX = x; lastY = y;
while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
- startPV = index;
+ lineStart = startPV = index;
while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
index = startPV;
do{ while(buf[index] && buf[index] != '\n') index++;
} while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
buf[index] = 0;
- ParsePV(buf+startPV, FALSE);
+ if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
+ int n = first.option[multi].value;
+ if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
+ snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
+ if(first.option[multi].value != n) SendToProgram(buf2, &first);
+ first.option[multi].value = n;
+ *start = *end = 0;
+ return FALSE;
+ } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
+ ExcludeClick(origIndex - lineStart);
+ return FALSE;
+ }
+ ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
*start = startPV; *end = index-1;
return TRUE;
}
+char *
+PvToSAN (char *pv)
+{
+ static char buf[10*MSG_SIZ];
+ int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
+ *buf = NULLCHAR;
+ if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
+ ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
+ for(i = forwardMostMove; i<endPV; i++){
+ if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
+ else snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
+ k += strlen(buf+k);
+ }
+ snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
+ if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
+ endPV = savedEnd;
+ return buf;
+}
+
Boolean
-LoadPV(int x, int y)
+LoadPV (int x, int y)
{ // called on right mouse click to load PV
int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
lastX = x; lastY = y;
- ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
+ ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
return TRUE;
}
void
-UnLoadPV()
+UnLoadPV ()
{
+ int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
if(endPV < 0) return;
+ if(appData.autoCopyPV) CopyFENToClipboard();
endPV = -1;
+ if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
+ Boolean saveAnimate = appData.animate;
+ if(pushed) {
+ if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
+ if(storedGames == 1) GreyRevert(FALSE); // we already pushed the tail, so just make it official
+ } else storedGames--; // abandon shelved tail of original game
+ }
+ pushed = FALSE;
+ forwardMostMove = currentMove;
+ currentMove = oldFMM;
+ appData.animate = FALSE;
+ ToNrEvent(forwardMostMove);
+ appData.animate = saveAnimate;
+ }
currentMove = forwardMostMove;
+ if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
ClearPremoveHighlights();
DrawPosition(TRUE, boards[currentMove]);
}
void
-MovePV(int x, int y, int h)
+MovePV (int x, int y, int h)
{ // step through PV based on mouse coordinates (called on mouse move)
- int margin = h>>3, step = 0;
+ int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
- if(endPV < 0) return;
// we must somehow check if right button is still down (might be released off board!)
- if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
- if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
- if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
+ if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
+ if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
+ if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
if(!step) return;
lastX = x; lastY = y;
+
+ if(pieceSweep != EmptySquare) { NextPiece(step); return; }
+ if(endPV < 0) return;
+ if(y < margin) step = 1; else
+ if(y > h - margin) step = -1;
if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
currentMove += step;
if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
int piecesLeft[(int)BlackPawn];
int seed, nrOfShuffles;
-void GetPositionNumber()
+void
+GetPositionNumber ()
{ // sets global variable seed
int i;
}
}
-int put(Board board, int pieceType, int rank, int n, int shade)
+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;
board[rank][i] = (ChessSquare) pieceType;
squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
squaresLeft[ANY]--;
- piecesLeft[pieceType]--;
+ piecesLeft[pieceType]--;
return i;
}
}
}
-void AddOnePiece(Board board, int pieceType, int rank, int shade)
+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;
put(board, pieceType, rank, i, shade);
}
-void AddTwoPieces(Board board, int pieceType, int rank)
+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;
put(board, pieceType, rank, i, ANY);
}
-void SetUpShuffle(Board board, int number)
+void
+SetUpShuffle (Board board, int number)
{
int i, p, first=1;
if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
}
-int SetCharTable( char *table, const char * map )
+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
+ if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
&& NrPieces >= 12 && !(NrPieces&1)) {
int i; /* [HGM] Accept even length from 12 to 34 */
return result;
}
-void Prelude(Board board)
+void
+Prelude (Board board)
{ // [HGM] superchess: random selection of exo-pieces
- int i, j, k; ChessSquare p;
+ 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++)
+ 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;
+ 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;
+ 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;
+ 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;
+ 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]++;
}
void
-InitPosition(redraw)
- int redraw;
+InitPosition (int redraw)
{
ChessSquare (* pieces)[BOARD_FILES];
int i, j, pawnRow, overrule,
oldx = gameInfo.boardWidth,
oldy = gameInfo.boardHeight,
- oldh = gameInfo.holdingsWidth,
- oldv = gameInfo.variant;
+ oldh = gameInfo.holdingsWidth;
+ static int oldv;
if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
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 */
if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
SetCharTable(pieceNickName, appData.pieceNickNames);
else SetCharTable(pieceNickName, "............");
+ pieces = FIDEArray;
switch (gameInfo.variant) {
case VariantFischeRandom:
shuffleOpenings = TRUE;
default:
- pieces = FIDEArray;
break;
case VariantShatranj:
pieces = ShatranjArray;
nrCastlingRights = 0;
- SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
+ SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
break;
case VariantMakruk:
pieces = makrukArray;
nrCastlingRights = 0;
startedFromSetupPosition = TRUE;
- SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
+ SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
break;
case VariantTwoKings:
pieces = twoKingsArray;
break;
+ case VariantGrand:
+ pieces = GrandArray;
+ nrCastlingRights = 0;
+ SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
+ gameInfo.boardWidth = 10;
+ gameInfo.boardHeight = 10;
+ gameInfo.holdingsSize = 7;
+ break;
case VariantCapaRandom:
shuffleOpenings = TRUE;
case VariantCapablanca:
pieces = CapablancaArray;
gameInfo.boardWidth = 10;
- SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
+ SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
break;
case VariantGothic:
pieces = GothicArray;
gameInfo.boardWidth = 10;
- SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
+ SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
+ break;
+ case VariantSChess:
+ SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
+ gameInfo.holdingsSize = 7;
break;
case VariantJanus:
pieces = JanusArray;
gameInfo.boardWidth = 10;
- SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
+ SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
nrCastlingRights = 6;
initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
case VariantFalcon:
pieces = FalconArray;
gameInfo.boardWidth = 10;
- SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
+ 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.");
+ SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
break;
case VariantShogi:
pieces = ShogiArray;
gameInfo.boardHeight = 9;
gameInfo.holdingsSize = 7;
nrCastlingRights = 0;
- SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
+ SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
break;
case VariantCourier:
pieces = CourierArray;
gameInfo.boardWidth = 12;
nrCastlingRights = 0;
- SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
+ SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
break;
case VariantKnightmate:
pieces = KnightmateArray;
- SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
+ SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
+ break;
+ case VariantSpartan:
+ pieces = SpartanArray;
+ SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
break;
case VariantFairy:
pieces = fairyArray;
- SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
+ SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
break;
case VariantGreat:
pieces = GreatArray;
case VariantCrazyhouse:
case VariantBughouse:
pieces = FIDEArray;
- SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
+ SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
gameInfo.holdingsSize = 5;
break;
case VariantWildCastle:
pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
if(pawnRow < 1) pawnRow = 1;
- if(gameInfo.variant == VariantMakruk) pawnRow = 2;
+ if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
/* User pieceToChar list overrules defaults */
if(appData.pieceToCharTable != NULL)
initialPosition[i][j] = s;
if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
- initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
+ initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
initialPosition[pawnRow][j] = WhitePawn;
- initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
+ initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
if(gameInfo.variant == VariantXiangqi) {
if(j&1) {
- initialPosition[pawnRow][j] =
+ 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-1][j] = pieces[1][j-gameInfo.holdingsWidth];
+ if(gameInfo.variant == VariantGrand) {
+ if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
+ initialPosition[0][j] = WhiteRook;
+ initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
+ }
+ }
+ initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] = pieces[1][j-gameInfo.holdingsWidth];
}
if( (gameInfo.variant == VariantShogi) && !overrule ) {
/* 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[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
}
+ if( gameInfo.variant == VariantSChess ) {
+ initialPosition[1][0] = BlackMarshall;
+ initialPosition[2][0] = BlackAngel;
+ initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
+ initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
+ initialPosition[1][1] = initialPosition[2][1] =
+ initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
+ }
if (appData.debugMode) {
fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
}
if(oldx != gameInfo.boardWidth ||
oldy != gameInfo.boardHeight ||
+ oldv != gameInfo.variant ||
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);
+ oldv = gameInfo.variant;
if (redraw)
DrawPosition(TRUE, boards[currentMove]);
}
void
-SendBoard(cps, moveNum)
- ChessProgramState *cps;
- int moveNum;
+SendBoard (ChessProgramState *cps, int moveNum)
{
char message[MSG_SIZ];
-
+
if (cps->useSetboard) {
char* fen = PositionToFEN(moveNum, cps->fenOverride);
- sprintf(message, "setboard %s\n", fen);
+ snprintf(message, MSG_SIZ,"setboard %s\n", fen);
SendToProgram(message, cps);
free(fen);
} else {
ChessSquare *bp;
- int i, j;
+ int i, j, left=0, right=BOARD_WIDTH;
/* Kludge to set black to move, avoiding the troublesome and now
* deprecated "black" command.
*/
- if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
+ if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
+ SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
+
+ if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
SendToProgram("edit\n", cps);
SendToProgram("#\n", cps);
for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
- bp = &boards[moveNum][i][BOARD_LEFT];
- for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
+ bp = &boards[moveNum][i][left];
+ for (j = left; j < right; j++, bp++) {
+ if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
if ((int) *bp < (int) BlackPawn) {
- sprintf(message, "%c%c%c\n", PieceToChar(*bp),
- AAA + j, ONE + i);
+ if(j == BOARD_RGHT+1)
+ snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
+ else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
if(message[0] == '+' || message[0] == '~') {
- sprintf(message, "%c%c%c+\n",
+ snprintf(message, MSG_SIZ,"%c%c%c+\n",
PieceToChar((ChessSquare)(DEMOTED *bp)),
AAA + j, ONE + i);
}
}
}
}
-
+
SendToProgram("c\n", cps);
for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
- bp = &boards[moveNum][i][BOARD_LEFT];
- for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
+ bp = &boards[moveNum][i][left];
+ for (j = left; j < right; j++, bp++) {
+ if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
if (((int) *bp != (int) EmptySquare)
&& ((int) *bp >= (int) BlackPawn)) {
- sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
+ if(j == BOARD_LEFT-2)
+ snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
+ else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
AAA + j, ONE + i);
if(message[0] == '+' || message[0] == '~') {
- sprintf(message, "%c%c%c+\n",
+ snprintf(message, MSG_SIZ,"%c%c%c+\n",
PieceToChar((ChessSquare)(DEMOTED *bp)),
AAA + j, ONE + i);
}
}
}
}
-
+
SendToProgram(".\n", cps);
}
setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
}
+char exclusionHeader[MSG_SIZ];
+int exCnt, excludePtr;
+typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
+static Exclusion excluTab[200];
+static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
+
+static void
+WriteMap (int s)
+{
+ int j;
+ for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
+ exclusionHeader[19] = s ? '-' : '+'; // update tail state
+}
+
+static void
+ClearMap ()
+{
+ safeStrCpy(exclusionHeader, "exclude: none best +tail \n", MSG_SIZ);
+ excludePtr = 24; exCnt = 0;
+ WriteMap(0);
+}
+
+static void
+UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
+{ // search given move in table of header moves, to know where it is listed (and add if not there), and update state
+ char buf[2*MOVE_LEN], *p;
+ Exclusion *e = excluTab;
+ int i;
+ for(i=0; i<exCnt; i++)
+ if(e[i].ff == fromX && e[i].fr == fromY &&
+ e[i].tf == toX && e[i].tr == toY && e[i].pc == promoChar) break;
+ if(i == exCnt) { // was not in exclude list; add it
+ CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
+ if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
+ if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
+ return; // abort
+ }
+ e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
+ excludePtr++; e[i].mark = excludePtr++;
+ for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
+ exCnt++;
+ }
+ exclusionHeader[e[i].mark] = state;
+}
+
+static int
+ExcludeOneMove (int fromY, int fromX, int toY, int toX, signed char promoChar, char state)
+{ // include or exclude the given move, as specified by state ('+' or '-'), or toggle
+ char buf[MSG_SIZ];
+ int j, k;
+ ChessMove moveType;
+ if(promoChar == -1) { // kludge to indicate best move
+ if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
+ return 1; // if unparsable, abort
+ }
+ // update exclusion map (resolving toggle by consulting existing state)
+ k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
+ j = k%8; k >>= 3;
+ if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
+ if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
+ excludeMap[k] |= 1<<j;
+ else excludeMap[k] &= ~(1<<j);
+ // update header
+ UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
+ // inform engine
+ snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
+ CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
+ SendToProgram(buf, &first);
+ return (state == '+');
+}
+
+static void
+ExcludeClick (int index)
+{
+ int i, j;
+ Exclusion *e = excluTab;
+ if(index < 25) { // none, best or tail clicked
+ if(index < 13) { // none: include all
+ WriteMap(0); // clear map
+ for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
+ SendToProgram("include all\n", &first); // and inform engine
+ } else if(index > 18) { // tail
+ if(exclusionHeader[19] == '-') { // tail was excluded
+ SendToProgram("include all\n", &first);
+ WriteMap(0); // clear map completely
+ // now re-exclude selected moves
+ for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
+ ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
+ } else { // tail was included or in mixed state
+ SendToProgram("exclude all\n", &first);
+ WriteMap(0xFF); // fill map completely
+ // now re-include selected moves
+ j = 0; // count them
+ for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
+ ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
+ if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
+ }
+ } else { // best
+ ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
+ }
+ } else {
+ for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
+ char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
+ ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
+ break;
+ }
+ }
+}
+
+ChessSquare
+DefaultPromoChoice (int white)
+{
+ ChessSquare result;
+ if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
+ result = WhiteFerz; // no choice
+ else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
+ result= WhiteKing; // in Suicide Q is the last thing we want
+ else if(gameInfo.variant == VariantSpartan)
+ result = white ? WhiteQueen : WhiteAngel;
+ else result = WhiteQueen;
+ if(!white) result = WHITE_TO_BLACK result;
+ return result;
+}
+
static int autoQueen; // [HGM] oneclick
int
-HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
+HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
{
/* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
/* [HGM] add Shogi promotions */
piece = boards[currentMove][fromY][fromX];
if(gameInfo.variant == VariantShogi) {
- promotionZoneSize = 3;
+ promotionZoneSize = BOARD_HEIGHT/3;
highestPromotingPiece = (int)WhiteFerz;
- } else if(gameInfo.variant == VariantMakruk) {
+ } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
promotionZoneSize = 3;
}
+ // Treat Lance as Pawn when it is not representing Amazon
+ if(gameInfo.variant != VariantSuper) {
+ if(piece == WhiteLance) piece = WhitePawn; else
+ if(piece == BlackLance) piece = BlackPawn;
+ }
+
// next weed out all moves that do not touch the promotion zone at all
if((int)piece >= BlackPawn) {
if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
*promoChoice = PieceToChar(BlackFerz); // no choice
return FALSE;
}
- if(autoQueen) { // predetermined
- if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
- *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
- else *promoChoice = PieceToChar(BlackQueen);
+ // no sense asking what we must promote to if it is going to explode...
+ if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
+ *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
return FALSE;
}
+ // give caller the default choice even if we will not make it
+ *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
+ if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
+ if( sweepSelect && gameInfo.variant != VariantGreat
+ && gameInfo.variant != VariantGrand
+ && gameInfo.variant != VariantSuper) return FALSE;
+ if(autoQueen) return FALSE; // predetermined
// 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)
+ fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
+ if(moveType != WhitePromotion && moveType != BlackPromotion)
return FALSE;
}
}
int
-InPalace(row, column)
- int row, column;
+InPalace (int row, int column)
{ /* [HGM] for Xiangqi */
if( (row < 3 || row > BOARD_HEIGHT-4) &&
column < (BOARD_WIDTH + 4)/2 &&
}
int
-PieceForSquare (x, y)
- int x;
- int y;
+PieceForSquare (int x, int y)
{
if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
return -1;
}
int
-OKToStartUserMove(x, y)
- int x, y;
+OKToStartUserMove (int x, int y)
{
ChessSquare from_piece;
int white_piece;
(int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
switch (gameMode) {
- case PlayFromGameFile:
case AnalyzeFile:
case TwoMachinesPlay:
case EndOfGame:
}
break;
+ case PlayFromGameFile:
+ if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
case EditGame:
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 */
}
}
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:
}
if (currentMove != forwardMostMove && gameMode != AnalyzeMode
&& gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
+ && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
&& gameMode != AnalyzeFile && gameMode != Training) {
DisplayMoveError(_("Displayed position is not current"));
return FALSE;
}
Boolean
-OnlyMove(int *x, int *y, Boolean captures) {
+OnlyMove (int *x, int *y, Boolean captures)
+{
DisambiguateClosure cl;
- if (appData.zippyPlay) return FALSE;
+ if (appData.zippyPlay || !appData.testLegality) return FALSE;
switch(gameMode) {
case MachinePlaysBlack:
case IcsPlayingWhite:
case IcsPlayingBlack:
if(WhiteOnMove(currentMove)) return FALSE;
break;
+ case EditGame:
+ break;
default:
return FALSE;
}
- cl.pieceIn = EmptySquare;
+ cl.pieceIn = EmptySquare;
cl.rfIn = *y;
cl.ffIn = *x;
cl.rtIn = -1;
Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
if( cl.kind == NormalMove ||
cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
- cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
- cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
+ cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
fromX = cl.ff;
fromY = cl.rf;
Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
if( cl.kind == NormalMove ||
cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
- cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
- cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
+ cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
fromX = cl.ff;
fromY = cl.rf;
int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
int lastLoadGameUseList = FALSE;
char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
-ChessMove lastLoadGameStart = (ChessMove) 0;
+ChessMove lastLoadGameStart = EndOfFile;
+int doubleClick;
-ChessMove
-UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
- int fromX, fromY, toX, toY;
- int promoChar;
- Boolean captureOwn;
+void
+UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
{
ChessMove moveType;
ChessSquare pdown, pup;
+ int ff=fromX, rf=fromY, ft=toX, rt=toY;
+
/* 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
*/
switch (gameMode) {
- case PlayFromGameFile:
case AnalyzeFile:
case TwoMachinesPlay:
case EndOfGame:
case IcsIdle:
/* We switched into a game mode where moves are not accepted,
perhaps while the mouse button was down. */
- return ImpossibleMove;
+ return;
case MachinePlaysWhite:
/* User is moving for Black */
if (WhiteOnMove(currentMove)) {
DisplayMoveError(_("It is White's turn"));
- return ImpossibleMove;
+ return;
}
break;
/* User is moving for White */
if (!WhiteOnMove(currentMove)) {
DisplayMoveError(_("It is Black's turn"));
- return ImpossibleMove;
+ return;
}
break;
+ case PlayFromGameFile:
+ if(!shiftKey ||!appData.variations) return; // [HGM] only variations
case EditGame:
case IcsExamining:
case BeginningOfGame:
case AnalyzeMode:
case Training:
+ if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
(int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
/* User is moving for Black */
if (WhiteOnMove(currentMove)) {
DisplayMoveError(_("It is White's turn"));
- return ImpossibleMove;
+ return;
}
} else {
/* User is moving for White */
if (!WhiteOnMove(currentMove)) {
DisplayMoveError(_("It is Black's turn"));
- return ImpossibleMove;
+ return;
}
}
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 ImpossibleMove;
+ return;
}
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 ImpossibleMove;
+ return;
}
break;
click-click move is possible */
if (toX == -2 || toY == -2) {
boards[0][fromY][fromX] = EmptySquare;
- return AmbiguousMove;
+ DrawPosition(FALSE, boards[currentMove]);
+ return;
} else if (toX >= 0 && toY >= 0) {
boards[0][toY][toX] = boards[0][fromY][fromX];
if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
if(boards[0][fromY][0] != EmptySquare) {
if(boards[0][fromY][1]) boards[0][fromY][1]--;
- if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
+ if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
}
} else
if(fromX == BOARD_RGHT+1) {
if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
- if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
+ if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
}
} else
- boards[0][fromY][fromX] = EmptySquare;
- return AmbiguousMove;
+ boards[0][fromY][fromX] = gatingPiece;
+ DrawPosition(FALSE, boards[currentMove]);
+ return;
}
- return ImpossibleMove;
+ return;
}
- if(toX < 0 || toY < 0) return ImpossibleMove;
+ if(toX < 0 || toY < 0) return;
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 */
+ /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
+ if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
+ if( pup != EmptySquare ) return;
+ 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] always test for legality, to get promotion info */
moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
fromY, fromX, toY, toX, promoChar);
+
+ if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
+
/* [HGM] but possibly ignore an IllegalMove result */
if (appData.testLegality) {
if (moveType == IllegalMove || moveType == ImpossibleMove) {
DisplayMoveError(_("Illegal move"));
- return ImpossibleMove;
+ return;
}
}
- return moveType;
- /* [HGM] <popupFix> in stead of calling FinishMove directly, this
- function is made into one that returns an OK move type if FinishMove
- should be called. This to give the calling driver routine the
- opportunity to finish the userMove input with a promotion popup,
- without bothering the user with this for invalid or illegal moves */
+ if(doubleClick) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
+ if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
+ ClearPremoveHighlights(); // was included
+ else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated by premove highlights
+ return;
+ }
-/* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
+ 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;
+FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
{
char *bookHit = 0;
- if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
- // [HGM] superchess: suppress promotions to non-available piece
+ if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
+ // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
if(WhiteOnMove(currentMove)) {
if(!boards[currentMove][k][BOARD_WIDTH-2]) 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;
- }
+ moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
/* [HGM] <popupFix> The following if has been moved here from
UserMoveEvent(). Because it seemed to belong here (why not allow
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;
/* Ok, now we know that the move is good, so we can kill
the previous line in Analysis Mode */
- if ((gameMode == AnalyzeMode || gameMode == EditGame)
+ if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
&& currentMove < forwardMostMove) {
- PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
+ if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
+ else forwardMostMove = currentMove;
}
+ ClearMap();
+
/* If we need the chess program but it's dead, restart it */
ResurrectChessProgram();
MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
- if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
+ if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
+ ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
+ return 1;
+ }
if (gameMode == BeginningOfGame) {
if (appData.noChessProgram) {
gameMode = MachinePlaysBlack;
StartClocks();
SetGameInfo();
- sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
+ snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
DisplayTitle(buf);
if (first.sendName) {
- sprintf(buf, "name %s\n", gameInfo.white);
+ snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
SendToProgram(buf, &first);
}
StartClocks();
if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
SendToICS("draw ");
- SendMoveToICS(moveType, fromX, fromY, toX, toY);
+ SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
}
// also send plain move, in case ICS does not understand atomic claims
- SendMoveToICS(moveType, fromX, fromY, toX, toY);
+ SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
ics_user_moved = 1;
}
} else {
// [HGM] book: if program might be playing, let it use book
bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
first.maybeThinking = TRUE;
+ } else if(fromY == DROP_RANK && fromX == EmptySquare) {
+ if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
+ SendBoard(&first, currentMove+1);
} else SendMoveToProgram(forwardMostMove-1, &first);
if (currentMove == cmailOldMove + 1) {
cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
switch (gameMode) {
case EditGame:
+ if(appData.testLegality)
switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
case MT_NONE:
case MT_CHECK:
break;
}
break;
-
+
case MachinePlaysBlack:
case MachinePlaysWhite:
/* disable certain menu options while machine is thinking */
}
userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
-
+ promoDefaultAltered = FALSE; // [HGM] fall back on default choice
+
if(bookHit) { // [HGM] book: simulate book reply
static char bookMove[MSG_SIZ]; // a bit generous?
- programStats.nodes = programStats.depth = programStats.time =
+ programStats.nodes = programStats.depth = programStats.time =
programStats.score = programStats.got_only_move = 0;
sprintf(programStats.movelist, "%s (xbook)", bookHit);
- strcpy(bookMove, "move ");
+ safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
strcat(bookMove, bookHit);
HandleMachineMove(bookMove, &first);
}
}
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
-Mark(board, flags, kind, rf, ff, rt, ft, closure)
- Board board;
- int flags;
- ChessMove kind;
- int rf, ff, rt, ft;
- VOIDSTAR closure;
+Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
{
typedef char Markers[BOARD_RANKS][BOARD_FILES];
Markers *m = (Markers *) closure;
}
void
-MarkTargetSquares(int clear)
+MarkTargetSquares (int clear)
{
int x, y;
- if(!appData.markers || !appData.highlightDragging ||
- !appData.testLegality || gameMode == EditPosition) return;
- if(clear) {
+ if(clear) // no reason to ever suppress clearing
for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
- } else {
+ if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
+ !appData.testLegality || gameMode == EditPosition) return;
+ if(!clear) {
int capt = 0;
- GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
+ GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
if(PosFlags(0) & F_MANDATORY_CAPTURE) {
for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
if(capt)
DrawPosition(TRUE, NULL);
}
-void LeftClick(ClickType clickType, int xPix, int yPix)
+int
+Explode (Board board, int fromX, int fromY, int toX, int toY)
+{
+ if(gameInfo.variant == VariantAtomic &&
+ (board[toY][toX] != EmptySquare || // capture?
+ toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
+ board[fromY][fromX] == BlackPawn )
+ )) {
+ AnimateAtomicCapture(board, fromX, fromY, toX, toY);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
+
+int
+CanPromote (ChessSquare piece, int y)
+{
+ if(gameMode == EditPosition) return FALSE; // no promotions when editing position
+ // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
+ if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantXiangqi ||
+ gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
+ gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
+ gameInfo.variant == VariantMakruk) return FALSE;
+ return (piece == BlackPawn && y == 1 ||
+ piece == WhitePawn && y == BOARD_HEIGHT-2 ||
+ piece == BlackLance && y == 1 ||
+ piece == WhiteLance && y == BOARD_HEIGHT-2 );
+}
+
+void
+LeftClick (ClickType clickType, int xPix, int yPix)
{
int x, y;
Boolean saveAnimate;
- static int second = 0, promotionChoice = 0, dragging = 0;
+ static int second = 0, promotionChoice = 0, clearFlag = 0;
char promoChoice = NULLCHAR;
+ ChessSquare piece;
+ static TimeMark lastClickTime, prevClickTime;
- if(appData.seekGraph && appData.icsActive && loggedOn &&
- (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
- SeekGraphClick(clickType, xPix, yPix, 0);
- return;
- }
+ if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
+
+ prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
if (clickType == Press) ErrorPopDown();
- MarkTargetSquares(1);
x = EventToSquare(xPix, BOARD_WIDTH);
y = EventToSquare(yPix, BOARD_HEIGHT);
x = BOARD_WIDTH - 1 - x;
}
+ if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
+ defaultPromoChoice = promoSweep;
+ promoSweep = EmptySquare; // terminate sweep
+ promoDefaultAltered = TRUE;
+ if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
+ }
+
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) ) {
+ if(gameInfo.holdingsWidth &&
+ (WhiteOnMove(currentMove)
+ ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
+ : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
// 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)));
+ if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
+ if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
+ FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
fromX = fromY = -1;
return;
}
|| x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
return;
+ if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
+ // could be static click on premove from-square: abort premove
+ gotPremove = 0;
+ ClearPremoveHighlights();
+ }
+
+ if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
+ fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
+
+ if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
+ int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
+ gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
+ defaultPromoChoice = DefaultPromoChoice(side);
+ }
+
autoQueen = appData.alwaysPromoteToQueen;
if (fromX == -1) {
- if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
- if (clickType == Press) {
+ int originalY = y;
+ gatingPiece = EmptySquare;
+ if (clickType != Press) {
+ if(dragging) { // [HGM] from-square must have been reset due to game end since last press
+ DragPieceEnd(xPix, yPix); dragging = 0;
+ DrawPosition(FALSE, NULL);
+ }
+ return;
+ }
+ doubleClick = FALSE;
+ fromX = x; fromY = y; toX = toY = -1;
+ if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
+ // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
+ appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
/* First square */
- if (OKToStartUserMove(x, y)) {
- fromX = x;
- fromY = y;
+ if (OKToStartUserMove(fromX, fromY)) {
second = 0;
MarkTargetSquares(0);
- DragPieceBegin(xPix, yPix); dragging = 1;
+ if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
+ DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
+ if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
+ promoSweep = defaultPromoChoice;
+ selectFlag = 0; lastX = xPix; lastY = yPix;
+ Sweep(0); // Pawn that is going to promote: preview promotion piece
+ DisplayMessage("", _("Pull pawn backwards to under-promote"));
+ }
if (appData.highlightDragging) {
- SetHighlights(x, y, -1, -1);
+ SetHighlights(fromX, fromY, -1, -1);
}
- }
- } else if(dragging) { // [HGM] from-square must have been reset due to game end since last press
- DragPieceEnd(xPix, yPix); dragging = 0;
- DrawPosition(FALSE, NULL);
+ } else fromX = fromY = -1;
+ return;
}
- return;
- }
}
/* fromX != -1 */
/* 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;
+ frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
if ((WhitePawn <= fromP && fromP <= WhiteKing &&
WhitePawn <= toP && toP <= WhiteKing &&
!(fromP == WhiteKing && toP == WhiteRook && frc) &&
!(fromP == WhiteRook && toP == WhiteKing && frc)) ||
- (BlackPawn <= fromP && fromP <= BlackKing &&
+ (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(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
+ second = FALSE; // first double-click rather than scond click
+ doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
+ }
+ promoDefaultAltered = FALSE;
+ MarkTargetSquares(1);
if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
if (appData.highlightDragging) {
SetHighlights(x, y, -1, -1);
ClearHighlights();
}
if (OKToStartUserMove(x, y)) {
+ if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
+ (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
+ y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
+ gatingPiece = boards[currentMove][fromY][fromX];
+ else gatingPiece = doubleClick ? fromP : EmptySquare;
fromX = x;
fromY = y; dragging = 1;
MarkTargetSquares(0);
- DragPieceBegin(xPix, yPix);
+ DragPieceBegin(xPix, yPix, FALSE);
+ if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
+ promoSweep = defaultPromoChoice;
+ selectFlag = 0; lastX = xPix; lastY = yPix;
+ Sweep(0); // Pawn that is going to promote: preview promotion piece
+ }
}
- return;
}
+ if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
+ second = FALSE;
}
// ignore clicks on holdings
if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
if (clickType == Release && x == fromX && y == fromY) {
DragPieceEnd(xPix, yPix); dragging = 0;
+ if(clearFlag) {
+ // a deferred attempt to click-click move an empty square on top of a piece
+ boards[currentMove][y][x] = EmptySquare;
+ ClearHighlights();
+ DrawPosition(FALSE, boards[currentMove]);
+ fromX = fromY = -1; clearFlag = 0;
+ return;
+ }
if (appData.animateDragging) {
/* Undo animation damage if any */
DrawPosition(FALSE, NULL);
/* Second up/down in same square; just abort move */
second = 0;
fromX = fromY = -1;
+ gatingPiece = EmptySquare;
ClearHighlights();
gotPremove = 0;
ClearPremoveHighlights();
return;
}
+ clearFlag = 0;
+
/* we now have a different from- and (possibly off-board) to-square */
/* Completed move */
toX = x;
toY = y;
saveAnimate = appData.animate;
+ MarkTargetSquares(1);
if (clickType == Press) {
+ if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
+ // must be Edit Position mode with empty-square selected
+ fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
+ if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
+ return;
+ }
+ if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
+ ChessSquare piece = boards[currentMove][fromY][fromX];
+ DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
+ promoSweep = defaultPromoChoice;
+ if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
+ selectFlag = 0; lastX = xPix; lastY = yPix;
+ Sweep(0); // Pawn that is going to promote: preview promotion piece
+ DisplayMessage("", _("Pull pawn backwards to under-promote"));
+ DrawPosition(FALSE, boards[currentMove]);
+ return;
+ }
/* Finish clickclick move */
if (appData.animate || appData.highlightLastMove) {
SetHighlights(fromX, fromY, toX, toY);
if(gameMode == EditPosition && piece != EmptySquare &&
fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
int n;
-
+
if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
n = PieceToNumber(piece - (int)BlackPawn);
if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
}
// off-board moves should not be highlighted
- if(x < 0 || x < 0) ClearHighlights();
+ if(x < 0 || y < 0) ClearHighlights();
- if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
+ if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
+
+ if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
SetHighlights(fromX, fromY, toX, toY);
- if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
+ if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
// [HGM] super: promotion to captured piece selected from holdings
ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
promotionChoice = TRUE;
}
PromotionPopUp();
} else {
+ int oldMove = currentMove;
UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
if (!appData.highlightLastMove || gotPremove) ClearHighlights();
if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
+ if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
+ Explode(boards[currentMove-1], fromX, fromY, toX, toY))
+ DrawPosition(TRUE, boards[currentMove]);
fromX = fromY = -1;
}
appData.animate = saveAnimate;
}
}
-int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
+int
+RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
{ // front-end-free part taken out of PieceMenuPopup
int whichMenu; int xSqr, ySqr;
xSqr = EventToSquare(x, BOARD_WIDTH);
ySqr = EventToSquare(y, BOARD_HEIGHT);
- if (action == Release) UnLoadPV(); // [HGM] pv
+ if (action == Release) {
+ if(pieceSweep != EmptySquare) {
+ EditPositionMenuEvent(pieceSweep, toX, toY);
+ pieceSweep = EmptySquare;
+ } else UnLoadPV(); // [HGM] pv
+ }
if (action != Press) return -2; // return code to be ignored
switch (gameMode) {
case IcsExamining:
- if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
+ if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
case EditPosition:
- if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
- if (xSqr < 0 || ySqr < 0) return -1;\r
- whichMenu = 0; // edit-position menu
- break;
+ if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
+ if (xSqr < 0 || ySqr < 0) return -1;
+ if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
+ pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
+ toX = xSqr; toY = ySqr; lastX = x, lastY = y;
+ if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
+ NextPiece(0);
+ return 2; // grab
case IcsObserving:
if(!appData.icsEngineAnalyze) return -1;
case IcsPlayingWhite:
return whichMenu;
}
-void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
+void
+SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
{
// char * hint = lastHint;
FrontEndProgramStats stats;
stats.an_move_count = cpstats->nr_moves;
}
- if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
+ if(stats.pv && stats.pv[0]) safeStrCpy(lastPV[stats.which], stats.pv, sizeof(lastPV[stats.which])/sizeof(lastPV[stats.which][0])); // [HGM] pv: remember last PV of each
SetProgramStats( &stats );
}
void
-Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
+ClearEngineOutputPane (int which)
+{
+ static FrontEndProgramStats dummyStats;
+ dummyStats.which = which;
+ dummyStats.pv = "#";
+ SetProgramStats( &dummyStats );
+}
+
+#define MAXPLAYERS 500
+
+char *
+TourneyStandings (int display)
+{
+ int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
+ int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
+ char result, *p, *names[MAXPLAYERS];
+
+ if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
+ return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
+ names[0] = p = strdup(appData.participants);
+ while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
+
+ for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
+
+ while(result = appData.results[nr]) {
+ color = Pairing(nr, nPlayers, &w, &b, &dummy);
+ if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
+ wScore = bScore = 0;
+ switch(result) {
+ case '+': wScore = 2; break;
+ case '-': bScore = 2; break;
+ case '=': wScore = bScore = 1; break;
+ case ' ':
+ case '*': return strdup("busy"); // tourney not finished
+ }
+ score[w] += wScore;
+ score[b] += bScore;
+ games[w]++;
+ games[b]++;
+ nr++;
+ }
+ if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
+ for(w=0; w<nPlayers; w++) {
+ bScore = -1;
+ for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
+ ranking[w] = b; points[w] = bScore; score[b] = -2;
+ }
+ p = malloc(nPlayers*34+1);
+ for(w=0; w<nPlayers && w<display; w++)
+ sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
+ free(names[0]);
+ return p;
+}
+
+void
+Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
{ // count all piece types
int p, f, r;
*nB = *nW = *wStale = *bStale = *bishopColor = 0;
}
int
-SufficientDefence(int pCnt[], int side, int nMine, int nHis)
+SufficientDefence (int pCnt[], int side, int nMine, int nHis)
{
int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
-
+
nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
if(myPawns == 2 && nMine == 3) // KPP
return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
if(myPawns) return FALSE;
if(pCnt[WhiteRook+side])
- return pCnt[BlackRook-side] ||
+ return pCnt[BlackRook-side] ||
pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
}
int
-MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
+MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
{
VariantClass v = gameInfo.variant;
} else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
-
+
if(nMine == 1) return FALSE; // bare King
if(nBishops && bisColor == 3) return TRUE; // There must be a second B/A/F, which can either block (his) or attack (mine) the escape square
nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
// by now we have King + 1 piece (or multiple Bishops on the same color)
if(pCnt[WhiteKnight+side])
- return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
+ return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
|| nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
if(nBishops)
}
int
-Adjudicate(ChessProgramState *cps)
+CompareWithRights (Board b1, Board b2)
+{
+ int rights = 0;
+ if(!CompareBoards(b1, b2)) return FALSE;
+ if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
+ /* compare castling rights */
+ if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
+ rights++; /* King lost rights, while rook still had them */
+ if( b1[CASTLING][2] != NoRights ) { /* king has rights */
+ if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
+ rights++; /* but at least one rook lost them */
+ }
+ if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
+ rights++;
+ if( b1[CASTLING][5] != NoRights ) {
+ if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
+ rights++;
+ }
+ return rights == 0;
+}
+
+int
+Adjudicate (ChessProgramState *cps)
{ // [HGM] some adjudications useful with buggy engines
// [HGM] adjudicate: made into separate routine, which now can be called after every move
// In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
Boolean canAdjudicate = !appData.icsActive;
- // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
- if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
+ // most tests only when we understand the game, i.e. legality-checking on
if( appData.testLegality )
{ /* [HGM] Some more adjudications for obstinate engines */
int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
if(canAdjudicate && appData.checkMates) {
if(engineOpponent)
SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
- ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
- GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
+ GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
"Xboard adjudication: King destroyed", GE_XBOARD );
return 1;
}
if(canAdjudicate && appData.checkMates) {
if(engineOpponent)
SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
- ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
- GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
+ GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
"Xboard adjudication: Bare king", GE_XBOARD );
return 1;
}
/* but only adjudicate if adjudication enabled */
if(engineOpponent)
SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
- ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
- GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
+ GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
"Xboard adjudication: Bare king", GE_XBOARD );
return 1;
}
case EP_WINS:
result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
default:
- result = (ChessMove) 0;
+ result = EndOfFile;
}
if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
if(engineOpponent)
SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
- ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
GameEnds( result, reason, GE_XBOARD );
return 1;
}
SendToProgram("force\n", engineOpponent); // suppress reply
SendMoveToProgram(forwardMostMove-1, engineOpponent); /* 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 1;
}
/* Then some trivial draws (only adjudicate, cannot be claimed) */
if(gameInfo.variant == VariantXiangqi ?
SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
- : nrW + nrB == 4 &&
+ : nrW + nrB == 4 &&
( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
|| nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
|| nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
SendToProgram("force\n", engineOpponent); // suppress reply
SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
}
- ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
return 1;
}
} 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]);
-
- }
// Repetition draws and 50-move rule can be applied independently of legality testing
}
if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
(boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
- rights++;
+ rights++;
if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
/* adjudicate after user-specified nr of repeats */
int result = GameIsDrawn;
char *details = "XBoard adjudication: repetition draw";
- if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
+ 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) {
hisPerpetual = PerpetualChase(k, forwardMostMove);
ourPerpetual = PerpetualChase(k+1, forwardMostMove);
if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
+ static char resdet[MSG_SIZ];
result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
- details = "Xboard adjudication: perpetual chasing";
+ details = resdet;
+ snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
} else
if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
break; // Abort repetition-checking loop.
SendToProgram("force\n", engineOpponent); // suppress reply
SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
}
- ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
GameEnds( result, details, GE_XBOARD );
return 1;
}
/* if we hit starting position, add initial plies */
if( count == backwardMostMove )
count -= initialRulePlies;
- count = forwardMostMove - count;
+ count = forwardMostMove - count;
if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
// adjust reversible move counter for checks in Xiangqi
int i = forwardMostMove - count, inCheck = 0, lastCheck;
SendToProgram("force\n", engineOpponent); // suppress reply
SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
}
- ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
return 1;
}
SendToProgram("force\n", engineOpponent); // suppress reply
SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
}
- ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
GameEnds( GameIsDrawn, p, GE_XBOARD );
return 1;
}
SendToProgram("force\n", engineOpponent); // suppress reply
SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
}
- ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
return 1;
}
return 0;
}
-char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
+char *
+SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
{ // [HGM] book: this routine intercepts moves to simulate book replies
char *bookHit = NULL;
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];
- sprintf(buf, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
+ char buf[MSG_SIZ], *move = bookHit;
+ if(cps->useSAN) {
+ int fromX, fromY, toX, toY;
+ char promoChar;
+ ChessMove moveType;
+ move = buf + 30;
+ if (ParseOneMove(bookHit, forwardMostMove, &moveType,
+ &fromX, &fromY, &toX, &toY, &promoChar)) {
+ (void) CoordsToAlgebraic(boards[forwardMostMove],
+ PosFlags(forwardMostMove),
+ fromY, fromX, toY, toX, promoChar, move);
+ } else {
+ if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
+ bookHit = NULL;
+ }
+ }
+ snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // 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
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);
+ 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
}
+int
+LoadError (char *errmess, ChessProgramState *cps)
+{ // unloads engine and switches back to -ncp mode if it was first
+ if(cps->initDone) return FALSE;
+ cps->isr = NULL; // this should suppress further error popups from breaking pipes
+ DestroyChildProcess(cps->pr, 9 ); // just to be sure
+ cps->pr = NoProc;
+ if(cps == &first) {
+ appData.noChessProgram = TRUE;
+ gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
+ gameMode = BeginningOfGame; ModeHighlight();
+ SetNCPMode();
+ }
+ if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
+ DisplayMessage("", ""); // erase waiting message
+ if(errmess) DisplayError(errmess, 0); // announce reason, if given
+ return TRUE;
+}
+
char *savedMessage;
ChessProgramState *savedState;
-void DeferredBookMove(void)
+void
+DeferredBookMove (void)
{
if(savedState->lastPing != savedState->lastPong)
ScheduleDelayedEvent(DeferredBookMove, 10);
HandleMachineMove(savedMessage, savedState);
}
+static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
+
void
-HandleMachineMove(message, cps)
- char *message;
- ChessProgramState *cps;
+HandleMachineMove (char *message, ChessProgramState *cps)
{
char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
char realname[MSG_SIZ];
int fromX, fromY, toX, toY;
ChessMove moveType;
char promoChar;
- char *p;
- int machineWhite;
+ char *p, *pv=buf1;
+ int machineWhite, oldError;
char *bookHit;
- cps->userError = 0;
+ if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
+ // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
+ if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
+ DisplayError(_("Invalid pairing from pairing engine"), 0);
+ return;
+ }
+ pairingReceived = 1;
+ NextMatchGame();
+ return; // Skim the pairing messages here.
+ }
+
+ oldError = cps->userError; cps->userError = 0;
FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
/*
* 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))
+ (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) {
return;
}
- 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"),
- machineMove, cps->which);
+ snprintf(buf1, MSG_SIZ*10, _("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",
+ snprintf(buf1, MSG_SIZ*10, "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,
/* 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 */
- ) {
+ if( gameMode==TwoMachinesPlay && appData.testLegality ) {
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",
+ snprintf(buf1, MSG_SIZ*10, "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);
if (cps->sendTime == 2) cps->sendTime = 1;
if (cps->offeredDraw) cps->offeredDraw--;
- /* currentMoveString is set as a side-effect of ParseOneMove */
- strcpy(machineMove, currentMoveString);
- strcat(machineMove, "\n");
- strcpy(moveList[forwardMostMove], machineMove);
-
/* [AS] Save move info*/
pvInfoList[ forwardMostMove ].score = programStats.score;
pvInfoList[ forwardMostMove ].depth = programStats.depth;
if( count >= adjudicateLossPlies ) {
ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
- GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
- "Xboard adjudication",
+ GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
+ "Xboard adjudication",
GE_XBOARD );
return;
}
}
- if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
+ if(Adjudicate(cps)) {
+ ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
+ return; // [HGM] adjudicate: for all automatic game ends
+ }
#if ZIPPY
if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
SendToICS("draw ");
- SendMoveToICS(moveType, fromX, fromY, toX, toY);
+ SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
}
- SendMoveToICS(moveType, fromX, fromY, toX, toY);
+ SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
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",
+ snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
programStats.score / 100.,
programStats.depth,
programStats.time / 100.,
}
ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
-
+
if (!pausing && appData.ringBellAfterMoves) {
RingBell();
}
- /*
+ /*
* Reenable menu items that were disabled while
* machine was thinking
*/
if(bookHit) {
static char bookMove[MSG_SIZ]; // a bit generous?
- strcpy(bookMove, "move ");
+ safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
strcat(bookMove, bookHit);
message = bookMove;
cps = cps->other;
- programStats.nodes = programStats.depth = programStats.time =
+ programStats.nodes = programStats.depth = programStats.time =
programStats.score = programStats.got_only_move = 0;
sprintf(programStats.movelist, "%s (xbook)", bookHit);
return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
}
+ if ((!appData.testLegality || gameInfo.variant == VariantFairy) &&
+ !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
+ int dummy, s=6; char buf[MSG_SIZ];
+ if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
+ if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
+ if(startedFromSetupPosition) return;
+ ParseFEN(boards[0], &dummy, message+s);
+ DrawPosition(TRUE, boards[0]);
+ startedFromSetupPosition = TRUE;
+ return;
+ }
/* [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.
*/
CopyBoard(boards[0], initial_position);
initialRulePlies = FENrulePlies;
if(blackPlaysFirst) gameMode = MachinePlaysWhite;
- else gameMode = MachinePlaysBlack;
+ else gameMode = MachinePlaysBlack;
DrawPosition(FALSE, boards[currentMove]);
}
return;
* Look for communication commands
*/
if (!strncmp(message, "telluser ", 9)) {
- EscapeExpand(message+9, message+9); // [HGM] esc: allow escape sequences in popup box
+ if(message[9] == '\\' && message[10] == '\\')
+ EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
+ PlayTellSound();
DisplayNote(message + 9);
return;
}
if (!strncmp(message, "tellusererror ", 14)) {
cps->userError = 1;
- EscapeExpand(message+14, message+14); // [HGM] esc: allow escape sequences in popup box
+ if(message[14] == '\\' && message[15] == '\\')
+ EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
+ PlayTellSound();
DisplayError(message + 14, 0);
return;
}
return;
}
if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
- strcpy(realname, cps->tidy);
+ safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
strcat(realname, " query");
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) {
*/
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") ||
if (StrStr(message, "analyze")) {
cps->analysisSupport = FALSE;
cps->analyzing = FALSE;
- Reset(FALSE, TRUE);
- sprintf(buf2, _("%s does not support analysis"), cps->tidy);
+// Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
+ EditGameEvent(); // [HGM] try to preserve loaded game
+ snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
DisplayError(buf2, 0);
return;
}
gameMode = EditGame;
ModeHighlight();
}
+ /* [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; // do not take back move we tested as valid
+ }
currentMove = forwardMostMove-1;
DisplayMove(currentMove-1); /* before DisplayMoveError */
SwitchClocks(forwardMostMove-1); // [HGM] race
DisplayBothClocks();
- sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
- parseList[currentMove], cps->which);
+ snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
+ parseList[currentMove], _(cps->which));
DisplayMoveError(buf1);
DrawPosition(FALSE, boards[currentMove]);
- /* [HGM] illegal-move claim should forfeit game when Xboard */
- /* only passes fully legal moves */
- if( appData.testLegality && gameMode == TwoMachinesPlay ) {
- GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
- "False illegal-move claim", GE_XBOARD );
- }
+ SetUserThinkingEnables();
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.
+ * Attempts to recover here are futile. [HGM] Well, we try anyway
*/
if ((StrStr(message, "unknown host") != NULL)
|| (StrStr(message, "No remote directory") != NULL)
cps->maybeThinking = FALSE;
snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
- cps->which, cps->program, cps->host, message);
+ _(cps->which), cps->program, cps->host, message);
RemoveInputSource(cps->isr);
- DisplayFatalError(buf1, 0, 1);
+ if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
+ if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
+ if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
+ }
return;
}
-
- /*
+
+ /*
* Look for hint output
*/
if (sscanf(message, "Hint: %s", buf1) == 1) {
/* Hint move could not be parsed!? */
snprintf(buf2, sizeof(buf2),
_("Illegal hint move \"%s\"\nfrom %s chess program"),
- buf1, cps->which);
+ buf1, _(cps->which));
DisplayError(buf2, 0);
}
} else {
- strcpy(lastHint, buf1);
+ safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
}
return;
}
r = p + 1;
}
}
-
+
GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
return;
}
}
-
+
/*
* Look for thinking output
*/
}
/* [AS] Negate score if machine is playing black and reporting absolute scores */
- if( cps->scoreIsAbsolute &&
+ if( cps->scoreIsAbsolute &&
( gameMode == MachinePlaysBlack ||
gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
curscore = -curscore;
}
+ if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
+
+ if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
+ char buf[MSG_SIZ];
+ FILE *f;
+ snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
+ buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
+ gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
+ if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
+ if(f = fopen(buf, "w")) { // export PV to applicable PV file
+ fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
+ fclose(f);
+ } else DisplayError(_("failed writing PV"), 0);
+ }
tempStats.depth = plylev;
tempStats.nodes = nodes;
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'))
+ gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
- gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
+ gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
}
/* Buffer overflow protection */
- if (buf1[0] != NULLCHAR) {
- if (strlen(buf1) >= sizeof(tempStats.movelist)
+ if (pv[0] != NULLCHAR) {
+ if (strlen(pv) >= sizeof(tempStats.movelist)
&& appData.debugMode) {
fprintf(debugFP,
"PV is too long; using the first %u bytes.\n",
(unsigned) sizeof(tempStats.movelist) - 1);
}
- safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist) );
+ safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
} else {
sprintf(tempStats.movelist, " no PV\n");
}
SendProgramStatsToFrontend( cps, &tempStats );
- /*
+ /*
[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 ? " " : "" );
+ snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
+ plylev,
+ (gameMode == TwoMachinesPlay ?
+ ToUpper(cps->twoMachinesColor[0]) : ' '),
+ ((double) curscore) / 100.0,
+ prefixHint ? lastHint : "",
+ prefixHint ? " " : "" );
if( buf1[0] != NULLCHAR ) {
unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
- if( strlen(buf1) > max_len ) {
+ if( strlen(pv) > max_len ) {
if( appData.debugMode) {
fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
}
- buf1[max_len+1] = '\0';
+ pv[max_len+1] = '\0';
}
- strcat( thinkOutput, buf1 );
+ strcat( thinkOutput, pv);
}
if (currentMove == forwardMostMove || gameMode == AnalyzeMode
* if there is only 1 legal move
*/
sscanf(p, "(only move) %s", buf1);
- sprintf(thinkOutput, "%s (only move)", buf1);
+ snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
sprintf(programStats.movelist, "%s (only move)", buf1);
programStats.depth = 1;
programStats.nr_moves = 1;
programStats.line_is_book = 1;
SendProgramStatsToFrontend( cps, &programStats );
-
- if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
+
+ if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
DisplayMove(currentMove - 1);
}
programStats.nodes = nodes;
programStats.moves_left = mvleft;
programStats.nr_moves = mvtot;
- strcpy(programStats.move_name, mvname);
+ safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
programStats.ok_to_send = 1;
programStats.movelist[0] = '\0';
buf1[0] = NULLCHAR;
if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
- &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
+ &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
{
ChessProgramStats cpstats;
cpstats.movelist[0] = '\0';
if (buf1[0] != NULLCHAR) {
- safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
+ safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
}
cpstats.ok_to_send = 0;
/* 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
-ParseGameHistory(game)
- char *game;
+ParseGameHistory (char *game)
{
ChessMove moveType;
int fromX, fromY, toX, toY, boardIndex;
yynewstr(game);
for (;;) {
yyboardindex = boardIndex;
- moveType = (ChessMove) yylex();
+ moveType = (ChessMove) Myylex();
switch (moveType) {
case IllegalMove: /* maybe suicide chess, etc. */
if (appData.debugMode) {
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 BlackPromotionRook:
- case WhitePromotionBishop:
- case BlackPromotionBishop:
- case WhitePromotionKnight:
- case BlackPromotionKnight:
- case WhitePromotionKing:
- case BlackPromotionKing:
+ case WhitePromotion:
+ case BlackPromotion:
+ case WhiteNonPromotion:
+ case BlackNonPromotion:
case NormalMove:
case WhiteCapturesEnPassant:
case BlackCapturesEnPassant:
break;
case WhiteDrop:
case BlackDrop:
+ if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
fromX = moveType == WhiteDrop ?
(int) CharToPiece(ToUpper(currentMoveString[0])) :
(int) CharToPiece(ToLower(currentMoveString[0]));
break;
case AmbiguousMove:
/* bug? */
- sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
+ snprintf(buf, MSG_SIZ, _("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);
return;
case ImpossibleMove:
/* bug? */
- sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
+ snprintf(buf, MSG_SIZ, _("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);
}
DisplayError(buf, 0);
return;
- case (ChessMove) 0: /* end of file */
+ case EndOfFile:
if (boardIndex < backwardMostMove) {
/* Oops, gap. How did that happen? */
DisplayError(_("Gap in move list"), 0);
if (q != NULL) *q = NULLCHAR;
p++;
}
+ while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
gameInfo.resultDetails = StrSave(p);
continue;
}
parseList[boardIndex]);
CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
/* currentMoveString is set as a side-effect of yylex */
- strcpy(moveList[boardIndex], currentMoveString);
+ safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
strcat(moveList[boardIndex], "\n");
boardIndex++;
ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
/* Apply a move to the given board */
void
-ApplyMove(fromX, fromY, toX, toY, promoChar, board)
- int fromX, fromY, toX, toY;
- int promoChar;
- Board board;
+ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
{
ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
- int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
+ int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
/* [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 (fromY == DROP_RANK) {
+ /* must be first */
+ if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
+ board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
+ return;
+ }
+ piece = board[toY][toX] = (ChessSquare) fromX;
+ } else {
+ int i;
+
+ if( board[toY][toX] != EmptySquare )
+ board[EP_STATUS] = EP_CAPTURE;
+ if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
+ if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
+ board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
+ } else
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;
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)
+ gameInfo.variant != VariantBerolina || toX > fromX)
board[EP_STATUS] = toX;
}
- } else
+ } 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;
+ 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)
+ 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] == 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 || gameInfo.variant == VariantMakruk)
- && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
-
- if (fromX == toX && fromY == toY) return;
+ if (fromX == toX && fromY == toY) return;
- if (fromY == DROP_RANK) {
- /* must be first */
- piece = board[toY][toX] = (ChessSquare) fromX;
- } else {
piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
if(gameInfo.variant == VariantKnightmate)
king += (int) WhiteUnicorn - (int) WhiteKing;
/* Code added by Tord: */
- /* FRC castling assumed when king captures friendly rook. */
- if (board[fromY][fromX] == WhiteKing &&
- board[toY][toX] == WhiteRook) {
+ /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
+ if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
+ board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
board[fromY][fromX] = EmptySquare;
board[toY][toX] = EmptySquare;
- if(toX > fromX) {
+ if((toX > fromX) != (piece == WhiteRook)) {
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) {
+ } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
+ board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
board[fromY][fromX] = EmptySquare;
board[toY][toX] = EmptySquare;
- if(toX > fromX) {
+ if((toX > fromX) != (piece == BlackRook)) {
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;
board[toY][toX] = king;
board[toY][toX+1] = board[fromY][BOARD_LEFT];
board[fromY][BOARD_LEFT] = EmptySquare;
- } else if (board[fromY][fromX] == WhitePawn
- && toY >= BOARD_HEIGHT-promoRank
- && gameInfo.variant != VariantXiangqi
+ } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
+ board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
+ && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
) {
/* white pawn promotion */
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 == BOARD_HEIGHT-4)
+ } else if ((fromY >= BOARD_HEIGHT>>1)
&& (toX != fromX)
&& gameInfo.variant != VariantXiangqi
&& gameInfo.variant != VariantBerolina
board[toY][toX] = BlackKing;
board[fromY][0] = EmptySquare;
board[toY][2] = BlackRook;
- } else if (board[fromY][fromX] == BlackPawn
- && toY < promoRank
- && gameInfo.variant != VariantXiangqi
+ } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
+ board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
+ && toY < promoRank && promoChar
) {
/* black pawn promotion */
board[toY][toX] = CharToPiece(ToLower(promoChar));
- if (board[toY][toX] == EmptySquare) {
- board[toY][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)
+ } else if ((fromY < BOARD_HEIGHT>>1)
&& (toX != fromX)
&& gameInfo.variant != VariantXiangqi
&& gameInfo.variant != VariantBerolina
board[toY][toX] = board[fromY][fromX];
board[fromY][fromX] = EmptySquare;
}
-
- /* [HGM] now we promote for Shogi, if needed */
- if(gameInfo.variant == VariantShogi && promoChar == 'q')
- board[toY][toX] = (ChessSquare) (PROMOTED piece);
}
if (gameInfo.holdingsWidth != 0) {
/* [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) {
+ if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
+ && promoChar && piece != WhitePawn && piece != BlackPawn) {
/* Delete from holdings, by decreasing count */
/* and erasing image if necessary */
- p = (int) fromX;
+ p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
if(p < (int) BlackPawn) { /* white drop */
p -= (int)WhitePawn;
p = PieceToNumber((ChessSquare)p);
}
}
if (captured != EmptySquare && gameInfo.holdingsSize > 0
- && gameInfo.variant != VariantBughouse ) {
+ && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
/* [HGM] holdings: Add to holdings, if holdings exist */
- if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
+ if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
// [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;
}
board[toY][toX] = EmptySquare;
}
}
- if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
- /* [HGM] Shogi promotions */
+ if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
+ board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
+ } else
+ if(promoChar == '+') {
+ /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
board[toY][toX] = (ChessSquare) (PROMOTED piece);
- }
-
- if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
- && promoChar != NULLCHAR && gameInfo.holdingsSize) {
+ } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
+ ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
+ if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
+ && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
+ board[toY][toX] = newPiece;
+ }
+ if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
+ && 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
/* Updates forwardMostMove */
void
-MakeMove(fromX, fromY, toX, toY, promoChar)
- int fromX, fromY, toX, toY;
- int promoChar;
+MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
{
// forwardMostMove++; // [HGM] bare: moved downstream
+ (void) CoordsToAlgebraic(boards[forwardMostMove],
+ PosFlags(forwardMostMove),
+ fromY, fromX, toY, toX, promoChar,
+ parseList[forwardMostMove]);
+
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];
if(gameInfo.variant == VariantKnightmate)
king += (int) WhiteUnicorn - (int) WhiteKing;
if(forwardMostMove == 0) {
- if(blackPlaysFirst)
+ if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
+ fprintf(serverMoves, "%s;", UserName());
+ else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
fprintf(serverMoves, "%s;", second.tidy);
fprintf(serverMoves, "%s;", first.tidy);
- if(!blackPlaysFirst)
+ if(gameMode == MachinePlaysWhite)
+ fprintf(serverMoves, "%s;", UserName());
+ else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
fprintf(serverMoves, "%s;", second.tidy);
} else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
lastLoadFlag = loadFlag;
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);
+ fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
if(!loadFlag) {
+ char buf[MOVE_LEN*2], *p; int len;
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);
+ strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
+ if(p = strchr(buf, '=')) *p = NULLCHAR;
+ len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
+ fprintf(serverMoves, "/%s", buf);
}
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);
+ if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
+ GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
return;
}
UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
timeRemaining[1][forwardMostMove] = blackTimeRemaining;
+ adjustedClock = FALSE;
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),
- fromY, fromX, toY, toX, promoChar,
- parseList[forwardMostMove - 1]);
switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
case MT_NONE:
case MT_STALEMATE:
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 */
void
-ShowMove(fromX, fromY, toX, toY)
+ShowMove (int fromX, int fromY, int toX, int toY)
{
int instant = (gameMode == PlayFromGameFile) ?
(matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
}
-void SendEgtPath(ChessProgramState *cps)
+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;
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] &&
+ 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);
+ snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
SendToProgram(buf,cps); // send egtbpath command for nalimov
} else
if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
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);
+ snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
*r = c;
SendToProgram(buf,cps); // send egtbpath command for this format
}
}
void
-InitChessProgram(cps, setup)
- ChessProgramState *cps;
- int setup; /* [HGM] needed to setup FRC opening position */
+InitChessProgram (ChessProgramState *cps, int setup)
+/* setup needed to setup FRC opening position */
{
char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
if (appData.noChessProgram) return;
hintRequested = FALSE;
bookRequested = FALSE;
+ ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
/* [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);
+ snprintf(buf, MSG_SIZ, "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);
+ snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
SendToProgram(buf, cps);
}
char *v = VariantName(gameInfo.variant);
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);
+ snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
DisplayFatalError(buf, 0, 1);
return;
}
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 )
+ if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
+ gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
if( gameInfo.variant == VariantCourier )
overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
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( gameInfo.variant == VariantSChess )
+ overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
+ if( gameInfo.variant == VariantGrand )
+ overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
if(overruled) {
- sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
- gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
+ snprintf(b, MSG_SIZ, "%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) {
+ 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",
+ snprintf(buf, MSG_SIZ, "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);
+ } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
+ snprintf(buf, MSG_SIZ, "variant %s\n", b);
SendToProgram(buf, cps);
}
currentlyInitializedVariant = gameInfo.variant;
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("easy\n", cps);
}
if (cps->usePing) {
- sprintf(buf, "ping %d\n", ++cps->lastPing);
+ snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
SendToProgram(buf, cps);
}
cps->initDone = TRUE;
-}
+ ClearEngineOutputPane(cps == &second);
+}
void
-StartChessProgram(cps)
- ChessProgramState *cps;
+StartChessProgram (ChessProgramState *cps)
{
char buf[MSG_SIZ];
int err;
}
err = StartChildProcess(buf, "", &cps->pr);
}
-
+
if (err != 0) {
- sprintf(buf, _("Startup failure on '%s'"), cps->program);
- DisplayFatalError(buf, err, 1);
- cps->pr = NoProc;
- cps->isr = NULL;
+ snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
+ DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
+ if(cps != &first) return;
+ appData.noChessProgram = TRUE;
+ ThawUI();
+ SetNCPMode();
+// 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);
+ snprintf(buf, MSG_SIZ, "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);
}
}
-
void
TwoMachinesEventIfReady P((void))
{
+ static int curMess = 0;
if (first.lastPing != first.lastPong) {
- DisplayMessage("", _("Waiting for first chess program"));
+ if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
return;
}
if (second.lastPing != second.lastPong) {
- DisplayMessage("", _("Waiting for second chess program"));
+ if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
return;
}
+ DisplayMessage("", ""); curMess = 0;
ThawUI();
TwoMachinesEvent();
}
+char *
+MakeName (char *template)
+{
+ time_t clock;
+ struct tm *tm;
+ static char buf[MSG_SIZ];
+ char *p = buf;
+ int i;
+
+ clock = time((time_t *)NULL);
+ tm = localtime(&clock);
+
+ while(*p++ = *template++) if(p[-1] == '%') {
+ switch(*template++) {
+ case 0: *p = 0; return buf;
+ case 'Y': i = tm->tm_year+1900; break;
+ case 'y': i = tm->tm_year-100; break;
+ case 'M': i = tm->tm_mon+1; break;
+ case 'd': i = tm->tm_mday; break;
+ case 'h': i = tm->tm_hour; break;
+ case 'm': i = tm->tm_min; break;
+ case 's': i = tm->tm_sec; break;
+ default: i = 0;
+ }
+ snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
+ }
+ return buf;
+}
+
+int
+CountPlayers (char *p)
+{
+ int n = 0;
+ while(p = strchr(p, '\n')) p++, n++; // count participants
+ return n;
+}
+
+FILE *
+WriteTourneyFile (char *results, FILE *f)
+{ // write tournament parameters on tourneyFile; on success return the stream pointer for closing
+ if(f == NULL) f = fopen(appData.tourneyFile, "w");
+ if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
+ // create a file with tournament description
+ fprintf(f, "-participants {%s}\n", appData.participants);
+ fprintf(f, "-seedBase %d\n", appData.seedBase);
+ fprintf(f, "-tourneyType %d\n", appData.tourneyType);
+ fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
+ fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
+ fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
+ fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
+ fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
+ fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
+ fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
+ fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
+ fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
+ fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
+ fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
+ if(searchTime > 0)
+ fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
+ else {
+ fprintf(f, "-mps %d\n", appData.movesPerSession);
+ fprintf(f, "-tc %s\n", appData.timeControl);
+ fprintf(f, "-inc %.2f\n", appData.timeIncrement);
+ }
+ fprintf(f, "-results \"%s\"\n", results);
+ }
+ return f;
+}
+
+char *command[MAXENGINES], *mnemonic[MAXENGINES];
+
void
-NextMatchGame P((void))
+Substitute (char *participants, int expunge)
{
- 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,
- 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,
- index,
- appData.loadPositionFile);
+ int i, changed, changes=0, nPlayers=0;
+ char *p, *q, *r, buf[MSG_SIZ];
+ if(participants == NULL) return;
+ if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
+ r = p = participants; q = appData.participants;
+ while(*p && *p == *q) {
+ if(*p == '\n') r = p+1, nPlayers++;
+ p++; q++;
+ }
+ if(*p) { // difference
+ while(*p && *p++ != '\n');
+ while(*q && *q++ != '\n');
+ changed = nPlayers;
+ changes = 1 + (strcmp(p, q) != 0);
+ }
+ if(changes == 1) { // a single engine mnemonic was changed
+ q = r; while(*q) nPlayers += (*q++ == '\n');
+ p = buf; while(*r && (*p = *r++) != '\n') p++;
+ *p = NULLCHAR;
+ NamesToList(firstChessProgramNames, command, mnemonic, "all");
+ for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
+ if(mnemonic[i]) { // The substitute is valid
+ FILE *f;
+ if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
+ flock(fileno(f), LOCK_EX);
+ ParseArgsFromFile(f);
+ fseek(f, 0, SEEK_SET);
+ FREE(appData.participants); appData.participants = participants;
+ if(expunge) { // erase results of replaced engine
+ int len = strlen(appData.results), w, b, dummy;
+ for(i=0; i<len; i++) {
+ Pairing(i, nPlayers, &w, &b, &dummy);
+ if((w == changed || b == changed) && appData.results[i] == '*') {
+ DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
+ fclose(f);
+ return;
+ }
+ }
+ for(i=0; i<len; i++) {
+ Pairing(i, nPlayers, &w, &b, &dummy);
+ if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
+ }
+ }
+ WriteTourneyFile(appData.results, f);
+ fclose(f); // release lock
+ return;
+ }
+ } else DisplayError(_("No engine with the name you gave is installed"), 0);
+ }
+ if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
+ if(changes > 1) DisplayError(_("You can only change one engine at the time"), 0);
+ free(participants);
+ return;
+}
+
+int
+CreateTourney (char *name)
+{
+ FILE *f;
+ if(matchMode && strcmp(name, appData.tourneyFile)) {
+ ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
+ }
+ if(name[0] == NULLCHAR) {
+ if(appData.participants[0])
+ DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
+ return 0;
+ }
+ f = fopen(name, "r");
+ if(f) { // file exists
+ ASSIGN(appData.tourneyFile, name);
+ ParseArgsFromFile(f); // parse it
+ } else {
+ if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
+ if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
+ DisplayError(_("Not enough participants"), 0);
+ return 0;
+ }
+ ASSIGN(appData.tourneyFile, name);
+ if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
+ if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
+ }
+ fclose(f);
+ appData.noChessProgram = FALSE;
+ appData.clockMode = TRUE;
+ SetGNUMode();
+ return 1;
+}
+
+int
+NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
+{
+ char buf[MSG_SIZ], *p, *q;
+ int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
+ insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
+ skip = !all && group[0]; // if group requested, we start in skip mode
+ for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
+ p = names; q = buf; header = 0;
+ while(*p && *p != '\n') *q++ = *p++;
+ *q = 0;
+ if(*p == '\n') p++;
+ if(buf[0] == '#') {
+ if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
+ depth++; // we must be entering a new group
+ if(all) continue; // suppress printing group headers when complete list requested
+ header = 1;
+ if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
+ }
+ if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
+ if(engineList[i]) free(engineList[i]);
+ engineList[i] = strdup(buf);
+ if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
+ if(engineMnemonic[i]) free(engineMnemonic[i]);
+ if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
+ strcat(buf, " (");
+ sscanf(q + 8, "%s", buf + strlen(buf));
+ strcat(buf, ")");
+ }
+ engineMnemonic[i] = strdup(buf);
+ i++;
}
- TwoMachinesEventIfReady();
+ engineList[i] = engineMnemonic[i] = NULL;
+ return i;
}
-void UserAdjudicationEvent( int result )
+// following implemented as macro to avoid type limitations
+#define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
+
+void
+SwapEngines (int n)
+{ // swap settings for first engine and other engine (so far only some selected options)
+ int h;
+ char *p;
+ if(n == 0) return;
+ SWAP(directory, p)
+ SWAP(chessProgram, p)
+ SWAP(isUCI, h)
+ SWAP(hasOwnBookUCI, h)
+ SWAP(protocolVersion, h)
+ SWAP(reuse, h)
+ SWAP(scoreIsAbsolute, h)
+ SWAP(timeOdds, h)
+ SWAP(logo, p)
+ SWAP(pgnName, p)
+ SWAP(pvSAN, h)
+ SWAP(engOptions, p)
+ SWAP(engInitString, p)
+ SWAP(computerString, p)
+ SWAP(features, p)
+ SWAP(fenOverride, p)
+ SWAP(NPS, h)
+ SWAP(accumulateTC, h)
+ SWAP(host, p)
+}
+
+int
+SetPlayer (int player, char *p)
+{ // [HGM] find the engine line of the partcipant given by number, and parse its options.
+ int i;
+ char buf[MSG_SIZ], *engineName;
+ for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
+ engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
+ for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
+ if(mnemonic[i]) {
+ snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
+ ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
+ appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
+ ParseArgsFromString(buf);
+ }
+ free(engineName);
+ return i;
+}
+
+char *recentEngines;
+
+void
+RecentEngineEvent (int nr)
+{
+ int n;
+// SwapEngines(1); // bump first to second
+// ReplaceEngine(&second, 1); // and load it there
+ NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
+ n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
+ if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
+ ReplaceEngine(&first, 0);
+ FloatToFront(&appData.recentEngineList, command[n]);
+ }
+}
+
+int
+Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
+{ // determine players from game number
+ int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
+
+ if(appData.tourneyType == 0) {
+ roundsPerCycle = (nPlayers - 1) | 1;
+ pairingsPerRound = nPlayers / 2;
+ } else if(appData.tourneyType > 0) {
+ roundsPerCycle = nPlayers - appData.tourneyType;
+ pairingsPerRound = appData.tourneyType;
+ }
+ gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
+ gamesPerCycle = gamesPerRound * roundsPerCycle;
+ appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
+ curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
+ curRound = nr / gamesPerRound; nr %= gamesPerRound;
+ curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
+ matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
+ roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
+
+ if(appData.cycleSync) *syncInterval = gamesPerCycle;
+ if(appData.roundSync) *syncInterval = gamesPerRound;
+
+ if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
+
+ if(appData.tourneyType == 0) {
+ if(curPairing == (nPlayers-1)/2 ) {
+ *whitePlayer = curRound;
+ *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
+ } else {
+ *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
+ if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
+ *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
+ if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
+ }
+ } else if(appData.tourneyType > 1) {
+ *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
+ *whitePlayer = curRound + appData.tourneyType;
+ } else if(appData.tourneyType > 0) {
+ *whitePlayer = curPairing;
+ *blackPlayer = curRound + appData.tourneyType;
+ }
+
+ // take care of white/black alternation per round.
+ // For cycles and games this is already taken care of by default, derived from matchGame!
+ return curRound & 1;
+}
+
+int
+NextTourneyGame (int nr, int *swapColors)
+{ // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
+ char *p, *q;
+ int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
+ FILE *tf;
+ if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
+ tf = fopen(appData.tourneyFile, "r");
+ if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
+ ParseArgsFromFile(tf); fclose(tf);
+ InitTimeControls(); // TC might be altered from tourney file
+
+ nPlayers = CountPlayers(appData.participants); // count participants
+ if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
+ *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
+
+ if(syncInterval) {
+ p = q = appData.results;
+ while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
+ if(firstBusy/syncInterval < (nextGame/syncInterval)) {
+ DisplayMessage(_("Waiting for other game(s)"),"");
+ waitingForGame = TRUE;
+ ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
+ return 0;
+ }
+ waitingForGame = FALSE;
+ }
+
+ if(appData.tourneyType < 0) {
+ if(nr>=0 && !pairingReceived) {
+ char buf[1<<16];
+ if(pairing.pr == NoProc) {
+ if(!appData.pairingEngine[0]) {
+ DisplayFatalError(_("No pairing engine specified"), 0, 1);
+ return 0;
+ }
+ StartChessProgram(&pairing); // starts the pairing engine
+ }
+ snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
+ SendToProgram(buf, &pairing);
+ snprintf(buf, 1<<16, "pairing %d\n", nr+1);
+ SendToProgram(buf, &pairing);
+ return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
+ }
+ pairingReceived = 0; // ... so we continue here
+ *swapColors = 0;
+ appData.matchGames = appData.tourneyCycles * syncInterval - 1;
+ whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
+ matchGame = 1; roundNr = nr / syncInterval + 1;
+ }
+
+ if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
+
+ // redefine engines, engine dir, etc.
+ NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
+ if(first.pr == NoProc) {
+ SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
+ InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
+ }
+ if(second.pr == NoProc) {
+ SwapEngines(1);
+ SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
+ SwapEngines(1); // and make that valid for second engine by swapping
+ InitEngine(&second, 1);
+ }
+ CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
+ UpdateLogos(FALSE); // leave display to ModeHiglight()
+ return 1;
+}
+
+void
+NextMatchGame ()
+{ // performs game initialization that does not invoke engines, and then tries to start the game
+ int res, firstWhite, swapColors = 0;
+ if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
+ if(matchMode && appData.debugMode) { // [HGM] debug split: game is part of a match; we might have to create a debug file just for this game
+ char buf[MSG_SIZ];
+ snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
+ if(strcmp(buf, currentDebugFile)) { // name has changed
+ FILE *f = fopen(buf, "w");
+ if(f) { // if opening the new file failed, just keep using the old one
+ ASSIGN(currentDebugFile, buf);
+ fclose(debugFP);
+ debugFP = f;
+ }
+ if(appData.serverFileName) {
+ if(serverFP) fclose(serverFP);
+ serverFP = fopen(appData.serverFileName, "w");
+ if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
+ if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
+ }
+ }
+ }
+ firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
+ firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
+ first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
+ second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
+ appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
+ if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
+ Reset(FALSE, first.pr != NoProc);
+ res = LoadGameOrPosition(matchGame); // setup game
+ appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
+ if(!res) return; // abort when bad game/pos file
+ TwoMachinesEvent();
+}
+
+void
+UserAdjudicationEvent (int result)
{
ChessMove gameResult = GameIsDrawn;
// [HGM] save: calculate checksum of game to make games easily identifiable
-int StringCheckSum(char *s)
+int
+StringCheckSum (char *s)
{
int i = 0;
if(s==NULL) return 0;
return i;
}
-int GameCheckSum()
+int
+GameCheckSum ()
{
int i, sum=0;
for(i=backwardMostMove; i<forwardMostMove; i++) {
} // end of save patch
void
-GameEnds(result, resultDetails, whosays)
- ChessMove result;
- char *resultDetails;
- int whosays;
+GameEnds (ChessMove result, char *resultDetails, int whosays)
{
GameMode nextGameMode;
int isIcsGame;
- char buf[MSG_SIZ], popupRequested = 0;
+ char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
if(endingGame) return; /* [HGM] crash: forbid recursion */
endingGame = 1;
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) {
/* 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 ) {
// 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) &&
+ || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
(result == WhiteWins && claimer == 'w' ||
result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
if (appData.debugMode) {
result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
}
if(result != trueResult) {
- sprintf(buf, "False win claim: '%s'", resultDetails);
+ snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
result = claimer == 'w' ? BlackWins : WhiteWins;
resultDetails = buf;
}
(claimer=='b')==(forwardMostMove&1))
) {
/* [HGM] verify: draws that were not flagged are false claims */
- sprintf(buf, "False draw claim: '%s'", resultDetails);
+ snprintf(buf, MSG_SIZ, "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
+ if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
+ || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
+ && 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);
}
if(k <= 1) {
result = GameIsDrawn;
- sprintf(buf, "%s but bare king", resultDetails);
+ snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
resultDetails = buf;
}
}
if(result==WhiteWins) c = '+';
if(result==BlackWins) c = '-';
if(resultDetails != NULL)
- fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
+ fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
}
if (resultDetails != NULL) {
gameInfo.result = result;
/* 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
gameMode == IcsPlayingBlack ||
gameMode == BeginningOfGame) {
char buf[MSG_SIZ];
- sprintf(buf, "result %s {%s}\n", PGNResult(result),
+ snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
resultDetails);
if (first.pr != NoProc) {
SendToProgram(buf, &first);
}
}
} else if (gameMode == EditGame ||
- gameMode == PlayFromGameFile ||
- gameMode == AnalyzeMode ||
+ gameMode == PlayFromGameFile ||
+ gameMode == AnalyzeMode ||
gameMode == AnalyzeFile) {
nextGameMode = gameMode;
} else {
SendToProgram("force\n", &first);
if (first.usePing) {
char buf[MSG_SIZ];
- sprintf(buf, "ping %d\n", ++first.lastPing);
+ snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
SendToProgram(buf, &first);
}
}
if (first.isr != NULL)
RemoveInputSource(first.isr);
first.isr = NULL;
-
+
if (first.pr != NoProc) {
ExitAnalyzeMode();
DoSleep( appData.delayBeforeQuit );
SendToProgram("force\n", &second);
if (second.usePing) {
char buf[MSG_SIZ];
- sprintf(buf, "ping %d\n", ++second.lastPing);
+ snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
SendToProgram(buf, &second);
}
}
if (second.isr != NULL)
RemoveInputSource(second.isr);
second.isr = NULL;
-
+
if (second.pr != NoProc) {
DoSleep( appData.delayBeforeQuit );
SendToProgram("quit\n", &second);
second.pr = NoProc;
}
- if (matchMode && gameMode == TwoMachinesPlay) {
+ if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
+ char resChar = '=';
switch (result) {
case WhiteWins:
+ resChar = '+';
if (first.twoMachinesColor[0] == 'w') {
first.matchWins++;
} else {
}
break;
case BlackWins:
+ resChar = '-';
if (first.twoMachinesColor[0] == 'b') {
first.matchWins++;
} else {
second.matchWins++;
}
break;
+ case GameUnfinished:
+ resChar = ' ';
default:
break;
}
- if (matchGame < appData.matchGames) {
- char *tmp;
- if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
- tmp = first.twoMachinesColor;
- first.twoMachinesColor = second.twoMachinesColor;
- second.twoMachinesColor = tmp;
- }
+
+ if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
+ if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
+ if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
+ ReserveGame(nextGame, resChar); // sets nextGame
+ if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
+ else ranking = strdup("busy"); //suppress popup when aborted but not finished
+ } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
+
+ if (nextGame <= appData.matchGames && !abortMatch) {
gameMode = nextGameMode;
- matchGame++;
- if(appData.matchPause>10000 || appData.matchPause<10)
- appData.matchPause = 10000; /* [HGM] make pause adjustable */
- ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
+ matchGame = nextGame; // this will be overruled in tourney mode!
+ GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
+ ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
endingGame = 0; /* [HGM] crash */
return;
} else {
gameMode = nextGameMode;
- sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
- first.tidy, second.tidy,
- first.matchWins, second.matchWins,
- appData.matchGames - (first.matchWins + second.matchWins));
+ snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
+ first.tidy, second.tidy,
+ first.matchWins, second.matchWins,
+ appData.matchGames - (first.matchWins + second.matchWins));
+ if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
+ if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
popupRequested++; // [HGM] crash: postpone to after resetting endingGame
+ if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
+ first.twoMachinesColor = "black\n";
+ second.twoMachinesColor = "white\n";
+ } else {
+ first.twoMachinesColor = "white\n";
+ second.twoMachinesColor = "black\n";
+ }
}
}
if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
ModeHighlight();
endingGame = 0; /* [HGM] crash */
if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
- if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
- matchMode = FALSE; appData.matchGames = matchGame = 0;
- DisplayNote(buf);
+ if(matchMode == TRUE) { // match through command line: exit with or without popup
+ if(ranking) {
+ ToNrEvent(forwardMostMove);
+ if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
+ else ExitEvent(0);
+ } else DisplayFatalError(buf, 0, 0);
+ } else { // match through menu; just stop, with or without popup
+ matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
+ ModeHighlight();
+ if(ranking){
+ if(strcmp(ranking, "busy")) DisplayNote(ranking);
+ } else DisplayNote(buf);
}
+ if(ranking) free(ranking);
}
}
/* Assumes program was just initialized (initString sent).
Leaves program in force mode. */
void
-FeedMovesToProgram(cps, upto)
- ChessProgramState *cps;
- int upto;
+FeedMovesToProgram (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];
+ 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));
+ snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
SendToProgram(buf, cps);
currentlyInitializedVariant = gameInfo.variant;
}
}
-void
-ResurrectChessProgram()
+int
+ResurrectChessProgram ()
{
/* The chess program may have exited.
If so, restart it and feed it all the moves made so far. */
+ static int doInit = 0;
- if (appData.noChessProgram || first.pr != NoProc) return;
-
- StartChessProgram(&first);
+ if (appData.noChessProgram) return 1;
+
+ if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
+ if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
+ if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
+ doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
+ } else {
+ if (first.pr != NoProc) return 1;
+ StartChessProgram(&first);
+ }
InitChessProgram(&first, FALSE);
FeedMovesToProgram(&first, currentMove);
SendToProgram("analyze\n", &first);
first.analyzing = TRUE;
}
+ return 1;
}
/*
* Button procedures
*/
void
-Reset(redraw, init)
- int redraw, init;
+Reset (int redraw, int init)
{
int i;
redraw, init, gameMode);
}
CleanupTail(); // [HGM] vari: delete any stored variations
+ CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
pausing = pauseExamInvalid = FALSE;
startedFromSetupPosition = blackPlaysFirst = FALSE;
firstMove = TRUE;
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;
gotPremove = FALSE;
alarmSounded = FALSE;
- GameEnds((ChessMove) 0, NULL, GE_PLAYER);
+ GameEnds(EndOfFile, NULL, GE_PLAYER);
if(appData.serverMovesName != NULL) {
/* [HGM] prepare to make moves file for broadcasting */
clock_t t = clock();
ModeHighlight();
if(appData.icsActive) gameInfo.variant = VariantNormal;
currentMove = forwardMostMove = backwardMostMove = 0;
+ MarkTargetSquares(1);
InitPosition(redraw);
for (i = 0; i < MAX_MOVES; i++) {
if (commentList[i] != NULL) {
ResetClocks();
timeRemaining[0][0] = whiteTimeRemaining;
timeRemaining[1][0] = blackTimeRemaining;
- if (first.pr == NULL) {
+
+ if (first.pr == NoProc) {
StartChessProgram(&first);
}
if (init) {
DisplayMessage("", "");
HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
+ ClearMap(); // [HGM] exclude: invalidate map
}
void
-AutoPlayGameLoop()
+AutoPlayGameLoop ()
{
for (;;) {
if (!AutoPlayOneMove())
return;
if (matchMode || appData.timeDelay == 0)
continue;
- if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
+ if (appData.timeDelay < 0)
return;
StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
break;
int
-AutoPlayOneMove()
+AutoPlayOneMove ()
{
int fromX, fromY, toX, toY;
fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
}
- if (gameMode != PlayFromGameFile)
+ if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
return FALSE;
+ if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
+ pvInfoList[currentMove].depth = programStats.depth;
+ pvInfoList[currentMove].score = programStats.score;
+ pvInfoList[currentMove].time = 0;
+ if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
+ }
+
if (currentMove >= forwardMostMove) {
- gameMode = EditGame;
- ModeHighlight();
+ if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
+// gameMode = EndOfGame;
+// ModeHighlight();
/* [AS] Clear current move marker at the end of a game */
/* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
return FALSE;
}
-
+
toX = moveList[currentMove][2] - AAA;
toY = moveList[currentMove][3] - ONE;
int
-LoadGameOneMove(readAhead)
- ChessMove readAhead;
+LoadGameOneMove (ChessMove readAhead)
{
int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
char promoChar = NULLCHAR;
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) {
+ if (readAhead != EndOfFile) {
moveType = readAhead;
} else {
if (gameFileFP == NULL)
return FALSE;
- moveType = (ChessMove) yylex();
+ moveType = (ChessMove) Myylex();
}
-
+
done = FALSE;
switch (moveType) {
case Comment:
- if (appData.debugMode)
+ if (appData.debugMode)
fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
p = yy_text;
case WhiteCapturesEnPassant:
case BlackCapturesEnPassant:
- case WhitePromotionChancellor:
- case BlackPromotionChancellor:
- case WhitePromotionArchbishop:
- case BlackPromotionArchbishop:
- case WhitePromotionCentaur:
- case BlackPromotionCentaur:
- case WhitePromotionQueen:
- case BlackPromotionQueen:
- case WhitePromotionRook:
- case BlackPromotionRook:
- case WhitePromotionBishop:
- case BlackPromotionBishop:
- case WhitePromotionKnight:
- case BlackPromotionKnight:
- case WhitePromotionKing:
- case BlackPromotionKing:
+ case WhitePromotion:
+ case BlackPromotion:
+ case WhiteNonPromotion:
+ case BlackNonPromotion:
case NormalMove:
case WhiteKingSideCastle:
case WhiteQueenSideCastle:
if (q != NULL) *q = NULLCHAR;
p++;
}
+ while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
GameEnds(moveType, p, GE_FILE);
done = TRUE;
if (cmailMsgLoaded) {
}
break;
- case (ChessMove) 0: /* end of file */
+ case EndOfFile:
if (appData.debugMode)
fprintf(debugFP, "Parser hit end of file\n");
switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
if (appData.debugMode)
fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
yy_text, (int) moveType);
- return LoadGameOneMove((ChessMove)0); /* tail recursion */
+ return LoadGameOneMove(EndOfFile); /* tail recursion */
}
/* else fall thru */
if (appData.debugMode)
fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
yy_text, (int) moveType);
- return LoadGameOneMove((ChessMove)0); /* tail recursion */
+ return LoadGameOneMove(EndOfFile); /* tail recursion */
case IllegalMove:
if (appData.testLegality) {
if (appData.debugMode)
fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
- sprintf(move, _("Illegal move: %d.%s%s"),
+ snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
(forwardMostMove / 2) + 1,
WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
DisplayError(move, 0);
case AmbiguousMove:
if (appData.debugMode)
fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
- sprintf(move, _("Ambiguous move: %d.%s%s"),
+ snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
(forwardMostMove / 2) + 1,
WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
DisplayError(move, 0);
case ImpossibleMove:
if (appData.debugMode)
fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
- sprintf(move, _("Illegal move: %d.%s%s"),
+ snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
(forwardMostMove / 2) + 1,
WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
DisplayError(move, 0);
return FALSE;
} else {
/* 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;
/* Load the nth game from the given file */
int
-LoadGameFromFile(filename, n, title, useList)
- char *filename;
- int n;
- char *title;
- /*Boolean*/ int useList;
+LoadGameFromFile (char *filename, int n, char *title, int useList)
{
FILE *f;
char buf[MSG_SIZ];
void
-MakeRegisteredMove()
+MakeRegisteredMove ()
{
int fromX, fromY, toX, toY;
char promoChar;
if (appData.debugMode)
fprintf(debugFP, "Restoring %s for game %d\n",
cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
-
+
thinkOutput[0] = NULLCHAR;
- strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
+ safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
promoChar = cmailMove[lastLoadGameNumber - 1][4];
MakeMove(fromX, fromY, toX, toY, promoChar);
ShowMove(fromX, fromY, toX, toY);
-
+
switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
case MT_NONE:
case MT_CHECK:
break;
-
+
case MT_CHECKMATE:
case MT_STAINMATE:
if (WhiteOnMove(currentMove)) {
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;
}
/* Wrapper around LoadGame for use when a Cmail message is loaded */
int
-CmailLoadGame(f, gameNumber, title, useList)
- FILE *f;
- int gameNumber;
- char *title;
- int useList;
+CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
{
int retVal;
/* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
int
-ReloadGame(offset)
- int offset;
+ReloadGame (int offset)
{
int gameNumber = lastLoadGameNumber + offset;
if (lastLoadGameFP == NULL) {
}
}
+int keys[EmptySquare+1];
+
+int
+PositionMatches (Board b1, Board b2)
+{
+ int r, f, sum=0;
+ switch(appData.searchMode) {
+ case 1: return CompareWithRights(b1, b2);
+ case 2:
+ for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
+ if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
+ }
+ return TRUE;
+ case 3:
+ for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
+ if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
+ sum += keys[b1[r][f]] - keys[b2[r][f]];
+ }
+ return sum==0;
+ case 4:
+ for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
+ sum += keys[b1[r][f]] - keys[b2[r][f]];
+ }
+ return sum==0;
+ }
+ return TRUE;
+}
+
+#define Q_PROMO 4
+#define Q_EP 3
+#define Q_BCASTL 2
+#define Q_WCASTL 1
+
+int pieceList[256], quickBoard[256];
+ChessSquare pieceType[256] = { EmptySquare };
+Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
+int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
+int soughtTotal, turn;
+Boolean epOK, flipSearch;
+
+typedef struct {
+ unsigned char piece, to;
+} Move;
+
+#define DSIZE (250000)
+
+Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
+Move *moveDatabase = initialSpace;
+unsigned int movePtr, dataSize = DSIZE;
+
+int
+MakePieceList (Board board, int *counts)
+{
+ int r, f, n=Q_PROMO, total=0;
+ for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
+ for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
+ int sq = f + (r<<4);
+ if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
+ quickBoard[sq] = ++n;
+ pieceList[n] = sq;
+ pieceType[n] = board[r][f];
+ counts[board[r][f]]++;
+ if(board[r][f] == WhiteKing) pieceList[1] = n; else
+ if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
+ total++;
+ }
+ }
+ epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
+ return total;
+}
+
+void
+PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
+{
+ int sq = fromX + (fromY<<4);
+ int piece = quickBoard[sq];
+ quickBoard[sq] = 0;
+ moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
+ if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
+ int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
+ moveDatabase[movePtr++].piece = Q_WCASTL;
+ quickBoard[sq] = piece;
+ piece = quickBoard[from]; quickBoard[from] = 0;
+ moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
+ } else
+ if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
+ int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
+ moveDatabase[movePtr++].piece = Q_BCASTL;
+ quickBoard[sq] = piece;
+ piece = quickBoard[from]; quickBoard[from] = 0;
+ moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
+ } else
+ if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
+ quickBoard[(fromY<<4)+toX] = 0;
+ moveDatabase[movePtr].piece = Q_EP;
+ moveDatabase[movePtr++].to = (fromY<<4)+toX;
+ moveDatabase[movePtr].to = sq;
+ } else
+ if(promoPiece != pieceType[piece]) {
+ moveDatabase[movePtr++].piece = Q_PROMO;
+ moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
+ }
+ moveDatabase[movePtr].piece = piece;
+ quickBoard[sq] = piece;
+ movePtr++;
+}
+
+int
+PackGame (Board board)
+{
+ Move *newSpace = NULL;
+ moveDatabase[movePtr].piece = 0; // terminate previous game
+ if(movePtr > dataSize) {
+ if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
+ dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
+ if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
+ if(newSpace) {
+ int i;
+ Move *p = moveDatabase, *q = newSpace;
+ for(i=0; i<movePtr; i++) *q++ = *p++; // copy to newly allocated space
+ if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
+ moveDatabase = newSpace;
+ } else { // calloc failed, we must be out of memory. Too bad...
+ dataSize = 0; // prevent calloc events for all subsequent games
+ return 0; // and signal this one isn't cached
+ }
+ }
+ movePtr++;
+ MakePieceList(board, counts);
+ return movePtr;
+}
+
+int
+QuickCompare (Board board, int *minCounts, int *maxCounts)
+{ // compare according to search mode
+ int r, f;
+ switch(appData.searchMode)
+ {
+ case 1: // exact position match
+ if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
+ for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
+ if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
+ }
+ break;
+ case 2: // can have extra material on empty squares
+ for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
+ if(board[r][f] == EmptySquare) continue;
+ if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
+ }
+ break;
+ case 3: // material with exact Pawn structure
+ for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
+ if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
+ if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
+ } // fall through to material comparison
+ case 4: // exact material
+ for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
+ break;
+ case 6: // material range with given imbalance
+ for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
+ // fall through to range comparison
+ case 5: // material range
+ for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
+ }
+ return TRUE;
+}
+
+int
+QuickScan (Board board, Move *move)
+{ // reconstruct game,and compare all positions in it
+ int cnt=0, stretch=0, total = MakePieceList(board, counts), delayedKing = -1;
+ do {
+ int piece = move->piece;
+ int to = move->to, from = pieceList[piece];
+ if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
+ if(!piece) return -1;
+ if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
+ piece = (++move)->piece;
+ from = pieceList[piece];
+ counts[pieceType[piece]]--;
+ pieceType[piece] = (ChessSquare) move->to;
+ counts[move->to]++;
+ } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
+ counts[pieceType[quickBoard[to]]]--;
+ quickBoard[to] = 0; total--;
+ move++;
+ continue;
+ } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
+ int rook;
+ piece = pieceList[piece]; // first two elements of pieceList contain King numbers
+ from = pieceList[piece]; // so this must be King
+ quickBoard[from] = 0;
+ pieceList[piece] = to;
+ from = pieceList[(++move)->piece]; // for FRC this has to be done here
+ quickBoard[from] = 0; // rook
+ quickBoard[to] = piece;
+ to = move->to; piece = move->piece;
+ goto aftercastle;
+ }
+ }
+ if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
+ if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
+ quickBoard[from] = 0;
+ aftercastle:
+ quickBoard[to] = piece;
+ pieceList[piece] = to;
+ cnt++; turn ^= 3;
+ if(QuickCompare(soughtBoard, minSought, maxSought) ||
+ appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
+ flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
+ appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
+ ) {
+ static int lastCounts[EmptySquare+1];
+ int i;
+ if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
+ if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
+ } else stretch = 0;
+ if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
+ move++; delayedKing = -1;
+ } while(1);
+}
+
+void
+InitSearch ()
+{
+ int r, f;
+ flipSearch = FALSE;
+ CopyBoard(soughtBoard, boards[currentMove]);
+ soughtTotal = MakePieceList(soughtBoard, maxSought);
+ soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
+ if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
+ CopyBoard(reverseBoard, boards[currentMove]);
+ for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
+ int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
+ if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
+ reverseBoard[r][f] = piece;
+ }
+ reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
+ for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
+ if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
+ || (boards[currentMove][CASTLING][2] == NoRights ||
+ boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
+ && (boards[currentMove][CASTLING][5] == NoRights ||
+ boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
+ ) {
+ flipSearch = TRUE;
+ CopyBoard(flipBoard, soughtBoard);
+ CopyBoard(rotateBoard, reverseBoard);
+ for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
+ flipBoard[r][f] = soughtBoard[r][BOARD_WIDTH-1-f];
+ rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
+ }
+ }
+ for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
+ if(appData.searchMode >= 5) {
+ for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
+ MakePieceList(soughtBoard, minSought);
+ for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
+ }
+ if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
+ soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
+}
+
+GameInfo dummyInfo;
+
+int
+GameContainsPosition (FILE *f, ListGame *lg)
+{
+ int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
+ int fromX, fromY, toX, toY;
+ char promoChar;
+ static int initDone=FALSE;
+
+ // weed out games based on numerical tag comparison
+ if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
+ if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
+ if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
+ if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
+ if(!initDone) {
+ for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
+ initDone = TRUE;
+ }
+ if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
+ else CopyBoard(boards[scratch], initialPosition); // default start position
+ if(lg->moves) {
+ turn = btm + 1;
+ if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
+ if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
+ }
+ if(btm) plyNr++;
+ if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
+ fseek(f, lg->offset, 0);
+ yynewfile(f);
+ while(1) {
+ yyboardindex = scratch;
+ quickFlag = plyNr+1;
+ next = Myylex();
+ quickFlag = 0;
+ switch(next) {
+ case PGNTag:
+ if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
+ default:
+ continue;
+
+ case XBoardGame:
+ case GNUChessGame:
+ if(plyNr) return -1; // after we have seen moves, this is for new game
+ continue;
+ case AmbiguousMove: // we cannot reconstruct the game beyond these two
+ case ImpossibleMove:
+ case WhiteWins: // game ends here with these four
+ case BlackWins:
+ case GameIsDrawn:
+ case GameUnfinished:
+ return -1;
+
+ case IllegalMove:
+ if(appData.testLegality) return -1;
+ case WhiteCapturesEnPassant:
+ case BlackCapturesEnPassant:
+ case WhitePromotion:
+ case BlackPromotion:
+ case WhiteNonPromotion:
+ case BlackNonPromotion:
+ case NormalMove:
+ case WhiteKingSideCastle:
+ case WhiteQueenSideCastle:
+ case BlackKingSideCastle:
+ case BlackQueenSideCastle:
+ case WhiteKingSideCastleWild:
+ case WhiteQueenSideCastleWild:
+ case BlackKingSideCastleWild:
+ case BlackQueenSideCastleWild:
+ case WhiteHSideCastleFR:
+ case WhiteASideCastleFR:
+ case BlackHSideCastleFR:
+ case BlackASideCastleFR:
+ fromX = currentMoveString[0] - AAA;
+ fromY = currentMoveString[1] - ONE;
+ toX = currentMoveString[2] - AAA;
+ toY = currentMoveString[3] - ONE;
+ promoChar = currentMoveString[4];
+ break;
+ case WhiteDrop:
+ case BlackDrop:
+ fromX = next == WhiteDrop ?
+ (int) CharToPiece(ToUpper(currentMoveString[0])) :
+ (int) CharToPiece(ToLower(currentMoveString[0]));
+ fromY = DROP_RANK;
+ toX = currentMoveString[2] - AAA;
+ toY = currentMoveString[3] - ONE;
+ promoChar = 0;
+ break;
+ }
+ // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
+ plyNr++;
+ ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
+ if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
+ if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
+ if(appData.findMirror) {
+ if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
+ if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
+ }
+ }
+}
/* Load the nth game from open file f */
int
-LoadGame(f, gameNumber, title, useList)
- FILE *f;
- int gameNumber;
- char *title;
- int useList;
+LoadGame (FILE *f, int gameNumber, char *title, int useList)
{
ChessMove cm;
char buf[MSG_SIZ];
int gn = gameNumber;
ListGame *lg = NULL;
int numPGNTags = 0;
- int err;
+ int err, pos = -1;
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);
+ pos = lg->position;
gn = 1;
}
else {
}
lastLoadGameFP = f;
lastLoadGameNumber = gameNumber;
- strcpy(lastLoadGameTitle, title);
+ safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
lastLoadGameUseList = useList;
yynewfile(f);
if (lg && lg->gameInfo.white && lg->gameInfo.black) {
- snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
+ snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
lg->gameInfo.black);
DisplayTitle(buf);
} else if (*title != NULLCHAR) {
if (gameNumber > 1) {
- sprintf(buf, "%s %d", title, gameNumber);
+ snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
DisplayTitle(buf);
} else {
DisplayTitle(title);
/*
* 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.
* 5-4-02: Let's try being more lenient and allowing a game to
* start with an unnumbered move. Does that break anything?
*/
- cm = lastLoadGameStart = (ChessMove) 0;
+ cm = lastLoadGameStart = EndOfFile;
while (gn > 0) {
yyboardindex = forwardMostMove;
- cm = (ChessMove) yylex();
+ cm = (ChessMove) Myylex();
switch (cm) {
- case (ChessMove) 0:
+ case EndOfFile:
if (cmailMsgLoaded) {
nCmailGames = CMAIL_MAX_GAMES - gn;
} else {
gn--;
lastLoadGameStart = cm;
break;
-
+
case MoveNumberOne:
switch (lastLoadGameStart) {
case GNUChessGame:
case PGNTag:
break;
case MoveNumberOne:
- case (ChessMove) 0:
+ case EndOfFile:
gn--; /* count this game */
lastLoadGameStart = cm;
break;
case GNUChessGame:
case PGNTag:
case MoveNumberOne:
- case (ChessMove) 0:
+ case EndOfFile:
gn--; /* count this game */
lastLoadGameStart = cm;
break;
if (gn > 0) {
do {
yyboardindex = forwardMostMove;
- cm = (ChessMove) yylex();
+ cm = (ChessMove) Myylex();
} while (cm == PGNTag || cm == Comment);
}
break;
case NormalMove:
/* Only a NormalMove can be at the start of a game
* without a position diagram. */
- if (lastLoadGameStart == (ChessMove) 0) {
+ if (lastLoadGameStart == EndOfFile ) {
gn--;
lastLoadGameStart = MoveNumberOne;
}
break;
}
}
-
+
if (appData.debugMode)
fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
/* Skip any header junk before position diagram and/or move 1 */
for (;;) {
yyboardindex = forwardMostMove;
- cm = (ChessMove) yylex();
+ cm = (ChessMove) Myylex();
- if (cm == (ChessMove) 0 ||
+ if (cm == EndOfFile ||
cm == GNUChessGame || cm == XBoardGame) {
/* Empty game; pretend end-of-file and handle later */
- cm = (ChessMove) 0;
+ cm = EndOfFile;
break;
}
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++;
ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
InitPosition(TRUE);
oldVariant = gameInfo.variant;
- if (appData.debugMode)
+ if (appData.debugMode)
fprintf(debugFP, "New variant %d\n", (int) oldVariant);
}
if (blackPlaysFirst) {
currentMove = forwardMostMove = backwardMostMove = 1;
CopyBoard(boards[1], initial_position);
- strcpy(moveList[0], "");
- strcpy(parseList[0], "");
+ safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
+ safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
timeRemaining[0][1] = whiteTimeRemaining;
timeRemaining[1][1] = blackTimeRemaining;
if (commentList[0] != NULL) {
}
yyboardindex = forwardMostMove;
- cm = (ChessMove) yylex();
+ cm = (ChessMove) Myylex();
/* 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;
AppendComment(currentMove, p, FALSE);
yyboardindex = forwardMostMove;
- cm = (ChessMove) yylex();
+ cm = (ChessMove) Myylex();
}
}
for (i = BOARD_HEIGHT - 1; i >= 0; i--)
for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
switch (*p) {
+ case '{':
case '[':
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;
CopyBoard(boards[1], initial_position);
- strcpy(moveList[0], "");
- strcpy(parseList[0], "");
+ safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
+ safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
timeRemaining[0][1] = whiteTimeRemaining;
timeRemaining[1][1] = blackTimeRemaining;
if (commentList[0] != NULL) {
}
}
yyboardindex = forwardMostMove;
- cm = (ChessMove) yylex();
+ cm = (ChessMove) Myylex();
}
if (first.pr == NoProc) {
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;
AppendComment(currentMove, p, FALSE);
yyboardindex = forwardMostMove;
- cm = (ChessMove) yylex();
+ cm = (ChessMove) Myylex();
}
- if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
+ if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
cm == WhiteWins || cm == BlackWins ||
cm == GameIsDrawn || cm == GameUnfinished) {
DisplayMessage("", _("No moves in game"));
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);
/* load the remaining moves from the file */
- while (LoadGameOneMove((ChessMove)0)) {
+ while (LoadGameOneMove(EndOfFile)) {
timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
timeRemaining[1][forwardMostMove] = blackTimeRemaining;
}
AnalyzeFileEvent();
}
+ if (!matchMode && pos > 0) {
+ ToNrEvent(pos); // [HGM] no autoplay if selected on position
+ } else
if (matchMode || appData.timeDelay == 0) {
ToEndEvent();
- gameMode = EditGame;
- ModeHighlight();
} else if (appData.timeDelay > 0) {
AutoPlayGameLoop();
}
- if (appData.debugMode)
+ if (appData.debugMode)
fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
loadFlag = 0; /* [HGM] true game starts */
/* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
int
-ReloadPosition(offset)
- int offset;
+ReloadPosition (int offset)
{
int positionNumber = lastLoadPositionNumber + offset;
if (lastLoadPositionFP == NULL) {
/* Load the nth position from the given file */
int
-LoadPositionFromFile(filename, n, title)
- char *filename;
- int n;
- char *title;
+LoadPositionFromFile (char *filename, int n, char *title)
{
FILE *f;
char buf[MSG_SIZ];
/* Load the nth position from the given open file, and close it */
int
-LoadPosition(f, positionNumber, title)
- FILE *f;
- int positionNumber;
- char *title;
+LoadPosition (FILE *f, int positionNumber, char *title)
{
char *p, line[MSG_SIZ];
Board initial_position;
int i, j, fenMode, pn;
-
+
if (gameMode == Training )
SetTrainingModeOff();
if (positionNumber == 0) positionNumber = 1;
lastLoadPositionFP = f;
lastLoadPositionNumber = positionNumber;
- strcpy(lastLoadPositionTitle, title);
- if (first.pr == NoProc) {
+ safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
+ if (first.pr == NoProc && !appData.noChessProgram) {
StartChessProgram(&first);
InitChessProgram(&first, FALSE);
- }
+ }
pn = positionNumber;
if (positionNumber < 0) {
/* Negative position number means to seek to that byte offset */
} else {
(void) fgets(line, MSG_SIZ, f);
(void) fgets(line, MSG_SIZ, f);
-
+
for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
(void) fgets(line, MSG_SIZ, f);
for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
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 = 1;
- strcpy(moveList[0], "");
- strcpy(parseList[0], "");
+ safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
+ safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
CopyBoard(boards[1], initial_position);
DisplayMessage("", _("Black to play"));
} else {
DisplayMessage("", _("White to play"));
}
initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
- SendBoard(&first, forwardMostMove);
+ if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
+ SendToProgram("force\n", &first);
+ 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");}
}
if (positionNumber > 1) {
- sprintf(line, "%s %d", title, positionNumber);
+ snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
DisplayTitle(line);
} else {
DisplayTitle(title);
timeRemaining[0][1] = whiteTimeRemaining;
timeRemaining[1][1] = blackTimeRemaining;
DrawPosition(FALSE, boards[currentMove]);
-
+
return TRUE;
}
void
-CopyPlayerNameIntoFileName(dest, src)
- char **dest, *src;
+CopyPlayerNameIntoFileName (char **dest, char *src)
{
while (*src != NULLCHAR && *src != ',') {
if (*src == ' ') {
}
}
-char *DefaultFileName(ext)
- char *ext;
+char *
+DefaultFileName (char *ext)
{
static char def[MSG_SIZ];
char *p;
*p++ = '-';
CopyPlayerNameIntoFileName(&p, gameInfo.black);
*p++ = '.';
- strcpy(p, ext);
+ safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
} else {
def[0] = NULLCHAR;
}
/* Save the current game to the given file */
int
-SaveGameToFile(filename, append)
- char *filename;
- int append;
+SaveGameToFile (char *filename, int append)
{
FILE *f;
char buf[MSG_SIZ];
+ int result, i, t,tot=0;
if (strcmp(filename, "-") == 0) {
return SaveGame(stdout, 0, NULL);
} else {
- f = fopen(filename, append ? "a" : "w");
+ for(i=0; i<10; i++) { // upto 10 tries
+ f = fopen(filename, append ? "a" : "w");
+ if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
+ if(f || errno != 13) break;
+ DoSleep(t = 5 + random()%11); // wait 5-15 msec
+ tot += t;
+ }
if (f == NULL) {
snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
DisplayError(buf, errno);
return FALSE;
} else {
- return SaveGame(f, 0, NULL);
+ safeStrCpy(buf, lastMsg, MSG_SIZ);
+ DisplayMessage(_("Waiting for access to save file"), "");
+ flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
+ DisplayMessage(_("Saving game"), "");
+ if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno); // better safe than sorry...
+ result = SaveGame(f, 0, NULL);
+ DisplayMessage(buf, "");
+ return result;
}
}
}
char *
-SavePart(str)
- char *str;
+SavePart (char *str)
{
static char buf[MSG_SIZ];
char *p;
-
+
p = strchr(str, ' ');
if (p == NULL) return str;
strncpy(buf, str, p - str);
#define PGN_SIDE_WHITE 0
#define PGN_SIDE_BLACK 1
-/* [AS] */
-static int FindFirstMoveOutOfBook( int side )
+static int
+FindFirstMoveOutOfBook (int side)
{
int result = -1;
return result;
}
-/* [AS] */
-void GetOutOfBookInfo( char * buf )
+void
+GetOutOfBookInfo (char * buf)
{
int oob[2];
int i;
}
sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
- sprintf( buf+strlen(buf), "%s%.2f",
+ 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)
- FILE *f;
+SaveGamePGN (FILE *f)
{
int i, offset, linelen, newblock;
time_t tm;
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(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
+
if (backwardMostMove > 0 || startedFromSetupPosition) {
char *fen = PositionToFEN(backwardMostMove, NULL);
fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
GetOutOfBookInfo( buf );
if( buf[0] != '\0' ) {
- fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
+ fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
}
}
}
/* Format move number */
- if ((i % 2) == 0) {
- sprintf(numtext, "%d.", (i - offset)/2 + 1);
- } else {
- if (newblock) {
- sprintf(numtext, "%d...", (i - offset)/2 + 1);
- } else {
- numtext[0] = NULLCHAR;
- }
- }
+ if ((i % 2) == 0)
+ snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
+ else
+ if (newblock)
+ snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
+ else
+ numtext[0] = NULLCHAR;
+
numlen = strlen(numtext);
newblock = FALSE;
linelen += numlen;
/* Get move */
- strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
+ safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
/* Print move */
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);
- }
+ if( seconds <= 0)
+ buf[0] = 0;
+ else
+ if( seconds < 30 )
+ snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
+ else
+ {
+ seconds = (seconds + 4)/10; // round to full seconds
+ if( seconds < 60 )
+ snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
+ else
+ snprintf(buf, MSG_SIZ, " %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 );
+ snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%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 */
i++;
}
-
+
/* Start a new line */
if (linelen > 0) fprintf(f, "\n");
/* Save game in old style and close the file */
int
-SaveGameOldStyle(f)
- FILE *f;
+SaveGameOldStyle (FILE *f)
{
int i, offset;
time_t tm;
-
+
tm = time((time_t *) NULL);
-
+
fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
PrintOpponents(f);
-
+
if (backwardMostMove > 0 || startedFromSetupPosition) {
fprintf(f, "\n[--------------\n");
PrintPosition(f, backwardMostMove);
i++;
}
}
-
+
if (commentList[i] != NULL) {
fprintf(f, "[%s]\n", commentList[i]);
}
/* Save the current game to open file f and close the file */
int
-SaveGame(f, dummy, dummy2)
- FILE *f;
- int dummy;
- char *dummy2;
+SaveGame (FILE *f, int dummy, char *dummy2)
{
if (gameMode == EditPosition) EditPositionDone(TRUE);
lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
/* Save the current position to the given file */
int
-SavePositionToFile(filename)
- char *filename;
+SavePositionToFile (char *filename)
{
FILE *f;
char buf[MSG_SIZ];
DisplayError(buf, errno);
return FALSE;
} else {
+ safeStrCpy(buf, lastMsg, MSG_SIZ);
+ DisplayMessage(_("Waiting for access to save file"), "");
+ flock(fileno(f), LOCK_EX); // [HGM] lock
+ DisplayMessage(_("Saving position"), "");
+ lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
SavePosition(f, 0, NULL);
+ DisplayMessage(buf, "");
return TRUE;
}
}
/* Save the current position to the given open file and close the file */
int
-SavePosition(f, dummy, dummy2)
- FILE *f;
- int dummy;
- char *dummy2;
+SavePosition (FILE *f, int dummy, char *dummy2)
{
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");
}
void
-ReloadCmailMsgEvent(unregister)
- int unregister;
+ReloadCmailMsgEvent (int unregister)
{
#if !WIN32
static char *inFilename = NULL;
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);
}
int
-RegisterMove()
+RegisterMove ()
{
FILE *f;
char string[MSG_SIZ];
cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
nCmailMovesRegistered --;
- if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
+ if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
{
free(cmailCommentList[lastLoadGameNumber - 1]);
cmailCommentList[lastLoadGameNumber - 1] = NULL;
cmailCommentList[lastLoadGameNumber - 1]
= StrSave(commentList[currentMove]);
}
- strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
+ safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
if (appData.debugMode)
fprintf(debugFP, "Saving %s for game %d\n",
cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
- sprintf(string,
- "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
-
+ snprintf(string, MSG_SIZ, "%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);
+
+ snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
f = fopen(string, "w");
SavePosition(f, 0, NULL); /* also closes the file */
} else {
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) {
}
void
-MailMoveEvent()
+MailMoveEvent ()
{
#if !WIN32
static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
#if CMAIL_PROHIBIT_REMAIL
if (cmailMailedMove) {
- sprintf(msg, _("You have already mailed a move.\nWait until a move arrives from your opponent.\nTo resend the same move, type\n\"cmail -remail -game %s\"\non the command line."), appData.cmailGameName);
+ snprintf(msg, MSG_SIZ, _("You have already mailed a move.\nWait until a move arrives from your opponent.\nTo resend the same move, type\n\"cmail -remail -game %s\"\non the command line."), appData.cmailGameName);
DisplayError(msg, 0);
return;
}
#endif
if (! (cmailMailedMove || RegisterMove())) return;
-
+
if ( cmailMailedMove
|| (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
- sprintf(string, partCommandString,
- appData.debugMode ? " -v" : "", appData.cmailGameName);
+ snprintf(string, MSG_SIZ, partCommandString,
+ appData.debugMode ? " -v" : "", appData.cmailGameName);
commandOutput = popen(string, "r");
if (commandOutput == NULL) {
if ( archived
&& ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
!= NULL)) {
- sprintf(buffer, "%s/%s.%s.archive",
- arcDir,
- appData.cmailGameName,
- gameInfo.date);
+ snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
+ arcDir,
+ appData.cmailGameName,
+ gameInfo.date);
LoadGameFromFile(buffer, 1, buffer, FALSE);
cmailMsgLoaded = FALSE;
}
}
char *
-CmailMsg()
+CmailMsg ()
{
#if WIN32
return NULL;
char number[5];
char string[MSG_SIZ]; /* Space for game-list */
int i;
-
+
if (!cmailMsgLoaded) return "";
if (cmailMailedMove) {
- sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
+ snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
} else {
/* Create a list of games left */
- sprintf(string, "[");
+ snprintf(string, MSG_SIZ, "[");
for (i = 0; i < nCmailGames; i ++) {
if (! ( cmailMoveRegistered[i]
|| (cmailResult[i] == CMAIL_OLD_RESULT))) {
if (prependComma) {
- sprintf(number, ",%d", i + 1);
+ snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
} else {
- sprintf(number, "%d", i + 1);
+ snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
prependComma = 1;
}
-
+
strcat(string, number);
}
}
if (nCmailMovesRegistered + nCmailResults == 0) {
switch (nCmailGames) {
case 1:
- sprintf(cmailMsg,
- _("Still need to make move for game\n"));
+ snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
break;
-
+
case 2:
- sprintf(cmailMsg,
- _("Still need to make moves for both games\n"));
+ snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
break;
-
+
default:
- sprintf(cmailMsg,
- _("Still need to make moves for all %d games\n"),
- nCmailGames);
+ snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
+ nCmailGames);
break;
}
} else {
switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
case 1:
- sprintf(cmailMsg,
- _("Still need to make a move for game %s\n"),
- string);
+ snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
+ string);
break;
-
+
case 0:
if (nCmailResults == nCmailGames) {
- sprintf(cmailMsg, _("No unfinished games\n"));
+ snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
} else {
- sprintf(cmailMsg, _("Ready to send mail\n"));
+ snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
}
break;
-
+
default:
- sprintf(cmailMsg,
- _("Still need to make moves for games %s\n"),
- string);
+ snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
+ string);
}
}
}
}
void
-ResetGameEvent()
+ResetGameEvent ()
{
if (gameMode == Training)
SetTrainingModeOff();
}
void
-ExitEvent(status)
- int status;
+ExitEvent (int status)
{
exiting++;
if (exiting > 2) {
/* Kill off chess programs */
if (first.pr != NoProc) {
ExitAnalyzeMode();
-
+
DoSleep( appData.delayBeforeQuit );
SendToProgram("quit\n", &first);
DoSleep( appData.delayAfterQuit );
RemoveInputSource(second.isr);
}
+ if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
+ if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
+
ShutDownFrontEnd();
exit(status);
}
void
-PauseEvent()
+PauseEvent ()
{
if (appData.debugMode)
fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
DisplayBothClocks();
}
if (gameMode == PlayFromGameFile) {
- if (appData.timeDelay >= 0)
+ if (appData.timeDelay >= 0)
AutoPlayGameLoop();
} else if (gameMode == IcsExamining && pauseExamInvalid) {
Reset(FALSE, TRUE);
}
void
-EditCommentEvent()
+EditCommentEvent ()
{
char title[MSG_SIZ];
if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
- strcpy(title, _("Edit comment"));
+ safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
} else {
- sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
- WhiteOnMove(currentMove - 1) ? " " : ".. ",
- parseList[currentMove - 1]);
+ snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
+ WhiteOnMove(currentMove - 1) ? " " : ".. ",
+ parseList[currentMove - 1]);
}
EditCommentPopUp(currentMove, title, commentList[currentMove]);
void
-EditTagsEvent()
+EditTagsEvent ()
{
char *tags = PGNTags(&gameInfo);
- EditTagsPopUp(tags);
+ bookUp = FALSE;
+ EditTagsPopUp(tags, NULL);
free(tags);
}
void
-AnalyzeModeEvent()
+AnalyzeModeEvent ()
{
if (appData.noChessProgram || gameMode == AnalyzeMode)
return;
}
void
-AnalyzeFileEvent()
+AnalyzeFileEvent ()
{
if (appData.noChessProgram || gameMode == AnalyzeFile)
return;
StartAnalysisClock();
GetTimeMark(&lastNodeCountTime);
lastNodeCount = 0;
+ if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
}
void
-MachineWhiteEvent()
+MachineWhiteEvent ()
{
char buf[MSG_SIZ];
char *bookHit = NULL;
return;
- if (gameMode == PlayFromGameFile ||
- gameMode == TwoMachinesPlay ||
- gameMode == Training ||
- gameMode == AnalyzeMode ||
+ if (gameMode == PlayFromGameFile ||
+ gameMode == TwoMachinesPlay ||
+ gameMode == Training ||
+ gameMode == AnalyzeMode ||
gameMode == EndOfGame)
EditGameEvent();
- if (gameMode == EditPosition)
+ if (gameMode == EditPosition)
EditPositionDone(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();
pausing = FALSE;
ModeHighlight();
SetGameInfo();
- sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
+ snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
DisplayTitle(buf);
if (first.sendName) {
- sprintf(buf, "name %s\n", gameInfo.black);
+ snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
SendToProgram(buf, &first);
}
if (first.sendTime) {
if(bookHit) { // [HGM] book: simulate book reply
static char bookMove[MSG_SIZ]; // a bit generous?
- programStats.nodes = programStats.depth = programStats.time =
+ programStats.nodes = programStats.depth = programStats.time =
programStats.score = programStats.got_only_move = 0;
sprintf(programStats.movelist, "%s (xbook)", bookHit);
- strcpy(bookMove, "move ");
+ safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
strcat(bookMove, bookHit);
HandleMachineMove(bookMove, &first);
}
}
void
-MachineBlackEvent()
+MachineBlackEvent ()
{
- char buf[MSG_SIZ];
- char *bookHit = NULL;
+ char buf[MSG_SIZ];
+ char *bookHit = NULL;
if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
return;
- if (gameMode == PlayFromGameFile ||
- gameMode == TwoMachinesPlay ||
- gameMode == Training ||
- gameMode == AnalyzeMode ||
+ if (gameMode == PlayFromGameFile ||
+ gameMode == TwoMachinesPlay ||
+ gameMode == Training ||
+ gameMode == AnalyzeMode ||
gameMode == EndOfGame)
EditGameEvent();
- if (gameMode == EditPosition)
+ if (gameMode == EditPosition)
EditPositionDone(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();
pausing = FALSE;
ModeHighlight();
SetGameInfo();
- sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
+ snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
DisplayTitle(buf);
if (first.sendName) {
- sprintf(buf, "name %s\n", gameInfo.white);
+ snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
SendToProgram(buf, &first);
}
if (first.sendTime) {
if(bookHit) { // [HGM] book: simulate book reply
static char bookMove[MSG_SIZ]; // a bit generous?
- programStats.nodes = programStats.depth = programStats.time =
+ programStats.nodes = programStats.depth = programStats.time =
programStats.score = programStats.got_only_move = 0;
sprintf(programStats.movelist, "%s (xbook)", bookHit);
- strcpy(bookMove, "move ");
+ safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
strcat(bookMove, bookHit);
HandleMachineMove(bookMove, &first);
}
void
-DisplayTwoMachinesTitle()
+DisplayTwoMachinesTitle ()
{
char buf[MSG_SIZ];
if (appData.matchGames > 0) {
+ if(appData.tourneyFile[0]) {
+ snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
+ gameInfo.white, _("vs."), gameInfo.black,
+ nextGame+1, appData.matchGames+1,
+ appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
+ } else
if (first.twoMachinesColor[0] == 'w') {
- sprintf(buf, "%s vs. %s (%d-%d-%d)",
- gameInfo.white, gameInfo.black,
- first.matchWins, second.matchWins,
- matchGame - 1 - (first.matchWins + second.matchWins));
+ snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
+ gameInfo.white, _("vs."), gameInfo.black,
+ first.matchWins, second.matchWins,
+ matchGame - 1 - (first.matchWins + second.matchWins));
} else {
- sprintf(buf, "%s vs. %s (%d-%d-%d)",
- gameInfo.white, gameInfo.black,
- second.matchWins, first.matchWins,
- matchGame - 1 - (first.matchWins + second.matchWins));
+ snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
+ gameInfo.white, _("vs."), gameInfo.black,
+ second.matchWins, first.matchWins,
+ matchGame - 1 - (first.matchWins + second.matchWins));
}
} else {
- sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
+ snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
}
DisplayTitle(buf);
}
void
+SettingsMenuIfReady ()
+{
+ if (second.lastPing != second.lastPong) {
+ DisplayMessage("", _("Waiting for second chess program"));
+ ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
+ return;
+ }
+ ThawUI();
+ DisplayMessage("", "");
+ SettingsPopUp(&second);
+}
+
+int
+WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
+{
+ char buf[MSG_SIZ];
+ if (cps->pr == NoProc) {
+ StartChessProgram(cps);
+ if (cps->protocolVersion == 1) {
+ retry();
+ } else {
+ /* kludge: allow timeout for initial "feature" command */
+ FreezeUI();
+ snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
+ DisplayMessage("", buf);
+ ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
+ }
+ return 1;
+ }
+ return 0;
+}
+
+void
TwoMachinesEvent P((void))
{
int i;
char buf[MSG_SIZ];
ChessProgramState *onmove;
char *bookHit = NULL;
-
+ static int stalling = 0;
+ TimeMark now;
+ long wait;
+
if (appData.noChessProgram) return;
+ if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
+ DisplayError("second engine does not play this", 0);
+ return;
+ }
+
switch (gameMode) {
case TwoMachinesPlay:
return;
// forwardMostMove = currentMove;
TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
- ResurrectChessProgram(); /* in case first program isn't running */
- if (second.pr == NULL) {
- StartChessProgram(&second);
- if (second.protocolVersion == 1) {
- TwoMachinesEventIfReady();
- } else {
- /* kludge: allow timeout for initial "feature" command */
- FreezeUI();
- DisplayMessage("", _("Starting second chess program"));
- ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
- }
+ if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
+
+ if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
+ if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
+ ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
+ return;
+ }
+ if(!stalling) {
+ InitChessProgram(&second, FALSE); // unbalances ping of second engine
+ SendToProgram("force\n", &second);
+ stalling = 1;
+ ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
+ return;
+ }
+ GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
+ if(appData.matchPause>10000 || appData.matchPause<10)
+ appData.matchPause = 10000; /* [HGM] make pause adjustable */
+ wait = SubtractTimeMarks(&now, &pauseStart);
+ if(wait < appData.matchPause) {
+ ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
return;
}
+ // we are now committed to starting the game
+ stalling = 0;
DisplayMessage("", "");
- InitChessProgram(&second, FALSE);
- SendToProgram("force\n", &second);
if (startedFromSetupPosition) {
SendBoard(&second, backwardMostMove);
if (appData.debugMode) {
gameMode = TwoMachinesPlay;
pausing = FALSE;
- ModeHighlight();
+ ModeHighlight(); // [HGM] logo: this triggers display update of logos
SetGameInfo();
DisplayTwoMachinesTitle();
firstMove = TRUE;
} else {
onmove = &second;
}
-
+ if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
SendToProgram(first.computerString, &first);
if (first.sendName) {
- sprintf(buf, "name %s\n", second.tidy);
+ snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
SendToProgram(buf, &first);
}
SendToProgram(second.computerString, &second);
if (second.sendName) {
- sprintf(buf, "name %s\n", first.tidy);
+ snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
SendToProgram(buf, &second);
}
if(bookHit) { // [HGM] book: simulate book reply
static char bookMove[MSG_SIZ]; // a bit generous?
- programStats.nodes = programStats.depth = programStats.time =
+ programStats.nodes = programStats.depth = programStats.time =
programStats.score = programStats.got_only_move = 0;
sprintf(programStats.movelist, "%s (xbook)", bookHit);
- strcpy(bookMove, "move ");
+ safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
strcat(bookMove, bookHit);
savedMessage = bookMove; // args for deferred call
savedState = onmove;
}
void
-TrainingEvent()
+TrainingEvent ()
{
if (gameMode == Training) {
SetTrainingModeOff();
}
void
-IcsClientEvent()
+IcsClientEvent ()
{
if (!appData.icsActive) return;
switch (gameMode) {
case AnalyzeFile:
ExitAnalyzeMode();
break;
-
+
default:
EditGameEvent();
break;
return;
}
-
void
-EditGameEvent()
+EditGameEvent ()
{
int i;
SendToProgram("force\n", &first);
break;
case TwoMachinesPlay:
- GameEnds((ChessMove) 0, NULL, GE_PLAYER);
+ GameEnds(EndOfFile, NULL, GE_PLAYER);
ResurrectChessProgram();
SetUserThinkingEnables();
break;
default:
return;
}
-
+
pausing = FALSE;
StopClocks();
first.offeredDraw = second.offeredDraw = 0;
SendToProgram("undo\n", &first);
i--;
}
+ if(!adjustedClock) {
whiteTimeRemaining = timeRemaining[0][currentMove];
blackTimeRemaining = timeRemaining[1][currentMove];
DisplayBothClocks();
+ }
if (whiteFlag || blackFlag) {
whiteFlag = blackFlag = 0;
}
DisplayTitle("");
- }
-
+ }
+
gameMode = EditGame;
ModeHighlight();
SetGameInfo();
void
-EditPositionEvent()
+EditPositionEvent ()
{
if (gameMode == EditPosition) {
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;
HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
DisplayMove(-1);
+ if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
}
void
-ExitAnalyzeMode()
+ExitAnalyzeMode ()
{
/* [DM] icsEngineAnalyze - possible call from other functions */
if (appData.icsEngineAnalyze) {
}
void
-EditPositionDone(Boolean fakeRights)
+EditPositionDone (Boolean fakeRights)
{
int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
}
SendToProgram("force\n", &first);
if (blackPlaysFirst) {
- strcpy(moveList[0], "");
- strcpy(parseList[0], "");
+ safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
+ safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
currentMove = forwardMostMove = backwardMostMove = 1;
CopyBoard(boards[1], boards[0]);
} else {
/* Pause for `ms' milliseconds */
/* !! Ugh, this is a kludge. Fix it sometime. --tpm */
void
-TimeDelay(ms)
- long ms;
+TimeDelay (long ms)
{
TimeMark m1, m2;
/* !! Ugh, this is a kludge. Fix it sometime. --tpm */
void
-SendMultiLineToICS(buf)
- char *buf;
+SendMultiLineToICS (char *buf)
{
char temp[MSG_SIZ+1], *p;
int len;
len = strlen(buf);
if (len > MSG_SIZ)
len = MSG_SIZ;
-
+
strncpy(temp, buf, len);
temp[len] = 0;
}
void
-SetWhiteToPlayEvent()
+SetWhiteToPlayEvent ()
{
if (gameMode == EditPosition) {
blackPlaysFirst = FALSE;
}
void
-SetBlackToPlayEvent()
+SetBlackToPlayEvent ()
{
if (gameMode == EditPosition) {
blackPlaysFirst = TRUE;
}
void
-EditPositionMenuEvent(selection, x, y)
- ChessSquare selection;
- int x, y;
+EditPositionMenuEvent (ChessSquare selection, int x, int y)
{
char buf[MSG_SIZ];
ChessSquare piece = boards[0][y][x];
for (y = 0; y < BOARD_HEIGHT; y++) {
if (gameMode == IcsExamining) {
if (boards[currentMove][y][x] != EmptySquare) {
- sprintf(buf, "%sx@%c%c\n", ics_prefix,
+ snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
AAA + x, ONE + y);
SendToICS(buf);
}
case EmptySquare:
if (gameMode == IcsExamining) {
if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
- sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
+ snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
SendToICS(buf);
} else {
if(x < BOARD_LEFT || x >= BOARD_RGHT) {
piece > (int)BlackMan && piece <= (int)BlackKing ) {
selection = (ChessSquare) (DEMOTED piece);
} else if(piece == EmptySquare) selection = BlackSilver;
- else selection = (ChessSquare)((int)piece + 1);
+ else selection = (ChessSquare)((int)piece + 1);
goto defaultlabel;
case WhiteQueen:
defaultlabel:
if (gameMode == IcsExamining) {
if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
- sprintf(buf, "%s%c@%c%c\n", ics_prefix,
- PieceToChar(selection), AAA + x, ONE + y);
+ snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
+ PieceToChar(selection), AAA + x, ONE + y);
SendToICS(buf);
} else {
if(x < BOARD_LEFT || x >= BOARD_RGHT) {
} else
boards[0][y][x] = selection;
DrawPosition(TRUE, boards[0]);
+ ClearHighlights();
+ fromX = fromY = -1;
}
break;
}
void
-DropMenuEvent(selection, x, y)
- ChessSquare selection;
- int x, y;
+DropMenuEvent (ChessSquare selection, int x, int y)
{
ChessMove moveType;
}
void
-AcceptEvent()
+AcceptEvent ()
{
/* Accept a pending offer of any kind from opponent */
-
+
if (appData.icsActive) {
SendToICS(ics_prefix);
SendToICS("accept\n");
}
void
-DeclineEvent()
+DeclineEvent ()
{
/* Decline a pending offer of any kind from opponent */
-
+
if (appData.icsActive) {
SendToICS(ics_prefix);
SendToICS("decline\n");
}
void
-RematchEvent()
+RematchEvent ()
{
/* Issue ICS rematch command */
if (appData.icsActive) {
}
void
-CallFlagEvent()
+CallFlagEvent ()
{
/* Call your opponent's flag (claim a win on time) */
if (appData.icsActive) {
}
void
-DrawEvent()
+ClockClick (int which)
+{ // [HGM] code moved to back-end from winboard.c
+ if(which) { // black clock
+ if (gameMode == EditPosition || gameMode == IcsExamining) {
+ if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
+ SetBlackToPlayEvent();
+ } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
+ UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
+ } else if (shiftKey) {
+ AdjustClock(which, -1);
+ } else if (gameMode == IcsPlayingWhite ||
+ gameMode == MachinePlaysBlack) {
+ CallFlagEvent();
+ }
+ } else { // white clock
+ if (gameMode == EditPosition || gameMode == IcsExamining) {
+ if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
+ SetWhiteToPlayEvent();
+ } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
+ UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
+ } else if (shiftKey) {
+ AdjustClock(which, -1);
+ } else if (gameMode == IcsPlayingBlack ||
+ gameMode == MachinePlaysWhite) {
+ CallFlagEvent();
+ }
+ }
+}
+
+void
+DrawEvent ()
{
/* Offer draw or accept pending draw offer from opponent */
-
+
if (appData.icsActive) {
/* Note: tournament rules require draw offers to be
made after you make your move but before you punch
your clock. Currently ICS doesn't let you do that;
instead, you immediately punch your clock after making
a move, but you can offer a draw at any time. */
-
+
SendToICS(ics_prefix);
SendToICS("draw\n");
userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
}
void
-AdjournEvent()
+AdjournEvent ()
{
/* Offer Adjourn or accept pending Adjourn offer from opponent */
-
+
if (appData.icsActive) {
SendToICS(ics_prefix);
SendToICS("adjourn\n");
void
-AbortEvent()
+AbortEvent ()
{
/* Offer Abort or accept pending Abort offer from opponent */
-
+
if (appData.icsActive) {
SendToICS(ics_prefix);
SendToICS("abort\n");
}
void
-ResignEvent()
+ResignEvent ()
{
/* Resign. You can do this even if it's not your turn. */
-
+
if (appData.icsActive) {
SendToICS(ics_prefix);
SendToICS("resign\n");
void
-StopObservingEvent()
+StopObservingEvent ()
{
/* Stop observing current games */
SendToICS(ics_prefix);
}
void
-StopExaminingEvent()
+StopExaminingEvent ()
{
/* Stop observing current game */
SendToICS(ics_prefix);
}
void
-ForwardInner(target)
- int target;
+ForwardInner (int target)
{
- int limit;
+ int limit; int oldSeekGraphUp = seekGraphUp;
if (appData.debugMode)
fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
if (gameMode == EditPosition)
return;
+ seekGraphUp = FALSE;
+ MarkTargetSquares(1);
+
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]) {
}
}
}
- 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];
}
DisplayBothClocks();
DisplayMove(currentMove - 1);
- DrawPosition(FALSE, boards[currentMove]);
+ DrawPosition(oldSeekGraphUp, boards[currentMove]);
HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
DisplayComment(currentMove - 1, commentList[currentMove]);
}
+ ClearMap(); // [HGM] exclude: invalidate map
}
void
-ForwardEvent()
+ForwardEvent ()
{
if (gameMode == IcsExamining && !pausing) {
SendToICS(ics_prefix);
}
void
-ToEndEvent()
+ToEndEvent ()
{
if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
/* 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");
}
void
-BackwardInner(target)
- int target;
+BackwardInner (int target)
{
int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
target, currentMove, forwardMostMove);
if (gameMode == EditPosition) return;
+ seekGraphUp = FALSE;
+ MarkTargetSquares(1);
if (currentMove <= backwardMostMove) {
ClearHighlights();
DrawPosition(full_redraw, boards[currentMove]);
}
if (gameMode == PlayFromGameFile && !pausing)
PauseEvent();
-
+
if (moveList[target][0]) {
int fromX, fromY, toX, toY;
toX = moveList[target][2] - AAA;
if (gameMode == EditGame || gameMode==AnalyzeMode ||
gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
while (currentMove > target) {
+ if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
+ // null move cannot be undone. Reload program with move history before it.
+ int i;
+ for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
+ if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
+ }
+ SendBoard(&first, i);
+ for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
+ break;
+ }
SendToProgram("undo\n", &first);
currentMove--;
}
} else {
currentMove = target;
}
-
+
if (gameMode == EditGame || gameMode == EndOfGame) {
whiteTimeRemaining = timeRemaining[0][currentMove];
blackTimeRemaining = timeRemaining[1][currentMove];
HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
// [HGM] PV info: routine tests if comment empty
DisplayComment(currentMove - 1, commentList[currentMove]);
+ ClearMap(); // [HGM] exclude: invalidate map
}
void
-BackwardEvent()
+BackwardEvent ()
{
if (gameMode == IcsExamining && !pausing) {
SendToICS(ics_prefix);
}
void
-ToStartEvent()
+ToStartEvent ()
{
if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
/* 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
-ToNrEvent(int to)
+ToNrEvent (int to)
{
if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
if (to >= forwardMostMove) to = forwardMostMove;
}
void
-RevertEvent(Boolean annotate)
+RevertEvent (Boolean annotate)
{
if(PopTail(annotate)) { // [HGM] vari: restore old game tail
return;
}
void
-RetractMoveEvent()
+RetractMoveEvent ()
{
switch (gameMode) {
case MachinePlaysWhite:
}
void
-MoveNowEvent()
+MoveNowEvent ()
{
ChessProgramState *cps;
}
void
-TruncateGameEvent()
+TruncateGameEvent ()
{
EditGameEvent();
if (gameMode != EditGame) return;
}
void
-TruncateGame()
+TruncateGame ()
{
CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
if (forwardMostMove > currentMove) {
}
void
-HintEvent()
+HintEvent ()
{
if (appData.noChessProgram) return;
switch (gameMode) {
}
void
-BookEvent()
+BookEvent ()
{
if (appData.noChessProgram) return;
switch (gameMode) {
}
void
-AboutGameEvent()
+AboutGameEvent ()
{
char *tags = PGNTags(&gameInfo);
TagsPopUp(tags, CmailMsg());
/* end button procedures */
void
-PrintPosition(fp, move)
- FILE *fp;
- int move;
+PrintPosition (FILE *fp, int move)
{
int i, j;
-
+
for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
char c = PieceToChar(boards[move][i][j]);
}
void
-PrintOpponents(fp)
- FILE *fp;
+PrintOpponents (FILE *fp)
{
if (gameInfo.white != NULL) {
fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
/* Find last component of program's own name, using some heuristics */
void
-TidyProgramName(prog, host, buf)
- char *prog, *host, buf[MSG_SIZ];
+TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
{
- char *p, *q;
+ char *p, *q, c;
int local = (strcmp(host, "localhost") == 0);
while (!local && (p = strchr(prog, ';')) != NULL) {
p++;
while (p >= prog && *p != '/' && *p != '\\') p--;
p++;
if(p == prog && *p == '"') p++;
- if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
+ c = *q; *q = 0;
+ if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
memcpy(buf, p, q - p);
buf[q - p] = NULLCHAR;
if (!local) {
}
char *
-TimeControlTagValue()
+TimeControlTagValue ()
{
char buf[MSG_SIZ];
if (!appData.clockMode) {
- strcpy(buf, "-");
+ safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
} else if (movesPerSession > 0) {
- sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
+ snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
} else if (timeIncrement == 0) {
- sprintf(buf, "%ld", timeControl/1000);
+ snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
} else {
- sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
+ snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
}
return StrSave(buf);
}
void
-SetGameInfo()
+SetGameInfo ()
{
/* This routine is used only for certain modes */
VariantClass v = gameInfo.variant;
char *p = NULL;
if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
- r = gameInfo.result;
- p = gameInfo.resultDetails;
+ r = gameInfo.result;
+ p = gameInfo.resultDetails;
gameInfo.resultDetails = NULL;
}
ClearGameInfo(&gameInfo);
gameInfo.event = StrSave( appData.pgnEventHeader );
gameInfo.site = StrSave(HostName());
gameInfo.date = PGNDate();
- if (matchGame > 0) {
+ if (roundNr > 0) {
char buf[MSG_SIZ];
- sprintf(buf, "%d", matchGame);
+ snprintf(buf, MSG_SIZ, "%d", roundNr);
gameInfo.round = StrSave(buf);
} else {
gameInfo.round = StrSave("-");
}
if (first.twoMachinesColor[0] == 'w') {
- gameInfo.white = StrSave(first.tidy);
- gameInfo.black = StrSave(second.tidy);
+ gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
+ gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
} else {
- gameInfo.white = StrSave(second.tidy);
- gameInfo.black = StrSave(first.tidy);
+ gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
+ gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
}
gameInfo.timeControl = TimeControlTagValue();
break;
}
void
-ReplaceComment(index, text)
- int index;
- char *text;
+ReplaceComment (int index, char *text)
{
int len;
+ char *p;
+ float score;
+ if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
+ pvInfoList[index-1].depth == len &&
+ fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
+ (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
while (*text == '\n') text++;
len = strlen(text);
while (len > 0 && text[len - 1] == '\n') len--;
strncpy(commentList[index], text, len);
commentList[index][len] = '\n';
commentList[index][len + 1] = NULLCHAR;
- } else {
+ } 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] = (char *) malloc(len + 7);
+ safeStrCpy(commentList[index], "{\n", 3);
+ safeStrCpy(commentList[index]+2, text, len+1);
commentList[index][len+2] = NULLCHAR;
while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
strcat(commentList[index], "\n}\n");
}
void
-CrushCRs(text)
- char *text;
+CrushCRs (char *text)
{
char *p = text;
char *q = text;
}
void
-AppendComment(index, text, addBraces)
- int index;
- char *text;
- Boolean addBraces; // [HGM] braces: tells if we should add {}
+AppendComment (int index, char *text, Boolean addBraces)
+/* addBraces tells if we should add {} */
{
int oldlen, len;
char *old;
while (*text == '\n') text++;
len = strlen(text);
while (len > 0 && text[len - 1] == '\n') len--;
+ text[len] = NULLCHAR;
if (len == 0) return;
if (commentList[index] != NULL) {
+ Boolean addClosingBrace = addBraces;
old = commentList[index];
oldlen = strlen(old);
while(commentList[index][oldlen-1] == '\n')
commentList[index][--oldlen] = NULLCHAR;
commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
- strcpy(commentList[index], old);
+ safeStrCpy(commentList[index], old, oldlen + len + 6);
free(old);
// [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--; }
+ if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
+ if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
while (*text == '\n') { text++; len--; }
commentList[index][--oldlen] = NULLCHAR;
}
- if(addBraces) strcat(commentList[index], "\n{\n");
+ if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
else strcat(commentList[index], "\n");
strcat(commentList[index], text);
- if(addBraces) strcat(commentList[index], "\n}\n");
+ if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
else strcat(commentList[index], "\n");
} else {
commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
if(addBraces)
- strcpy(commentList[index], "{\n");
+ safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
else commentList[index][0] = NULLCHAR;
strcat(commentList[index], text);
- strcat(commentList[index], "\n");
- if(addBraces) strcat(commentList[index], "}\n");
+ strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
+ if(addBraces == TRUE) strcat(commentList[index], "}\n");
}
}
-static char * FindStr( char * text, char * sub_text )
+static char *
+FindStr (char * text, char * sub_text)
{
char * result = strstr( text, sub_text );
/* [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 *
+GetInfoFromComment (int index, char * text)
{
- char * sep = text;
+ char * sep = text, *p;
if( text != NULL && index > 0 ) {
int score = 0;
return text;
}
+ p = text;
+ if(p[1] == '(') { // comment starts with PV
+ p = strchr(p, ')'); // locate end of PV
+ if(p == NULL || sep < p+5) return text;
+ // at this point we have something like "{(.*) +0.23/6 ..."
+ p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
+ *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
+ // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
+ }
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 ) {
+ if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
+ sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
+ sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
+ sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
return text;
}
/* [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
+ while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // 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++;
+ while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
}
if( depth <= 0 ) {
pvInfoList[index-1].score = score;
pvInfoList[index-1].time = 10*time; // centi-sec
if(*sep == '}') *sep = 0; else *--sep = '{';
+ if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
}
return sep;
}
void
-SendToProgram(message, cps)
- char *message;
- ChessProgramState *cps;
+SendToProgram (char *message, ChessProgramState *cps)
{
int count, outCount, error;
char buf[MSG_SIZ];
- if (cps->pr == NULL) return;
+ if (cps->pr == NoProc) 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);
+ if(serverFP)
+ fprintf(serverFP, "%ld >%-6s: %s",
+ SubtractTimeMarks(&now, &programStartTime),
+ cps->which, message), fflush(serverFP);
}
-
+
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);
+ if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
+ snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
+ snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
+ if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
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;
+ ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
+ if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
+ gameInfo.result = res;
}
gameInfo.resultDetails = StrSave(buf);
}
+ if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
}
}
void
-ReceiveFromProgram(isr, closure, message, count, error)
- InputSourceRef isr;
- VOIDSTAR closure;
- char *message;
- int count;
- int error;
+ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
{
char *end_str;
char buf[MSG_SIZ];
if (isr != cps->isr) return; /* Killed intentionally */
if (count <= 0) {
if (count == 0) {
- 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 */
+ RemoveInputSource(cps->isr);
+ snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
+ _(cps->which), cps->program);
+ if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
+ if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
+ snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
+ if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
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;
+ ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
+ if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
+ gameInfo.result = res;
}
gameInfo.resultDetails = StrSave(buf);
}
- RemoveInputSource(cps->isr);
+ if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
} else {
- sprintf(buf,
- _("Error reading from %s chess program (%s)"),
- cps->which, cps->program);
+ snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
+ _(cps->which), cps->program);
RemoveInputSource(cps->isr);
/* [AS] Program is misbehaving badly... kill it */
}
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; 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 &&
+ 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, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
+ sscanf(message, "hint: %c", &c)!=1 &&
sscanf(message, "pong %c", &c)!=1 && start != '#') {
quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
print = (appData.engineComments >= 2);
}
if(print) {
GetTimeMark(&now);
- fprintf(debugFP, "%ld <%-6s: %s%s\n",
- SubtractTimeMarks(&now, &programStartTime), cps->which,
+ fprintf(debugFP, "%ld <%-6s: %s%s\n",
+ SubtractTimeMarks(&now, &programStartTime), cps->which,
quote,
message);
+ if(serverFP)
+ fprintf(serverFP, "%ld <%-6s: %s%s\n",
+ SubtractTimeMarks(&now, &programStartTime), cps->which,
+ quote,
+ message), fflush(serverFP);
}
}
/* [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, "kibitz") != NULL ||
strstr(message, "tellics") != NULL) return;
}
void
-SendTimeControl(cps, mps, tc, inc, sd, st)
- ChessProgramState *cps;
- int mps, inc, sd, st;
- long tc;
+SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
{
char buf[MSG_SIZ];
int seconds;
/* GNU Chess 4 has no st command; uses level in a nonstandard way */
seconds = st % 60;
if (seconds == 0) {
- sprintf(buf, "level 1 %d\n", st/60);
+ snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
} else {
- sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
+ snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
}
} else {
- sprintf(buf, "st %d\n", st);
+ snprintf(buf, MSG_SIZ, "st %d\n", st);
}
} else {
/* Set conventional or incremental time control, using level command */
/* Note old gnuchess bug -- minutes:seconds used to not work.
Fixed in later versions, but still avoid :seconds
when seconds is 0. */
- sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
+ snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
} else {
- sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
- seconds, inc/1000);
+ snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
+ seconds, inc/1000.);
}
}
SendToProgram(buf, cps);
/* Orthogonally, limit search to given depth */
if (sd > 0) {
if (cps->sdKludge) {
- sprintf(buf, "depth\n%d\n", sd);
+ snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
} else {
- sprintf(buf, "sd %d\n", sd);
+ snprintf(buf, MSG_SIZ, "sd %d\n", sd);
}
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!
+ 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);
+ snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
+ SendToProgram(buf, cps);
}
}
}
-ChessProgramState *WhitePlayer()
+ChessProgramState *
+WhitePlayer ()
/* [HGM] return pointer to 'first' or 'second', depending on who plays white */
{
- if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
+ if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
return &second;
return &first;
}
void
-SendTimeRemaining(cps, machineWhite)
- ChessProgramState *cps;
- int /*boolean*/ machineWhite;
+SendTimeRemaining (ChessProgramState *cps, int machineWhite)
{
char message[MSG_SIZ];
long time, otime;
}
/* [HGM] translate opponent's time by time-odds factor */
otime = (otime * cps->other->timeOdds) / cps->timeOdds;
- if (appData.debugMode) {
- fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
- }
if (time <= 0) time = 1;
if (otime <= 0) otime = 1;
-
- sprintf(message, "time %ld\n", time);
+
+ snprintf(message, MSG_SIZ, "time %ld\n", time);
SendToProgram(message, cps);
- sprintf(message, "otim %ld\n", otime);
+ snprintf(message, MSG_SIZ, "otim %ld\n", otime);
SendToProgram(message, cps);
}
int
-BoolFeature(p, name, loc, cps)
- char **p;
- char *name;
- int *loc;
- ChessProgramState *cps;
+BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
{
char buf[MSG_SIZ];
int len = strlen(name);
int val;
+
if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
(*p) += len + 1;
sscanf(*p, "%d", &val);
*loc = (val != 0);
- while (**p && **p != ' ') (*p)++;
- sprintf(buf, "accepted %s\n", name);
+ while (**p && **p != ' ')
+ (*p)++;
+ snprintf(buf, MSG_SIZ, "accepted %s\n", name);
SendToProgram(buf, cps);
return TRUE;
}
}
int
-IntFeature(p, name, loc, cps)
- char **p;
- char *name;
- int *loc;
- ChessProgramState *cps;
+IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
{
char buf[MSG_SIZ];
int len = strlen(name);
(*p) += len + 1;
sscanf(*p, "%d", loc);
while (**p && **p != ' ') (*p)++;
- sprintf(buf, "accepted %s\n", name);
+ snprintf(buf, MSG_SIZ, "accepted %s\n", name);
SendToProgram(buf, cps);
return TRUE;
}
}
int
-StringFeature(p, name, loc, cps)
- char **p;
- char *name;
- char loc[];
- ChessProgramState *cps;
+StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
{
char buf[MSG_SIZ];
int len = strlen(name);
sscanf(*p, "%[^\"]", loc);
while (**p && **p != '\"') (*p)++;
if (**p == '\"') (*p)++;
- sprintf(buf, "accepted %s\n", name);
+ snprintf(buf, MSG_SIZ, "accepted %s\n", name);
SendToProgram(buf, cps);
return TRUE;
}
return FALSE;
}
-int
-ParseOption(Option *opt, ChessProgramState *cps)
+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
{
} 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;
+ opt->type = FileName; // 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;
+ opt->type = PathName; // 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
+ } else if(p = strstr(opt->name, " -combo ")) {
+ opt->textValue = (char*) (opt->choice = &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;
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);
+ snprintf(buf, MSG_SIZ, "option %s", p);
if(p = strstr(buf, ",")) *p = 0;
+ if(q = strchr(buf, '=')) switch(opt->type) {
+ case ComboBox:
+ for(n=0; n<opt->max; n++)
+ if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
+ break;
+ case TextBox:
+ safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
+ break;
+ case Spin:
+ case CheckBox:
+ opt->value = atoi(q+1);
+ default:
+ break;
+ }
strcat(buf, "\n");
SendToProgram(buf, cps);
}
}
void
-FeatureDone(cps, val)
- ChessProgramState* cps;
- int val;
+FeatureDone (ChessProgramState *cps, int val)
{
DelayedEventCallback cb = GetDelayedEvent();
if ((cb == InitBackEnd3 && cps == &first) ||
- (cb == TwoMachinesEventIfReady && cps == &second)) {
+ (cb == SettingsMenuIfReady && cps == &second) ||
+ (cb == LoadEngine) ||
+ (cb == TwoMachinesEventIfReady)) {
CancelDelayedEvent();
ScheduleDelayedEvent(cb, val ? 1 : 3600000);
}
/* Parse feature command from engine */
void
-ParseFeatures(args, cps)
- char* args;
- ChessProgramState *cps;
+ParseFeatures (char *args, 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, "xedit", &cps->extendedEdit, 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;
continue;
}
if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
- if (StringFeature(&p, "myname", &cps->tidy, cps)) {
+ if (StringFeature(&p, "myname", cps->tidy, cps)) {
if (gameMode == TwoMachinesPlay) {
DisplayTwoMachinesTitle();
} else {
}
continue;
}
- if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
+ if (StringFeature(&p, "variants", cps->variants, cps)) continue;
if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
+ if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
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 (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
+ if (StringFeature(&p, "option", buf, cps)) {
+ FREE(cps->option[cps->nrOptions].name);
+ cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
+ safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
- sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
+ snprintf(buf, MSG_SIZ, "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);
+ snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
DisplayError(buf, 0);
}
continue;
/* unknown feature: complain and skip */
q = p;
while (*q && *q != '=') q++;
- sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
+ snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
SendToProgram(buf, cps);
p = q;
if (*p == '=') {
}
void
-PeriodicUpdatesEvent(newState)
- int newState;
+PeriodicUpdatesEvent (int newState)
{
if (newState == appData.periodicUpdates)
return;
}
void
-PonderNextMoveEvent(newState)
- int newState;
+PonderNextMoveEvent (int newState)
{
if (newState == appData.ponderNextMove) return;
if (gameMode == EditPosition) EditPositionDone(TRUE);
}
void
-NewSettingEvent(option, feature, command, value)
- char *command;
- int option, value, *feature;
+NewSettingEvent (int option, int *feature, char *command, int value)
{
char buf[MSG_SIZ];
if (gameMode == EditPosition) EditPositionDone(TRUE);
- sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
+ snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
if(feature == NULL || *feature) SendToProgram(buf, &first);
if (gameMode == TwoMachinesPlay) {
if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
}
void
-ShowThinkingEvent()
+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);
}
void
-AskQuestionEvent(title, question, replyPrefix, which)
- char *title; char *question; char *replyPrefix; char *which;
+AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
{
ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
if (pr == NoProc) return;
}
void
-DisplayMove(moveNumber)
- int moveNumber;
+TypeInEvent (char firstChar)
+{
+ if ((gameMode == BeginningOfGame && !appData.icsActive) ||
+ gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
+ gameMode == AnalyzeMode || gameMode == EditGame ||
+ gameMode == EditPosition || gameMode == IcsExamining ||
+ gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
+ isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
+ ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
+ gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
+ gameMode == Training) PopUpMoveDialog(firstChar);
+}
+
+void
+TypeInDoneEvent (char *move)
+{
+ Board board;
+ int n, fromX, fromY, toX, toY;
+ char promoChar;
+ ChessMove moveType;
+
+ // [HGM] FENedit
+ if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
+ EditPositionPasteFEN(move);
+ return;
+ }
+ // [HGM] movenum: allow move number to be typed in any mode
+ if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
+ ToNrEvent(2*n-1);
+ return;
+ }
+ // undocumented kludge: allow command-line option to be typed in!
+ // (potentially fatal, and does not implement the effect of the option.)
+ // should only be used for options that are values on which future decisions will be made,
+ // and definitely not on options that would be used during initialization.
+ if(strstr(move, "!!! -") == move) {
+ ParseArgsFromString(move+4);
+ return;
+ }
+
+ if (gameMode != EditGame && currentMove != forwardMostMove &&
+ gameMode != Training) {
+ DisplayMoveError(_("Displayed move is not current"));
+ } else {
+ int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
+ &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
+ if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
+ if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
+ &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
+ UserMoveEvent(fromX, fromY, toX, toY, promoChar);
+ } else {
+ DisplayMoveError(_("Could not parse move"));
+ }
+ }
+}
+
+void
+DisplayMove (int moveNumber)
{
char message[MSG_SIZ];
char res[MSG_SIZ];
char cpThinkOutput[MSG_SIZ];
if(appData.noGUI) return; // [HGM] fast: suppress display of moves
-
- if (moveNumber == forwardMostMove - 1 ||
+
+ if (moveNumber == forwardMostMove - 1 ||
gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
- safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
+ safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
if (strchr(cpThinkOutput, '\n')) {
*strchr(cpThinkOutput, '\n') = NULLCHAR;
if (moveNumber == forwardMostMove - 1 &&
gameInfo.resultDetails != NULL) {
if (gameInfo.resultDetails[0] == NULLCHAR) {
- sprintf(res, " %s", PGNResult(gameInfo.result));
+ snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
} else {
- sprintf(res, " {%s} %s",
- gameInfo.resultDetails, PGNResult(gameInfo.result));
+ snprintf(res, MSG_SIZ, " {%s} %s",
+ T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
}
} else {
res[0] = NULLCHAR;
if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
DisplayMessage(res, cpThinkOutput);
} else {
- sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
+ snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
WhiteOnMove(moveNumber) ? " " : ".. ",
parseList[moveNumber], res);
DisplayMessage(message, cpThinkOutput);
}
void
-DisplayComment(moveNumber, text)
- int moveNumber;
- char *text;
+DisplayComment (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");
+ safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
} else {
- sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
+ snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
WhiteOnMove(moveNumber) ? " " : ".. ",
parseList[moveNumber]);
}
- // [HGM] PV info: display PV info together with (or as) comment
- if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
- if(text == NULL) text = "";
- score = pvInfoList[moveNumber].score;
- sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
- depth, (pvInfoList[moveNumber].time+50)/100, text);
- text = buf;
- }
if (text != NULL && (appData.autoDisplayComment || commentUp))
CommentPopUp(title, text);
}
* ioctl, which does not work properly on some flavors of Unix.
*/
void
-Attention(cps)
- ChessProgramState *cps;
+Attention (ChessProgramState *cps)
{
#if ATTENTION
if (!cps->useSigint) return;
}
int
-CheckFlags()
+CheckFlags ()
{
if (whiteTimeRemaining <= 0) {
if (!whiteFlag) {
}
void
-CheckTimeControl()
+CheckTimeControl ()
{
if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
gameMode == PlayFromGameFile || forwardMostMove == 0) return;
/*
* add time to clocks when time control is achieved ([HGM] now also used for increment)
*/
- if ( !WhiteOnMove(forwardMostMove) )
+ if ( !WhiteOnMove(forwardMostMove) ) {
/* White made time control */
- whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
+ lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
+ whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
/* [HGM] time odds: correct new time quota for time odds! */
/ WhitePlayer()->timeOdds;
- else
+ lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
+ } else {
+ lastBlack -= blackTimeRemaining;
/* Black made time control */
- blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
+ blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
/ WhitePlayer()->other->timeOdds;
+ lastWhite = whiteTimeRemaining;
+ }
}
void
-DisplayBothClocks()
+DisplayBothClocks ()
{
int wom = gameMode == EditPosition ?
!blackPlaysFirst : WhiteOnMove(currentMove);
/* Get the current time as a TimeMark */
void
-GetTimeMark(tm)
- TimeMark *tm;
+GetTimeMark (TimeMark *tm)
{
#if HAVE_GETTIMEOFDAY
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*/
time marks. We assume the difference will fit in a long!
*/
long
-SubtractTimeMarks(tm2, tm1)
- TimeMark *tm2, *tm1;
+SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
{
return 1000L*(tm2->sec - tm1->sec) +
(long) (tm2->ms - tm1->ms);
* 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;
static long intendedTickLength;
long
-NextTickLength(timeRemaining)
- long timeRemaining;
+NextTickLength (long timeRemaining)
{
long nominalTickLength, nextTickLength;
/* Adjust clock one minute up or down */
void
-AdjustClock(Boolean which, int dir)
+AdjustClock (Boolean which, int dir)
{
+ if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
if(which) blackTimeRemaining += 60000*dir;
else whiteTimeRemaining += 60000*dir;
DisplayBothClocks();
+ adjustedClock = TRUE;
}
/* Stop clocks and reset to a fresh time control */
void
-ResetClocks()
+ResetClocks ()
{
(void) StopClockTimer();
if (appData.icsActive) {
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;
+ whiteTC = blackTC = fullTimeControlString;
+ whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
+ blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
}
if (whiteFlag || blackFlag) {
DisplayTitle("");
whiteFlag = blackFlag = FALSE;
}
+ lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
DisplayBothClocks();
+ adjustedClock = FALSE;
}
#define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
/* Decrement running clock by amount of time that has passed */
void
-DecrementClocks()
+DecrementClocks ()
{
long timeRemaining;
long lastTickLength, fudge;
if (!appData.clockMode) return;
if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
-
+
GetTimeMark(&now);
lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
if (WhiteOnMove(forwardMostMove)) {
if(whiteNPS >= 0) lastTickLength = 0;
timeRemaining = whiteTimeRemaining -= lastTickLength;
+ if(timeRemaining < 0 && !appData.icsActive) {
+ GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
+ if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
+ whiteStartMove = forwardMostMove; whiteTC = nextSession;
+ lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
+ }
+ }
DisplayWhiteClock(whiteTimeRemaining - fudge,
WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
} else {
if(blackNPS >= 0) lastTickLength = 0;
timeRemaining = blackTimeRemaining -= lastTickLength;
+ if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
+ GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
+ if(suddenDeath) {
+ blackStartMove = forwardMostMove;
+ lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
+ }
+ }
DisplayBlackClock(blackTimeRemaining - fudge,
!WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
}
-
if (CheckFlags()) return;
-
+
+ if(twoBoards) { // count down secondary board's clocks as well
+ activePartnerTime -= lastTickLength;
+ partnerUp = 1;
+ if(activePartner == 'W')
+ DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
+ else
+ DisplayBlackClock(activePartnerTime, TRUE);
+ partnerUp = 0;
+ }
+
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;
}
from the color that is *not* on move now.
*/
void
-SwitchClocks(int newMoveNr)
+SwitchClocks (int newMoveNr)
{
long lastTickLength;
TimeMark now;
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
+// if(pvInfoList[forwardMostMove].time == -1)
+ pvInfoList[forwardMostMove].time = // use GUI time
(timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
} else {
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 =
+// if(pvInfoList[forwardMostMove].time == -1)
+ pvInfoList[forwardMostMove].time =
(timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
}
flagged = CheckFlags();
whiteTimeRemaining : blackTimeRemaining);
StartClockTimer(intendedTickLength);
}
-
+
/* Stop both clocks */
void
-StopClocks()
-{
+StopClocks ()
+{
long lastTickLength;
TimeMark now;
}
CheckFlags();
}
-
+
/* Start clock of player on move. Time may have been reset, so
if clock is already running, stop and restart it. */
void
-StartClocks()
+StartClocks ()
{
(void) StopClockTimer(); /* in case it was running already */
DisplayBothClocks();
whiteTimeRemaining : blackTimeRemaining);
/* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
- whiteNPS = blackNPS = -1;
+ 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;
}
char *
-TimeString(ms)
- long ms;
+TimeString (long ms)
{
long second, minute, hour, day;
char *sign = "";
static char buf[32];
-
+
if (ms > 0 && ms <= 9900) {
/* convert milliseconds to tenths, rounding up */
double tenths = floor( ((double)(ms + 99L)) / 100.00 );
- sprintf(buf, " %03.1f ", tenths/10.0);
+ snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
return buf;
}
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 ",
+ snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
sign, day, hour, minute, second);
else if (hour > 0)
- sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
+ snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
else
- sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
-
+ snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
+
return buf;
}
* This is necessary because some C libraries aren't ANSI C compliant yet.
*/
char *
-StrStr(string, match)
- char *string, *match;
+StrStr (char *string, char *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 *
-StrCaseStr(string, match)
- char *string, *match;
+StrCaseStr (char *string, char *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]))
#ifndef _amigados
int
-StrCaseCmp(s1, s2)
- char *s1, *s2;
+StrCaseCmp (char *s1, char *s2)
{
char c1, c2;
-
+
for (;;) {
c1 = ToLower(*s1++);
c2 = ToLower(*s2++);
int
-ToLower(c)
- int c;
+ToLower (int c)
{
return isupper(c) ? tolower(c) : c;
}
int
-ToUpper(c)
- int c;
+ToUpper (int c)
{
return islower(c) ? toupper(c) : c;
}
#endif /* !_amigados */
char *
-StrSave(s)
- char *s;
+StrSave (char *s)
{
- char *ret;
+ char *ret;
- if ((ret = (char *) malloc(strlen(s) + 1))) {
- strcpy(ret, s);
+ if ((ret = (char *) malloc(strlen(s) + 1)))
+ {
+ safeStrCpy(ret, s, strlen(s)+1);
}
- return ret;
+ return ret;
}
char *
-StrSavePtr(s, savePtr)
- char *s, **savePtr;
+StrSavePtr (char *s, char **savePtr)
{
if (*savePtr) {
free(*savePtr);
}
if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
- strcpy(*savePtr, s);
+ safeStrCpy(*savePtr, s, strlen(s)+1);
}
return(*savePtr);
}
char *
-PGNDate()
+PGNDate ()
{
time_t clock;
struct tm *tm;
clock = time((time_t *)NULL);
tm = localtime(&clock);
- sprintf(buf, "%04d.%02d.%02d",
+ snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
return StrSave(buf);
}
char *
-PositionToFEN(move, overrideCastling)
- int move;
- char *overrideCastling;
+PositionToFEN (int move, char *overrideCastling)
{
int i, j, fromX, fromY, toX, toY;
int whiteToPlay;
- char buf[128];
+ char buf[MSG_SIZ];
char *p, *q;
int emptycount;
ChessSquare piece;
/* Piece placement data */
for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
+ if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
emptycount = 0;
for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
if (boards[move][i][j] == EmptySquare) {
/* [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) */
}
if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
- gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
+ gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
/* En passant target square */
if (move > backwardMostMove) {
fromX = moveList[move - 1][0] - AAA;
}
/* Fullmove number */
sprintf(p, "%d", (move / 2) + 1);
-
+
return StrSave(buf);
}
Boolean
-ParseFEN(board, blackPlaysFirst, fen)
- Board board;
- int *blackPlaysFirst;
- char *fen;
+ParseFEN (Board board, int *blackPlaysFirst, char *fen)
{
int i, j;
char *p, c;
while(*p==' ') p++;
if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
if(*p == '[') p++;
- if(*p == '-' ) *p++; /* empty holdings */ else {
+ 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++;
+ p++;
if((int) piece >= (int) BlackPawn ) {
i = (int)piece - (int)BlackPawn;
i = PieceToNumber((ChessSquare)i);
}
}
}
- if(*p == ']') *p++;
+ if(*p == ']') p++;
}
while(*p == ' ') p++;
case 'w':
*blackPlaysFirst = FALSE;
break;
- case 'b':
+ case 'b':
*blackPlaysFirst = TRUE;
break;
default:
/* read e.p. field in games that know e.p. capture */
if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
- gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
+ gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
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++;
+ if(*p >= '0' && *p <='9') p++;
board[EP_STATUS] = c;
}
}
return TRUE;
}
-
+
void
-EditPositionPasteFEN(char *fen)
+EditPositionPasteFEN (char *fen)
{
if (fen != NULL) {
Board initial_position;
static char cseq[12] = "\\ ";
-Boolean set_cont_sequence(char *new_seq)
+Boolean
+set_cont_sequence (char *new_seq)
{
int len;
Boolean ret;
len = strlen(new_seq);
ret = (len > 0) && (len < sizeof(cseq));
if (ret)
- strcpy(cseq, new_seq);
+ safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
else if (appData.debugMode)
- fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
+ fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
return ret;
}
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
+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;
}
// [HGM] vari: routines for shelving variations
+Boolean modeRestore = FALSE;
-void
-PushTail(int firstMove, int lastMove)
+void
+PushInner (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;
storedGames++;
forwardMostMove = firstMove; // truncate game so we can start variation
+}
+
+void
+PushTail (int firstMove, int lastMove)
+{
+ if(appData.icsActive) { // only in local mode
+ forwardMostMove = currentMove; // mimic old ICS behavior
+ return;
+ }
+ if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
+
+ PushInner(firstMove, lastMove);
if(storedGames == 1) GreyRevert(FALSE);
+ if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
}
-Boolean
-PopTail(Boolean annotate)
+void
+PopInner (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
- CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
-
- storedGames--;
- ToNrEvent(savedFirst[storedGames]); // sets currentMove
+ ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
+ storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
nrMoves = savedLast[storedGames] - currentMove;
if(annotate) {
int cnt = 10;
- if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
- else strcpy(buf, "(");
+ if(!WhiteOnMove(currentMove))
+ snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
+ else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
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]));
+ snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
+ else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
strcat(buf, moveBuf);
if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
}
gameInfo.resultDetails = savedDetails[storedGames];
forwardMostMove = currentMove + nrMoves;
- if(storedGames == 0) GreyRevert(TRUE);
+}
+
+Boolean
+PopTail (Boolean annotate)
+{
+ if(appData.icsActive) return FALSE; // only in local mode
+ if(!storedGames) return FALSE; // sanity
+ CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
+
+ PopInner(annotate);
+ if(currentMove < forwardMostMove) ForwardEvent(); else
+ HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
+
+ if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
return TRUE;
}
-void
-CleanupTail()
+void
+CleanupTail ()
{ // remove all shelved variations
int i;
for(i=0; i<storedGames; i++) {
}
void
-LoadVariation(int index, char *text)
+LoadVariation (int index, char *text)
{ // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
int level = 0, move;
- if(gameMode != EditGame && gameMode != AnalyzeMode) return;
+ if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
// first find outermost bracketing variation
while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
// kludge: use ParsePV() to append variation to game
move = currentMove;
- ParsePV(start, TRUE);
+ ParsePV(start, TRUE, TRUE);
forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
ClearPremoveHighlights();
CommentPopDown();