* Massachusetts.
*
* Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
- * 2007, 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
+ * 2007, 2008, 2009, 2010, 2011, 2012, 2013 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 "evalgraph.h"
#include "gettext.h"
#ifdef ENABLE_NLS
#endif
-/* A point in time */
-typedef struct {
- long sec; /* Assuming this is >= 32 bits */
- int ms; /* Assuming this is >= 16 bits */
-} TimeMark;
-
int establish P((void));
void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
char *buf, int count, int error));
void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
char *buf, int count, int error));
-void ics_printf P((char *format, ...));
void SendToICS P((char *s));
void SendToICSDelayed P((char *s, long msdelay));
void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
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,
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));
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 *WriteTourneyFile P((char *results, FILE *f));
+void DisplayTwoMachinesTitle P(());
+static void ExcludeClick P((int index));
+void ToggleSecond P((void));
+void PauseEngine P((ChessProgramState *cps));
#ifdef WIN32
extern void ConsoleCreate();
ChessSquare pieceSweep = EmptySquare;
ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
int promoDefaultAltered;
+int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
/* States for ics_getting_history */
#define H_FALSE 0
#define TN_PORT 23
char*
-safeStrCpy( char *dst, const char *src, size_t count )
+safeStrCpy (char *dst, const char *src, size_t count)
{ // [HGM] made safe
int i;
assert( dst != NULL );
* 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 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
/* end premove variables */
char *ics_prefix = "$";
-int ics_type = ICS_GENERIC;
+enum ICS_TYPE ics_type = ICS_GENERIC;
int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
int pauseExamForwardMostMove = 0;
char white_holding[64], black_holding[64];
TimeMark lastNodeCountTime;
long lastNodeCount=0;
-int shiftKey; // [HGM] set by mouse handler
+int shiftKey, controlKey; // [HGM] set by mouse handler
int have_sent_ICS_logon = 0;
int movesPerSession;
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;
+long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
+Boolean adjustedClock;
long timeControl_2; /* [AS] Allow separate time controls */
-char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [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, nextGame = 0, roundNr = 0;
-Boolean waitingForGame = FALSE;
+Boolean waitingForGame = FALSE, startingEngine = FALSE;
TimeMark programStartTime, pauseStart;
char ics_handle[MSG_SIZ];
int have_set_title = 0;
int initialRulePlies, FENrulePlies;
FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
int loadFlag = 0;
-int shuffleOpenings;
+Boolean shuffleOpenings;
int mute; // mute all sounds
// [HGM] vari: next 12 to save and restore variations
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,
#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
-CommonEngineInit()
+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";
}
void
-UnloadEngine(ChessProgramState *cps)
+UnloadEngine (ChessProgramState *cps)
{
/* Kill off first chess program */
if (cps->isr != NULL)
}
void
-ClearOptions(ChessProgramState *cps)
+ClearOptions (ChessProgramState *cps)
{
int i;
cps->nrOptions = cps->comboCnt = 0;
}
char *engineNames[] = {
-"first",
-"second"
+ /* 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)
+InitEngine (ChessProgramState *cps, int n)
{ // [HGM] all engine initialiation put in a function that does one engine
ClearOptions(cps);
cps->analysisSupport = 2; /* detect */
cps->analyzing = FALSE;
cps->initDone = FALSE;
+ cps->reload = FALSE;
/* New features added by Tord: */
cps->useFEN960 = FALSE;
len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
appData.protocolVersion[n]);
- if( (len > MSG_SIZ) && appData.debugMode )
+ if( (len >= MSG_SIZ) && appData.debugMode )
fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
DisplayFatalError(buf, 0, 2);
}
InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
+ ParseFeatures(appData.featureDefaults, cps);
}
ChessProgramState *savCps;
+GameMode oldMode;
+
void
-LoadEngine()
+LoadEngine ()
{
int i;
if(WaitForEngine(savCps, LoadEngine)) return;
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
+ oldMode = BeginningOfGame; // to prevent restoring old mode
}
InitChessProgram(savCps, FALSE);
- SendToProgram("force\n", savCps);
+ if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
DisplayMessage("", "");
if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
- for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
+ for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
ThawUI();
SetGNUMode();
+ if(oldMode == AnalyzeMode) AnalyzeModeEvent();
}
void
-ReplaceEngine(ChessProgramState *cps, int n)
+ReplaceEngine (ChessProgramState *cps, int n)
{
- EditGameEvent();
+ oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
+ keepInfo = 1;
+ if(oldMode != BeginningOfGame) EditGameEvent();
+ keepInfo = 0;
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[] =
+static char resetOptions[] =
"-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
- "-firstOptions \"\" -firstNPS -1 -fn \"\"";
+ "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
+ "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
+ "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
void
-Load(ChessProgramState *cps, int i)
+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[0]) { // an engine was selected from the combo box
+ 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.fenOverride[0] = NULL;
+ 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)
- appData.directory[i] = engineDir;
- else if(p != engineName) { // derive directory from engine path, when not given
+ 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;
- appData.directory[i] = strdup(engineName);
+ ASSIGN(appData.directory[i], engineName);
p[-1] = SLASH;
- } else appData.directory[i] = ".";
- if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
+ 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;
}
- appData.chessProgram[i] = strdup(p);
+ ASSIGN(appData.chessProgram[i], p);
appData.isUCI[i] = isUCI;
appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
appData.hasOwnBookUCI[i] = hasBook;
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],
+ quote, p, quote, appData.directory[i],
useNick ? " -fn \"" : "",
useNick ? nickName : "",
useNick ? "\"" : "",
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);
- snprintf(firstChessProgramNames, len, "%s%s", q, buf);
+ 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()
+InitTimeControls ()
{
int matched, min, sec;
/*
}
void
-InitBackEnd1()
+InitBackEnd1 ()
{
ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
GetTimeMark(&programStartTime);
srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
+ appData.seedBase = random() + (random()<<15);
pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
ClearProgramStats();
case VariantKriegspiel: /* need to hide pieces and move details */
/* 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 )
+ if( (len >= MSG_SIZ) && appData.debugMode )
fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
DisplayFatalError(buf, 0, 2);
case Variant36:
default:
len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
- if( (len > MSG_SIZ) && appData.debugMode )
+ if( (len >= MSG_SIZ) && appData.debugMode )
fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
DisplayFatalError(buf, 0, 2);
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;
}
}
-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, int *incType)
+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;
return result;
}
-int GetTimeQuota(int movenr, int lastUsed, char *tcString)
+int
+GetTimeQuota (int movenr, int lastUsed, char *tcString)
{ /* [HGM] get time to add from the multi-session time-control string */
int incType, moves=1; /* kludge to force reading of first session */
long time, increment;
char *s = tcString;
- if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
- if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
+ if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
do {
if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
nextSession = s; suddenDeath = moves == 0 && increment == 0;
- if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
if(movenr == -1) return time; /* last move before new session */
if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
if(incType == '!' && lastUsed < increment) increment = lastUsed;
}
int
-ParseTimeControl(tc, ti, mps)
- char *tc;
- float ti;
- int mps;
+ParseTimeControl (char *tc, float ti, int mps)
{
long tc1;
long tc2;
if(mps)
snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
- else
+ else
snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
} else {
if(mps)
snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
- else
+ else
snprintf(buf, MSG_SIZ, ":%s", mytc);
}
fullTimeControlString = StrSave(buf); // this should now be in PGN format
-
+
if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
return FALSE;
}
}
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)
+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
}
int
-LoadGameOrPosition(int gameNr)
+LoadGameOrPosition (int gameNr)
{ // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
if (*appData.loadGameFile != NULLCHAR) {
if (!LoadGameFromFile(appData.loadGameFile,
}
void
-ReserveGame(int gameNr, char resChar)
+ReserveGame (int gameNr, char resChar)
{
FILE *tf = fopen(appData.tourneyFile, "r+");
char *p, *q, c, buf[MSG_SIZ];
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)
+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
if(strchr(appData.results, '*') == NULL) {
FILE *f;
appData.tourneyCycles++;
- if(f = WriteTourneyFile(appData.results)) { // make a tourney file with increased number of cycles
+ if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
fclose(f);
NextTourneyGame(-1, &dummy);
ReserveGame(-1, 0);
}
matchMode = mode;
matchGame = roundNr = 1;
- first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches\r
+ 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))
{
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) {
len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
appData.icsHost, appData.icsPort);
- if( (len > MSG_SIZ) && appData.debugMode )
+ if( (len >= MSG_SIZ) && appData.debugMode )
fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
DisplayFatalError(buf, err, 1);
initialMode = Training;
} else {
len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
- if( (len > MSG_SIZ) && appData.debugMode )
+ if( (len >= MSG_SIZ) && appData.debugMode )
fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
DisplayFatalError(buf, 0, 2);
}
}
+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];
}
}
-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;
+ static FILE *ini;
/* Pass data read from player on to ICS */
if (count > 0) {
if (outCount < count) {
DisplayFatalError(_("Error writing to ICS"), outError, 1);
}
+ if(have_sent_ICS_logon == 2) {
+ if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
+ fprintf(ini, "%s", message);
+ have_sent_ICS_logon = 3;
+ } else
+ have_sent_ICS_logon = 1;
+ } else if(have_sent_ICS_logon == 3) {
+ fprintf(ini, "%s", message);
+ fclose(ini);
+ have_sent_ICS_logon = 1;
+ }
} else if (count < 0) {
RemoveInputSource(isr);
DisplayFatalError(_("Error reading from keyboard"), error, 1);
}
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) {
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;
break;
default:
len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
- if( (len > MSG_SIZ) && appData.debugMode )
+ if( (len >= MSG_SIZ) && appData.debugMode )
fprintf(debugFP, "StringToVariant: buffer truncated.\n");
DisplayError(buf, 0);
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;
}
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;
}
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;
board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
board[i][j];
}
+ board[HOLDINGS_SET] = 0;
gameInfo.boardWidth = newWidth;
gameInfo.boardHeight = newHeight;
gameInfo.holdingsWidth = newHoldingsWidth;
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
if(r < minRating+100 && r >=0 ) r = minRating+100;
if(r > maxRating) r = maxRating;
- if(tc < 1.) tc = 1.;
- if(tc > 95.) tc = 95.;
+ if(tc < 1.f) tc = 1.f;
+ if(tc > 95.f) tc = 95.f;
x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
y = ((double)r - minRating)/(maxRating - minRating)
* (h-vMargin-squareSize/8-1) + vMargin;
}
void
-AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
+PlotSingleSeekAd (int i)
+{
+ PlotSeekAd(i);
+}
+
+void
+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);
seekNrList[nrOfSeekAds] = nr;
zList[nrOfSeekAds] = 0;
seekAdList[nrOfSeekAds++] = StrSave(buf);
- if(plot) PlotSeekAd(nrOfSeekAds-1);
+ if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
}
}
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;
for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
yy = h-1-yy;
- DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
+ DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
if(i%500 == 0) {
char buf[MSG_SIZ];
snprintf(buf, MSG_SIZ, "%d", i);
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;
}
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
OutputKibitz(suppressKibitz, parse);
} else {
char tmp[MSG_SIZ];
+ 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));
}
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: ")) &&
+ 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;
}
} // [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:") ||
continue;
}
- if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
- ICSInitScript();
- have_sent_ICS_logon = 1;
+ if (looking_at(buf, &i, "login:")) {
+ if (!have_sent_ICS_logon) {
+ if(ICSInitScript())
+ have_sent_ICS_logon = 1;
+ else // no init script was found
+ have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
+ } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
+ have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
+ }
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();
flipView = appData.flipView;
DrawPosition(TRUE, boards[currentMove]);
DisplayBothClocks();
- snprintf(str, MSG_SIZ, "%s vs. %s",
- gameInfo.white, gameInfo.black);
+ snprintf(str, MSG_SIZ, "%s %s %s",
+ gameInfo.white, _("vs."), gameInfo.black);
DisplayTitle(str);
gameMode = IcsIdle;
} else {
strncmp(why, "Continuing ", 11) == 0) {
gs_gamenum = gamenum;
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);
char wh[16], bh[16];
PackHolding(wh, white_holding);
PackHolding(bh, black_holding);
- snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
+ snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
gameInfo.white, gameInfo.black);
} else {
- snprintf(str, MSG_SIZ, "%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...
#define RELATION_STARTING_POSITION -4 /* FICS only */
void
-ParseBoard12(string)
- char *string;
+ParseBoard12 (char *string)
{
+#if ZIPPY
+ int i, takeback;
+ char *bookHit = NULL; // [HGM] book
+#endif
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 gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
+ int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
char to_play, board_chars[200];
char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
int fromX, fromY, toX, toY;
char promoChar;
int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
- char *bookHit = NULL; // [HGM] book
Boolean weird = FALSE, reqFlag = FALSE;
fromX = fromY = toX = toY = -1;
newGameMode =
((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
IcsPlayingWhite : IcsPlayingBlack;
+ soughtPending =FALSE; // [HGM] seekgraph: solve race condition
break;
case RELATION_EXAMINING:
newGameMode = IcsExamining;
break;
}
- if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
+ if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
+ gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
&& 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;
+ static int lastBgGame = -1;
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
- snprintf(partnerStatus, MSG_SIZ,"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;
+ if(gamenum != lastBgGame) {
+ char buf[MSG_SIZ];
+ snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
+ DisplayTitle(buf);
+ }
+ lastBgGame = gamenum;
+ 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;
}
+ if(appData.dualBoard && appData.bgObserve) {
+ if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
+ SendToICS(ics_prefix), SendToICS("pobserve\n");
+ else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
+ char buf[MSG_SIZ];
+ snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
+ SendToICS(buf);
+ }
+ }
+
/* Modify behavior for initial board display on move listing
of wild games.
*/
}
if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
+ move_str[1] == '@' && !gameInfo.holdingsWidth ||
weird && (int)gameInfo.variant < (int)VariantShogi) {
/* [HGM] We seem to have switched variant unexpectedly
* Try to guess new variant from board size
if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
if(ranks == 8 && files == 12) newVariant = VariantCourier; else
if(ranks == 9 && files == 9) newVariant = VariantShogi; else
- if(!weird) newVariant = VariantNormal;
+ if(ranks == 10 && files == 10) newVariant = VariantGrand; else
+ if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
VariantSwitch(boards[currentMove], newVariant); /* temp guess */
/* Get a move list just to see the header, which
will tell us whether this is really bug or zh */
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 ||
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) // inherit info that ICS does not give from previous board
+ 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
basetime, increment, (int) gameInfo.variant);
} else {
if(gameInfo.variant == VariantNormal)
- snprintf(str, MSG_SIZ, "%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
- snprintf(str, MSG_SIZ, "%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);
}
void
-GetMoveListEvent()
+GetMoveListEvent ()
{
char buf[MSG_SIZ];
if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
}
void
-AnalysisPeriodicEvent(force)
- int force;
+SendToBoth (char *msg)
+{ // to make it easy to keep two engines in step in dual analysis
+ SendToProgram(msg, &first);
+ if(second.analyzing) SendToProgram(msg, &second);
+}
+
+void
+AnalysisPeriodicEvent (int force)
{
if (((programStats.ok_to_send == 0 || programStats.line_is_book)
&& !force) || !appData.periodicUpdates)
return;
/* Send . command to Crafty to collect stats */
- SendToProgram(".\n", &first);
+ SendToBoth(".\n");
/* Don't send another until we get a response (this makes
us stop sending to old Crafty's which don't understand
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);
}
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, promoChar)
- ChessMove moveType;
- int fromX, fromY, toX, toY;
- char promoChar;
+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:
case WhiteHSideCastleFR:
case BlackHSideCastleFR:
/* POP Fabien */
- snprintf(user_move, MSG_SIZ, "o-o\n");
+ snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
break;
case WhiteQueenSideCastle:
case BlackQueenSideCastle:
case WhiteASideCastleFR:
case BlackASideCastleFR:
/* POP Fabien */
- snprintf(user_move, MSG_SIZ, "o-o-o\n");
+ snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
break;
case WhiteNonPromotion:
case BlackNonPromotion:
}
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;
for(i = backwardMostMove; i<last; i++) {
char buf[20];
snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
+ if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
+ int len = strlen(moveList[i]);
+ snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
+ if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
+ }
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) {
+ if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
sprintf(move, "%c@%c%c\n",
ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
} else {
}
void
-ProcessICSInitScript(f)
- FILE *f;
+ProcessICSInitScript (FILE *f)
{
char buf[MSG_SIZ];
static int lastX, lastY, selectFlag, dragging;
void
-Sweep(int step)
+Sweep (int step)
{
ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
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;
+ 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));
+ if(toX >= 0) {
+ int victim = boards[currentMove][toY][toX];
+ boards[currentMove][toY][toX] = promoSweep;
+ DrawPosition(FALSE, boards[currentMove]);
+ boards[currentMove][toY][toX] = victim;
+ } else
ChangeDragPiece(promoSweep);
}
-int PromoScroll(int x, int y)
+int
+PromoScroll (int x, int y)
{
int step = 0;
if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
- if(abs(x - lastX) < 15 && abs(y - lastY) < 15) 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;
}
void
-NextPiece(int step)
+NextPiece (int step)
{
ChessSquare piece = boards[currentMove][toY][toX];
do {
}
/* [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;
/* 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;
+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);
}
Boolean pushed = FALSE;
+char *lastParseAttempt;
void
-ParsePV(char *pv, Boolean storeComments, Boolean atEnd)
+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) {
+ lastParseAttempt = pv; if(!*pv) return; // turns out we crash when we parse an empty PV
+ if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
pushed = TRUE;
}
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)){
nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
fromY, fromX, toY, toX, promoChar,
parseList[endPV - 1]);
} while(valid);
+ 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,
}
int
-MultiPV(ChessProgramState *cps)
+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++)
return -1;
}
+Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
+
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 pane)
{
int startPV, multi, lineStart, origIndex = index;
char *p, buf2[MSG_SIZ];
+ ChessProgramState *cps = (pane ? &second : &first);
if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
lastX = x; lastY = y;
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;
- if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
- int n = first.option[multi].value;
+ if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
+ int n = cps->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;
+ if(cps->option[multi].value != n) SendToProgram(buf2, cps);
+ cps->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;
+ extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
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); // shelve PV of PV-walk
+ 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(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
+ 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, TRUE); // load the PV of the thinking engine in the boards array.
+ extendGame = FALSE;
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) {
+ if(extendGame && currentMove > forwardMostMove) {
Boolean saveAnimate = appData.animate;
if(pushed) {
if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
}
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);
// we must somehow check if right button is still down (might be released off board!)
if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
- if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
+ 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;
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;
}
-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 */
{
return result;
}
-void Prelude(Board board)
+void
+Prelude (Board board)
{ // [HGM] superchess: random selection of exo-pieces
int i, j, k; ChessSquare p;
static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
}
void
-InitPosition(redraw)
- int redraw;
+InitPosition (int redraw)
{
ChessSquare (* pieces)[BOARD_FILES];
int i, j, pawnRow, overrule,
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:
case VariantSChess:
SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
gameInfo.holdingsSize = 7;
+ for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
break;
case VariantJanus:
pieces = JanusArray;
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] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
if(gameInfo.variant == VariantXiangqi) {
}
}
}
- 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 ) {
initialPosition[2][0] = BlackAngel;
initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
- initialPosition[1][1] = initialPosition[2][1] =
+ initialPosition[1][1] = initialPosition[2][1] =
initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
}
if (appData.debugMode) {
}
void
-SendBoard(cps, moveNum)
- ChessProgramState *cps;
- int moveNum;
+SendBoard (ChessProgramState *cps, int moveNum)
{
char message[MSG_SIZ];
} 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)) // [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) {
- snprintf(message, MSG_SIZ, "%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] == '~') {
snprintf(message, MSG_SIZ,"%c%c%c+\n",
PieceToChar((ChessSquare)(DEMOTED *bp)),
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)) {
- snprintf(message,MSG_SIZ, "%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] == '~') {
snprintf(message, MSG_SIZ,"%c%c%c+\n",
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, 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((signed char)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);
+ SendToBoth(buf);
+ 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
+ SendToBoth("include all\n"); // and inform engine
+ } else if(index > 18) { // tail
+ if(exclusionHeader[19] == '-') { // tail was excluded
+ SendToBoth("include all\n");
+ 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
+ SendToBoth("exclude all\n");
+ 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)
+DefaultPromoChoice (int white)
{
ChessSquare result;
if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
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 */
if(gameInfo.variant == VariantShogi) {
promotionZoneSize = BOARD_HEIGHT/3;
highestPromotingPiece = (int)WhiteFerz;
- } else if(gameInfo.variant == VariantMakruk) {
+ } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
promotionZoneSize = 3;
}
}
// give caller the default choice even if we will not make it
*promoChoice = ToLower(PieceToChar(defaultPromoChoice));
- if(gameInfo.variant == VariantShogi) *promoChoice = '+';
- if(appData.sweepSelect && gameInfo.variant != VariantGreat
- && gameInfo.variant != VariantShogi
+ 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
}
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"));
}
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:
int lastLoadGameUseList = FALSE;
char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
ChessMove lastLoadGameStart = EndOfFile;
+int doubleClick;
void
-UserMoveEvent(fromX, fromY, toX, toY, promoChar)
- int fromX, fromY, toX, toY;
- int promoChar;
+UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
{
ChessMove moveType;
- ChessSquare pdown, pup;
+ ChessSquare 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:
}
break;
+ case PlayFromGameFile:
+ if(!shiftKey ||!appData.variations) return; // [HGM] only variations
case EditGame:
case IcsExamining:
case BeginningOfGame:
if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
}
} else
- boards[0][fromY][fromX] = EmptySquare;
+ boards[0][fromY][fromX] = gatingPiece;
DrawPosition(FALSE, boards[currentMove]);
return;
}
}
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. 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",
+ 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++;
+ 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) {
}
}
+ if(doubleClick && gameMode == AnalyzeMode) { // [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);
}
/* 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;
/* 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) {
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();
gameMode = MachinePlaysBlack;
StartClocks();
SetGameInfo();
- snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
+ snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
DisplayTitle(buf);
if (first.sendName) {
snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
gameMode == MachinePlaysBlack)) {
SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
}
- if (gameMode != EditGame && gameMode != PlayFromGameFile) {
+ if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
// [HGM] book: if program might be playing, let it use book
bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
first.maybeThinking = TRUE;
- } else SendMoveToProgram(forwardMostMove-1, &first);
+ } 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);
+ if(second.analyzing) {
+ if(!second.useSetboard) SendToProgram("undo\n", &second);
+ SendBoard(&second, currentMove+1);
+ }
+ } else {
+ SendMoveToProgram(forwardMostMove-1, &first);
+ if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
+ }
if (currentMove == cmailOldMove + 1) {
cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
}
}
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)
for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
}
}
- DrawPosition(TRUE, NULL);
+ DrawPosition(FALSE, NULL);
}
int
-Explode(Board board, int fromX, int fromY, int toX, int toY)
+Explode (Board board, int fromX, int fromY, int toX, int toY)
{
if(gameInfo.variant == VariantAtomic &&
(board[toY][toX] != EmptySquare || // capture?
ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
-int CanPromote(ChessSquare piece, int y)
+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
piece == WhiteLance && y == BOARD_HEIGHT-2 );
}
-void LeftClick(ClickType clickType, int xPix, int yPix)
+void
+LeftClick (ClickType clickType, int xPix, int yPix)
{
int x, y;
Boolean saveAnimate;
- static int second = 0, promotionChoice = 0, clearFlag = 0;
+ static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 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);
defaultPromoChoice = promoSweep;
promoSweep = EmptySquare; // terminate sweep
promoDefaultAltered = TRUE;
- if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
+ if(!selectFlag && !sweepSelecting && (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(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) ) {
+ ? 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(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
+ 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
}
return;
}
- fromX = x; fromY = y;
+ doubleClick = FALSE;
+ if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
+ doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
+ }
+ 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) {
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;
}
if (appData.highlightDragging) {
SetHighlights(fromX, fromY, -1, -1);
+ } else {
+ ClearHighlights();
}
} else fromX = fromY = -1;
return;
!(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);
(fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
gatingPiece = boards[currentMove][fromY][fromX];
- else gatingPiece = EmptySquare;
+ 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;
}
}
if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
- second = FALSE;
+ second = FALSE;
}
// ignore clicks on holdings
if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
/* Undo animation damage if any */
DrawPosition(FALSE, NULL);
}
- if (second) {
+ if (second || sweepSelecting) {
/* Second up/down in same square; just abort move */
- second = 0;
+ if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
+ second = sweepSelecting = 0;
fromX = fromY = -1;
gatingPiece = EmptySquare;
ClearHighlights();
/* we now have a different from- and (possibly off-board) to-square */
/* Completed move */
- toX = x;
- toY = y;
+ if(!sweepSelecting) {
+ toX = x;
+ toY = y;
+ } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
+
saveAnimate = appData.animate;
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); dragging = 1; // consider this a new attempt to drag
+ 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(HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
+ if(appData.sweepSelect) {
+ ChessSquare piece = boards[currentMove][fromY][fromX];
+ 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
+ sweepSelecting = 1;
+ DisplayMessage("", _("Pull pawn backwards to under-promote"));
+ MarkTargetSquares(1);
+ }
+ return; // promo popup appears on up-click
+ }
/* Finish clickclick move */
if (appData.animate || appData.highlightLastMove) {
SetHighlights(fromX, fromY, toX, toY);
ClearHighlights();
}
} else {
+#if 0
+// [HGM] this must be done after the move is made, as with arrow it could lead to a board redraw with piece still on from square
/* Finish drag move */
if (appData.highlightLastMove) {
SetHighlights(fromX, fromY, toX, toY);
} else {
ClearHighlights();
}
+#endif
DragPieceEnd(xPix, yPix); dragging = 0;
/* Don't animate move and drag both */
appData.animate = FALSE;
}
ClearHighlights();
fromX = fromY = -1;
+ MarkTargetSquares(1);
DrawPosition(TRUE, boards[currentMove]);
return;
}
// off-board moves should not be highlighted
if(x < 0 || y < 0) ClearHighlights();
- if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
+ if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
- if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
+ if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
SetHighlights(fromX, fromY, toX, toY);
- if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
+ MarkTargetSquares(1);
+ 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;
if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
Explode(boards[currentMove-1], fromX, fromY, toX, toY))
DrawPosition(TRUE, boards[currentMove]);
+ MarkTargetSquares(1);
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;
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 == 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;\r
+ 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;
}
void
-ClearEngineOutputPane(int which)
+ClearEngineOutputPane (int which)
{
static FrontEndProgramStats dummyStats;
dummyStats.which = which;
#define MAXPLAYERS 500
char *
-TourneyStandings(int display)
+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
games[b]++;
nr++;
}
- if(appData.tourneyType < 0) return strdup("Swiss tourney finished"); // standings of Swiss yet TODO
if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
for(w=0; w<nPlayers; w++) {
bScore = -1;
}
void
-Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
+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];
}
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;
|| majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
// TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
+ } else if(v == VariantKnightmate) {
+ if(nMine == 1) return FALSE;
+ if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
} else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
}
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).
// Actually ending the game is now based on the additional internal condition canAdjudicate.
// Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
- int k, count = 0; static int bare = 1;
+ int k, drop, count = 0; static int bare = 1;
ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
Boolean canAdjudicate = !appData.icsActive;
boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
EP_CHECKMATE : EP_WINS);
- else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
+ else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
}
break;
case MT_CHECKMATE:
reason = "Xboard adjudication: Checkmate";
boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
+ if(gameInfo.variant == VariantShogi) {
+ if(forwardMostMove > backwardMostMove
+ && moveList[forwardMostMove-1][1] == '@'
+ && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
+ reason = "XBoard adjudication: pawn-drop mate";
+ boards[forwardMostMove][EP_STATUS] = EP_WINS;
+ }
+ }
break;
}
}
} 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
/* Check for rep-draws */
count = 0;
+ drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
+ && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
for(k = forwardMostMove-2;
- k>=backwardMostMove && k>=forwardMostMove-100 &&
+ k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
(signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
- (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
+ (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
k-=2)
{ int rights=0;
if(CompareBoards(boards[k], boards[forwardMostMove])) {
/* 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 || gameInfo.variant == VariantShogi) && appData.testLegality) {
// [HGM] xiangqi: check for forbidden perpetuals
int m, ourPerpetual = 1, hisPerpetual = 1;
for(m=forwardMostMove; m>k; m-=2) {
if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
break; // (or we would have caught him before). Abort repetition-checking loop.
} else
+ if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
+ if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
+ result = BlackWins;
+ details = "Xboard adjudication: repetition";
+ }
+ } else // it must be XQ
// Now check for perpetual chases
if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
hisPerpetual = PerpetualChase(k, forwardMostMove);
ourPerpetual = PerpetualChase(k+1, forwardMostMove);
if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
+ 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.
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;
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);
}
static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
+static ChessProgramState *stalledEngine;
+static char stashedInputMove[MSG_SIZ];
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;
if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
return; // Skim the pairing messages here.
}
- cps->userError = 0;
+ oldError = cps->userError; cps->userError = 0;
FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
/*
if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
(sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
{
+ if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
+ if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
+ safeStrCpy(stashedInputMove, message, MSG_SIZ);
+ stalledEngine = cps;
+ if(appData.ponderNextMove) { // bring opponent out of ponder
+ if(gameMode == TwoMachinesPlay) {
+ if(cps->other->pause)
+ PauseEngine(cps->other);
+ else
+ SendToProgram("easy\n", cps->other);
+ }
+ }
+ StopClocks();
+ return;
+ }
+
/* This method is only useful on engines that support ping */
if (cps->lastPing != cps->lastPong) {
if (gameMode == BeginningOfGame) {
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. */
snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
machineMove, _(cps->which));
- DisplayError(buf1, 0);
+ DisplayMoveError(buf1);
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) {
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) {
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);
return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
}
- if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
+ 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 || startedFromSetupPosition) return;
+ 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;
if (!strncmp(message, "telluser ", 9)) {
if(message[9] == '\\' && message[10] == '\\')
EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
+ PlayTellSound();
DisplayNote(message + 9);
return;
}
cps->userError = 1;
if(message[14] == '\\' && message[15] == '\\')
EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
+ PlayTellSound();
DisplayError(message + 14, 0);
return;
}
snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
SendToICS(buf1);
}
- }
+ } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
return;
}
if (!strncmp(message, "tellall ", 8)) {
if (StrStr(message, "analyze")) {
cps->analysisSupport = FALSE;
cps->analyzing = FALSE;
- Reset(FALSE, TRUE);
+// 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;
parseList[currentMove], _(cps->which));
DisplayMoveError(buf1);
DrawPosition(FALSE, boards[currentMove]);
+
+ SetUserThinkingEnables();
return;
}
if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
/*
* 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->which), cps->program, cps->host, message);
RemoveInputSource(cps->isr);
if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
- if(cps == &first) appData.noChessProgram = TRUE;
- DisplayError(buf1, 0);
+ 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;
}
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;
}
/* 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)/sizeof(tempStats.movelist[0]) );
+ safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
} else {
sprintf(tempStats.movelist, " no PV\n");
}
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
The display is not updated in any way.
*/
void
-ParseGameHistory(game)
- char *game;
+ParseGameHistory (char *game)
{
ChessMove moveType;
int fromX, fromY, toX, toY, boardIndex;
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]));
/* 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 */
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
) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
}
+ if(gameInfo.variant == VariantSChess) { // update virginity
+ if(fromY == 0) board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
+ if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
+ if(toY == 0) board[VIRGIN][toX] &= ~VIRGIN_W; // loss by capture
+ if(toY == BOARD_HEIGHT-1) board[VIRGIN][toX] &= ~VIRGIN_B;
+ }
+
if (fromX == toX && fromY == toY) return;
piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
board[fromY][BOARD_LEFT] = EmptySquare;
} else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
- && toY >= BOARD_HEIGHT-promoRank
+ && 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 */
+ if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
board[fromY][fromX] = EmptySquare;
- } else if ((fromY == BOARD_HEIGHT-4)
+ } else if ((fromY >= BOARD_HEIGHT>>1)
+ && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
&& (toX != fromX)
&& gameInfo.variant != VariantXiangqi
&& gameInfo.variant != VariantBerolina
board[toY][2] = BlackRook;
} else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
- && toY < promoRank
+ && 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 */
+ if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
board[fromY][fromX] = EmptySquare;
- } else if ((fromY == 3)
+ } else if ((fromY < BOARD_HEIGHT>>1)
+ && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
&& (toX != fromX)
&& gameInfo.variant != VariantXiangqi
&& gameInfo.variant != VariantBerolina
if (captured != EmptySquare && gameInfo.holdingsSize > 0
&& 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[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 orinary Pawn promotion) */
+ /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
board[toY][toX] = (ChessSquare) (PROMOTED piece);
} else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
- board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(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)
- && promoChar != NULLCHAR && gameInfo.holdingsSize) {
+ 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;
&& fromX != toX && fromY != toY)
fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
// promotion suffix
- if(promoChar != NULLCHAR)
- fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
+ if(promoChar != NULLCHAR) {
+ if(fromY == 0 || fromY == BOARD_HEIGHT-1)
+ fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
+ ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
+ else 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; else
+ 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;
AnimateMove(boards[forwardMostMove - 1],
fromX, fromY, toX, toY);
}
- if (appData.highlightLastMove) {
- SetHighlights(fromX, fromY, toX, toY);
- }
}
currentMove = forwardMostMove;
}
if (instant) return;
DisplayMove(currentMove - 1);
+ if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
+ if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
+ SetHighlights(fromX, fromY, toX, toY);
+ }
+ }
DrawPosition(FALSE, boards[currentMove]);
DisplayBothClocks();
HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
- DisplayBook(currentMove);
}
-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;
}
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 */
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) {
snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
void
-StartChessProgram(cps)
- ChessProgramState *cps;
+ResendOptions (ChessProgramState *cps)
+{ // send the stored value of the options
+ int i;
+ char buf[MSG_SIZ];
+ Option *opt = cps->option;
+ for(i=0; i<cps->nrOptions; i++, opt++) {
+ switch(opt->type) {
+ case Spin:
+ case Slider:
+ case CheckBox:
+ snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
+ break;
+ case ComboBox:
+ snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
+ break;
+ default:
+ snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
+ break;
+ case Button:
+ case SaveButton:
+ continue;
+ }
+ SendToProgram(buf, cps);
+ }
+}
+
+void
+StartChessProgram (ChessProgramState *cps)
{
char buf[MSG_SIZ];
int err;
cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
if (cps->protocolVersion > 1) {
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
+ if(!cps->reload) { // do not clear options when reloading because of -xreuse
+ cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
+ cps->comboCnt = 0; // and values of combo boxes
+ }
SendToProgram(buf, cps);
+ if(cps->reload) ResendOptions(cps);
} else {
SendToProgram("xboard\n", cps);
}
return;
}
DisplayMessage("", ""); curMess = 0;
- ThawUI();
TwoMachinesEvent();
}
char *
-MakeName(char *template)
+MakeName (char *template)
{
time_t clock;
struct tm *tm;
}
int
-CountPlayers(char *p)
+CountPlayers (char *p)
{
int n = 0;
while(p = strchr(p, '\n')) p++, n++; // count participants
}
FILE *
-WriteTourneyFile(char *results)
+WriteTourneyFile (char *results, FILE *f)
{ // write tournament parameters on tourneyFile; on success return the stream pointer for closing
- FILE *f = fopen(appData.tourneyFile, "w");
+ 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, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
+ fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
+ fprintf(f, "-polyglotBook %s\n", appData.polyglotBook);
+ fprintf(f, "-bookDepth %d\n", appData.bookDepth);
+ fprintf(f, "-bookVariation %d\n", appData.bookStrength);
+ fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
+ fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
+ fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
+ fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
+ fprintf(f, "-smpCores %d\n", appData.smpCores);
if(searchTime > 0)
- fprintf(f, "-searchTime \"%s\"\n", appData.searchTime);
+ 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);
return f;
}
+char *command[MAXENGINES], *mnemonic[MAXENGINES];
+
+void
+Substitute (char *participants, int expunge)
+{
+ 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)
+CheckPlayers (char *participants)
+{
+ int i;
+ char buf[MSG_SIZ], *p;
+ NamesToList(firstChessProgramNames, command, mnemonic, "all");
+ while(p = strchr(participants, '\n')) {
+ *p = NULLCHAR;
+ for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
+ if(!mnemonic[i]) {
+ snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
+ *p = '\n';
+ DisplayError(buf, 0);
+ return 1;
+ }
+ *p = '\n';
+ participants = p + 1;
+ }
+ return 0;
+}
+
+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);
DisplayError(_("Not enough participants"), 0);
return 0;
}
+ if(CheckPlayers(appData.participants)) return 0;
ASSIGN(appData.tourneyFile, name);
- if((f = WriteTourneyFile("")) == NULL) return 0;
+ if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
+ if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
}
fclose(f);
appData.noChessProgram = FALSE;
return 1;
}
-#define MAXENGINES 1000
-char *command[MAXENGINES], *mnemonic[MAXENGINES];
-
-void NamesToList(char *names, char **engineList, char **engineMnemonic)
+int
+NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
{
char buf[MSG_SIZ], *p, *q;
- int i=1;
- while(*names) {
- p = names; q = buf;
+ 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(*p == '\n') p++;
- TidyProgramName(engineList[i], "localhost", 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, " (");
strcat(buf, ")");
}
engineMnemonic[i] = strdup(buf);
- names = p; i++;
- if(i > MAXENGINES - 2) break;
+ i++;
}
- engineList[i] = NULL;
+ engineList[i] = engineMnemonic[i] = NULL;
+ return i;
}
// 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)
+void
+SwapEngines (int n)
{ // swap settings for first engine and other engine (so far only some selected options)
int h;
char *p;
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)
}
-void
-SetPlayer(int player)
+int
+GetEngineLine (char *s, int n)
+{
+ int i;
+ char buf[MSG_SIZ];
+ extern char *icsNames;
+ if(!s || !*s) return 0;
+ NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
+ for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
+ if(!mnemonic[i]) return 0;
+ if(n == 11) return 1; // just testing if there was a match
+ snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
+ if(n == 1) SwapEngines(n);
+ ParseArgsFromString(buf);
+ if(n == 1) SwapEngines(n);
+ if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
+ SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
+ ParseArgsFromString(buf);
+ }
+ return 1;
+}
+
+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, *p = appData.participants;
+ 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;
+ ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
+ appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
ParseArgsFromString(buf);
+ } else { // no engine with this nickname is installed!
+ snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
+ ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
+ matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
+ ModeHighlight();
+ DisplayError(buf, 0);
+ return 0;
}
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)
+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;
*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.
+ // 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)
+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;
+ int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
FILE *tf;
if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
tf = fopen(appData.tourneyFile, "r");
SendToProgram(buf, &pairing);
return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
}
- pairingReceived = 0; // ... so we continue here
+ 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) return 1; // engines already loaded
+ if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
// redefine engines, engine dir, etc.
- NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
- SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
- SwapEngines(1);
- SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
- SwapEngines(1); // and make that valid for second engine by swapping
- InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
- InitEngine(&second, 1);
+ NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
+ if(first.pr == NoProc) {
+ if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // 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);
+ if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // 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
- return 1;
+ UpdateLogos(FALSE); // leave display to ModeHiglight()
+ return OK;
}
void
-NextMatchGame()
+NextMatchGame ()
{ // performs game initialization that does not invoke engines, and then tries to start the game
- int firstWhite, swapColors = 0;
+ 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);
- appData.noChessProgram = FALSE;
- if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
+ 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 )
+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;
fromX = fromY = -1; // [HGM] abort any move the user is entering.
+ if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
+
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
// 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) {
resultDetails = buf;
}
/* (Claiming a loss is accepted no questions asked!) */
+ } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
+ forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
+ result = GameUnfinished;
+ if(!*appData.tourneyFile) matchGame--; // replay even in plain match
}
/* [HGM] bare: don't allow bare King to win */
- if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
+ 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)
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;
&& lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
) {
if (*appData.saveGameFile != NULLCHAR) {
+ if(result == GameUnfinished && matchMode && *appData.tourneyFile)
+ AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
+ else
SaveGameToFile(appData.saveGameFile, TRUE);
} else if (appData.autoSaveGames) {
- AutoSaveGame();
+ if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
}
if (*appData.savePositionFile != NULLCHAR) {
SavePositionToFile(appData.savePositionFile);
}
+ AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
}
}
SendToProgram("quit\n", &first);
DoSleep( appData.delayAfterQuit );
DestroyChildProcess(first.pr, first.useSigterm);
+ first.reload = TRUE;
}
first.pr = NoProc;
}
SendToProgram("quit\n", &second);
DoSleep( appData.delayAfterQuit );
DestroyChildProcess(second.pr, second.useSigterm);
+ second.reload = TRUE;
}
second.pr = NoProc;
}
- if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
+ if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
char resChar = '=';
switch (result) {
case WhiteWins:
break;
}
- if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
+ if(exiting) 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
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";
} 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);
/* 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;
int
-ResurrectChessProgram()
+ResurrectChessProgram ()
{
/* The chess program may have exited.
If so, restart it and feed it all the moves made so far. */
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(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, because we started engine
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 {
* 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;
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) {
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())
continue;
if (appData.timeDelay < 0)
return;
- StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
+ StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
break;
}
}
+void
+AnalyzeNextGame()
+{
+ ReloadGame(1); // next game
+}
int
-AutoPlayOneMove()
+AutoPlayOneMove ()
{
int fromX, fromY, toX, toY;
}
if (currentMove >= forwardMostMove) {
- if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
- gameMode = EditGame;
- ModeHighlight();
+ if(gameMode == AnalyzeFile) {
+ if(appData.loadGameIndex == -1) {
+ GameEnds(EndOfFile, NULL, GE_FILE);
+ ScheduleDelayedEvent(AnalyzeNextGame, 10);
+ } else {
+ ExitAnalyzeMode(); SendToProgram("force\n", &first);
+ }
+ }
+// gameMode = EndOfGame;
+// ModeHighlight();
/* [AS] Clear current move marker at the end of a game */
/* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
int
-LoadGameOneMove(readAhead)
- ChessMove readAhead;
+LoadGameOneMove (ChessMove readAhead)
{
int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
char promoChar = NULLCHAR;
/* 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;
/* 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);
+ 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)
+ 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++;
+ } 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;
+static int creatingBook;
+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 (lg) {
fseek(f, lg->offset, 0);
GameListHighlight(gameNumber);
+ pos = lg->position;
gn = 1;
}
else {
+ if(gameMode == AnalyzeFile && appData.loadGameIndex == -1)
+ appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
+ else
DisplayError(_("Game number out of range"), 0);
return FALSE;
}
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) {
cm = (ChessMove) Myylex();
}
+ if(!creatingBook) {
if (first.pr == NoProc) {
StartChessProgram(&first);
}
}
DisplayBothClocks();
}
+ }
/* [HGM] server: flag to write setup moves in broadcast file as one */
loadFlag = appData.suppressLoadMoves;
if (oldGameMode == AnalyzeFile ||
oldGameMode == AnalyzeMode) {
+ appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
AnalyzeFileEvent();
}
+ if(creatingBook) return TRUE;
+ 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();
}
/* 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;
lastLoadPositionFP = f;
lastLoadPositionNumber = positionNumber;
safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
- if (first.pr == NoProc) {
+ if (first.pr == NoProc && !appData.noChessProgram) {
StartChessProgram(&first);
InitChessProgram(&first, FALSE);
}
}
startedFromSetupPosition = TRUE;
- SendToProgram("force\n", &first);
CopyBoard(boards[0], initial_position);
if (blackPlaysFirst) {
currentMove = forwardMostMove = backwardMostMove = 1;
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");}
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;
/* 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;
+ 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);
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...
+ 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;
#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;
/* 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 *movetext;
char numtext[32];
int movelen, numlen, blank;
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);
/* Save game in old style and close the file */
int
-SaveGameOldStyle(f)
- FILE *f;
+SaveGameOldStyle (FILE *f)
{
int i, offset;
time_t tm;
/* 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];
/* 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;
}
void
-ReloadCmailMsgEvent(unregister)
- int unregister;
+ReloadCmailMsgEvent (int unregister)
{
#if !WIN32
static char *inFilename = NULL;
}
int
-RegisterMove()
+RegisterMove ()
{
FILE *f;
char string[MSG_SIZ];
}
void
-MailMoveEvent()
+MailMoveEvent ()
{
#if !WIN32
static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
}
char *
-CmailMsg()
+CmailMsg ()
{
#if WIN32
return NULL;
}
void
-ResetGameEvent()
+ResetGameEvent ()
{
if (gameMode == Training)
SetTrainingModeOff();
}
void
-ExitEvent(status)
- int status;
+ExitEvent (int status)
{
exiting++;
if (exiting > 2) {
}
void
-PauseEvent()
+PauseEngine (ChessProgramState *cps)
+{
+ SendToProgram("pause\n", cps);
+ cps->pause = 2;
+}
+
+void
+UnPauseEngine (ChessProgramState *cps)
+{
+ SendToProgram("resume\n", cps);
+ cps->pause = 1;
+}
+
+void
+PauseEvent ()
{
if (appData.debugMode)
fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
if (pausing) {
pausing = FALSE;
ModeHighlight();
+ if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
+ StartClocks();
+ if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
+ if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
+ else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
+ }
+ if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
+ HandleMachineMove(stashedInputMove, stalledEngine);
+ stalledEngine = NULL;
+ return;
+ }
if (gameMode == MachinePlaysWhite ||
- gameMode == MachinePlaysBlack) {
+ gameMode == TwoMachinesPlay ||
+ gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
+ if(first.pause) UnPauseEngine(&first);
+ else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
+ if(second.pause) UnPauseEngine(&second);
+ else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
StartClocks();
} else {
DisplayBothClocks();
Reset(FALSE, TRUE);
SendToICS(ics_prefix);
SendToICS("refresh\n");
- } else if (currentMove < forwardMostMove) {
+ } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
ForwardInner(forwardMostMove);
}
pauseExamInvalid = FALSE;
case TwoMachinesPlay:
if (forwardMostMove == 0)
return; /* don't pause if no one has moved */
- if ((gameMode == MachinePlaysWhite &&
- !WhiteOnMove(forwardMostMove)) ||
- (gameMode == MachinePlaysBlack &&
- WhiteOnMove(forwardMostMove))) {
+ if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
+ ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
+ if(onMove->pause) { // thinking engine can be paused
+ PauseEngine(onMove); // do it
+ if(onMove->other->pause) // pondering opponent can always be paused immediately
+ PauseEngine(onMove->other);
+ else
+ SendToProgram("easy\n", onMove->other);
+ StopClocks();
+ } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
+ } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
+ if(first.pause) {
+ PauseEngine(&first);
+ StopClocks();
+ } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
+ } else { // human on move, pause pondering by either method
+ if(first.pause)
+ PauseEngine(&first);
+ else if(appData.ponderNextMove)
+ SendToProgram("easy\n", &first);
StopClocks();
}
+ // if no immediate pausing is possible, wait for engine to move, and stop clocks then
+ case AnalyzeMode:
pausing = TRUE;
ModeHighlight();
break;
}
void
-EditCommentEvent()
+EditCommentEvent ()
{
char title[MSG_SIZ];
void
-EditTagsEvent()
+EditTagsEvent ()
{
char *tags = PGNTags(&gameInfo);
bookUp = FALSE;
}
void
-AnalyzeModeEvent()
+ToggleSecond ()
{
+ if(second.analyzing) {
+ SendToProgram("exit\n", &second);
+ second.analyzing = FALSE;
+ } else {
+ if (second.pr == NoProc) StartChessProgram(&second);
+ InitChessProgram(&second, FALSE);
+ FeedMovesToProgram(&second, currentMove);
+
+ SendToProgram("analyze\n", &second);
+ second.analyzing = TRUE;
+ }
+}
+
+/* Toggle ShowThinking */
+void
+ToggleShowThinking()
+{
+ appData.showThinking = !appData.showThinking;
+ ShowThinkingEvent();
+}
+
+int
+AnalyzeModeEvent ()
+{
+ char buf[MSG_SIZ];
+
+ if (!first.analysisSupport) {
+ snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
+ DisplayError(buf, 0);
+ return 0;
+ }
+ /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
+ if (appData.icsActive) {
+ if (gameMode != IcsObserving) {
+ snprintf(buf, MSG_SIZ, _("You are not observing a game"));
+ DisplayError(buf, 0);
+ /* secure check */
+ if (appData.icsEngineAnalyze) {
+ if (appData.debugMode)
+ fprintf(debugFP, _("Found unexpected active ICS engine analyze \n"));
+ ExitAnalyzeMode();
+ ModeHighlight();
+ }
+ return 0;
+ }
+ /* if enable, user wants to disable icsEngineAnalyze */
+ if (appData.icsEngineAnalyze) {
+ ExitAnalyzeMode();
+ ModeHighlight();
+ return 0;
+ }
+ appData.icsEngineAnalyze = TRUE;
+ if (appData.debugMode)
+ fprintf(debugFP, _("ICS engine analyze starting... \n"));
+ }
+
+ if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
if (appData.noChessProgram || gameMode == AnalyzeMode)
- return;
+ return 0;
if (gameMode != AnalyzeFile) {
if (!appData.icsEngineAnalyze) {
EditGameEvent();
- if (gameMode != EditGame) return;
+ if (gameMode != EditGame) return 0;
}
+ if (!appData.showThinking) ToggleShowThinking();
ResurrectChessProgram();
SendToProgram("analyze\n", &first);
first.analyzing = TRUE;
StartAnalysisClock();
GetTimeMark(&lastNodeCountTime);
lastNodeCount = 0;
+ return 1;
}
void
-AnalyzeFileEvent()
+AnalyzeFileEvent ()
{
if (appData.noChessProgram || gameMode == AnalyzeFile)
return;
+ if (!first.analysisSupport) {
+ char buf[MSG_SIZ];
+ snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
+ DisplayError(buf, 0);
+ return;
+ }
+
if (gameMode != AnalyzeMode) {
+ keepInfo = 1; // mere annotating should not alter PGN tags
EditGameEvent();
+ keepInfo = 0;
if (gameMode != EditGame) return;
+ if (!appData.showThinking) ToggleShowThinking();
ResurrectChessProgram();
SendToProgram("analyze\n", &first);
first.analyzing = TRUE;
gameMode = AnalyzeFile;
pausing = FALSE;
ModeHighlight();
- SetGameInfo();
StartAnalysisClock();
GetTimeMark(&lastNodeCountTime);
lastNodeCount = 0;
+ if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
+ AnalysisPeriodicEvent(1);
}
void
-MachineWhiteEvent()
+MachineWhiteEvent ()
{
char buf[MSG_SIZ];
char *bookHit = NULL;
pausing = FALSE;
ModeHighlight();
SetGameInfo();
- snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
+ snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
DisplayTitle(buf);
if (first.sendName) {
snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
}
void
-MachineBlackEvent()
+MachineBlackEvent ()
{
char buf[MSG_SIZ];
char *bookHit = NULL;
pausing = FALSE;
ModeHighlight();
SetGameInfo();
- snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
+ snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
DisplayTitle(buf);
if (first.sendName) {
snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
void
-DisplayTwoMachinesTitle()
+DisplayTwoMachinesTitle ()
{
char buf[MSG_SIZ];
if (appData.matchGames > 0) {
if(appData.tourneyFile[0]) {
- snprintf(buf, MSG_SIZ, "%s vs. %s (%d/%d%s)",
- gameInfo.white, gameInfo.black,
+ 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
+ } else
if (first.twoMachinesColor[0] == 'w') {
- snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
- gameInfo.white, gameInfo.black,
+ 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 {
- snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
- gameInfo.white, gameInfo.black,
+ 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 {
- snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
+ snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
}
DisplayTitle(buf);
}
void
-SettingsMenuIfReady()
+SettingsMenuIfReady ()
{
if (second.lastPing != second.lastPong) {
DisplayMessage("", _("Waiting for second chess program"));
}
int
-WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
+WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
{
char buf[MSG_SIZ];
- if (cps->pr == NULL) {
+ if (cps->pr == NoProc) {
StartChessProgram(cps);
if (cps->protocolVersion == 1) {
retry();
+ ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
} else {
/* kludge: allow timeout for initial "feature" command */
- FreezeUI();
- snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
+ if(retry != TwoMachinesEventIfReady) FreezeUI();
+ snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
DisplayMessage("", buf);
ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
}
// forwardMostMove = currentMove;
TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
+ startingEngine = TRUE;
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.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
return;
}
+ if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
+
+ if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
+ startingEngine = FALSE;
+ DisplayError("second engine does not play this", 0);
+ return;
+ }
+
if(!stalling) {
InitChessProgram(&second, FALSE); // unbalances ping of second engine
SendToProgram("force\n", &second);
ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
return;
}
+ // we are now committed to starting the game
stalling = 0;
DisplayMessage("", "");
if (startedFromSetupPosition) {
}
gameMode = TwoMachinesPlay;
- pausing = FALSE;
- ModeHighlight();
+ pausing = startingEngine = FALSE;
+ ModeHighlight(); // [HGM] logo: this triggers display update of logos
SetGameInfo();
DisplayTwoMachinesTitle();
firstMove = TRUE;
}
void
-TrainingEvent()
+TrainingEvent ()
{
if (gameMode == Training) {
SetTrainingModeOff();
}
void
-IcsClientEvent()
+IcsClientEvent ()
{
if (!appData.icsActive) return;
switch (gameMode) {
return;
}
-
void
-EditGameEvent()
+EditGameEvent ()
{
int i;
SendToProgram("undo\n", &first);
i--;
}
+ if(!adjustedClock) {
whiteTimeRemaining = timeRemaining[0][currentMove];
blackTimeRemaining = timeRemaining[1][currentMove];
DisplayBothClocks();
+ }
if (whiteFlag || blackFlag) {
whiteFlag = blackFlag = 0;
}
void
-EditPositionEvent()
+EditPositionEvent ()
{
if (gameMode == EditPosition) {
EditGameEvent();
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) {
DisplayMessage("",_("Close ICS engine analyze..."));
}
if (first.analysisSupport && first.analyzing) {
- SendToProgram("exit\n", &first);
- first.analyzing = FALSE;
+ SendToBoth("exit\n");
+ first.analyzing = second.analyzing = FALSE;
}
thinkOutput[0] = NULLCHAR;
}
void
-EditPositionDone(Boolean fakeRights)
+EditPositionDone (Boolean fakeRights)
{
int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
boards[0][EP_STATUS] = EP_NONE;
boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
- if(boards[0][0][BOARD_WIDTH>>1] == king) {
- boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
+ if(boards[0][0][BOARD_WIDTH>>1] == king) {
+ boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
} else boards[0][CASTLING][2] = NoRights;
- if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
- boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
+ if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
+ boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
} else boards[0][CASTLING][5] = NoRights;
+ if(gameInfo.variant == VariantSChess) {
+ int i;
+ for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
+ boards[0][VIRGIN][i] = 0;
+ if(boards[0][0][i] == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
+ if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
+ }
+ }
}
SendToProgram("force\n", &first);
if (blackPlaysFirst) {
fprintf(debugFP, "EditPosDone\n");
}
DisplayTitle("");
+ DisplayMessage("", "");
timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
timeRemaining[1][forwardMostMove] = blackTimeRemaining;
gameMode = EditGame;
/* 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;
}
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];
} 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 */
}
void
-DeclineEvent()
+DeclineEvent ()
{
/* Decline a pending offer of any kind from opponent */
}
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
-ClockClick(int which)
+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 == EditGame || shiftKey) {
+ } 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) {
if (gameMode == EditPosition || gameMode == IcsExamining) {
if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
SetWhiteToPlayEvent();
- } else if (gameMode == EditGame || shiftKey) {
+ } 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) {
}
void
-DrawEvent()
+DrawEvent ()
{
/* Offer draw or accept pending draw offer from opponent */
}
void
-AdjournEvent()
+AdjournEvent ()
{
/* Offer Adjourn or accept pending Adjourn offer from opponent */
void
-AbortEvent()
+AbortEvent ()
{
/* Offer Abort or accept pending Abort offer from opponent */
}
void
-ResignEvent()
+ResignEvent ()
{
/* Resign. You can do this even if it's not your turn. */
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();
gameMode == Training || gameMode == PlayFromGameFile ||
gameMode == AnalyzeFile) {
while (currentMove < target) {
+ if(second.analyzing) SendMoveToProgram(currentMove, &second);
SendMoveToProgram(currentMove++, &first);
}
} else {
}
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]);
}
- DisplayBook(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
}
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 == EditGame || gameMode==AnalyzeMode ||
gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
while (currentMove > target) {
- SendToProgram("undo\n", &first);
+ 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);
+ if(second.analyzing) SendBoard(&second, i);
+ for(currentMove=i; currentMove<target; currentMove++) {
+ SendMoveToProgram(currentMove, &first);
+ if(second.analyzing) SendMoveToProgram(currentMove, &second);
+ }
+ break;
+ }
+ SendToBoth("undo\n");
currentMove--;
}
} else {
HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
// [HGM] PV info: routine tests if comment empty
DisplayComment(currentMove - 1, commentList[currentMove]);
- DisplayBook(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
}
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()
+CreateBookEvent ()
+{
+ ListGame * lg = (ListGame *) gameList.head;
+ FILE *f;
+ int nItem;
+ static int secondTime = FALSE;
+
+ if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
+ DisplayError(_("Game list not loaded or empty"), 0);
+ return;
+ }
+
+ if(!secondTime && (f = fopen(appData.polyglotBook, "r"))) {
+ fclose(f);
+ secondTime++;
+ DisplayNote(_("Book file exists! Try again for overwrite."));
+ return;
+ }
+
+ creatingBook = TRUE;
+ secondTime = FALSE;
+
+ /* Get list size */
+ for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
+ LoadGame(f, nItem, "", TRUE);
+ AddGameToBook(TRUE);
+ lg = (ListGame *) lg->node.succ;
+ }
+
+ creatingBook = FALSE;
+ FlushBook();
+}
+
+void
+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;
}
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) {
}
void
-SetGameInfo()
+SetGameInfo ()
{
/* This routine is used only for certain modes */
VariantClass v = gameInfo.variant;
ChessMove r = GameUnfinished;
char *p = NULL;
+ if(keepInfo) return;
+
if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
r = gameInfo.result;
p = gameInfo.resultDetails;
}
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 &&
+ 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
}
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')
if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
else strcat(commentList[index], "\n");
strcat(commentList[index], text);
- if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\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...
}
}
-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, *p;
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 ) {
}
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) {
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);
}
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 (count <= 0) {
if (count == 0) {
RemoveInputSource(cps->isr);
- if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
_(cps->which), cps->program);
- if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
+ if(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; }
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, "hint: %c", &c)!=1 &&
sscanf(message, "pong %c", &c)!=1 && start != '#') {
quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
print = (appData.engineComments >= 2);
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);
}
}
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;
}
}
-ChessProgramState *WhitePlayer()
+ChessProgramState *
+WhitePlayer ()
/* [HGM] return pointer to 'first' or 'second', depending on who plays white */
{
if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
}
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;
}
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
-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);
}
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);
}
int
-ParseOption(Option *opt, ChessProgramState *cps)
+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
{
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;
}
void
-FeatureDone(cps, val)
- ChessProgramState* cps;
- int val;
+FeatureDone (ChessProgramState *cps, int val)
{
DelayedEventCallback cb = GetDelayedEvent();
if ((cb == InitBackEnd3 && cps == &first) ||
ScheduleDelayedEvent(cb, val ? 1 : 3600000);
}
cps->initDone = val;
+ if(val) cps->reload = FALSE;
}
/* 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, "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;
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 (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
if (IntFeature(&p, "done", &val, cps)) {
FeatureDone(cps, val);
continue;
if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
- if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
- if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
+ if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
+ if (StringFeature(&p, "option", buf, cps)) {
+ if(cps->reload) continue; // we are reloading because of xreuse
+ 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
snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
SendToProgram(buf, cps);
}
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];
}
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
}
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
-TypeInEvent(char firstChar)
-{
- if ((gameMode == BeginningOfGame && !appData.icsActive) || \r
- gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||\r
- gameMode == AnalyzeMode || gameMode == EditGame || \r
- gameMode == EditPosition || gameMode == IcsExamining ||\r
- gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
- isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes\r
- ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||\r
- gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||\r
+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)
+TypeInDoneEvent (char *move)
{
Board board;
int n, fromX, fromY, toX, toY;
char promoChar;
- ChessMove moveType;\r
-
- // [HGM] FENedit\r
- if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {\r
- EditPositionPasteFEN(move);\r
- return;\r
- }\r
- // [HGM] movenum: allow move number to be typed in any mode\r
- if(sscanf(move, "%d", &n) == 1 && n != 0 ) {\r
- ToNrEvent(2*n-1);\r
- return;\r
- }\r
-
- if (gameMode != EditGame && currentMove != forwardMostMove && \r
- gameMode != Training) {\r
- DisplayMoveError(_("Displayed move is not current"));\r
- } else {\r
- int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
- &moveType, &fromX, &fromY, &toX, &toY, &promoChar);\r
- if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized\r
- if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
- &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {\r
- UserMoveEvent(fromX, fromY, toX, toY, promoChar); \r
- } else {\r
- DisplayMoveError(_("Could not parse move"));\r
- }
- }\r
-}\r
+ 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(moveNumber)
- int moveNumber;
+DisplayMove (int moveNumber)
{
char message[MSG_SIZ];
char res[MSG_SIZ];
}
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) {
safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
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;
- snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%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;
}
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
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);
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) {
}
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 (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);
from the color that is *not* on move now.
*/
void
-SwitchClocks(int newMoveNr)
+SwitchClocks (int newMoveNr)
{
long lastTickLength;
TimeMark now;
/* Stop both clocks */
void
-StopClocks()
+StopClocks ()
{
long lastTickLength;
TimeMark now;
/* 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();
}
char *
-TimeString(ms)
- long ms;
+TimeString (long ms)
{
long second, minute, hour, day;
char *sign = "";
* 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;
}
char *
-StrCaseStr(string, match)
- char *string, *match;
+StrCaseStr (char *string, char *match)
{
int i, j, length;
#ifndef _amigados
int
-StrCaseCmp(s1, s2)
- char *s1, *s2;
+StrCaseCmp (char *s1, char *s2)
{
char c1, c2;
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 *
-StrSavePtr(s, savePtr)
- char *s, **savePtr;
+StrSavePtr (char *s, char **savePtr)
{
if (*savePtr) {
free(*savePtr);
}
char *
-PGNDate()
+PGNDate ()
{
time_t clock;
struct tm *tm;
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 true castling rights */
if( nrCastlingRights == 6 ) {
+ int q, k=0;
if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
- boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
- if(boards[move][CASTLING][1] == BOARD_LEFT &&
- boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
+ boards[move][CASTLING][2] != NoRights ) k = 1, *p++ = 'K';
+ q = (boards[move][CASTLING][1] == BOARD_LEFT &&
+ boards[move][CASTLING][2] != NoRights );
+ if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
+ for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
+ for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
+ if((boards[move][0][i] != WhiteKing || k+q == 0) &&
+ boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
+ }
+ if(q) *p++ = 'Q';
+ k = 0;
if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
- boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
- if(boards[move][CASTLING][4] == BOARD_LEFT &&
- boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
+ boards[move][CASTLING][5] != NoRights ) k = 1, *p++ = 'k';
+ q = (boards[move][CASTLING][4] == BOARD_LEFT &&
+ boards[move][CASTLING][5] != NoRights );
+ if(gameInfo.variant == VariantSChess) {
+ for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
+ for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
+ if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
+ boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
+ }
+ if(q) *p++ = 'q';
}
}
if (q == p) *p++ = '-'; /* No castling rights */
}
Boolean
-ParseFEN(board, blackPlaysFirst, fen)
- Board board;
- int *blackPlaysFirst;
- char *fen;
+ParseFEN (Board board, int *blackPlaysFirst, char *fen)
{
int i, j;
char *p, c;
- int emptycount;
+ int emptycount, virgin[BOARD_FILES];
ChessSquare piece;
p = fen;
while(*p==' ') p++;
if(nrCastlingRights) {
- if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
+ if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
+ if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
/* castling indicator present, so default becomes no castlings */
for(i=0; i<nrCastlingRights; i++ ) {
board[CASTLING][i] = NoRights;
}
}
while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
- (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
+ (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
- char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
+ int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
board[CASTLING][2] = whiteKingFile;
+ if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
+ if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
break;
case'Q':
for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
board[CASTLING][2] = whiteKingFile;
+ if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
+ if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
break;
case'k':
for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
board[CASTLING][3] = i != blackKingFile ? i : NoRights;
board[CASTLING][5] = blackKingFile;
+ if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
+ if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
break;
case'q':
for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
board[CASTLING][4] = i != blackKingFile ? i : NoRights;
board[CASTLING][5] = blackKingFile;
+ if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
+ if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
case '-':
break;
default: /* FRC castlings */
if(c >= 'a') { /* black rights */
+ if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
if(i == BOARD_RGHT) break;
else
board[CASTLING][4] = c;
} else { /* white rights */
+ if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
if(board[0][i] == WhiteKing) break;
if(i == BOARD_RGHT) break;
}
for(i=0; i<nrCastlingRights; i++)
if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
+ if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
if (appData.debugMode) {
fprintf(debugFP, "FEN castling rights:");
for(i=0; i<nrCastlingRights; i++)
}
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;
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
-PushInner(int firstMove, int lastMove)
+PushInner (int firstMove, int lastMove)
{
int i, j, nrMoves = lastMove - firstMove;
}
void
-PushTail(int firstMove, int lastMove)
+PushTail (int firstMove, int lastMove)
{
if(appData.icsActive) { // only in local mode
forwardMostMove = currentMove; // mimic old ICS behavior
PushInner(firstMove, lastMove);
if(storedGames == 1) GreyRevert(FALSE);
+ if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
}
void
-PopInner(Boolean annotate)
+PopInner (Boolean annotate)
{
int i, j, nrMoves;
char buf[8000], moveBuf[20];
- 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;
}
Boolean
-PopTail(Boolean annotate)
+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(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
return TRUE;
}
void
-CleanupTail()
+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 ]}
ToNrEvent(currentMove+1);
}
+void
+LoadTheme ()
+{
+ char *p, *q, buf[MSG_SIZ];
+ if(engineLine && engineLine[0]) { // a theme was selected from the listbox
+ snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
+ ParseArgsFromString(buf);
+ ActivateTheme(TRUE); // also redo colors
+ return;
+ }
+ p = nickName;
+ if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
+ {
+ int len;
+ q = appData.themeNames;
+ snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
+ if(appData.useBitmaps) {
+ snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
+ appData.liteBackTextureFile, appData.darkBackTextureFile,
+ appData.liteBackTextureMode,
+ appData.darkBackTextureMode );
+ } else {
+ snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
+ Col2Text(2), // lightSquareColor
+ Col2Text(3) ); // darkSquareColor
+ }
+ if(appData.useBorder) {
+ snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
+ appData.border);
+ } else {
+ snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
+ }
+ if(appData.useFont) {
+ snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
+ appData.renderPiecesWithFont,
+ appData.fontToPieceTable,
+ Col2Text(9), // appData.fontBackColorWhite
+ Col2Text(10) ); // appData.fontForeColorBlack
+ } else {
+ snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
+ appData.pieceDirectory);
+ if(!appData.pieceDirectory[0])
+ snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
+ Col2Text(0), // whitePieceColor
+ Col2Text(1) ); // blackPieceColor
+ }
+ snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
+ Col2Text(4), // highlightSquareColor
+ Col2Text(5) ); // premoveHighlightColor
+ appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
+ if(insert != q) insert[-1] = NULLCHAR;
+ snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
+ if(q) free(q);
+ }
+ ActivateTheme(FALSE);
+}