int flock(int f, int code);
#define LOCK_EX 2
+#define SLASH '\\'
#else
#define DoSleep( n ) if( (n) >= 0) sleep(n)
+#define SLASH '/'
#endif
char *TimeControlTagValue P((void));
void Attention P((ChessProgramState *cps));
void FeedMovesToProgram P((ChessProgramState *cps, int upto));
-void ResurrectChessProgram P((void));
+int ResurrectChessProgram P((void));
void DisplayComment P((int moveNumber, char *text));
void DisplayMove P((int moveNumber));
int PerpetualChase(int first, int last);
int EngineOutputIsUp();
void InitDrawingSizes(int x, int y);
+void NextMatchGame P((void));
+int NextTourneyGame P((int nr, int *swap));
+int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
+FILE *WriteTourneyFile P((char *results));
+void DisplayTwoMachinesTitle P(());
#ifdef WIN32
extern void ConsoleCreate();
void ics_update_width P((int new_width));
extern char installDir[MSG_SIZ];
VariantClass startVariant; /* [HGM] nicks: initial variant */
+Boolean abortMatch;
extern int tinyLayout, smallLayout;
ChessProgramStats programStats;
char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
char thinkOutput1[MSG_SIZ*10];
-ChessProgramState first, second;
+ChessProgramState first, second, pairing;
/* premove variables */
int premoveToX = 0;
long timeControl_2; /* [AS] Allow separate time controls */
char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
long timeRemaining[2][MAX_MOVES];
-int matchGame = 0;
-TimeMark programStartTime;
+int matchGame = 0, nextGame = 0, roundNr = 0;
+Boolean waitingForGame = FALSE;
+TimeMark programStartTime, pauseStart;
char ics_handle[MSG_SIZ];
int have_set_title = 0;
int 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
void PushTail P((int firstMove, int lastMove));
Boolean PopTail P((Boolean annotate));
+void PushInner P((int firstMove, int lastMove));
+void PopInner P((Boolean annotate));
void CleanupTail P((void));
ChessSquare FIDEArray[2][BOARD_FILES] = {
DestroyChildProcess(cps->pr, cps->useSigterm);
}
cps->pr = NoProc;
+ if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
}
void
"second"
};
+void
InitEngine(ChessProgramState *cps, int n)
{ // [HGM] all engine initialiation put in a function that does one engine
/* [HGM] debug */
cps->debug = FALSE;
+
cps->supportsNPS = UNKNOWN;
+ cps->memSize = FALSE;
+ cps->maxCores = FALSE;
+ cps->egtFormats[0] = NULLCHAR;
/* [HGM] options */
cps->optionSettings = appData.engOptions[n];
{
EditGameEvent();
UnloadEngine(cps);
- appData.noChessProgram = False;
- appData.clockMode = True;
+ appData.noChessProgram = FALSE;
+ appData.clockMode = TRUE;
InitEngine(cps, n);
+ UpdateLogos(TRUE);
if(n) return; // only startup first engine immediately; second can wait
savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
LoadEngine();
}
+extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
+extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
+
+static char resetOptions[] =
+ "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
+ "-firstOptions \"\" -firstNPS -1 -fn \"\"";
+
void
-InitBackEnd1()
+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
+ 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(buf);
+ SwapEngines(i);
+ ReplaceEngine(cps, i);
+ 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
+ p[-1] = 0;
+ appData.directory[i] = strdup(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(params[0]) {
+ snprintf(command, MSG_SIZ, "%s %s", p, params);
+ p = command;
+ }
+ appData.chessProgram[i] = strdup(p);
+ appData.isUCI[i] = isUCI;
+ appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
+ appData.hasOwnBookUCI[i] = hasBook;
+ if(!nickName[0]) useNick = FALSE;
+ if(useNick) ASSIGN(appData.pgnName[i], nickName);
+ if(addToList) {
+ int len;
+ char quote;
+ q = firstChessProgramNames;
+ if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
+ quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
+ snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
+ quote, p, quote, appData.directory[i],
+ useNick ? " -fn \"" : "",
+ useNick ? nickName : "",
+ useNick ? "\"" : "",
+ v1 ? " -firstProtocolVersion 1" : "",
+ hasBook ? "" : " -fNoOwnBookUCI",
+ isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
+ storeVariant ? " -variant " : "",
+ storeVariant ? VariantName(gameInfo.variant) : "");
+ firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
+ snprintf(firstChessProgramNames, len, "%s%s", q, buf);
+ if(q) free(q);
+ }
+ ReplaceEngine(cps, i);
+}
+
+void
+InitTimeControls()
{
int matched, min, sec;
+ /*
+ * Parse timeControl resource
+ */
+ if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
+ appData.movesPerSession)) {
+ char buf[MSG_SIZ];
+ snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
+ DisplayFatalError(buf, 0, 2);
+ }
+
+ /*
+ * Parse searchTime resource
+ */
+ if (*appData.searchTime != NULLCHAR) {
+ matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
+ if (matched == 1) {
+ searchTime = min * 60;
+ } else if (matched == 2) {
+ searchTime = min * 60 + sec;
+ } else {
+ char buf[MSG_SIZ];
+ snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
+ DisplayFatalError(buf, 0, 2);
+ }
+ }
+}
+
+void
+InitBackEnd1()
+{
ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
GetTimeMark(&programStartTime);
srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
+ pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
ClearProgramStats();
programStats.ok_to_send = 1;
}
}
- /*
- * Parse timeControl resource
- */
- if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
- appData.movesPerSession)) {
- char buf[MSG_SIZ];
- snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
- DisplayFatalError(buf, 0, 2);
- }
-
- /*
- * Parse searchTime resource
- */
- if (*appData.searchTime != NULLCHAR) {
- matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
- if (matched == 1) {
- searchTime = min * 60;
- } else if (matched == 2) {
- searchTime = min * 60 + sec;
- } else {
- char buf[MSG_SIZ];
- snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
- DisplayFatalError(buf, 0, 2);
- }
- }
+ InitTimeControls();
/* [AS] Adjudication threshold */
adjudicateLossThreshold = appData.adjudicateLossThreshold;
InitEngine(&second, 1);
CommonEngineInit();
+ pairing.which = "pairing"; // pairing engine
+ pairing.pr = NoProc;
+ pairing.isr = NULL;
+ pairing.program = appData.pairingEngine;
+ pairing.host = "localhost";
+ pairing.dir = ".";
+
if (appData.icsActive) {
appData.clockMode = TRUE; /* changes dynamically in ICS mode */
} else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
}
void
+ReserveGame(int gameNr, char resChar)
+{
+ FILE *tf = fopen(appData.tourneyFile, "r+");
+ char *p, *q, c, buf[MSG_SIZ];
+ if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
+ safeStrCpy(buf, lastMsg, MSG_SIZ);
+ DisplayMessage(_("Pick new game"), "");
+ flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
+ ParseArgsFromFile(tf);
+ p = q = appData.results;
+ if(appData.debugMode) {
+ char *r = appData.participants;
+ fprintf(debugFP, "results = '%s'\n", p);
+ while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
+ fprintf(debugFP, "\n");
+ }
+ while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
+ nextGame = q - p;
+ q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
+ safeStrCpy(q, p, strlen(p) + 2);
+ if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
+ if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
+ if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
+ if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
+ q[nextGame] = '*';
+ }
+ fseek(tf, -(strlen(p)+4), SEEK_END);
+ c = fgetc(tf);
+ if(c != '"') // depending on DOS or Unix line endings we can be one off
+ fseek(tf, -(strlen(p)+2), SEEK_END);
+ else fseek(tf, -(strlen(p)+3), SEEK_END);
+ fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
+ DisplayMessage(buf, "");
+ free(p); appData.results = q;
+ if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
+ (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
+ UnloadEngine(&first); // next game belongs to other pairing;
+ UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
+ }
+}
+
+void
MatchEvent(int mode)
{ // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
+ int dummy;
+ if(matchMode) { // already in match mode: switch it off
+ abortMatch = TRUE;
+ if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
+ return;
+ }
+// if(gameMode != BeginningOfGame) {
+// DisplayError(_("You can only start a match from the initial position."), 0);
+// return;
+// }
+ abortMatch = FALSE;
+ if(mode == 2) appData.matchGames = appData.defaultMatchGames;
/* Set up machine vs. machine match */
- if (appData.noChessProgram) {
+ nextGame = 0;
+ NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
+ if(appData.tourneyFile[0]) {
+ ReserveGame(-1, 0);
+ if(nextGame > appData.matchGames) {
+ char buf[MSG_SIZ];
+ if(strchr(appData.results, '*') == NULL) {
+ FILE *f;
+ appData.tourneyCycles++;
+ if(f = WriteTourneyFile(appData.results)) { // make a tourney file with increased number of cycles
+ fclose(f);
+ NextTourneyGame(-1, &dummy);
+ ReserveGame(-1, 0);
+ if(nextGame <= appData.matchGames) {
+ DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
+ matchMode = mode;
+ ScheduleDelayedEvent(NextMatchGame, 10000);
+ return;
+ }
+ }
+ }
+ snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
+ DisplayError(buf, 0);
+ appData.tourneyFile[0] = 0;
+ return;
+ }
+ } else
+ if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
DisplayFatalError(_("Can't have a match with no chess programs"),
0, 2);
return;
}
matchMode = mode;
- matchGame = 1;
- if(!LoadGameOrPositionFile(1)) return;
- first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches\r
- TwoMachinesEvent();
+ matchGame = roundNr = 1;
+ first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
+ NextMatchGame();
}
void
}
if (appData.matchMode) {
+ if(appData.tourneyFile[0]) { // start tourney from command line
+ FILE *f;
+ if(f = fopen(appData.tourneyFile, "r")) {
+ ParseArgsFromFile(f); // make sure tourney parmeters re known
+ fclose(f);
+ appData.clockMode = TRUE;
+ SetGNUMode();
+ } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
+ }
MatchEvent(TRUE);
} else if (*appData.cmailGameName != NULLCHAR) {
/* Set up cmail mode */
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
+ 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 */
}
}
+Boolean pushed = FALSE;
+char *lastParseAttempt;
void
-ParsePV(char *pv, Boolean storeComments)
+ParsePV(char *pv, Boolean storeComments, Boolean atEnd)
{ // Parse a string of PV moves, and append to current game, behind forwardMostMove
int fromX, fromY, toX, toY; char promoChar;
ChessMove moveType;
Boolean valid;
int nr = 0;
+ if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
+ PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
+ pushed = TRUE;
+ }
endPV = forwardMostMove;
do {
while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
+ lastParseAttempt = pv;
valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
if(appData.debugMode){
fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, yy_textstr, pv);
endPV++;
CopyBoard(boards[endPV], boards[endPV-1]);
ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
- moveList[endPV-1][0] = fromX + AAA;
- moveList[endPV-1][1] = fromY + ONE;
- moveList[endPV-1][2] = toX + AAA;
- moveList[endPV-1][3] = toY + ONE;
- moveList[endPV-1][4] = promoChar;
- moveList[endPV-1][5] = NULLCHAR;
+ CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
strncat(moveList[endPV-1], "\n", MOVE_LEN);
- if(storeComments)
- CoordsToAlgebraic(boards[endPV - 1],
+ CoordsToAlgebraic(boards[endPV - 1],
PosFlags(endPV - 1),
fromY, fromX, toY, toX, promoChar,
parseList[endPV - 1]);
- else
- parseList[endPV-1][0] = NULLCHAR;
} while(valid);
- currentMove = endPV;
+ if(atEnd == 2) return; // used hidden, for PV conversion
+ currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
DrawPosition(TRUE, boards[currentMove]);
}
+int
+MultiPV(ChessProgramState *cps)
+{ // check if engine supports MultiPV, and if so, return the number of the option that sets it
+ int i;
+ for(i=0; i<cps->nrOptions; i++)
+ if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
+ return i;
+ return -1;
+}
+
Boolean
LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
{
- int startPV;
- char *p;
+ int startPV, multi, lineStart, origIndex = index;
+ char *p, buf2[MSG_SIZ];
if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
lastX = x; lastY = y;
while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
- startPV = index;
+ lineStart = startPV = index;
while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
index = startPV;
do{ while(buf[index] && buf[index] != '\n') index++;
} while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
buf[index] = 0;
- ParsePV(buf+startPV, FALSE);
+ if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
+ int n = first.option[multi].value;
+ if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
+ snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
+ if(first.option[multi].value != n) SendToProgram(buf2, &first);
+ first.option[multi].value = n;
+ *start = *end = 0;
+ return FALSE;
+ }
+ ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
*start = startPV; *end = index-1;
return TRUE;
}
+char *
+PvToSAN(char *pv)
+{
+ static char buf[10*MSG_SIZ];
+ int i, k=0, savedEnd=endPV;
+ *buf = NULLCHAR;
+ if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
+ ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
+ for(i = forwardMostMove; i<endPV; i++){
+ if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
+ else snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
+ k += strlen(buf+k);
+ }
+ snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
+ if(forwardMostMove < savedEnd) PopInner(0);
+ endPV = savedEnd;
+ return buf;
+}
+
Boolean
LoadPV(int x, int y)
{ // called on right mouse click to load PV
int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
lastX = x; lastY = y;
- ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
+ ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
return TRUE;
}
void
UnLoadPV()
{
+ int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
if(endPV < 0) return;
endPV = -1;
+ if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
+ Boolean saveAnimate = appData.animate;
+ if(pushed) {
+ if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
+ if(storedGames == 1) GreyRevert(FALSE); // we already pushed the tail, so just make it official
+ } else storedGames--; // abandon shelved tail of original game
+ }
+ pushed = FALSE;
+ forwardMostMove = currentMove;
+ currentMove = oldFMM;
+ appData.animate = FALSE;
+ ToNrEvent(forwardMostMove);
+ appData.animate = saveAnimate;
+ }
currentMove = forwardMostMove;
+ if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
ClearPremoveHighlights();
DrawPosition(TRUE, boards[currentMove]);
}
for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
} else {
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)
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;
case IcsObserving:
if(!appData.icsEngineAnalyze) return -1;
case IcsPlayingWhite:
}
void
+ClearEngineOutputPane(int which)
+{
+ static FrontEndProgramStats dummyStats;
+ dummyStats.which = which;
+ dummyStats.pv = "#";
+ SetProgramStats( &dummyStats );
+}
+
+#define MAXPLAYERS 500
+
+char *
+TourneyStandings(int display)
+{
+ int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
+ int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
+ char result, *p, *names[MAXPLAYERS];
+
+ if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
+ return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
+ names[0] = p = strdup(appData.participants);
+ while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
+
+ for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
+
+ while(result = appData.results[nr]) {
+ color = Pairing(nr, nPlayers, &w, &b, &dummy);
+ if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
+ wScore = bScore = 0;
+ switch(result) {
+ case '+': wScore = 2; break;
+ case '-': bScore = 2; break;
+ case '=': wScore = bScore = 1; break;
+ case ' ':
+ case '*': return strdup("busy"); // tourney not finished
+ }
+ score[w] += wScore;
+ score[b] += bScore;
+ games[w]++;
+ games[b]++;
+ nr++;
+ }
+ if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
+ for(w=0; w<nPlayers; w++) {
+ bScore = -1;
+ for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
+ ranking[w] = b; points[w] = bScore; score[b] = -2;
+ }
+ p = malloc(nPlayers*34+1);
+ for(w=0; w<nPlayers && w<display; w++)
+ sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
+ free(names[0]);
+ return p;
+}
+
+void
Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
{ // count all piece types
int p, f, r;
if(bookHit) {
// after a book hit we never send 'go', and the code after the call to this routine
// has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
- char buf[MSG_SIZ];
- snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
+ char buf[MSG_SIZ], *move = bookHit;
+ if(cps->useSAN) {
+ int fromX, fromY, toX, toY;
+ char promoChar;
+ ChessMove moveType;
+ move = buf + 30;
+ if (ParseOneMove(bookHit, forwardMostMove, &moveType,
+ &fromX, &fromY, &toX, &toY, &promoChar)) {
+ (void) CoordsToAlgebraic(boards[forwardMostMove],
+ PosFlags(forwardMostMove),
+ fromY, fromX, toY, toX, promoChar, move);
+ } else {
+ if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
+ bookHit = NULL;
+ }
+ }
+ snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
SendToProgram(buf, cps);
if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
} else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
HandleMachineMove(savedMessage, savedState);
}
+static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
+
void
HandleMachineMove(message, cps)
char *message;
int fromX, fromY, toX, toY;
ChessMove moveType;
char promoChar;
- char *p;
+ char *p, *pv=buf1;
int machineWhite;
char *bookHit;
+ if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
+ // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
+ if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
+ DisplayError(_("Invalid pairing from pairing engine"), 0);
+ return;
+ }
+ pairingReceived = 1;
+ NextMatchGame();
+ return; // Skim the pairing messages here.
+ }
+
cps->userError = 0;
FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
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;
}
curscore = -curscore;
}
+ if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
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
DrawPosition(FALSE, boards[currentMove]);
DisplayBothClocks();
HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
+ DisplayBook(currentMove);
}
void SendEgtPath(ChessProgramState *cps)
SendToProgram(buf, cps);
}
cps->initDone = TRUE;
+ ClearEngineOutputPane(cps == &second);
}
if (err != 0) {
snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
- DisplayFatalError(buf, err, 1);
- cps->pr = NoProc;
- cps->isr = NULL;
+ DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
+ if(cps != &first) return;
+ appData.noChessProgram = TRUE;
+ ThawUI();
+ SetNCPMode();
+// DisplayFatalError(buf, err, 1);
+// cps->pr = NoProc;
+// cps->isr = NULL;
return;
}
void
TwoMachinesEventIfReady P((void))
{
+ static int curMess = 0;
if (first.lastPing != first.lastPong) {
- DisplayMessage("", _("Waiting for first chess program"));
+ if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
return;
}
if (second.lastPing != second.lastPong) {
- DisplayMessage("", _("Waiting for second chess program"));
+ if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
return;
}
+ DisplayMessage("", ""); curMess = 0;
ThawUI();
TwoMachinesEvent();
}
-void
-NextMatchGame P((void))
+char *
+MakeName(char *template)
+{
+ time_t clock;
+ struct tm *tm;
+ static char buf[MSG_SIZ];
+ char *p = buf;
+ int i;
+
+ clock = time((time_t *)NULL);
+ tm = localtime(&clock);
+
+ while(*p++ = *template++) if(p[-1] == '%') {
+ switch(*template++) {
+ case 0: *p = 0; return buf;
+ case 'Y': i = tm->tm_year+1900; break;
+ case 'y': i = tm->tm_year-100; break;
+ case 'M': i = tm->tm_mon+1; break;
+ case 'd': i = tm->tm_mday; break;
+ case 'h': i = tm->tm_hour; break;
+ case 'm': i = tm->tm_min; break;
+ case 's': i = tm->tm_sec; break;
+ default: i = 0;
+ }
+ snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
+ }
+ return buf;
+}
+
+int
+CountPlayers(char *p)
+{
+ int n = 0;
+ while(p = strchr(p, '\n')) p++, n++; // count participants
+ return n;
+}
+
+FILE *
+WriteTourneyFile(char *results)
+{ // write tournament parameters on tourneyFile; on success return the stream pointer for closing
+ FILE *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, "-tourneyType %d\n", appData.tourneyType);
+ fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
+ fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
+ fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
+ fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
+ fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
+ fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
+ fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
+ fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
+ fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
+ fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
+ if(searchTime > 0)
+ fprintf(f, "-searchTime \"%s\"\n", appData.searchTime);
+ else {
+ fprintf(f, "-mps %d\n", appData.movesPerSession);
+ fprintf(f, "-tc %s\n", appData.timeControl);
+ fprintf(f, "-inc %.2f\n", appData.timeIncrement);
+ }
+ fprintf(f, "-results \"%s\"\n", results);
+ }
+ return f;
+}
+
+int
+CreateTourney(char *name)
{
- Reset(FALSE, TRUE);
- LoadGameOrPosition(matchGame);
- TwoMachinesEventIfReady();
+ FILE *f;
+ if(name[0] == NULLCHAR) {
+ if(appData.participants[0])
+ DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
+ return 0;
+ }
+ f = fopen(name, "r");
+ if(f) { // file exists
+ ASSIGN(appData.tourneyFile, name);
+ ParseArgsFromFile(f); // parse it
+ } else {
+ if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
+ if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
+ DisplayError(_("Not enough participants"), 0);
+ return 0;
+ }
+ ASSIGN(appData.tourneyFile, name);
+ if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
+ if((f = WriteTourneyFile("")) == NULL) return 0;
+ }
+ fclose(f);
+ appData.noChessProgram = FALSE;
+ appData.clockMode = TRUE;
+ SetGNUMode();
+ return 1;
+}
+
+#define MAXENGINES 1000
+char *command[MAXENGINES], *mnemonic[MAXENGINES];
+
+void NamesToList(char *names, char **engineList, char **engineMnemonic)
+{
+ char buf[MSG_SIZ], *p, *q;
+ int i=1;
+ while(*names) {
+ p = names; q = buf;
+ while(*p && *p != '\n') *q++ = *p++;
+ *q = 0;
+ if(engineList[i]) free(engineList[i]);
+ engineList[i] = strdup(buf);
+ if(*p == '\n') p++;
+ TidyProgramName(engineList[i], "localhost", buf);
+ if(engineMnemonic[i]) free(engineMnemonic[i]);
+ if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
+ strcat(buf, " (");
+ sscanf(q + 8, "%s", buf + strlen(buf));
+ strcat(buf, ")");
+ }
+ engineMnemonic[i] = strdup(buf);
+ names = p; i++;
+ if(i > MAXENGINES - 2) break;
+ }
+ engineList[i] = NULL;
+}
+
+// following implemented as macro to avoid type limitations
+#define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
+
+void SwapEngines(int n)
+{ // swap settings for first engine and other engine (so far only some selected options)
+ int h;
+ char *p;
+ if(n == 0) return;
+ SWAP(directory, p)
+ SWAP(chessProgram, p)
+ SWAP(isUCI, h)
+ SWAP(hasOwnBookUCI, h)
+ SWAP(protocolVersion, h)
+ SWAP(reuse, h)
+ SWAP(scoreIsAbsolute, h)
+ SWAP(timeOdds, h)
+ SWAP(logo, p)
+ SWAP(pgnName, p)
+ SWAP(pvSAN, h)
+}
+
+void
+SetPlayer(int player)
+{ // [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;
+ 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(buf);
+ }
+ free(engineName);
+}
+
+int
+Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
+{ // determine players from game number
+ int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
+
+ if(appData.tourneyType == 0) {
+ roundsPerCycle = (nPlayers - 1) | 1;
+ pairingsPerRound = nPlayers / 2;
+ } else if(appData.tourneyType > 0) {
+ roundsPerCycle = nPlayers - appData.tourneyType;
+ pairingsPerRound = appData.tourneyType;
+ }
+ gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
+ gamesPerCycle = gamesPerRound * roundsPerCycle;
+ appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
+ curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
+ curRound = nr / gamesPerRound; nr %= gamesPerRound;
+ curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
+ matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
+ roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
+
+ if(appData.cycleSync) *syncInterval = gamesPerCycle;
+ if(appData.roundSync) *syncInterval = gamesPerRound;
+
+ if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
+
+ if(appData.tourneyType == 0) {
+ if(curPairing == (nPlayers-1)/2 ) {
+ *whitePlayer = curRound;
+ *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
+ } else {
+ *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
+ if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
+ *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
+ if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
+ }
+ } else if(appData.tourneyType > 0) {
+ *whitePlayer = curPairing;
+ *blackPlayer = curRound + appData.tourneyType;
+ }
+
+ // take care of white/black alternation per round.
+ // For cycles and games this is already taken care of by default, derived from matchGame!
+ return curRound & 1;
+}
+
+int
+NextTourneyGame(int nr, int *swapColors)
+{ // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
+ char *p, *q;
+ int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
+ FILE *tf;
+ if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
+ tf = fopen(appData.tourneyFile, "r");
+ if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
+ ParseArgsFromFile(tf); fclose(tf);
+ InitTimeControls(); // TC might be altered from tourney file
+
+ nPlayers = CountPlayers(appData.participants); // count participants
+ if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
+ *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
+
+ if(syncInterval) {
+ p = q = appData.results;
+ while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
+ if(firstBusy/syncInterval < (nextGame/syncInterval)) {
+ DisplayMessage(_("Waiting for other game(s)"),"");
+ waitingForGame = TRUE;
+ ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
+ return 0;
+ }
+ waitingForGame = FALSE;
+ }
+
+ if(appData.tourneyType < 0) {
+ if(nr>=0 && !pairingReceived) {
+ char buf[1<<16];
+ if(pairing.pr == NoProc) {
+ if(!appData.pairingEngine[0]) {
+ DisplayFatalError(_("No pairing engine specified"), 0, 1);
+ return 0;
+ }
+ StartChessProgram(&pairing); // starts the pairing engine
+ }
+ snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
+ SendToProgram(buf, &pairing);
+ snprintf(buf, 1<<16, "pairing %d\n", nr+1);
+ SendToProgram(buf, &pairing);
+ return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
+ }
+ pairingReceived = 0; // ... so we continue here
+ *swapColors = 0;
+ appData.matchGames = appData.tourneyCycles * syncInterval - 1;
+ whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
+ matchGame = 1; roundNr = nr / syncInterval + 1;
+ }
+
+ if(first.pr != NoProc) 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);
+ CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
+ UpdateLogos(FALSE); // leave display to ModeHiglight()
+ return 1;
+}
+
+void
+NextMatchGame()
+{ // performs game initialization that does not invoke engines, and then tries to start the game
+ int firstWhite, swapColors = 0;
+ if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
+ 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
+ Reset(FALSE, first.pr != NoProc);
+ appData.noChessProgram = FALSE;
+ if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
+ TwoMachinesEvent();
}
void UserAdjudicationEvent( int result )
{
GameMode nextGameMode;
int isIcsGame;
- char buf[MSG_SIZ], popupRequested = 0;
+ char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
if(endingGame) return; /* [HGM] crash: forbid recursion */
endingGame = 1;
second.pr = NoProc;
}
- if (matchMode && gameMode == TwoMachinesPlay) {
+ if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
+ char resChar = '=';
switch (result) {
case WhiteWins:
+ resChar = '+';
if (first.twoMachinesColor[0] == 'w') {
first.matchWins++;
} else {
}
break;
case BlackWins:
+ resChar = '-';
if (first.twoMachinesColor[0] == 'b') {
first.matchWins++;
} else {
second.matchWins++;
}
break;
+ case GameUnfinished:
+ resChar = ' ';
default:
break;
}
- if (matchGame < appData.matchGames) {
- char *tmp;
- if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
- tmp = first.twoMachinesColor;
- first.twoMachinesColor = second.twoMachinesColor;
- second.twoMachinesColor = tmp;
- }
+
+ if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
+ if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
+ ReserveGame(nextGame, resChar); // sets nextGame
+ if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
+ else ranking = strdup("busy"); //suppress popup when aborted but not finished
+ } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
+
+ if (nextGame <= appData.matchGames && !abortMatch) {
gameMode = nextGameMode;
- matchGame++;
- if(appData.matchPause>10000 || appData.matchPause<10)
- appData.matchPause = 10000; /* [HGM] make pause adjustable */
- ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
+ matchGame = nextGame; // this will be overruled in tourney mode!
+ GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
+ ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
endingGame = 0; /* [HGM] crash */
return;
} else {
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
popupRequested++; // [HGM] crash: postpone to after resetting endingGame
if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
first.twoMachinesColor = "black\n";
ModeHighlight();
endingGame = 0; /* [HGM] crash */
if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
- if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
- matchMode = FALSE; appData.matchGames = matchGame = 0;
- DisplayNote(buf);
+ if(matchMode == TRUE) { // match through command line: exit with or without popup
+ if(ranking) {
+ ToNrEvent(forwardMostMove);
+ if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
+ else ExitEvent(0);
+ } else DisplayFatalError(buf, 0, 0);
+ } else { // match through menu; just stop, with or without popup
+ matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
+ ModeHighlight();
+ if(ranking){
+ if(strcmp(ranking, "busy")) DisplayNote(ranking);
+ } else DisplayNote(buf);
}
+ if(ranking) free(ranking);
}
}
}
-void
+int
ResurrectChessProgram()
{
/* The chess program may have exited.
If so, restart it and feed it all the moves made so far. */
+ static int doInit = 0;
- if (appData.noChessProgram || first.pr != NoProc) return;
+ if (appData.noChessProgram) return 1;
- StartChessProgram(&first);
+ if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
+ if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
+ if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
+ doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
+ } else {
+ if (first.pr != NoProc) return 1;
+ StartChessProgram(&first);
+ }
InitChessProgram(&first, FALSE);
FeedMovesToProgram(&first, currentMove);
SendToProgram("analyze\n", &first);
first.analyzing = TRUE;
}
+ return 1;
}
/*
ResetClocks();
timeRemaining[0][0] = whiteTimeRemaining;
timeRemaining[1][0] = blackTimeRemaining;
+
if (first.pr == NULL) {
StartChessProgram(&first);
}
RemoveInputSource(second.isr);
}
+ if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
+ if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
+
ShutDownFrontEnd();
exit(status);
}
EditTagsEvent()
{
char *tags = PGNTags(&gameInfo);
+ bookUp = FALSE;
EditTagsPopUp(tags, NULL);
free(tags);
}
{
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,
+ nextGame+1, appData.matchGames+1,
+ appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
+ } else
if (first.twoMachinesColor[0] == 'w') {
snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
gameInfo.white, gameInfo.black,
ChessProgramState *onmove;
char *bookHit = NULL;
static int stalling = 0;
+ TimeMark now;
+ long wait;
if (appData.noChessProgram) return;
// forwardMostMove = currentMove;
TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
- ResurrectChessProgram(); /* in case first program isn't running */
- if(WaitForEngine(&second, TwoMachinesEventIfReady)) return;
+ if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
+
+ if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
- DisplayMessage("", _("Waiting for first chess program"));
- ScheduleDelayedEvent(TwoMachinesEvent, 10);
+ ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
return;
}
if(!stalling) {
- InitChessProgram(&second, FALSE);
+ InitChessProgram(&second, FALSE); // unbalances ping of second engine
SendToProgram("force\n", &second);
- }
- if(second.lastPing != second.lastPong) { // [HGM] second engine might have to reallocate hash
- if(!stalling) DisplayMessage("", _("Waiting for second chess program"));
stalling = 1;
- ScheduleDelayedEvent(TwoMachinesEvent, 10);
+ ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
return;
}
+ GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
+ if(appData.matchPause>10000 || appData.matchPause<10)
+ appData.matchPause = 10000; /* [HGM] make pause adjustable */
+ wait = SubtractTimeMarks(&now, &pauseStart);
+ if(wait < appData.matchPause) {
+ ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
+ return;
+ }
stalling = 0;
DisplayMessage("", "");
if (startedFromSetupPosition) {
gameMode = TwoMachinesPlay;
pausing = FALSE;
- ModeHighlight();
+ ModeHighlight(); // [HGM] logo: this triggers display update of logos
SetGameInfo();
DisplayTwoMachinesTitle();
firstMove = TRUE;
} else {
onmove = &second;
}
-
+ if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
SendToProgram(first.computerString, &first);
if (first.sendName) {
snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
DisplayComment(currentMove - 1, commentList[currentMove]);
}
+ DisplayBook(currentMove);
}
HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
// [HGM] PV info: routine tests if comment empty
DisplayComment(currentMove - 1, commentList[currentMove]);
+ DisplayBook(currentMove);
}
void
gameInfo.event = StrSave( appData.pgnEventHeader );
gameInfo.site = StrSave(HostName());
gameInfo.date = PGNDate();
- if (matchGame > 0) {
+ if (roundNr > 0) {
char buf[MSG_SIZ];
- snprintf(buf, MSG_SIZ, "%d", matchGame);
+ snprintf(buf, MSG_SIZ, "%d", roundNr);
gameInfo.round = StrSave(buf);
} else {
gameInfo.round = StrSave("-");
}
if (first.twoMachinesColor[0] == 'w') {
- gameInfo.white = StrSave(first.tidy);
- gameInfo.black = StrSave(second.tidy);
+ gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
+ gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
} else {
- gameInfo.white = StrSave(second.tidy);
- gameInfo.black = StrSave(first.tidy);
+ gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
+ gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
}
gameInfo.timeControl = TimeControlTagValue();
break;
snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
- gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
+ if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
+ gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
} else {
- gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
+ ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
+ if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
+ gameInfo.result = res;
}
gameInfo.resultDetails = StrSave(buf);
}
+ if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
}
}
if (isr != cps->isr) return; /* Killed intentionally */
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((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
- gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
+ if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
+ gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
} else {
- gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
+ ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
+ if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
+ gameInfo.result = res;
}
gameInfo.resultDetails = StrSave(buf);
}
- RemoveInputSource(cps->isr);
+ if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
} else {
snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
if ((cb == InitBackEnd3 && cps == &first) ||
(cb == SettingsMenuIfReady && cps == &second) ||
(cb == LoadEngine) ||
- (cb == TwoMachinesEventIfReady && cps == &second)) {
+ (cb == TwoMachinesEventIfReady)) {
CancelDelayedEvent();
ScheduleDelayedEvent(cb, val ? 1 : 3600000);
}
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
+ 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);
}
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;
+ }
+
+ 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)
// [HGM] vari: routines for shelving variations
void
-PushTail(int firstMove, int lastMove)
+PushInner(int firstMove, int lastMove)
{
int i, j, nrMoves = lastMove - firstMove;
- if(appData.icsActive) { // only in local mode
- forwardMostMove = currentMove; // mimic old ICS behavior
- return;
- }
- if(storedGames >= MAX_VARIATIONS-1) return;
-
// push current tail of game on stack
savedResult[storedGames] = gameInfo.result;
savedDetails[storedGames] = gameInfo.resultDetails;
storedGames++;
forwardMostMove = firstMove; // truncate game so we can start variation
+}
+
+void
+PushTail(int firstMove, int lastMove)
+{
+ if(appData.icsActive) { // only in local mode
+ forwardMostMove = currentMove; // mimic old ICS behavior
+ return;
+ }
+ if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
+
+ PushInner(firstMove, lastMove);
if(storedGames == 1) GreyRevert(FALSE);
}
-Boolean
-PopTail(Boolean annotate)
+void
+PopInner(Boolean annotate)
{
int i, j, nrMoves;
char buf[8000], moveBuf[20];
- if(appData.icsActive) return FALSE; // only in local mode
- if(!storedGames) return FALSE; // sanity
- CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
-
storedGames--;
ToNrEvent(savedFirst[storedGames]); // sets currentMove
nrMoves = savedLast[storedGames] - currentMove;
}
gameInfo.resultDetails = savedDetails[storedGames];
forwardMostMove = currentMove + nrMoves;
+}
+
+Boolean
+PopTail(Boolean annotate)
+{
+ if(appData.icsActive) return FALSE; // only in local mode
+ if(!storedGames) return FALSE; // sanity
+ CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
+
+ PopInner(annotate);
+
if(storedGames == 0) GreyRevert(TRUE);
return TRUE;
}
PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
// kludge: use ParsePV() to append variation to game
move = currentMove;
- ParsePV(start, TRUE);
+ ParsePV(start, TRUE, TRUE);
forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
ClearPremoveHighlights();
CommentPopDown();