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 Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
FILE *WriteTourneyFile P((char *results, FILE *f));
void DisplayTwoMachinesTitle P(());
+static void ExcludeClick P((int index));
#ifdef WIN32
extern void ConsoleCreate();
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
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;
}
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
SendToProgram("force\n", savCps);
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();
}
static char resetOptions[] =
"-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
"-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
- "-firstOptions \"\" -firstNPS -1 -fn \"\"";
+ "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
+ "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
+
+void
+FloatToFront(char **list, char *engineLine)
+{
+ char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
+ int i=0;
+ if(appData.recentEngines <= 0) return;
+ TidyProgramName(engineLine, "localhost", tidy+1);
+ tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
+ strncpy(buf+1, *list, MSG_SIZ-50);
+ if(p = strstr(buf, tidy)) { // tidy name appears in list
+ q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
+ while(*p++ = *++q); // squeeze out
+ }
+ strcat(tidy, buf+1); // put list behind tidy name
+ p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
+ if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
+ ASSIGN(*list, tidy+1);
+}
+
+char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
void
Load (ChessProgramState *cps, int i)
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; appData.pvSAN[0] = FALSE;
+ 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(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;
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);
}
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;
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) {
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
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) {
board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
board[i][j];
}
+ board[HOLDINGS_SET] = 0;
gameInfo.boardWidth = newWidth;
gameInfo.boardHeight = newHeight;
gameInfo.holdingsWidth = newHoldingsWidth;
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;
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;
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;
if (looking_at(buf, &i, "% ") ||
((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
&& looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
- if(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();
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 */
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 ||
} 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);
}
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
first.option[multi].value = n;
*start = *end = 0;
return FALSE;
+ } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
+ ExcludeClick(origIndex - lineStart);
+ return FALSE;
}
ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
*start = startPV; *end = index-1;
{
int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
if(endPV < 0) return;
+ if(appData.autoCopyPV) CopyFENToClipboard();
endPV = -1;
if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
Boolean saveAnimate = appData.animate;
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);
+ SendToProgram(buf, &first);
+ return (state == '+');
+}
+
+static void
+ExcludeClick (int index)
+{
+ int i, j;
+ Exclusion *e = excluTab;
+ if(index < 25) { // none, best or tail clicked
+ if(index < 13) { // none: include all
+ WriteMap(0); // clear map
+ for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
+ SendToProgram("include all\n", &first); // and inform engine
+ } else if(index > 18) { // tail
+ if(exclusionHeader[19] == '-') { // tail was excluded
+ SendToProgram("include all\n", &first);
+ WriteMap(0); // clear map completely
+ // now re-exclude selected moves
+ for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
+ ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
+ } else { // tail was included or in mixed state
+ SendToProgram("exclude all\n", &first);
+ WriteMap(0xFF); // fill map completely
+ // now re-include selected moves
+ j = 0; // count them
+ for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
+ ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
+ if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
+ }
+ } else { // best
+ ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
+ }
+ } else {
+ for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
+ char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
+ ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
+ break;
+ }
+ }
+}
+
ChessSquare
DefaultPromoChoice (int white)
{
int lastLoadGameUseList = FALSE;
char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
ChessMove lastLoadGameStart = EndOfFile;
+int doubleClick;
void
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
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(doubleClick) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
+ if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
+ ClearPremoveHighlights(); // was included
+ else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated by premove highlights
+ return;
+ }
+
FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
}
else forwardMostMove = currentMove;
}
+ ClearMap();
+
/* If we need the chess program but it's dead, restart it */
ResurrectChessProgram();
MarkTargetSquares (int clear)
{
int x, y;
+ 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;
if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
!appData.testLegality || gameMode == EditPosition) return;
- if(clear) {
- for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
- } else {
+ if(!clear) {
int capt = 0;
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) marker[y][x] = 0;
}
}
- DrawPosition(TRUE, NULL);
+ DrawPosition(FALSE, NULL);
}
int
{
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();
defaultPromoChoice = promoSweep;
promoSweep = EmptySquare; // terminate sweep
promoDefaultAltered = TRUE;
- if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
+ if(!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
|| 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;
}
+ doubleClick = FALSE;
fromX = x; fromY = y; toX = toY = -1;
if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
// even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
if (OKToStartUserMove(fromX, fromY)) {
second = 0;
MarkTargetSquares(0);
+ 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;
}
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)) {
(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);
/* 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;
- MarkTargetSquares(1);
if (clickType == Press) {
if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
// must be Edit Position mode with empty-square selected
if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
return;
}
- if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
+ if(HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
+ if(appData.sweepSelect) {
ChessSquare piece = boards[currentMove][fromY][fromX];
- DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
promoSweep = defaultPromoChoice;
if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
selectFlag = 0; lastX = xPix; lastY = yPix;
Sweep(0); // Pawn that is going to promote: preview promotion piece
+ sweepSelecting = 1;
DisplayMessage("", _("Pull pawn backwards to under-promote"));
- DrawPosition(FALSE, boards[currentMove]);
- return;
+ MarkTargetSquares(1);
+ }
+ return; // promo popup appears on up-click
}
/* Finish clickclick move */
if (appData.animate || appData.highlightLastMove) {
/* Don't animate move and drag both */
appData.animate = FALSE;
}
+ MarkTargetSquares(1);
// moves into holding are invalid for now (except in EditPosition, adapting to-square)
if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
// 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, appData.sweepSelect)) {
SetHighlights(fromX, fromY, toX, toY);
}
} 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
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
ChessMove moveType;
char promoChar;
char *p, *pv=buf1;
- int machineWhite;
+ 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
/*
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)) {
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);
/*
* 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;
}
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 || gameInfo.variant == VariantGrand)
&& promoChar != NULLCHAR && gameInfo.holdingsSize) {
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]);
- }
}
return f;
}
-#define MAXENGINES 1000
char *command[MAXENGINES], *mnemonic[MAXENGINES];
void
q = r; while(*q) nPlayers += (*q++ == '\n');
p = buf; while(*r && (*p = *r++) != '\n') p++;
*p = NULLCHAR;
- NamesToList(firstChessProgramNames, command, mnemonic);
+ 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;
return 1;
}
-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] = engineMnemonic[i] = NULL;
+ return i;
}
// following implemented as macro to avoid type limitations
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
+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; appData.pvSAN[0] = FALSE;
- appData.firstHasOwnBookUCI = !appData.defNoBook;
+ appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
ParseArgsFromString(buf);
}
free(engineName);
+ return i;
+}
+
+char *recentEngines;
+
+void
+RecentEngineEvent (int nr)
+{
+ int n;
+// SwapEngines(1); // bump first to second
+// ReplaceEngine(&second, 1); // and load it there
+ NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
+ n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
+ if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
+ ReplaceEngine(&first, 0);
+ FloatToFront(&appData.recentEngineList, command[n]);
+ }
}
int
*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;
matchGame = 1; roundNr = nr / syncInterval + 1;
}
- if(first.pr != NoProc || second.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) {
+ SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
+ InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
+ }
+ if(second.pr == NoProc) {
+ SwapEngines(1);
+ SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
+ SwapEngines(1); // and make that valid for second engine by swapping
+ InitEngine(&second, 1);
+ }
CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
UpdateLogos(FALSE); // leave display to ModeHiglight()
return 1;
{ // performs game initialization that does not invoke engines, and then tries to start the game
int res, firstWhite, swapColors = 0;
if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
+ if(matchMode && appData.debugMode) { // [HGM] debug split: game is part of a match; we might have to create a debug file just for this game
+ char buf[MSG_SIZ];
+ snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
+ if(strcmp(buf, currentDebugFile)) { // name has changed
+ FILE *f = fopen(buf, "w");
+ if(f) { // if opening the new file failed, just keep using the old one
+ ASSIGN(currentDebugFile, buf);
+ fclose(debugFP);
+ debugFP = f;
+ }
+ if(appData.serverFileName) {
+ if(serverFP) fclose(serverFP);
+ serverFP = fopen(appData.serverFileName, "w");
+ if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
+ if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
+ }
+ }
+ }
firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
DisplayMessage("", "");
HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
+ ClearMap(); // [HGM] exclude: invalidate map
}
void
continue;
if (appData.timeDelay < 0)
return;
- StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
+ StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
break;
}
}
piece = pieceList[piece]; // first two elements of pieceList contain King numbers
from = pieceList[piece]; // so this must be King
quickBoard[from] = 0;
- quickBoard[to] = piece;
pieceList[piece] = to;
- move++;
- continue;
+ 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;
AnalyzeFileEvent();
}
- if (!matchMode && pos >= 0) {
+ if (!matchMode && pos > 0) {
ToNrEvent(pos); // [HGM] no autoplay if selected on position
} else
if (matchMode || appData.timeDelay == 0) {
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);
StartAnalysisClock();
GetTimeMark(&lastNodeCountTime);
lastNodeCount = 0;
- if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
+ if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
}
void
} else {
/* kludge: allow timeout for initial "feature" command */
FreezeUI();
- snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
+ snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
DisplayMessage("", buf);
ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
}
ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
return;
}
+
+ if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
+ 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) {
currentMove = forwardMostMove = backwardMostMove = 0;
HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
DisplayMove(-1);
+ if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
}
void
} else
boards[0][y][x] = selection;
DrawPosition(TRUE, boards[0]);
+ ClearHighlights();
+ fromX = fromY = -1;
}
break;
}
void
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)
}
DisplayBothClocks();
DisplayMove(currentMove - 1);
- DrawPosition(FALSE, boards[currentMove]);
+ DrawPosition(oldSeekGraphUp, boards[currentMove]);
HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
DisplayComment(currentMove - 1, commentList[currentMove]);
}
+ ClearMap(); // [HGM] exclude: invalidate map
}
target, currentMove, forwardMostMove);
if (gameMode == EditPosition) return;
+ seekGraphUp = FALSE;
MarkTargetSquares(1);
if (currentMove <= backwardMostMove) {
ClearHighlights();
HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
// [HGM] PV info: routine tests if comment empty
DisplayComment(currentMove - 1, commentList[currentMove]);
+ ClearMap(); // [HGM] exclude: invalidate map
}
void
void
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) {
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);
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; }
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);
}
}
}
/* [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;
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;
continue;
}
if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
- if (StringFeature(&p, "myname", &cps->tidy, cps)) {
+ if (StringFeature(&p, "myname", cps->tidy, cps)) {
if (gameMode == TwoMachinesPlay) {
DisplayTwoMachinesTitle();
} else {
}
continue;
}
- if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
+ if (StringFeature(&p, "variants", cps->variants, cps)) continue;
if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
+ if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
- if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
- if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
+ if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
+ if (StringFeature(&p, "option", buf, cps)) {
+ FREE(cps->option[cps->nrOptions].name);
+ cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
+ safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
SendToProgram(buf, cps);
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) {
}
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);
(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
( *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;