* Massachusetts.
*
* Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
- * 2007, 2008, 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
+ * 2007, 2008, 2009, 2010, 2011, 2012, 2013 Free Software Foundation, Inc.
*
* Enhancements Copyright 2005 Alessandro Scotti
*
# include "zippy.h"
#endif
#include "backendz.h"
+#include "evalgraph.h"
#include "gettext.h"
#ifdef ENABLE_NLS
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));
+void ToggleSecond P((void));
+void PauseEngine P((ChessProgramState *cps));
+static int NonStandardBoardSize P((void));
#ifdef WIN32
extern void ConsoleCreate();
ChessSquare pieceSweep = EmptySquare;
ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
int promoDefaultAltered;
+int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
/* States for ics_getting_history */
#define H_FALSE 0
{
dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
if(appData.debugMode)
- fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
+ fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
}
return dst;
case VariantShatranj:
case VariantCourier:
case VariantMakruk:
+ case VariantASEAN:
case VariantGrand:
flags &= ~F_ALL_CASTLE_OK;
break;
/* end premove variables */
char *ics_prefix = "$";
-int ics_type = ICS_GENERIC;
+enum ICS_TYPE ics_type = ICS_GENERIC;
int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
int pauseExamForwardMostMove = 0;
char white_holding[64], black_holding[64];
TimeMark lastNodeCountTime;
long lastNodeCount=0;
-int shiftKey; // [HGM] set by mouse handler
+int shiftKey, controlKey; // [HGM] set by mouse handler
int have_sent_ICS_logon = 0;
int movesPerSession;
int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
-long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
+long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
Boolean adjustedClock;
long timeControl_2; /* [AS] Allow separate time controls */
-char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
+char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
long timeRemaining[2][MAX_MOVES];
int matchGame = 0, nextGame = 0, roundNr = 0;
-Boolean waitingForGame = FALSE;
+Boolean waitingForGame = FALSE, startingEngine = FALSE;
TimeMark programStartTime, pauseStart;
char ics_handle[MSG_SIZ];
int have_set_title = 0;
BlackKing, BlackMan, BlackKnight, BlackRook }
};
+ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
+ { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
+ WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
+ { BlackRook, BlackKnight, BlackMan, BlackFerz,
+ BlackKing, BlackMan, BlackKnight, BlackRook }
+};
+
#if (BOARD_FILES>=10)
ChessSquare ShogiArray[2][BOARD_FILES] = {
cps->sendName = appData.icsActive;
cps->sdKludge = FALSE;
cps->stKludge = FALSE;
+ if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
TidyProgramName(cps->program, cps->host, cps->tidy);
cps->matchWins = 0;
- safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
+ ASSIGN(cps->variants, appData.variant);
cps->analysisSupport = 2; /* detect */
cps->analyzing = FALSE;
cps->initDone = FALSE;
+ cps->reload = FALSE;
/* New features added by Tord: */
cps->useFEN960 = FALSE;
cps->supportsNPS = UNKNOWN;
cps->memSize = FALSE;
cps->maxCores = FALSE;
- cps->egtFormats[0] = NULLCHAR;
+ ASSIGN(cps->egtFormats, "");
/* [HGM] options */
cps->optionSettings = appData.engOptions[n];
ChessProgramState *savCps;
+GameMode oldMode;
+
void
LoadEngine ()
{
if(gameInfo.variant != StringToVariant(appData.variant)) {
// we changed variant when loading the engine; this forces us to reset
Reset(TRUE, savCps != &first);
- EditGameEvent(); // for consistency with other path, as Reset changes mode
+ oldMode = BeginningOfGame; // to prevent restoring old mode
}
InitChessProgram(savCps, FALSE);
- SendToProgram("force\n", savCps);
+ if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
DisplayMessage("", "");
if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
- for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
+ for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
ThawUI();
SetGNUMode();
+ if(oldMode == AnalyzeMode) AnalyzeModeEvent();
}
void
ReplaceEngine (ChessProgramState *cps, int n)
{
- EditGameEvent();
+ oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
+ keepInfo = 1;
+ if(oldMode != BeginningOfGame) EditGameEvent();
+ keepInfo = 0;
UnloadEngine(cps);
appData.noChessProgram = FALSE;
appData.clockMode = TRUE;
extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
-static char resetOptions[] =
+static char resetOptions[] =
"-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
"-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)
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);
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;
if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
- } else appData.directory[i] = ".";
+ } else { ASSIGN(appData.directory[i], "."); }
if(params[0]) {
if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
snprintf(command, MSG_SIZ, "%s %s", p, params);
p = command;
}
- appData.chessProgram[i] = strdup(p);
+ ASSIGN(appData.chessProgram[i], p);
appData.isUCI[i] = isUCI;
appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
appData.hasOwnBookUCI[i] = hasBook;
if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
- quote, p, quote, appData.directory[i],
+ quote, p, quote, appData.directory[i],
useNick ? " -fn \"" : "",
useNick ? nickName : "",
useNick ? "\"" : "",
isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
storeVariant ? " -variant " : "",
storeVariant ? VariantName(gameInfo.variant) : "");
+ if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
- snprintf(firstChessProgramNames, len, "%s%s", q, buf);
+ if(insert != q) insert[-1] = NULLCHAR;
+ snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
if(q) free(q);
FloatToFront(&appData.recentEngineList, buf);
}
case Variant3Check: /* should work except for win condition */
case VariantShatranj: /* should work except for all win conditions */
case VariantMakruk: /* should work except for draw countdown */
+ case VariantASEAN : /* should work except for draw countdown */
case VariantBerolina: /* might work if TestLegality is off */
case VariantCapaRandom: /* should work */
case VariantJanus: /* should work */
long time, increment;
char *s = tcString;
- if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
+ 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(mps)
snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
- else
+ else
snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
} else {
if(mps)
snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
- else
+ else
snprintf(buf, MSG_SIZ, ":%s", mytc);
}
fullTimeControlString = StrSave(buf); // this should now be in PGN format
-
+
if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
return FALSE;
}
InitBackEnd2 ()
{
if (appData.debugMode) {
- fprintf(debugFP, "%s\n", programVersion);
+# ifdef __GIT_VERSION
+ fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
+# else
+ fprintf(debugFP, "Version: %s\n", programVersion);
+# endif
}
ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
{
int outError, outCount;
static int gotEof = 0;
+ static FILE *ini;
/* Pass data read from player on to ICS */
if (count > 0) {
if (outCount < count) {
DisplayFatalError(_("Error writing to ICS"), outError, 1);
}
+ if(have_sent_ICS_logon == 2) {
+ if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
+ fprintf(ini, "%s", message);
+ have_sent_ICS_logon = 3;
+ } else
+ have_sent_ICS_logon = 1;
+ } else if(have_sent_ICS_logon == 3) {
+ fprintf(ini, "%s", message);
+ fclose(ini);
+ have_sent_ICS_logon = 1;
+ }
} else if (count < 0) {
RemoveInputSource(isr);
DisplayFatalError(_("Error reading from keyboard"), error, 1);
}
}
if (appData.debugMode) {
- fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
+ fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
e, wnum, VariantName(v));
}
return v;
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;
}
void
+PlotSingleSeekAd (int i)
+{
+ PlotSeekAd(i);
+}
+
+void
AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
{
char buf[MSG_SIZ], *ext = "";
seekNrList[nrOfSeekAds] = nr;
zList[nrOfSeekAds] = 0;
seekAdList[nrOfSeekAds++] = StrSave(buf);
- if(plot) PlotSeekAd(nrOfSeekAds-1);
+ if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
}
}
for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
yy = h-1-yy;
- DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
+ DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
if(i%500 == 0) {
char buf[MSG_SIZ];
snprintf(buf, MSG_SIZ, "%d", i);
continue;
}
- if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
- ICSInitScript();
- have_sent_ICS_logon = 1;
+ if (looking_at(buf, &i, "login:")) {
+ if (!have_sent_ICS_logon) {
+ if(ICSInitScript())
+ have_sent_ICS_logon = 1;
+ else // no init script was found
+ have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
+ } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
+ have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
+ }
continue;
}
gameInfo.whiteRating = string_to_rating(star_match[1]);
gameInfo.blackRating = string_to_rating(star_match[3]);
if (appData.debugMode)
- fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
+ fprintf(debugFP, "Ratings from header: W %d, B %d\n",
gameInfo.whiteRating, gameInfo.blackRating);
}
continue;
void
ParseBoard12 (char *string)
{
+#if ZIPPY
+ int i, takeback;
+ char *bookHit = NULL; // [HGM] book
+#endif
GameMode newGameMode;
- int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
- int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
+ int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
+ int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
char to_play, board_chars[200];
char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
int fromX, fromY, toX, toY;
char promoChar;
int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
- char *bookHit = NULL; // [HGM] book
Boolean weird = FALSE, reqFlag = FALSE;
fromX = fromY = toX = toY = -1;
newGame = FALSE;
if (appData.debugMode)
- fprintf(debugFP, _("Parsing board: %s\n"), string);
+ fprintf(debugFP, "Parsing board: %s\n", string);
move_str[0] = NULLCHAR;
elapsed_time[0] = NULLCHAR;
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);
- DisplayMessage(partnerStatus, "");
+ 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);
+ if(!twoBoards) 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 */
to canonical algebraic form. */
if (moveNum > 0) {
if (appData.debugMode) {
- if (appData.debugMode) { int f = forwardMostMove;
- fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
- boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
- boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
- }
+ int f = forwardMostMove;
+ fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
+ boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
+ boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
fprintf(debugFP, "moveNum = %d\n", moveNum);
fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
}
void
+SendToBoth (char *msg)
+{ // to make it easy to keep two engines in step in dual analysis
+ SendToProgram(msg, &first);
+ if(second.analyzing) SendToProgram(msg, &second);
+}
+
+void
AnalysisPeriodicEvent (int force)
{
if (((programStats.ok_to_send == 0 || programStats.line_is_book)
return;
/* Send . command to Crafty to collect stats */
- SendToProgram(".\n", &first);
+ SendToBoth(".\n");
/* Don't send another until we get a response (this makes
us stop sending to old Crafty's which don't understand
break;
case WhitePromotion:
case BlackPromotion:
- if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
+ if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
+ gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
PieceToChar(WhiteFerz));
SendToICS(ics_prefix);
SendToICS(buf);
if(startedFromSetupPosition || backwardMostMove != 0) {
- fen = PositionToFEN(backwardMostMove, NULL);
+ fen = PositionToFEN(backwardMostMove, NULL, 1);
if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
SendToICS(buf);
for(i = backwardMostMove; i<last; i++) {
char buf[20];
snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
+ if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
+ int len = strlen(moveList[i]);
+ snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
+ if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
+ }
SendToICS(buf);
}
SendToICS(ics_prefix);
} 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);
}
Boolean valid;
int nr = 0;
- if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
+ lastParseAttempt = pv; if(!*pv) return; // turns out we crash when we parse an empty PV
+ if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
pushed = TRUE;
}
return -1;
}
+Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
+
Boolean
-LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end)
+LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
{
int startPV, multi, lineStart, origIndex = index;
char *p, buf2[MSG_SIZ];
+ ChessProgramState *cps = (pane ? &second : &first);
if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
lastX = x; lastY = y;
do{ while(buf[index] && buf[index] != '\n') index++;
} while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
buf[index] = 0;
- if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
- int n = first.option[multi].value;
+ if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
+ int n = cps->option[multi].value;
if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
- if(first.option[multi].value != n) SendToProgram(buf2, &first);
- first.option[multi].value = n;
+ if(cps->option[multi].value != n) SendToProgram(buf2, cps);
+ cps->option[multi].value = n;
*start = *end = 0;
return FALSE;
+ } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
+ ExcludeClick(origIndex - lineStart);
+ return FALSE;
}
ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
*start = startPV; *end = index-1;
+ extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
return TRUE;
}
static char buf[10*MSG_SIZ];
int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
*buf = NULLCHAR;
- if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
+ if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
for(i = forwardMostMove; i<endPV; i++){
if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
k += strlen(buf+k);
}
snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
+ if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
endPV = savedEnd;
return buf;
int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
lastX = x; lastY = y;
ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
+ extendGame = FALSE;
return TRUE;
}
if(endPV < 0) return;
if(appData.autoCopyPV) CopyFENToClipboard();
endPV = -1;
- if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
+ if(extendGame && currentMove > forwardMostMove) {
Boolean saveAnimate = appData.animate;
if(pushed) {
if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
case VariantMakruk:
pieces = makrukArray;
nrCastlingRights = 0;
- startedFromSetupPosition = TRUE;
SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
break;
+ case VariantASEAN:
+ pieces = aseanArray;
+ nrCastlingRights = 0;
+ SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
+ break;
case VariantTwoKings:
pieces = twoKingsArray;
break;
case VariantSChess:
SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
gameInfo.holdingsSize = 7;
+ for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
break;
case VariantJanus:
pieces = JanusArray;
pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
if(pawnRow < 1) pawnRow = 1;
- if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
+ if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN || gameInfo.variant == VariantGrand) pawnRow = 2;
/* User pieceToChar list overrules defaults */
if(appData.pieceToCharTable != NULL)
initialPosition[2][0] = BlackAngel;
initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
- initialPosition[1][1] = initialPosition[2][1] =
+ initialPosition[1][1] = initialPosition[2][1] =
initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
}
if (appData.debugMode) {
char message[MSG_SIZ];
if (cps->useSetboard) {
- char* fen = PositionToFEN(moveNum, cps->fenOverride);
+ char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
snprintf(message, MSG_SIZ,"setboard %s\n", fen);
SendToProgram(message, cps);
free(fen);
setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
}
+char exclusionHeader[MSG_SIZ];
+int exCnt, excludePtr;
+typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
+static Exclusion excluTab[200];
+static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
+
+static void
+WriteMap (int s)
+{
+ int j;
+ for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
+ exclusionHeader[19] = s ? '-' : '+'; // update tail state
+}
+
+static void
+ClearMap ()
+{
+ safeStrCpy(exclusionHeader, "exclude: none best +tail \n", MSG_SIZ);
+ excludePtr = 24; exCnt = 0;
+ WriteMap(0);
+}
+
+static void
+UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
+{ // search given move in table of header moves, to know where it is listed (and add if not there), and update state
+ char buf[2*MOVE_LEN], *p;
+ Exclusion *e = excluTab;
+ int i;
+ for(i=0; i<exCnt; i++)
+ if(e[i].ff == fromX && e[i].fr == fromY &&
+ e[i].tf == toX && e[i].tr == toY && e[i].pc == promoChar) break;
+ if(i == exCnt) { // was not in exclude list; add it
+ CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
+ if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
+ if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
+ return; // abort
+ }
+ e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
+ excludePtr++; e[i].mark = excludePtr++;
+ for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
+ exCnt++;
+ }
+ exclusionHeader[e[i].mark] = state;
+}
+
+static int
+ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
+{ // include or exclude the given move, as specified by state ('+' or '-'), or toggle
+ char buf[MSG_SIZ];
+ int j, k;
+ ChessMove moveType;
+ if((signed char)promoChar == -1) { // kludge to indicate best move
+ if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
+ return 1; // if unparsable, abort
+ }
+ // update exclusion map (resolving toggle by consulting existing state)
+ k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
+ j = k%8; k >>= 3;
+ if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
+ if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
+ excludeMap[k] |= 1<<j;
+ else excludeMap[k] &= ~(1<<j);
+ // update header
+ UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
+ // inform engine
+ snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
+ CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
+ SendToBoth(buf);
+ return (state == '+');
+}
+
+static void
+ExcludeClick (int index)
+{
+ int i, j;
+ Exclusion *e = excluTab;
+ if(index < 25) { // none, best or tail clicked
+ if(index < 13) { // none: include all
+ WriteMap(0); // clear map
+ for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
+ SendToBoth("include all\n"); // and inform engine
+ } else if(index > 18) { // tail
+ if(exclusionHeader[19] == '-') { // tail was excluded
+ SendToBoth("include all\n");
+ WriteMap(0); // clear map completely
+ // now re-exclude selected moves
+ for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
+ ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
+ } else { // tail was included or in mixed state
+ SendToBoth("exclude all\n");
+ WriteMap(0xFF); // fill map completely
+ // now re-include selected moves
+ j = 0; // count them
+ for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
+ ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
+ if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
+ }
+ } else { // best
+ ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
+ }
+ } else {
+ for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
+ char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
+ ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
+ break;
+ }
+ }
+}
+
ChessSquare
DefaultPromoChoice (int white)
{
ChessSquare result;
- if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
+ if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
+ gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
result = WhiteFerz; // no choice
else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
result= WhiteKing; // in Suicide Q is the last thing we want
}
// we either have a choice what to promote to, or (in Shogi) whether to promote
- if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
+ if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
+ gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
*promoChoice = PieceToChar(BlackFerz); // no choice
return FALSE;
}
}
Boolean
-OnlyMove (int *x, int *y, Boolean captures)
+OnlyMove (int *x, int *y, Boolean captures)
{
DisambiguateClosure cl;
if (appData.zippyPlay || !appData.testLegality) return FALSE;
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( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
if( pup != EmptySquare ) return;
moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
- if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
+ if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
// holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
- while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
+ while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
fromY = DROP_RANK;
}
}
}
+ if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
+ if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
+ ClearPremoveHighlights(); // was included
+ else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated by premove highlights
+ return;
+ }
+
FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
}
else forwardMostMove = currentMove;
}
+ ClearMap();
+
/* If we need the chess program but it's dead, restart it */
ResurrectChessProgram();
gameMode == MachinePlaysBlack)) {
SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
}
- if (gameMode != EditGame && gameMode != PlayFromGameFile) {
+ if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
// [HGM] book: if program might be playing, let it use book
bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
first.maybeThinking = TRUE;
} else if(fromY == DROP_RANK && fromX == EmptySquare) {
if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
SendBoard(&first, currentMove+1);
- } else SendMoveToProgram(forwardMostMove-1, &first);
+ if(second.analyzing) {
+ if(!second.useSetboard) SendToProgram("undo\n", &second);
+ SendBoard(&second, currentMove+1);
+ }
+ } else {
+ SendMoveToProgram(forwardMostMove-1, &first);
+ if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
+ }
if (currentMove == cmailOldMove + 1) {
cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
}
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
if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantXiangqi ||
gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
- gameInfo.variant == VariantMakruk) return FALSE;
+ gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) return FALSE;
return (piece == BlackPawn && y == 1 ||
piece == WhitePawn && y == BOARD_HEIGHT-2 ||
piece == BlackLance && y == 1 ||
{
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(SeekGraphClick(clickType, xPix, yPix, 0)) return;
+ prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
+
if (clickType == Press) ErrorPopDown();
x = EventToSquare(xPix, BOARD_WIDTH);
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
ClearPremoveHighlights();
}
- if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
+ 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;
+ if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
+ doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
+ }
fromX = x; fromY = y; toX = toY = -1;
if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
// even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
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);
}
}
if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
- second = FALSE;
+ second = FALSE;
}
// ignore clicks on holdings
if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
/* Undo animation damage if any */
DrawPosition(FALSE, NULL);
}
- if (second) {
+ if (second || sweepSelecting) {
/* Second up/down in same square; just abort move */
- second = 0;
+ if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
+ second = sweepSelecting = 0;
fromX = fromY = -1;
gatingPiece = EmptySquare;
ClearHighlights();
/* we now have a different from- and (possibly off-board) to-square */
/* Completed move */
- toX = x;
- toY = y;
+ if(!sweepSelecting) {
+ toX = x;
+ toY = y;
+ } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
+
saveAnimate = appData.animate;
- 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) {
ClearHighlights();
}
} else {
+#if 0
+// [HGM] this must be done after the move is made, as with arrow it could lead to a board redraw with piece still on from square
/* Finish drag move */
if (appData.highlightLastMove) {
SetHighlights(fromX, fromY, toX, toY);
} else {
ClearHighlights();
}
+#endif
DragPieceEnd(xPix, yPix); dragging = 0;
/* Don't animate move and drag both */
appData.animate = FALSE;
}
ClearHighlights();
fromX = fromY = -1;
+ MarkTargetSquares(1);
DrawPosition(TRUE, boards[currentMove]);
return;
}
// off-board moves should not be highlighted
if(x < 0 || y < 0) ClearHighlights();
- if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
+ if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
SetHighlights(fromX, fromY, toX, toY);
+ MarkTargetSquares(1);
if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
// [HGM] super: promotion to captured piece selected from holdings
ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
Explode(boards[currentMove-1], fromX, fromY, toX, toY))
DrawPosition(TRUE, boards[currentMove]);
+ MarkTargetSquares(1);
fromX = fromY = -1;
}
appData.animate = saveAnimate;
|| majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
// TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
+ } else if(v == VariantKnightmate) {
+ if(nMine == 1) return FALSE;
+ if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
} else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
// In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
// Actually ending the game is now based on the additional internal condition canAdjudicate.
// Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
- int k, count = 0; static int bare = 1;
+ int k, drop, count = 0; static int bare = 1;
ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
Boolean canAdjudicate = !appData.icsActive;
boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
EP_CHECKMATE : EP_WINS);
- else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
+ else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
}
break;
case MT_CHECKMATE:
reason = "Xboard adjudication: Checkmate";
boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
+ if(gameInfo.variant == VariantShogi) {
+ if(forwardMostMove > backwardMostMove
+ && moveList[forwardMostMove-1][1] == '@'
+ && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
+ reason = "XBoard adjudication: pawn-drop mate";
+ boards[forwardMostMove][EP_STATUS] = EP_WINS;
+ }
+ }
break;
}
/* Check for rep-draws */
count = 0;
+ drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
+ && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
for(k = forwardMostMove-2;
- k>=backwardMostMove && k>=forwardMostMove-100 &&
+ k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
(signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
- (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
+ (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
k-=2)
{ int rights=0;
if(CompareBoards(boards[k], boards[forwardMostMove])) {
/* adjudicate after user-specified nr of repeats */
int result = GameIsDrawn;
char *details = "XBoard adjudication: repetition draw";
- if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
+ if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
// [HGM] xiangqi: check for forbidden perpetuals
int m, ourPerpetual = 1, hisPerpetual = 1;
for(m=forwardMostMove; m>k; m-=2) {
if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
break; // (or we would have caught him before). Abort repetition-checking loop.
} else
+ if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
+ if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
+ result = BlackWins;
+ details = "Xboard adjudication: repetition";
+ }
+ } else // it must be XQ
// Now check for perpetual chases
if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
hisPerpetual = PerpetualChase(k, forwardMostMove);
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
}
static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
+static ChessProgramState *stalledEngine;
+static char stashedInputMove[MSG_SIZ];
void
HandleMachineMove (char *message, ChessProgramState *cps)
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
/*
if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
(sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
{
+ if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
+ if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
+ safeStrCpy(stashedInputMove, message, MSG_SIZ);
+ stalledEngine = cps;
+ if(appData.ponderNextMove) { // bring opponent out of ponder
+ if(gameMode == TwoMachinesPlay) {
+ if(cps->other->pause)
+ PauseEngine(cps->other);
+ else
+ SendToProgram("easy\n", cps->other);
+ }
+ }
+ StopClocks();
+ return;
+ }
+
/* This method is only useful on engines that support ping */
if (cps->lastPing != cps->lastPong) {
if (gameMode == BeginningOfGame) {
/* Machine move could not be parsed; ignore it. */
snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
machineMove, _(cps->which));
- DisplayError(buf1, 0);
+ DisplayMoveError(buf1);
snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
if (gameMode == TwoMachinesPlay) {
return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
}
- if ((!appData.testLegality || gameInfo.variant == VariantFairy) &&
- !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
+ if (!strncmp(message, "setup ", 6) &&
+ (!appData.testLegality || gameInfo.variant == VariantFairy || NonStandardBoardSize())
+ ) { // [HGM] allow first engine to define opening position
int dummy, s=6; char buf[MSG_SIZ];
if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
if(startedFromSetupPosition) return;
+ if(sscanf(message+s, "%dx%d+%d", &dummy, &dummy, &dummy) == 3) while(message[s] && message[s++] != ' '); // for compatibility with Alien Edition
ParseFEN(boards[0], &dummy, message+s);
DrawPosition(TRUE, boards[0]);
startedFromSetupPosition = TRUE;
snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
SendToICS(buf1);
}
- }
+ } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
return;
}
if (!strncmp(message, "tellall ", 8)) {
_(cps->which), cps->program, cps->host, message);
RemoveInputSource(cps->isr);
if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
- 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
- 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[CASTLING][i] = NoRights; // revoke for moved or captured piece
}
+ if(gameInfo.variant == VariantSChess) { // update virginity
+ if(fromY == 0) board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
+ if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
+ if(toY == 0) board[VIRGIN][toX] &= ~VIRGIN_W; // loss by capture
+ if(toY == BOARD_HEIGHT-1) board[VIRGIN][toX] &= ~VIRGIN_B;
+ }
+
if (fromX == toX && fromY == toY) return;
piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
) {
/* white pawn promotion */
board[toY][toX] = CharToPiece(ToUpper(promoChar));
- if(gameInfo.variant==VariantBughouse ||
- gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
+ if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
board[fromY][fromX] = EmptySquare;
} else if ((fromY >= BOARD_HEIGHT>>1)
+ && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
&& (toX != fromX)
&& gameInfo.variant != VariantXiangqi
&& gameInfo.variant != VariantBerolina
) {
/* black pawn promotion */
board[toY][toX] = CharToPiece(ToLower(promoChar));
- if(gameInfo.variant==VariantBughouse ||
- gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
+ if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
board[fromY][fromX] = EmptySquare;
} else if ((fromY < BOARD_HEIGHT>>1)
+ && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
&& (toX != fromX)
&& gameInfo.variant != VariantXiangqi
&& gameInfo.variant != VariantBerolina
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) {
&& fromX != toX && fromY != toY)
fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
// promotion suffix
- if(promoChar != NULLCHAR)
- fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
+ if(promoChar != NULLCHAR) {
+ if(fromY == 0 || fromY == BOARD_HEIGHT-1)
+ fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
+ ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
+ else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
+ }
if(!loadFlag) {
char buf[MOVE_LEN*2], *p; int len;
fprintf(serverMoves, "/%d/%d",
else timeLeft = blackTimeRemaining/1000;
fprintf(serverMoves, "/%d", timeLeft);
strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
+ if(p = strchr(buf, '/')) *p = NULLCHAR; else
if(p = strchr(buf, '=')) *p = NULLCHAR;
len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
fprintf(serverMoves, "/%s", buf);
AnimateMove(boards[forwardMostMove - 1],
fromX, fromY, toX, toY);
}
- if (appData.highlightLastMove) {
- SetHighlights(fromX, fromY, toX, toY);
- }
}
currentMove = forwardMostMove;
}
if (instant) return;
DisplayMove(currentMove - 1);
+ if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
+ if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
+ SetHighlights(fromX, fromY, toX, toY);
+ }
+ }
DrawPosition(FALSE, boards[currentMove]);
DisplayBothClocks();
HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
}
}
+static int
+NonStandardBoardSize ()
+{
+ /* [HGM] Awkward testing. Should really be a table */
+ int overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
+ if( gameInfo.variant == VariantXiangqi )
+ overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
+ if( gameInfo.variant == VariantShogi )
+ overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
+ if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
+ overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
+ if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
+ gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
+ overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
+ if( gameInfo.variant == VariantCourier )
+ overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
+ if( gameInfo.variant == VariantSuper )
+ overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
+ if( gameInfo.variant == VariantGreat )
+ overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
+ if( gameInfo.variant == VariantSChess )
+ overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
+ if( gameInfo.variant == VariantGrand )
+ overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
+ return overruled;
+}
+
void
InitChessProgram (ChessProgramState *cps, int setup)
/* setup needed to setup FRC opening position */
{
- char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
+ char buf[MSG_SIZ], b[MSG_SIZ];
if (appData.noChessProgram) return;
hintRequested = FALSE;
bookRequested = FALSE;
return;
}
- /* [HGM] make prefix for non-standard board size. Awkward testing... */
- overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
- if( gameInfo.variant == VariantXiangqi )
- overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
- if( gameInfo.variant == VariantShogi )
- overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
- if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
- overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
- if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
- gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
- overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
- if( gameInfo.variant == VariantCourier )
- overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
- if( gameInfo.variant == VariantSuper )
- overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
- if( gameInfo.variant == VariantGreat )
- overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
- if( gameInfo.variant == VariantSChess )
- overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
- if( gameInfo.variant == VariantGrand )
- overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
-
- if(overruled) {
+ if(NonStandardBoardSize()) { /* [HGM] make prefix for non-standard board size. */
snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
/* [HGM] varsize: try first if this defiant size variant is specifically known */
void
+ResendOptions (ChessProgramState *cps)
+{ // send the stored value of the options
+ int i;
+ char buf[MSG_SIZ];
+ Option *opt = cps->option;
+ for(i=0; i<cps->nrOptions; i++, opt++) {
+ switch(opt->type) {
+ case Spin:
+ case Slider:
+ case CheckBox:
+ snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
+ break;
+ case ComboBox:
+ snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
+ break;
+ default:
+ snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
+ break;
+ case Button:
+ case SaveButton:
+ continue;
+ }
+ SendToProgram(buf, cps);
+ }
+}
+
+void
StartChessProgram (ChessProgramState *cps)
{
char buf[MSG_SIZ];
cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
if (cps->protocolVersion > 1) {
snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
- cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
- cps->comboCnt = 0; // and values of combo boxes
+ if(!cps->reload) { // do not clear options when reloading because of -xreuse
+ cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
+ cps->comboCnt = 0; // and values of combo boxes
+ }
SendToProgram(buf, cps);
+ if(cps->reload) ResendOptions(cps);
} else {
SendToProgram("xboard\n", cps);
}
return;
}
DisplayMessage("", ""); curMess = 0;
- ThawUI();
TwoMachinesEvent();
}
fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
+ fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
+ fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
+ fprintf(f, "-bookDepth %d\n", appData.bookDepth);
+ fprintf(f, "-bookVariation %d\n", appData.bookStrength);
fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
+ fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
+ fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
+ fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
+ fprintf(f, "-smpCores %d\n", appData.smpCores);
if(searchTime > 0)
fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
else {
}
int
+CheckPlayers (char *participants)
+{
+ int i;
+ char buf[MSG_SIZ], *p;
+ NamesToList(firstChessProgramNames, command, mnemonic, "all");
+ while(p = strchr(participants, '\n')) {
+ *p = NULLCHAR;
+ for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
+ if(!mnemonic[i]) {
+ snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
+ *p = '\n';
+ DisplayError(buf, 0);
+ return 1;
+ }
+ *p = '\n';
+ participants = p + 1;
+ }
+ return 0;
+}
+
+int
CreateTourney (char *name)
{
FILE *f;
DisplayError(_("Not enough participants"), 0);
return 0;
}
+ if(CheckPlayers(appData.participants)) return 0;
ASSIGN(appData.tourneyFile, name);
if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
{
char buf[MSG_SIZ], *p, *q;
int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
+ insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
skip = !all && group[0]; // if group requested, we start in skip mode
for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
p = names; q = buf; header = 0;
*q = 0;
if(*p == '\n') p++;
if(buf[0] == '#') {
- if(strstr(buf, "# end") == buf) { depth--; continue; } // leave group, and suppress printing label
+ 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(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
if(engineList[i]) free(engineList[i]);
engineList[i] = strdup(buf);
- if(buf[0] != '#') TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
+ 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, " (");
SWAP(pgnName, p)
SWAP(pvSAN, h)
SWAP(engOptions, p)
+ SWAP(engInitString, p)
+ SWAP(computerString, p)
+ SWAP(features, p)
+ SWAP(fenOverride, p)
+ SWAP(NPS, h)
+ SWAP(accumulateTC, h)
+ SWAP(host, p)
+}
+
+int
+GetEngineLine (char *s, int n)
+{
+ int i;
+ char buf[MSG_SIZ];
+ extern char *icsNames;
+ if(!s || !*s) return 0;
+ NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
+ for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
+ if(!mnemonic[i]) return 0;
+ if(n == 11) return 1; // just testing if there was a match
+ snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
+ if(n == 1) SwapEngines(n);
+ ParseArgsFromString(buf);
+ if(n == 1) SwapEngines(n);
+ if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
+ SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
+ ParseArgsFromString(buf);
+ }
+ return 1;
}
int
ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
ParseArgsFromString(buf);
+ } else { // no engine with this nickname is installed!
+ snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
+ ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
+ matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
+ ModeHighlight();
+ DisplayError(buf, 0);
+ return 0;
}
free(engineName);
return i;
*blackPlayer = curRound + appData.tourneyType;
}
- // take care of white/black alternation per round.
+ // take care of white/black alternation per round.
// For cycles and games this is already taken care of by default, derived from matchGame!
return curRound & 1;
}
NextTourneyGame (int nr, int *swapColors)
{ // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
char *p, *q;
- int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
+ int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
FILE *tf;
if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
tf = fopen(appData.tourneyFile, "r");
SendToProgram(buf, &pairing);
return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
}
- pairingReceived = 0; // ... so we continue here
+ pairingReceived = 0; // ... so we continue here
*swapColors = 0;
appData.matchGames = appData.tourneyCycles * syncInterval - 1;
whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
// redefine engines, engine dir, etc.
NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
if(first.pr == NoProc) {
- SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
+ if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
}
if(second.pr == NoProc) {
SwapEngines(1);
- SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
+ if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
SwapEngines(1); // and make that valid for second engine by swapping
InitEngine(&second, 1);
}
CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
UpdateLogos(FALSE); // leave display to ModeHiglight()
- return 1;
+ return OK;
}
void
fromX = fromY = -1; // [HGM] abort any move the user is entering.
+ if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
+
if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
/* If we are playing on ICS, the server decides when the
game is over, but the engine can offer to draw, claim
resultDetails = buf;
}
/* (Claiming a loss is accepted no questions asked!) */
+ } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
+ forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
+ result = GameUnfinished;
+ if(!*appData.tourneyFile) matchGame--; // replay even in plain match
}
/* [HGM] bare: don't allow bare King to win */
if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
&& lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
) {
if (*appData.saveGameFile != NULLCHAR) {
+ if(result == GameUnfinished && matchMode && *appData.tourneyFile)
+ AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
+ else
SaveGameToFile(appData.saveGameFile, TRUE);
} else if (appData.autoSaveGames) {
- AutoSaveGame();
+ if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
}
if (*appData.savePositionFile != NULLCHAR) {
SavePositionToFile(appData.savePositionFile);
}
+ AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
}
}
SendToProgram("quit\n", &first);
DoSleep( appData.delayAfterQuit );
DestroyChildProcess(first.pr, first.useSigterm);
+ first.reload = TRUE;
}
first.pr = NoProc;
}
SendToProgram("quit\n", &second);
DoSleep( appData.delayAfterQuit );
DestroyChildProcess(second.pr, second.useSigterm);
+ second.reload = TRUE;
}
second.pr = NoProc;
}
- if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
+ if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
char resChar = '=';
switch (result) {
case WhiteWins:
break;
}
- if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
+ if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
ReserveGame(nextGame, resChar); // sets nextGame
if (appData.noChessProgram) return 1;
- if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
- if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
+ if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
+ if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
} else {
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;
}
}
+void
+AnalyzeNextGame()
+{
+ ReloadGame(1); // next game
+}
int
AutoPlayOneMove ()
if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
return FALSE;
- if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
+ if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
pvInfoList[currentMove].depth = programStats.depth;
pvInfoList[currentMove].score = programStats.score;
pvInfoList[currentMove].time = 0;
if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
+ else { // append analysis of final position as comment
+ char buf[MSG_SIZ];
+ snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
+ AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
+ }
+ programStats.depth = 0;
}
if (currentMove >= forwardMostMove) {
- if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
+ if(gameMode == AnalyzeFile) {
+ if(appData.loadGameIndex == -1) {
+ GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
+ ScheduleDelayedEvent(AnalyzeNextGame, 10);
+ } else {
+ ExitAnalyzeMode(); SendToProgram("force\n", &first);
+ }
+ }
// gameMode = EndOfGame;
// ModeHighlight();
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;
if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
reverseBoard[r][f] = piece;
}
- reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
+ reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
- || (boards[currentMove][CASTLING][2] == NoRights ||
+ || (boards[currentMove][CASTLING][2] == NoRights ||
boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
- && (boards[currentMove][CASTLING][5] == NoRights ||
+ && (boards[currentMove][CASTLING][5] == NoRights ||
boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
) {
flipSearch = TRUE;
}
GameInfo dummyInfo;
+static int creatingBook;
int
GameContainsPosition (FILE *f, ListGame *lg)
gn = 1;
}
else {
+ if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
+ appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
+ else
DisplayError(_("Game number out of range"), 0);
return FALSE;
}
cm = (ChessMove) Myylex();
}
+ if(!creatingBook) {
if (first.pr == NoProc) {
StartChessProgram(&first);
}
}
DisplayBothClocks();
}
+ }
/* [HGM] server: flag to write setup moves in broadcast file as one */
loadFlag = appData.suppressLoadMoves;
HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
- if (oldGameMode == AnalyzeFile ||
- oldGameMode == AnalyzeMode) {
+ if (oldGameMode == AnalyzeFile) {
+ appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
+ AnalyzeFileEvent();
+ } else
+ if (oldGameMode == AnalyzeMode) {
AnalyzeFileEvent();
}
- if (!matchMode && pos >= 0) {
+ if(creatingBook) return TRUE;
+ 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);
+ char *fen = PositionToFEN(backwardMostMove, NULL, 1);
fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
fprintf(f, "\n{--------------\n");
PrintPosition(f, backwardMostMove);
PrintPosition(f, currentMove);
fprintf(f, "--------------]\n");
} else {
- fen = PositionToFEN(currentMove, NULL);
+ fen = PositionToFEN(currentMove, NULL, 1);
fprintf(f, "%s\n", fen);
free(fen);
}
}
void
+PauseEngine (ChessProgramState *cps)
+{
+ SendToProgram("pause\n", cps);
+ cps->pause = 2;
+}
+
+void
+UnPauseEngine (ChessProgramState *cps)
+{
+ SendToProgram("resume\n", cps);
+ cps->pause = 1;
+}
+
+void
PauseEvent ()
{
if (appData.debugMode)
if (pausing) {
pausing = FALSE;
ModeHighlight();
+ if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
+ StartClocks();
+ if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
+ if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
+ else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
+ }
+ if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
+ HandleMachineMove(stashedInputMove, stalledEngine);
+ stalledEngine = NULL;
+ return;
+ }
if (gameMode == MachinePlaysWhite ||
- gameMode == MachinePlaysBlack) {
+ gameMode == TwoMachinesPlay ||
+ gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
+ if(first.pause) UnPauseEngine(&first);
+ else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
+ if(second.pause) UnPauseEngine(&second);
+ else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
StartClocks();
} else {
DisplayBothClocks();
Reset(FALSE, TRUE);
SendToICS(ics_prefix);
SendToICS("refresh\n");
- } else if (currentMove < forwardMostMove) {
+ } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
ForwardInner(forwardMostMove);
}
pauseExamInvalid = FALSE;
case TwoMachinesPlay:
if (forwardMostMove == 0)
return; /* don't pause if no one has moved */
- if ((gameMode == MachinePlaysWhite &&
- !WhiteOnMove(forwardMostMove)) ||
- (gameMode == MachinePlaysBlack &&
- WhiteOnMove(forwardMostMove))) {
+ if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
+ ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
+ if(onMove->pause) { // thinking engine can be paused
+ PauseEngine(onMove); // do it
+ if(onMove->other->pause) // pondering opponent can always be paused immediately
+ PauseEngine(onMove->other);
+ else
+ SendToProgram("easy\n", onMove->other);
+ StopClocks();
+ } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
+ } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
+ if(first.pause) {
+ PauseEngine(&first);
+ StopClocks();
+ } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
+ } else { // human on move, pause pondering by either method
+ if(first.pause)
+ PauseEngine(&first);
+ else if(appData.ponderNextMove)
+ SendToProgram("easy\n", &first);
StopClocks();
}
+ // if no immediate pausing is possible, wait for engine to move, and stop clocks then
+ case AnalyzeMode:
pausing = TRUE;
ModeHighlight();
break;
}
void
+ToggleSecond ()
+{
+ if(second.analyzing) {
+ SendToProgram("exit\n", &second);
+ second.analyzing = FALSE;
+ } else {
+ if (second.pr == NoProc) StartChessProgram(&second);
+ InitChessProgram(&second, FALSE);
+ FeedMovesToProgram(&second, currentMove);
+
+ SendToProgram("analyze\n", &second);
+ second.analyzing = TRUE;
+ }
+}
+
+/* Toggle ShowThinking */
+void
+ToggleShowThinking()
+{
+ appData.showThinking = !appData.showThinking;
+ ShowThinkingEvent();
+}
+
+int
AnalyzeModeEvent ()
{
+ char buf[MSG_SIZ];
+
+ if (!first.analysisSupport) {
+ snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
+ DisplayError(buf, 0);
+ return 0;
+ }
+ /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
+ if (appData.icsActive) {
+ if (gameMode != IcsObserving) {
+ snprintf(buf, MSG_SIZ, _("You are not observing a game"));
+ DisplayError(buf, 0);
+ /* secure check */
+ if (appData.icsEngineAnalyze) {
+ if (appData.debugMode)
+ fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
+ ExitAnalyzeMode();
+ ModeHighlight();
+ }
+ return 0;
+ }
+ /* if enable, user wants to disable icsEngineAnalyze */
+ if (appData.icsEngineAnalyze) {
+ ExitAnalyzeMode();
+ ModeHighlight();
+ return 0;
+ }
+ appData.icsEngineAnalyze = TRUE;
+ if (appData.debugMode)
+ fprintf(debugFP, "ICS engine analyze starting... \n");
+ }
+
+ if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
if (appData.noChessProgram || gameMode == AnalyzeMode)
- return;
+ return 0;
if (gameMode != AnalyzeFile) {
if (!appData.icsEngineAnalyze) {
EditGameEvent();
- if (gameMode != EditGame) return;
+ if (gameMode != EditGame) return 0;
}
+ if (!appData.showThinking) ToggleShowThinking();
ResurrectChessProgram();
SendToProgram("analyze\n", &first);
first.analyzing = TRUE;
StartAnalysisClock();
GetTimeMark(&lastNodeCountTime);
lastNodeCount = 0;
+ return 1;
}
void
if (appData.noChessProgram || gameMode == AnalyzeFile)
return;
+ if (!first.analysisSupport) {
+ char buf[MSG_SIZ];
+ snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
+ DisplayError(buf, 0);
+ return;
+ }
+
if (gameMode != AnalyzeMode) {
+ keepInfo = 1; // mere annotating should not alter PGN tags
EditGameEvent();
+ keepInfo = 0;
if (gameMode != EditGame) return;
+ if (!appData.showThinking) ToggleShowThinking();
ResurrectChessProgram();
SendToProgram("analyze\n", &first);
first.analyzing = TRUE;
gameMode = AnalyzeFile;
pausing = FALSE;
ModeHighlight();
- SetGameInfo();
StartAnalysisClock();
GetTimeMark(&lastNodeCountTime);
lastNodeCount = 0;
- if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
+ if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
+ AnalysisPeriodicEvent(1);
}
void
gameInfo.white, _("vs."), gameInfo.black,
nextGame+1, appData.matchGames+1,
appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
- } else
+ } else
if (first.twoMachinesColor[0] == 'w') {
snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
gameInfo.white, _("vs."), gameInfo.black,
StartChessProgram(cps);
if (cps->protocolVersion == 1) {
retry();
+ ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
} else {
/* kludge: allow timeout for initial "feature" command */
- FreezeUI();
+ if(retry != TwoMachinesEventIfReady) FreezeUI();
snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
DisplayMessage("", buf);
ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
// forwardMostMove = currentMove;
TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
+ startingEngine = TRUE;
if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
- if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
+ if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
return;
}
+ if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
+
+ if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
+ startingEngine = FALSE;
+ DisplayError("second engine does not play this", 0);
+ return;
+ }
+
if(!stalling) {
InitChessProgram(&second, FALSE); // unbalances ping of second engine
SendToProgram("force\n", &second);
}
gameMode = TwoMachinesPlay;
- pausing = FALSE;
+ pausing = startingEngine = FALSE;
ModeHighlight(); // [HGM] logo: this triggers display update of logos
SetGameInfo();
DisplayTwoMachinesTitle();
currentMove = forwardMostMove = backwardMostMove = 0;
HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
DisplayMove(-1);
- if(!appData.pieceMenu) DisplayMessage("Click clock to clear board", "");
+ if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
}
void
DisplayMessage("",_("Close ICS engine analyze..."));
}
if (first.analysisSupport && first.analyzing) {
- SendToProgram("exit\n", &first);
- first.analyzing = FALSE;
+ SendToBoth("exit\n");
+ first.analyzing = second.analyzing = FALSE;
}
thinkOutput[0] = NULLCHAR;
}
if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
boards[0][EP_STATUS] = EP_NONE;
boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
- if(boards[0][0][BOARD_WIDTH>>1] == king) {
- boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
+ if(boards[0][0][BOARD_WIDTH>>1] == king) {
+ boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
} else boards[0][CASTLING][2] = NoRights;
- if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
- boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
+ if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
+ boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
} else boards[0][CASTLING][5] = NoRights;
+ if(gameInfo.variant == VariantSChess) {
+ int i;
+ for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
+ boards[0][VIRGIN][i] = 0;
+ if(boards[0][0][i] == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
+ if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
+ }
+ }
}
SendToProgram("force\n", &first);
if (blackPlaysFirst) {
fprintf(debugFP, "EditPosDone\n");
}
DisplayTitle("");
+ DisplayMessage("", "");
timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
timeRemaining[1][forwardMostMove] = blackTimeRemaining;
gameMode = EditGame;
if(gameInfo.variant == VariantShatranj ||
gameInfo.variant == VariantXiangqi ||
gameInfo.variant == VariantCourier ||
+ gameInfo.variant == VariantASEAN ||
gameInfo.variant == VariantMakruk )
selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
goto defaultlabel;
gameMode == Training || gameMode == PlayFromGameFile ||
gameMode == AnalyzeFile) {
while (currentMove < target) {
+ if(second.analyzing) SendMoveToProgram(currentMove, &second);
SendMoveToProgram(currentMove++, &first);
}
} else {
if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
DisplayComment(currentMove - 1, commentList[currentMove]);
}
+ ClearMap(); // [HGM] exclude: invalidate map
}
for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
}
- SendBoard(&first, i);
- for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
+ SendBoard(&first, i);
+ if(second.analyzing) SendBoard(&second, i);
+ for(currentMove=i; currentMove<target; currentMove++) {
+ SendMoveToProgram(currentMove, &first);
+ if(second.analyzing) SendMoveToProgram(currentMove, &second);
+ }
break;
}
- SendToProgram("undo\n", &first);
+ SendToBoth("undo\n");
currentMove--;
}
} else {
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
+CreateBookEvent ()
+{
+ ListGame * lg = (ListGame *) gameList.head;
+ FILE *f;
+ int nItem;
+ static int secondTime = FALSE;
+
+ if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
+ DisplayError(_("Game list not loaded or empty"), 0);
+ return;
+ }
+
+ if(!secondTime && (f = fopen(appData.polyglotBook, "r"))) {
+ fclose(f);
+ secondTime++;
+ DisplayNote(_("Book file exists! Try again for overwrite."));
+ return;
+ }
+
+ creatingBook = TRUE;
+ secondTime = FALSE;
+
+ /* Get list size */
+ for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
+ LoadGame(f, nItem, "", TRUE);
+ AddGameToBook(TRUE);
+ lg = (ListGame *) lg->node.succ;
+ }
+
+ creatingBook = FALSE;
+ FlushBook();
+}
+
+void
BookEvent ()
{
if (appData.noChessProgram) return;
ChessMove r = GameUnfinished;
char *p = NULL;
+ if(keepInfo) return;
+
if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
r = gameInfo.result;
p = gameInfo.resultDetails;
char *p;
float score;
- if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
+ if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
pvInfoList[index-1].depth == len &&
fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
(p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
int oldlen, len;
char *old;
-if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
+if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
+ if(addBraces == 3) addBraces = 0; else // force appending literally
text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
CrushCRs(text);
int time = -1, sec = 0, deci;
char * s_eval = FindStr( text, "[%eval " );
char * s_emt = FindStr( text, "[%emt " );
-
+#if 0
if( s_eval != NULL || s_emt != NULL ) {
+#else
+ if(0) { // [HGM] this code is not finished, and could actually be detrimental
+#endif
/* New style */
char delim;
}
p = text;
+ if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
if(p[1] == '(') { // comment starts with PV
p = strchr(p, ')'); // locate end of PV
if(p == NULL || sep < p+5) return text;
if(sec >= 0) time = 600*time + 10*sec; else
if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
- score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
+ score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
/* [HGM] PV time: now locate end of PV info */
while( *++sep >= '0' && *sep <= '9'); // strip depth
if (count <= 0) {
if (count == 0) {
RemoveInputSource(cps->isr);
- if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
_(cps->which), cps->program);
- if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
+ if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
+ if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
- sscanf(message, "hint: %c", &c)!=1 &&
+ sscanf(message, "hint: %c", &c)!=1 &&
sscanf(message, "pong %c", &c)!=1 && start != '#') {
quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
print = (appData.engineComments >= 2);
}
int
-StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
+StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
{
char buf[MSG_SIZ];
int len = strlen(name);
if (strncmp((*p), name, len) == 0
&& (*p)[len] == '=' && (*p)[len+1] == '\"') {
(*p) += len + 2;
- sscanf(*p, "%[^\"]", loc);
+ ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
+ sscanf(*p, "%[^\"]", *loc);
while (**p && **p != '\"') (*p)++;
if (**p == '\"') (*p)++;
snprintf(buf, MSG_SIZ, "accepted %s\n", name);
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;
ScheduleDelayedEvent(cb, val ? 1 : 3600000);
}
cps->initDone = val;
+ if(val) cps->reload = FALSE;
}
/* Parse feature command from engine */
ParseFeatures (char *args, ChessProgramState *cps)
{
char *p = args;
- char *q;
+ char *q = NULL;
int val;
char buf[MSG_SIZ];
continue;
}
if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
- if (StringFeature(&p, "myname", cps->tidy, cps)) {
+ if (StringFeature(&p, "myname", &cps->tidy, cps)) {
if (gameMode == TwoMachinesPlay) {
DisplayTwoMachinesTitle();
} else {
}
continue;
}
- if (StringFeature(&p, "variants", cps->variants, cps)) continue;
+ if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
+ if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
- if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
+ if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
if (IntFeature(&p, "done", &val, cps)) {
FeatureDone(cps, val);
continue;
if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
- if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
- if (StringFeature(&p, "option", cps->option[cps->nrOptions].name, cps)) {
+ if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
+ if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
+ if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
+ FREE(cps->option[cps->nrOptions].name);
+ cps->option[cps->nrOptions].name = q; q = NULL;
if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
SendToProgram(buf, cps);
void
TypeInEvent (char firstChar)
{
- if ((gameMode == BeginningOfGame && !appData.icsActive) ||
+ if ((gameMode == BeginningOfGame && !appData.icsActive) ||
gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
- gameMode == AnalyzeMode || gameMode == EditGame ||
+ 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
return;
}
- if (gameMode != EditGame && currentMove != forwardMostMove &&
+ if (gameMode != EditGame && currentMove != forwardMostMove &&
gameMode != Training) {
DisplayMoveError(_("Displayed move is not current"));
} else {
- int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
+ 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,
+ if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
&moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
- UserMoveEvent(fromX, fromY, toX, toY, promoChar);
+ UserMoveEvent(fromX, fromY, toX, toY, promoChar);
} else {
DisplayMoveError(_("Could not parse move"));
}
}
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);
char *
-PositionToFEN (int move, char *overrideCastling)
+PositionToFEN (int move, char *overrideCastling, int moveCounts)
{
int i, j, fromX, fromY, toX, toY;
int whiteToPlay;
/* [HGM] write true castling rights */
if( nrCastlingRights == 6 ) {
+ int q, k=0;
if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
- boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
- if(boards[move][CASTLING][1] == BOARD_LEFT &&
- boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
+ boards[move][CASTLING][2] != NoRights ) k = 1, *p++ = 'K';
+ q = (boards[move][CASTLING][1] == BOARD_LEFT &&
+ boards[move][CASTLING][2] != NoRights );
+ if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
+ for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
+ for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
+ if((boards[move][0][i] != WhiteKing || k+q == 0) &&
+ boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
+ }
+ if(q) *p++ = 'Q';
+ k = 0;
if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
- boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
- if(boards[move][CASTLING][4] == BOARD_LEFT &&
- boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
+ boards[move][CASTLING][5] != NoRights ) k = 1, *p++ = 'k';
+ q = (boards[move][CASTLING][4] == BOARD_LEFT &&
+ boards[move][CASTLING][5] != NoRights );
+ if(gameInfo.variant == VariantSChess) {
+ for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
+ for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
+ if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
+ boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
+ }
+ if(q) *p++ = 'q';
}
}
if (q == p) *p++ = '-'; /* No castling rights */
}
if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
- gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
+ gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
+ gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
/* En passant target square */
if (move > backwardMostMove) {
fromX = moveList[move - 1][0] - AAA;
}
}
- /* [HGM] find reversible plies */
+ if(moveCounts)
{ int i = 0, j=move;
+ /* [HGM] find reversible plies */
if (appData.debugMode) { int k;
fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
for(k=backwardMostMove; k<=forwardMostMove; k++)
if( j == backwardMostMove ) i += initialRulePlies;
sprintf(p, "%d ", i);
p += i>=100 ? 4 : i >= 10 ? 3 : 2;
- }
- /* Fullmove number */
- sprintf(p, "%d", (move / 2) + 1);
+
+ /* Fullmove number */
+ sprintf(p, "%d", (move / 2) + 1);
+ } else *--p = NULLCHAR;
return StrSave(buf);
}
{
int i, j;
char *p, c;
- int emptycount;
+ int emptycount, virgin[BOARD_FILES];
ChessSquare piece;
p = fen;
while(*p==' ') p++;
if(nrCastlingRights) {
- if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
+ if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
+ if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
/* castling indicator present, so default becomes no castlings */
for(i=0; i<nrCastlingRights; i++ ) {
board[CASTLING][i] = NoRights;
}
}
while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
- (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
+ (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
- char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
+ int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
board[CASTLING][2] = whiteKingFile;
+ if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
+ if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
break;
case'Q':
for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
board[CASTLING][2] = whiteKingFile;
+ if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
+ if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
break;
case'k':
for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
board[CASTLING][3] = i != blackKingFile ? i : NoRights;
board[CASTLING][5] = blackKingFile;
+ if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
+ if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
break;
case'q':
for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
board[CASTLING][4] = i != blackKingFile ? i : NoRights;
board[CASTLING][5] = blackKingFile;
+ if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
+ if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
case '-':
break;
default: /* FRC castlings */
if(c >= 'a') { /* black rights */
+ if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
if(i == BOARD_RGHT) break;
else
board[CASTLING][4] = c;
} else { /* white rights */
+ if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
if(board[0][i] == WhiteKing) break;
if(i == BOARD_RGHT) break;
}
for(i=0; i<nrCastlingRights; i++)
if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
+ if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
if (appData.debugMode) {
fprintf(debugFP, "FEN castling rights:");
for(i=0; i<nrCastlingRights; i++)
/* read e.p. field in games that know e.p. capture */
if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
- gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
+ gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
+ gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
if(*p=='-') {
p++; board[EP_STATUS] = EP_NONE;
} else {
ToNrEvent(currentMove+1);
}
+void
+LoadTheme ()
+{
+ char *p, *q, buf[MSG_SIZ];
+ if(engineLine && engineLine[0]) { // a theme was selected from the listbox
+ snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
+ ParseArgsFromString(buf);
+ ActivateTheme(TRUE); // also redo colors
+ return;
+ }
+ p = nickName;
+ if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
+ {
+ int len;
+ q = appData.themeNames;
+ snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
+ if(appData.useBitmaps) {
+ snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
+ appData.liteBackTextureFile, appData.darkBackTextureFile,
+ appData.liteBackTextureMode,
+ appData.darkBackTextureMode );
+ } else {
+ snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
+ Col2Text(2), // lightSquareColor
+ Col2Text(3) ); // darkSquareColor
+ }
+ if(appData.useBorder) {
+ snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
+ appData.border);
+ } else {
+ snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
+ }
+ if(appData.useFont) {
+ snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
+ appData.renderPiecesWithFont,
+ appData.fontToPieceTable,
+ Col2Text(9), // appData.fontBackColorWhite
+ Col2Text(10) ); // appData.fontForeColorBlack
+ } else {
+ snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
+ appData.pieceDirectory);
+ if(!appData.pieceDirectory[0])
+ snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
+ Col2Text(0), // whitePieceColor
+ Col2Text(1) ); // blackPieceColor
+ }
+ snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
+ Col2Text(4), // highlightSquareColor
+ Col2Text(5) ); // premoveHighlightColor
+ appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
+ if(insert != q) insert[-1] = NULLCHAR;
+ snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
+ if(q) free(q);
+ }
+ ActivateTheme(FALSE);
+}