* Massachusetts.
*
* Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
- * 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014 Free Software Foundation, Inc.
+ * 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Free
+ * Software Foundation, Inc.
*
* Enhancements Copyright 2005 Alessandro Scotti
*
void EditPositionDone P((Boolean fakeRights));
void PrintOpponents P((FILE *fp));
void PrintPosition P((FILE *fp, int move));
-void StartChessProgram P((ChessProgramState *cps));
void SendToProgram P((char *message, ChessProgramState *cps));
void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
extern char installDir[MSG_SIZ];
VariantClass startVariant; /* [HGM] nicks: initial variant */
Boolean abortMatch;
+int deadRanks;
extern int tinyLayout, smallLayout;
ChessProgramStats programStats;
int promoDefaultAltered;
int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
static int initPing = -1;
+int border; /* [HGM] width of board rim, needed to size seek graph */
+char bestMove[MSG_SIZ], avoidMove[MSG_SIZ];
+int solvingTime, totalTime;
/* States for ics_getting_history */
#define H_FALSE 0
by this function.
*/
int
-PosFlags (index)
+PosFlags (int index)
{
int flags = F_ALL_CASTLE_OK;
if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
case VariantGrand:
flags &= ~F_ALL_CASTLE_OK;
break;
+ case VariantChu:
+ case VariantChuChess:
+ case VariantLion:
+ flags |= F_NULL_MOVE;
+ break;
default:
break;
}
char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
char thinkOutput1[MSG_SIZ*10];
+char promoRestrict[MSG_SIZ];
ChessProgramState first, second, pairing;
Board boards[MAX_MOVES];
/* [HGM] Following 7 needed for accurate legality tests: */
signed char castlingRank[BOARD_FILES]; // and corresponding ranks
-signed char initialRights[BOARD_FILES];
+unsigned char initialRights[BOARD_FILES];
int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
int initialRulePlies, FENrulePlies;
FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
ChessSquare SpartanArray[2][BOARD_FILES] = {
{ WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
- { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
- BlackDragon, BlackKing, BlackAngel, BlackAlfil }
+ { BlackAlfil, BlackDragon, BlackKing, BlackTower,
+ BlackTower, BlackKing, BlackAngel, BlackAlfil }
};
ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
};
ChessSquare ChuArray[6][BOARD_FILES] = {
- { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
- WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
- { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
- BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
- { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
- WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
- { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
- BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
- { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
- WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
- { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
- BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
+ { WhiteLance, WhiteCat, WhiteCopper, WhiteFerz, WhiteWazir, WhiteKing,
+ WhiteAlfil, WhiteWazir, WhiteFerz, WhiteCopper, WhiteCat, WhiteLance },
+ { BlackLance, BlackCat, BlackCopper, BlackFerz, BlackWazir, BlackAlfil,
+ BlackKing, BlackWazir, BlackFerz, BlackCopper, BlackCat, BlackLance },
+ { WhiteAxe, EmptySquare, WhiteBishop, EmptySquare, WhiteClaw, WhiteMarshall,
+ WhiteAngel, WhiteClaw, EmptySquare, WhiteBishop, EmptySquare, WhiteAxe },
+ { BlackAxe, EmptySquare, BlackBishop, EmptySquare, BlackClaw, BlackAngel,
+ BlackMarshall, BlackClaw, EmptySquare, BlackBishop, EmptySquare, BlackAxe },
+ { WhiteDagger, WhiteSword, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
+ WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSword, WhiteDagger },
+ { BlackDagger, BlackSword, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
+ BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSword, BlackDagger }
};
#else // !(BOARD_FILES>=12)
#define CourierArray CapablancaArray
if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
TidyProgramName(cps->program, cps->host, cps->tidy);
cps->matchWins = 0;
- ASSIGN(cps->variants, appData.variant);
+ ASSIGN(cps->variants, appData.noChessProgram ? "" : appData.variant);
cps->analysisSupport = 2; /* detect */
cps->analyzing = FALSE;
cps->initDone = FALSE;
cps->reload = FALSE;
+ cps->pseudo = appData.pseudo[n];
/* New features added by Tord: */
cps->useFEN960 = FALSE;
static char resetOptions[] =
"-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
"-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
- "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
+ "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 -fd \".\" "
"-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
void
SwapEngines(i);
ReplaceEngine(cps, i);
FloatToFront(&appData.recentEngineList, engineLine);
+ if(gameMode == BeginningOfGame) Reset(TRUE, TRUE);
return;
}
p = engineName;
DisplayFatalError(buf, 0, 2);
return;
+ case VariantNormal: /* definitely works! */
+ if(strcmp(appData.variant, "normal") && !appData.noChessProgram) { // [HGM] hope this is an engine-defined variant
+ safeStrCpy(engineVariant, appData.variant, MSG_SIZ);
+ return;
+ }
case VariantXiangqi: /* [HGM] repetition rules not implemented */
case VariantFairy: /* [HGM] TestLegality definitely off! */
case VariantGothic: /* [HGM] should work */
case VariantFalcon: /* [HGM] untested */
case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
offboard interposition not understood */
- case VariantNormal: /* definitely works! */
case VariantWildCastle: /* pieces not automatically shuffled */
case VariantNoCastle: /* pieces not automatically shuffled */
case VariantFischeRandom: /* [HGM] works and shuffles pieces */
}
matchMode = mode;
matchGame = roundNr = 1;
- first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
+ first.matchWins = second.matchWins = totalTime = 0; // [HGM] match: needed in later matches
NextMatchGame();
}
if(!blackPlaysFirst) {
startedFromPositionFile = TRUE;
CopyBoard(filePosition, boards[0]);
+ CopyBoard(initialPosition, boards[0]);
}
+ } else if(*appData.fen != NULLCHAR) {
+ if(ParseFEN(filePosition, &blackPlaysFirst, appData.fen, TRUE) && !blackPlaysFirst) {
+ startedFromPositionFile = TRUE;
+ Reset(TRUE, TRUE);
+ }
}
if (initialMode == AnalyzeMode) {
if (appData.noChessProgram) {
DisplayFatalError(_("Error reading from keyboard"), error, 1);
} else if (gotEof++ > 0) {
RemoveInputSource(isr);
- DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
+ DisplayFatalError(_("Got end of file from keyboard"), 0, 666); // [HGM] 666 is kludge to alert front end
}
}
int wnum = -1;
VariantClass v = VariantNormal;
int i, found = FALSE;
- char buf[MSG_SIZ];
+ char buf[MSG_SIZ], c;
int len;
if (!e) return v;
/* [HGM] skip over optional board-size prefixes */
- if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
- sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
+ if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 ||
+ sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) {
while( *e++ != '_');
}
} else
for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
if (p = StrCaseStr(e, variantNames[i])) {
- if(p && i >= VariantShogi && isalpha(p[strlen(variantNames[i])])) continue;
+ if(p && i >= VariantShogi && (p != e && !appData.icsActive || isalpha(p[strlen(variantNames[i])]))) continue;
v = (VariantClass) i;
found = TRUE;
break;
{
int i;
if(!seekGraphUp) return FALSE;
- h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
- w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
+ h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
+ w = BOARD_WIDTH * (squareSize + lineGap) + lineGap + 2*border;
DrawSeekBackground(0, 0, w, h);
DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
char mess[MSG_SIZ];
snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
OutputChatMessage(chattingPartner, mess);
- if(collective) { // broadcasted talk also goes to private chatbox of talker
+ if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
int p;
talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
}
}
chattingPartner = -1;
- next_out = i+1; // [HGM] suppress printing in ICS window
+ if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
+ collective = 0;
} else
if(!suppressKibitz) // [HGM] kibitz
AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
for(p=0; p<MAX_CHAT; p++) {
+ collective = 1;
if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
talker[0] = '['; strcat(talker, "] ");
- Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
- collective = 1;
+ Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
chattingPartner = p; break;
}
} else
if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
for(p=0; p<MAX_CHAT; p++) {
+ collective = 1;
if(!strcmp("kibitzes", chatPartner[p])) {
talker[0] = '['; strcat(talker, "] ");
- collective = 1;
chattingPartner = p; break;
}
} else
if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
for(p=0; p<MAX_CHAT; p++) {
+ collective = 1;
if(!strcmp("whispers", chatPartner[p])) {
talker[0] = '['; strcat(talker, "] ");
- collective = 1;
chattingPartner = p; break;
}
} else
if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
if(buf[i-8] == '-' && buf[i-3] == 't')
for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
+ collective = 1;
if(!strcmp("c-shouts", chatPartner[p])) {
talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
- collective = 1;
chattingPartner = p; break;
}
}
if(chattingPartner < 0)
for(p=0; p<MAX_CHAT; p++) {
+ collective = 1;
if(!strcmp("shouts", chatPartner[p])) {
if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
- collective = 1;
chattingPartner = p; break;
}
}
}
if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
- talker[0] = 0; Colorize(ColorTell, FALSE);
+ talker[0] = 0;
+ Colorize(ColorTell, FALSE);
+ if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
+ collective |= 2;
chattingPartner = p; break;
}
if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
- if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
- if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
started = STARTED_COMMENT;
parse_pos = 0; parse[0] = NULLCHAR;
savingComment = 3 + chattingPartner; // counts as TRUE
- suppressKibitz = TRUE;
- continue;
+ if(collective == 3) i = oldi; else {
+ suppressKibitz = TRUE;
+ if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
+ if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
+ continue;
+ }
}
} // [HGM] chat: end of patch
#if ZIPPY
if (loggedOn == TRUE)
if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
- (appData.zippyPlay && ZippyMatch(buf, &backup)));
+ (appData.zippyPlay && ZippyMatch(buf, &backup)))
+ ;
#endif
} // [DM] 'else { ' deleted
if (
parse[parse_pos] = NULLCHAR;
started = STARTED_COMMENT;
savingComment = TRUE;
- } else {
+ } else if(collective != 3) {
started = STARTED_CHATTER;
savingComment = FALSE;
}
fprintf(debugFP, "Sending premove:\n");
SendToICS(str);
} else if (gotPremove) {
+ int oldFMM = forwardMostMove;
gotPremove = 0;
ClearPremoveHighlights();
if (appData.debugMode)
UserMoveEvent(premoveFromX, premoveFromY,
premoveToX, premoveToY,
premovePromoChar);
+ if(forwardMostMove == oldFMM) { // premove was rejected, highlight last opponent move
+ if(moveList[oldFMM-1][1] != '@')
+ SetHighlights(moveList[oldFMM-1][0]-AAA, moveList[oldFMM-1][1]-ONE,
+ moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
+ else // (drop)
+ SetHighlights(-1, -1, moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
+ }
}
}
if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
if(old == new) continue;
- if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
+ if(old == PROMOTED(new)) boards[moveNum][k][j] = old;// prevent promoted pieces to revert to primordial ones
else if(new == WhiteWazir || new == BlackWazir) {
if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
- boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
+ boards[moveNum][k][j] = PROMOTED(old); // choose correct type of Gold in promotion
else boards[moveNum][k][j] = old; // preserve type of Gold
} else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
- boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
+ boards[moveNum][k][j] = PROMOTED(new); // use non-primordial representation of chosen piece
}
} else {
/* Move from ICS was illegal!? Punt. */
else SendToProgram(moveList[moveNum], cps);
} else
if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
- snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", moveList[moveNum][0], moveList[moveNum][1] - '0', // convert to two moves
- moveList[moveNum][5], moveList[moveNum][6] - '0',
- moveList[moveNum][5], moveList[moveNum][6] - '0',
- moveList[moveNum][2], moveList[moveNum][3] - '0');
+ char *m = moveList[moveNum];
+ static char c[2];
+ *c = m[7]; if(*c == '\n') *c = NULLCHAR; // promoChar
+ if((boards[moveNum][m[6]-ONE][m[5]-AAA] < BlackPawn) == (boards[moveNum][m[1]-ONE][m[0]-AAA] < BlackPawn)) // move is kludge to indicate castling
+ snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
+ m[2], m[3] - '0',
+ m[5], m[6] - '0',
+ m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
+ else if(*c && m[8]) { // kill square followed by 2 characters: 2nd kill square rather than promo suffix
+ *c = m[9]; if(*c == '\n') *c = NULLCHAR;
+ snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to three moves
+ m[7], m[8] - '0',
+ m[7], m[8] - '0',
+ m[5], m[6] - '0',
+ m[5], m[6] - '0',
+ m[2], m[3] - '0', c);
+ } else
+ snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to two moves
+ m[5], m[6] - '0',
+ m[5], m[6] - '0',
+ m[2], m[3] - '0', c);
SendToProgram(buf, cps);
} else
if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
case WhitePromotion:
case BlackPromotion:
if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
- gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
+ gameInfo.variant == VariantMakruk)
snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
PieceToChar(WhiteFerz));
SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
}
-int killX = -1, killY = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
+int killX = -1, killY = -1, kill2X = -1, kill2Y = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
+int legNr = 1;
void
-CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
+CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[9])
{
if (rf == DROP_RANK) {
if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
if (promoChar == 'x' || promoChar == NULLCHAR) {
sprintf(move, "%c%c%c%c\n",
AAA + ff, ONE + rf, AAA + ft, ONE + rt);
- if(killX >= 0 && killY >= 0) sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
+ if(killX >= 0 && killY >= 0) {
+ sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
+ if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + kill2X, ONE + kill2Y);
+ }
} else {
sprintf(move, "%c%c%c%c%c\n",
AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
+ if(killX >= 0 && killY >= 0) {
+ sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
+ if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c%c\n", AAA + kill2X, ONE + kill2Y, promoChar);
+ }
}
}
}
static ClickType lastClickType;
int
+PieceInString (char *s, ChessSquare piece)
+{
+ char *p, ID = ToUpper(PieceToChar(piece)), suffix = PieceSuffix(piece);
+ while((p = strchr(s, ID))) {
+ if(!suffix || p[1] == suffix) return TRUE;
+ s = p;
+ }
+ return FALSE;
+}
+
+int
Partner (ChessSquare *p)
{ // change piece into promotion partner if one shogi-promotes to the other
- int stride = gameInfo.variant == VariantChu ? 22 : 11;
- ChessSquare partner;
- partner = (*p/stride & 1 ? *p - stride : *p + stride);
+ ChessSquare partner = promoPartner[*p];
if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
+ if(PieceToChar(*p) == '+') partner = boards[currentMove][fromY][fromX];
*p = partner;
return 1;
}
if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
else if((int)promoSweep == -1) promoSweep = WhiteKing;
- else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
- else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
+ else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
+ else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
if(!step) step = -1;
- } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
+ } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' ||
!toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
+ promoRestrict[0] ? !PieceInString(promoRestrict, promoSweep) : // if choice set available, use it
+ promoSweep == pawn ||
appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
(promoSweep == WhiteLion || promoSweep == BlackLion)));
if(toX >= 0) {
case BlackASideCastleFR:
/* End of code added by Tord */
case IllegalMove: /* bug or odd chess variant */
+ if(currentMoveString[1] == '@') { // illegal drop
+ *fromX = WhiteOnMove(moveNum) ?
+ (int) CharToPiece(ToUpper(currentMoveString[0])) :
+ (int) CharToPiece(ToLower(currentMoveString[0]));
+ goto drop;
+ }
*fromX = currentMoveString[0] - AAA;
*fromY = currentMoveString[1] - ONE;
*toX = currentMoveString[2] - AAA;
*toY = currentMoveString[3] - ONE;
*promoChar = currentMoveString[4];
+ if(*promoChar == ';') *promoChar = currentMoveString[7 + 2*(currentMoveString[8] != 0)];
if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
*toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
if (appData.debugMode) {
*fromX = *moveType == WhiteDrop ?
(int) CharToPiece(ToUpper(currentMoveString[0])) :
(int) CharToPiece(ToLower(currentMoveString[0]));
+ drop:
*fromY = DROP_RANK;
*toX = currentMoveString[2] - AAA;
*toY = currentMoveString[3] - ONE;
}
int
-MultiPV (ChessProgramState *cps)
+MultiPV (ChessProgramState *cps, int kind)
{ // check if engine supports MultiPV, and if so, return the number of the option that sets it
int i;
- for(i=0; i<cps->nrOptions; i++)
- if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
- return i;
+ for(i=0; i<cps->nrOptions; i++) {
+ char *s = cps->option[i].name;
+ if((kind & 1) && !StrCaseCmp(s, "MultiPV") && cps->option[i].type == Spin) return i;
+ if((kind & 2) && StrCaseStr(s, "multi") && StrCaseStr(s, "PV")
+ && StrCaseStr(s, "margin") && cps->option[i].type == Spin) return -i-2;
+ }
return -1;
}
Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
+static int multi, pv_margin;
+static ChessProgramState *activeCps;
Boolean
LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
{
- int startPV, multi, lineStart, origIndex = index;
+ int startPV, lineStart, origIndex = index;
char *p, buf2[MSG_SIZ];
ChessProgramState *cps = (pane ? &second : &first);
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(cps)) >= 0) {
- int n = cps->option[multi].value;
- if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
+ if(lineStart == 0 && gameMode == AnalyzeMode) {
+ int n = 0;
+ if(origIndex > 17 && origIndex < 24) n--; else if(origIndex > index - 6) n++;
+ if(n == 0) { // click not on "fewer" or "more"
+ if((multi = -2 - MultiPV(cps, 2)) >= 0) {
+ pv_margin = cps->option[multi].value;
+ activeCps = cps; // non-null signals margin adjustment
+ }
+ } else if((multi = MultiPV(cps, 1)) >= 0) {
+ n += cps->option[multi].value; if(n < 1) n = 1;
snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", 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);
+ extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
return TRUE;
}
UnLoadPV ()
{
int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
+ if(activeCps) {
+ if(pv_margin != activeCps->option[multi].value) {
+ char buf[MSG_SIZ];
+ snprintf(buf, MSG_SIZ, "option %s=%d\n", "Multi-PV Margin", pv_margin);
+ SendToProgram(buf, activeCps);
+ activeCps->option[multi].value = pv_margin;
+ }
+ activeCps = NULL;
+ return;
+ }
if(endPV < 0) return;
if(appData.autoCopyPV) CopyFENToClipboard();
endPV = -1;
{ // step through PV based on mouse coordinates (called on mouse move)
int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
+ if(activeCps) { // adjusting engine's multi-pv margin
+ if(x > lastX) pv_margin++; else
+ if(x < lastX) pv_margin -= (pv_margin > 0);
+ if(x != lastX) {
+ char buf[MSG_SIZ];
+ snprintf(buf, MSG_SIZ, "margin = %d", pv_margin);
+ DisplayMessage(buf, "");
+ }
+ lastX = x;
+ return;
+ }
// we must somehow check if right button is still down (might be released off board!)
if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
}
int
-SetCharTable (char *table, const char * map)
+ptclen (const char *s, char *escapes)
+{
+ int n = 0;
+ if(!*escapes) return strlen(s);
+ while(*s) n += (*s != '/' && *s != '-' && *s != '^' && *s != '*' && !strchr(escapes, *s)) - 2*(*s == '='), s++;
+ return n;
+}
+
+int
+SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
/* [HGM] moved here from winboard.c because of its general usefulness */
/* Basically a safe strcpy that uses the last character as King */
{
int result = FALSE; int NrPieces;
+ unsigned char partner[EmptySquare];
- if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
+ if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
&& NrPieces >= 12 && !(NrPieces&1)) {
- int i; /* [HGM] Accept even length from 12 to 34 */
+ int i, ii, offs, j = 0; /* [HGM] Accept even length from 12 to 88 */
for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
- for( i=0; i<NrPieces/2-1; i++ ) {
- table[i] = map[i];
- table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
+ for( i=offs=0; i<NrPieces/2-1; i++ ) {
+ char *p, c=0;
+ if(map[j] == '/') offs = WhitePBishop - i, j++;
+ if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
+ table[i+offs] = map[j++];
+ if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
+ if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
+ if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
+ }
+ table[(int) WhiteKing] = map[j++];
+ for( ii=offs=0; ii<NrPieces/2-1; ii++ ) {
+ char *p, c=0;
+ if(map[j] == '/') offs = WhitePBishop - ii, j++;
+ i = WHITE_TO_BLACK ii;
+ if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
+ table[i+offs] = map[j++];
+ if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
+ if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
+ if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
+ }
+ table[(int) BlackKing] = map[j++];
+
+
+ if(*escapes) { // set up promotion pairing
+ for( i=0; i<(int) EmptySquare; i++ ) promoPartner[i] = (i%BlackPawn < 11 ? i + 11 : i%BlackPawn < 22 ? i - 11 : i); // default
+ // pieceToChar entirely filled, so we can look up specified partners
+ for(i=0; i<EmptySquare; i++) { // adjust promotion pairing
+ int c = table[i];
+ if(c == '^' || c == '-') { // has specified partner
+ int p;
+ for(p=0; p<EmptySquare; p++) if(table[p] == partner[i]) break;
+ if(c == '^') table[i] = '+';
+ if(p < EmptySquare) {
+ if(promoPartner[promoPartner[p]] == p) promoPartner[promoPartner[p]] = promoPartner[p]; // divorce old partners
+ if(promoPartner[promoPartner[i]] == i) promoPartner[promoPartner[i]] = promoPartner[i];
+ promoPartner[p] = i, promoPartner[i] = p; // and marry this couple
+ }
+ } else if(c == '*') {
+ table[i] = partner[i];
+ promoPartner[i] = (i < BlackPawn ? WhiteTokin : BlackTokin); // promotes to Tokin
+ }
+ }
}
- table[(int) WhiteKing] = map[NrPieces/2-1];
- table[(int) BlackKing] = map[NrPieces-1];
result = TRUE;
}
return result;
}
+int
+SetCharTable (unsigned char *table, const char * map)
+{
+ return SetCharTableEsc(table, map, "");
+}
+
void
Prelude (Board board)
{ // [HGM] superchess: random selection of exo-pieces
gameInfo.boardHeight = 8;
gameInfo.holdingsSize = 0;
nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
- for(i=0; i<BOARD_FILES-2; i++)
+ for(i=0; i<BOARD_FILES-6; i++)
initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
initialPosition[EP_STATUS] = EP_NONE;
- SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
+ initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
+ SetCharTableEsc(pieceToChar, "PNBRQ...........Kpnbrq...........k", SUFFIXES);
if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
SetCharTable(pieceNickName, appData.pieceNickNames);
else SetCharTable(pieceNickName, "............");
case VariantFalcon:
pieces = FalconArray;
gameInfo.boardWidth = 10;
- SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
+ SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
break;
case VariantXiangqi:
pieces = XiangqiArray;
gameInfo.boardWidth = 12;
gameInfo.boardHeight = 12;
nrCastlingRights = 0;
- SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
- "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
+// SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN.........^T..^L......^A^H/^F^G^M.^E^X^O^I.^P.^B^R..^D^S^C^VK"
+ // "p.brqsexogcathd.vmlifn.........^t..^l......^a^h/^f^g^m.^e^x^o^i.^p.^b^r..^d^s^c^vk", SUFFIXES);
+ SetCharTableEsc(pieceToChar, "P.BRQSEXOG...HD..^DLI^HNV........^T..^L.C...A^AFT/^F^G^M.^E^X^O^I.^P.^B^R..M^S^C^VK"
+ "p.brqsexog...hd..^dli^hnv........^t..^l.c...a^aft/^f^g^m.^e^x^o^i.^p.^b^r..m^s^c^vk", SUFFIXES);
break;
case VariantCourier:
pieces = CourierArray;
break;
case VariantSpartan:
pieces = SpartanArray;
- SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
+ SetCharTable(pieceToChar, "PNBRQ.....................K......lw......g...h......ck");
break;
case VariantLion:
pieces = lionArray;
/* User pieceToChar list overrules defaults */
if(appData.pieceToCharTable != NULL)
- SetCharTable(pieceToChar, appData.pieceToCharTable);
+ SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
initialRights[i] = filePosition[CASTLING][i];
startedFromSetupPosition = TRUE;
}
+ if(*appData.men) LoadPieceDesc(appData.men);
CopyBoard(boards[0], initialPosition);
if ((int) *bp < (int) BlackPawn) {
if(j == BOARD_RGHT+1)
snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
- else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
+ else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
if(message[0] == '+' || message[0] == '~') {
- snprintf(message, MSG_SIZ,"%c%c%c+\n",
- PieceToChar((ChessSquare)(DEMOTED *bp)),
- AAA + j, ONE + i);
+ snprintf(message, MSG_SIZ,"%c%c%d+\n",
+ PieceToChar((ChessSquare)(DEMOTED(*bp))),
+ AAA + j, ONE + i - '0');
}
if(cps->alphaRank) { /* [HGM] shogi: translate coords */
message[1] = BOARD_RGHT - 1 - j + '1';
&& ((int) *bp >= (int) BlackPawn)) {
if(j == BOARD_LEFT-2)
snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
- else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
- AAA + j, ONE + i);
+ else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
+ AAA + j, ONE + i - '0');
if(message[0] == '+' || message[0] == '~') {
- snprintf(message, MSG_SIZ,"%c%c%c+\n",
- PieceToChar((ChessSquare)(DEMOTED *bp)),
- AAA + j, ONE + i);
+ snprintf(message, MSG_SIZ,"%c%c%d+\n",
+ PieceToChar((ChessSquare)(DEMOTED(*bp))),
+ AAA + j, ONE + i - '0');
}
if(cps->alphaRank) { /* [HGM] shogi: translate coords */
message[1] = BOARD_RGHT - 1 - j + '1';
{
ChessSquare result;
if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
- gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
+ gameInfo.variant == VariantMakruk)
result = WhiteFerz; // no choice
+ else if(gameInfo.variant == VariantASEAN)
+ result = WhiteRook; // no choice
else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
result= WhiteKing; // in Suicide Q is the last thing we want
else if(gameInfo.variant == VariantSpartan)
piece = boards[currentMove][fromY][fromX];
if(gameInfo.variant == VariantChu) {
- int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
- promotionZoneSize = BOARD_HEIGHT/3;
- highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
- } else if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantChuChess) {
promotionZoneSize = BOARD_HEIGHT/3;
+ highestPromotingPiece = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing;
+ } else if(gameInfo.variant == VariantShogi) {
+ promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
highestPromotingPiece = (int)WhiteAlfil;
} else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
promotionZoneSize = 3;
// 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 || gameInfo.variant == VariantASEAN) {
+ gameInfo.variant == VariantMakruk) {
ChessSquare p=BlackFerz; // no choice
while(p < EmptySquare) { //but make sure we use piece that exists
*promoChoice = PieceToChar(p++);
if(*promoChoice != '.') break;
}
- return FALSE;
+ if(!*engineVariant) return FALSE; // if used as parent variant there might be promotion choice
}
// no sense asking what we must promote to if it is going to explode...
if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
case PlayFromGameFile:
if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
case EditGame:
+ case AnalyzeMode:
if (!white_piece && WhiteOnMove(currentMove)) {
DisplayMoveError(_("It is White's turn"));
return FALSE;
char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
ChessMove lastLoadGameStart = EndOfFile;
int doubleClick;
+Boolean addToBookFlag;
+static Board rightsBoard, nullBoard;
void
-UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
+UserMoveEvent (int fromX, int fromY, int toX, int toY, int promoChar)
{
ChessMove moveType;
ChessSquare pup;
"fromY %d, toX %d, toY %d\n",
fromX, fromY, toX, toY);
}
+ DrawPosition(TRUE, boards[currentMove]); // [HGM] repair animation damage done by premove (in particular emptying from-square)
return;
}
break;
"fromY %d, toX %d, toY %d\n",
fromX, fromY, toX, toY);
}
+ DrawPosition(TRUE, boards[currentMove]);
return;
}
break;
/* EditPosition, empty square, or different color piece;
click-click move is possible */
if (toX == -2 || toY == -2) {
- boards[0][fromY][fromX] = EmptySquare;
+ boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
DrawPosition(FALSE, boards[currentMove]);
return;
} else if (toX >= 0 && toY >= 0) {
+ if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
+ ChessSquare p = boards[0][rf][ff];
+ if(PieceToChar(p) == '+') gatingPiece = CHUDEMOTED(p); else
+ if(PieceToChar(CHUPROMOTED(p)) =='+') gatingPiece = CHUPROMOTED(p); else
+ if(p == WhiteKing || p == BlackKing || p == WhiteRook || p == BlackRook || p == WhitePawn || p == BlackPawn) {
+ int n = rightsBoard[toY][toX] ^= 1; // toggle virginity of K or R
+ DisplayMessage("", n ? _("rights granted") : _("rights revoked"));
+ gatingPiece = p;
+ }
+ } else rightsBoard[toY][toX] = 0; // revoke rights on moving
boards[0][toY][toX] = boards[0][fromY][fromX];
if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
if(boards[0][fromY][0] != EmptySquare) {
}
} else
boards[0][fromY][fromX] = gatingPiece;
+ ClearHighlights();
DrawPosition(FALSE, boards[currentMove]);
return;
}
return;
}
- if(toX < 0 || toY < 0) return;
+ if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
pup = boards[currentMove][toY][toX];
/* [HGM] If move started in holdings, it means a drop. Convert to standard form */
// 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) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
fromY = DROP_RANK;
}
moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
fromY, fromX, toY, toX, promoChar);
- if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
+ if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
+
+ if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal
/* [HGM] but possibly ignore an IllegalMove result */
if (appData.testLegality) {
if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
ClearPremoveHighlights(); // was included
else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated by premove highlights
+ DrawPosition(FALSE, NULL);
+ return;
+ }
+
+ if(addToBookFlag) { // adding moves to book
+ char buf[MSG_SIZ], move[MSG_SIZ];
+ CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
+ if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d%c", fromX + AAA, fromY + ONE - '0',
+ killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0', promoChar);
+ snprintf(buf, MSG_SIZ, " 0.0%% 1 %s\n", move);
+ AddBookMove(buf);
+ addToBookFlag = FALSE;
+ ClearHighlights();
return;
}
int r, f;
if(!appData.markers || !appData.highlightDragging) return;
for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
- r=BOARD_HEIGHT-1; f=BOARD_LEFT;
+ r=BOARD_HEIGHT-1-deadRanks; f=BOARD_LEFT;
while(*fen) {
int s = 0;
marker[r][f] = 0;
if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
- if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
+ if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 3; else
if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
if(*fen == 'T') marker[r][f++] = 0; else
{
typedef char Markers[BOARD_RANKS][BOARD_FILES];
Markers *m = (Markers *) closure;
- if(rf == fromY && ff == fromX && (killX < 0 && !(rt == rf && ft == ff) || abs(ft-killX) < 2 && abs(rt-killY) < 2))
+ if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 :
+ kill2X < 0 ? rt == killY && ft == killX || legNr & 2 : rt == killY && ft == killX || legNr & 4))
(*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
|| kind == WhiteCapturesEnPassant
- || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0);
- else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
+ || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && (killX < 0 & legNr || legNr & 2 && kill2X < 0)), legal[rt][ft] = 3;
+ else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
}
static int hoverSavedValid;
if(clear) { // no reason to ever suppress clearing
for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
hoverSavedValid = 0;
- if(!sum) return; // nothing was cleared,no redraw needed
+ if(!sum || clear < 0) return; // nothing was cleared,no redraw needed
} else {
int capt = 0;
if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
- !appData.testLegality || gameMode == EditPosition) return;
+ !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
if(PosFlags(0) & F_MANDATORY_CAPTURE) {
for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
// some variants have fixed promotion piece, no promotion at all, or another selection mechanism
if(IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantXiangqi ||
gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
- gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
- gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) return FALSE;
+ (gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
+ gameInfo.variant == VariantMakruk) && !*engineVariant) return FALSE;
return (piece == BlackPawn && y <= zone ||
piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
- piece == BlackLance && y == 1 ||
- piece == WhiteLance && y == BOARD_HEIGHT-2 );
+ piece == BlackLance && y <= zone ||
+ piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
}
void
char buf[MSG_SIZ]; // Inform engine of what user does
int r, f;
if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
- for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
+ for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
+ legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
if(!first.highlight || gameMode == EditPosition) return;
snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
SendToProgram(buf, &first);
}
+Boolean right; // instructs front-end to use button-1 events as if they were button 3
+
void
LeftClick (ClickType clickType, int xPix, int yPix)
{
int x, y;
Boolean saveAnimate;
- static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
+ static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0, flashing = 0, saveFlash;
char promoChoice = NULLCHAR;
ChessSquare piece;
static TimeMark lastClickTime, prevClickTime;
- if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
-
- prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
-
- if (clickType == Press) ErrorPopDown();
- lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
+ if(flashing) return;
x = EventToSquare(xPix, BOARD_WIDTH);
y = EventToSquare(yPix, BOARD_HEIGHT);
x = BOARD_WIDTH - 1 - x;
}
+ if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
+ static int dummy;
+ RightClick(clickType, xPix, yPix, &dummy, &dummy);
+ right = TRUE;
+ return;
+ }
+
+ if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
+
+ prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
+
+ if (clickType == Press) ErrorPopDown();
+ lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
+
if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
defaultPromoChoice = promoSweep;
promoSweep = EmptySquare; // terminate sweep
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 = killX = killY = -1;
+ fromX = x; fromY = y; toX = toY = killX = killY = kill2X = kill2Y = -1;
if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
// even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
promoSweep = defaultPromoChoice;
- selectFlag = 0; lastX = xPix; lastY = yPix;
+ selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
Sweep(0); // Pawn that is going to promote: preview promotion piece
DisplayMessage("", _("Pull pawn backwards to under-promote"));
}
toP = boards[currentMove][y][x];
frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
+ marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
((WhitePawn <= fromP && fromP <= WhiteKing &&
WhitePawn <= toP && toP <= WhiteKing &&
!(fromP == WhiteKing && toP == WhiteRook && frc) &&
!(fromP == BlackKing && toP == BlackRook && frc)))) {
/* Clicked again on same color piece -- changed his mind */
second = (x == fromX && y == fromY);
- killX = killY = -1;
+ killX = killY = kill2X = kill2Y = -1;
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) MarkTargetSquares(1);
if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
if (appData.highlightDragging) {
SetHighlights(x, y, -1, -1);
else gatingPiece = doubleClick ? fromP : EmptySquare;
fromX = x;
fromY = y; dragging = 1;
- ReportClick("lift", x, y);
+ if(!second) ReportClick("lift", x, y);
MarkTargetSquares(0);
DragPieceBegin(xPix, yPix, FALSE);
if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
promoSweep = defaultPromoChoice;
- selectFlag = 0; lastX = xPix; lastY = yPix;
+ selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
Sweep(0); // Pawn that is going to promote: preview promotion piece
}
}
if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
}
- if (clickType == Release && x == fromX && y == fromY && killX < 0) {
+ if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
+ gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
+ DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
+ return;
+ }
+
+ if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
DragPieceEnd(xPix, yPix); dragging = 0;
if(clearFlag) {
// a deferred attempt to click-click move an empty square on top of a piece
/* Undo animation damage if any */
DrawPosition(FALSE, NULL);
}
- if (second || sweepSelecting) {
+ if (second) {
/* Second up/down in same square; just abort move */
- if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
- second = sweepSelecting = 0;
+ second = 0;
fromX = fromY = -1;
gatingPiece = EmptySquare;
- MarkTargetSquares(1);
ClearHighlights();
gotPremove = 0;
ClearPremoveHighlights();
+ MarkTargetSquares(-1);
+ DrawPosition(FALSE, NULL); // make user highlights are drawn (and deferred marker clearing)
} else {
/* First upclick in same square; start click-click mode */
SetHighlights(x, y, -1, -1);
clearFlag = 0;
- if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] && (x != killX || y != killY) && !sweepSelecting) {
+ if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
+ fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
DisplayMessage(_("only marked squares are legal"),"");
DrawPosition(TRUE, NULL);
return;
}
if(x == killX && y == killY) { // second click on this square, which was selected as first-leg target
- killX = killY = -1; // this informs us no second leg is coming, so treat as to-click without intermediate
+ killX = kill2X; killY = kill2Y; kill2X = kill2Y = -1; // this informs us no second leg is coming, so treat as to-click without intermediate
} else
if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
if(appData.sweepSelect) {
promoSweep = defaultPromoChoice;
- if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
+ if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
selectFlag = 0; lastX = xPix; lastY = yPix;
+ ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
+ saveFlash = appData.flashCount; appData.flashCount = 0;
Sweep(0); // Pawn that is going to promote: preview promotion piece
sweepSelecting = 1;
DisplayMessage("", _("Pull pawn backwards to under-promote"));
} else {
ClearHighlights();
}
+ MarkTargetSquares(1);
} else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
+ *promoRestrict = 0; appData.flashCount = saveFlash;
if (appData.animate || appData.highlightLastMove) {
SetHighlights(fromX, fromY, toX, toY);
} else {
ClearHighlights();
}
+ MarkTargetSquares(1);
} 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
ClearHighlights();
}
#endif
+ if(PieceToChar(CHUPROMOTED(boards[currentMove][fromY][fromX])) == '+')
+ defaultPromoChoice = CHUPROMOTED(boards[currentMove][fromY][fromX]);
if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
dragging *= 2; // flag button-less dragging if we are dragging
MarkTargetSquares(1);
- if(x == killX && y == killY) killX = killY = -1; else {
- killX = x; killY = y; //remeber this square as intermediate
+ if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
+ else {
+ kill2X = killX; kill2Y = killY;
+ killX = x; killY = y; // remember this square as intermediate
ReportClick("put", x, y); // and inform engine
ReportClick("lift", x, y);
MarkTargetSquares(0);
DragPieceEnd(xPix, yPix); dragging = 0;
/* Don't animate move and drag both */
appData.animate = FALSE;
+ MarkTargetSquares(-1); // -1 defers displaying marker change to prevent piece reappearing on from-square!
}
// moves into holding are invalid for now (except in EditPosition, adapting to-square)
if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
- if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
+ if(legal[toY][toX] == 2) { // highlight-induced promotion
+ if(piece == defaultPromoChoice) promoChoice = NULLCHAR; // deferral
+ else promoChoice = ToLower(PieceToChar(defaultPromoChoice));
+ }
+
+ if (legal[toY][toX] == 2 && !appData.sweepSelect || 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) {
DisplayMessage("Click in holdings to choose piece", "");
return;
}
+ DrawPosition(FALSE, NULL); // shows piece on from-square during promo popup
PromotionPopUp(promoChoice);
} else {
int oldMove = currentMove;
+ flashing = 1; // prevent recursive calling (by release of to-click) while flashing piece
UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
if (!appData.highlightLastMove || gotPremove) ClearHighlights();
- if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
+ if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY), DrawPosition(FALSE, NULL);
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;
+ flashing = 0;
}
appData.animate = saveAnimate;
if (appData.animate || appData.animateDragging) {
/* Undo animation damage if needed */
- DrawPosition(FALSE, NULL);
+// DrawPosition(FALSE, NULL);
}
}
}
void
+Wheel (int dir, int x, int y)
+{
+ if(gameMode == EditPosition) {
+ int xSqr = EventToSquare(x, BOARD_WIDTH);
+ int ySqr = EventToSquare(y, BOARD_HEIGHT);
+ if(ySqr < 0 || xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return;
+ if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
+ do {
+ boards[currentMove][ySqr][xSqr] += dir;
+ if((int) boards[currentMove][ySqr][xSqr] < WhitePawn) boards[currentMove][ySqr][xSqr] = BlackKing;
+ if((int) boards[currentMove][ySqr][xSqr] > BlackKing) boards[currentMove][ySqr][xSqr] = WhitePawn;
+ } while(PieceToChar(boards[currentMove][ySqr][xSqr]) == '.');
+ DrawPosition(FALSE, boards[currentMove]);
+ } else if(dir > 0) ForwardEvent(); else BackwardEvent();
+}
+
+void
SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
{
// char * hint = lastHint;
if(stats.pv && stats.pv[0]) safeStrCpy(lastPV[stats.which], stats.pv, sizeof(lastPV[stats.which])/sizeof(lastPV[stats.which][0])); // [HGM] pv: remember last PV of each
+ if( gameMode == AnalyzeMode && stats.pv && stats.pv[0]
+ && appData.analysisBell && stats.time >= 100*appData.analysisBell ) RingBell();
+
SetProgramStats( &stats );
}
// most tests only when we understand the game, i.e. legality-checking on
if( appData.testLegality )
{ /* [HGM] Some more adjudications for obstinate engines */
- int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
+ int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
static int moveCount = 6;
ChessMove result;
char *reason = NULL;
// probe EGBB
if(loaded == 2) return 13; // loading failed before
if(loaded == 0) {
- loaded = 2; // prepare for failure
char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
HMODULE lib;
PLOAD_EGBB loadBB;
+ loaded = 2; // prepare for failure
if(!path) return 13; // no egbb installed
strncpy(buf, path + 8, MSG_SIZ);
if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
static ChessProgramState *stalledEngine;
-static char stashedInputMove[MSG_SIZ];
+static char stashedInputMove[MSG_SIZ], abortEngineThink;
void
HandleMachineMove (char *message, ChessProgramState *cps)
{
- static char firstLeg[20];
+ static char firstLeg[20], legs;
char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
char realname[MSG_SIZ];
int fromX, fromY, toX, toY;
ChessMove moveType;
char promoChar, roar;
char *p, *pv=buf1;
- int machineWhite, oldError;
+ int oldError;
char *bookHit;
if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
return;
}
+ if(cps->usePing) {
+
/* This method is only useful on engines that support ping */
+ if(abortEngineThink) {
+ if (appData.debugMode) {
+ fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
+ }
+ SendToProgram("undo\n", cps);
+ return;
+ }
+
if (cps->lastPing != cps->lastPong) {
- if (gameMode == BeginningOfGame) {
/* Extra move from before last new; ignore */
if (appData.debugMode) {
fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
}
- } else {
- if (appData.debugMode) {
- fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
- cps->which, gameMode);
- }
-
- SendToProgram("undo\n", cps);
- }
return;
}
+ } else {
+
+ int machineWhite = FALSE;
+
switch (gameMode) {
case BeginningOfGame:
/* Extra move from before last reset; ignore */
}
return;
}
+ }
if(cps->alphaRank) AlphaRank(machineMove, 4);
// [HGM] lion: (some very limited) support for Alien protocol
- killX = killY = -1;
+ killX = killY = kill2X = kill2Y = -1;
if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
+ if(legs++) return; // middle leg contains only redundant info, ignore (but count it)
safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
return;
- } else if(firstLeg[0]) { // there was a previous leg;
- // only support case where same piece makes two step (and don't even test that!)
+ }
+ if(p = strchr(machineMove, ',')) { // we got both legs in one (happens on book move)
+ char *q = strchr(p+1, ','); // second comma?
+ safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
+ if(q) legs = 2, p = q; else legs = 1; // with 3-leg move we clipof first two legs!
+ safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
+ }
+ if(firstLeg[0]) { // there was a previous leg;
+ // only support case where same piece makes two step
char buf[20], *p = machineMove+1, *q = buf+1, f;
safeStrCpy(buf, machineMove, 20);
while(isdigit(*q)) q++; // find start of to-square
safeStrCpy(machineMove, firstLeg, 20);
- while(isdigit(*p)) p++;
+ while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
+ if(legs == 2) sscanf(p, "%c%d", &f, &kill2Y), kill2X = f - AAA, kill2Y -= ONE - '0'; // in 3-leg move 2nd kill is to-sqr of 1st leg
+ else if(*p == *buf) // if first-leg to not equal to second-leg from first leg says unmodified (assume it is King move of castling)
safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
- firstLeg[0] = NULLCHAR;
+ firstLeg[0] = NULLCHAR; legs = 0;
}
if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
machineMove, _(cps->which));
DisplayMoveError(buf1);
- snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
- machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
+ snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
+ machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
if (gameMode == TwoMachinesPlay) {
- GameEnds(machineWhite ? BlackWins : WhiteWins,
+ GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
buf1, GE_XBOARD);
}
return;
if(moveType == IllegalMove) {
snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
- GameEnds(machineWhite ? BlackWins : WhiteWins,
+ GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
buf1, GE_XBOARD);
return;
} else if(!appData.fischerCastling)
free(fen);
GameEnds(GameUnfinished, NULL, GE_XBOARD);
}
+ if(appData.epd) {
+ if(solvingTime >= 0) {
+ snprintf(buf1, MSG_SIZ, "%d. %4.2fs: %s ", matchGame, solvingTime/100., parseList[backwardMostMove]);
+ totalTime += solvingTime; first.matchWins++; solvingTime = -1;
+ } else {
+ snprintf(buf1, MSG_SIZ, "%d. %s?%s ", matchGame, parseList[backwardMostMove], solvingTime == -2 ? " ???" : "");
+ if(solvingTime == -2) second.matchWins++;
+ }
+ OutputKibitz(2, buf1);
+ GameEnds(GameUnfinished, NULL, GE_XBOARD);
+ }
/* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
- if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
+ if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
int count = 0;
while( count < adjudicateLossPlies ) {
score = -score; /* Flip score for winning side */
}
- if( score > adjudicateLossThreshold ) {
+ if( score > appData.adjudicateLossThreshold ) {
break;
}
(unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
programStats.movelist);
SendToICS(buf);
-if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
}
}
#endif
int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
*buf = NULLCHAR;
- if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
- if(startedFromSetupPosition) return;
+ if(sscanf(message, "setup (%s", buf) == 1) {
+ s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
+ ASSIGN(appData.pieceToCharTable, buf);
+ }
dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
if(dummy >= 3) {
while(message[s] && message[s++] != ' ');
if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
+ if(hand <= h) deadRanks = 0; else deadRanks = hand - h, h = hand; // adapt board to over-sized holdings
appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
if(dummy == 4) gameInfo.variant = StringToVariant(varName); // parent variant
InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
- if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
+ if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
+ startedFromSetupPosition = FALSE;
}
}
+ if(startedFromSetupPosition) return;
ParseFEN(boards[0], &dummy, message+s, FALSE);
DrawPosition(TRUE, boards[0]);
+ CopyBoard(initialPosition, boards[0]);
startedFromSetupPosition = TRUE;
return;
}
+ if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
+ ChessSquare piece = WhitePawn;
+ char *p=message+6, *q, *s = SUFFIXES, ID = *p, promoted = 0;
+ if(*p == '+') promoted++, ID = *++p;
+ if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
+ piece = CharToPiece(ID & 255); if(promoted) piece = CHUPROMOTED(piece);
+ if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
+ /* always accept definition of */ && piece != WhiteFalcon && piece != BlackFalcon
+ /* wild-card pieces. */ && piece != WhiteCobra && piece != BlackCobra
+ /* For variants we don't have */ && gameInfo.variant != VariantBerolina
+ /* correct rules for, we cannot */ && gameInfo.variant != VariantCylinder
+ /* enforce legality on our own! */ && gameInfo.variant != VariantUnknown
+ && gameInfo.variant != VariantGreat
+ && gameInfo.variant != VariantFairy ) return;
+ if(piece < EmptySquare) {
+ pieceDefs = TRUE;
+ ASSIGN(pieceDesc[piece], buf1);
+ if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
+ }
+ return;
+ }
+ if(sscanf(message, "choice %s", promoRestrict) == 1 && promoSweep != EmptySquare) {
+ promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
+ Sweep(0);
+ return;
+ }
/* [HGM] Allow engine to set up a position. Don't ask me why one would
* want this, I was asked to put it in, and obliged.
*/
if(initPing == cps->lastPong) {
if(gameInfo.variant == VariantUnknown) {
DisplayError(_("Engine did not send setup for non-standard variant"), 0);
- *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
+ *engineVariant = NULLCHAR; ASSIGN(appData.variant, "normal"); // back to normal as error recovery?
GameEnds(GameUnfinished, NULL, GE_XBOARD);
}
initPing = -1;
}
+ if(cps->lastPing == cps->lastPong && abortEngineThink) {
+ abortEngineThink = FALSE;
+ DisplayMessage("", "");
+ ThawUI();
+ }
return;
}
if(!strncmp(message, "highlight ", 10)) {
- if(appData.testLegality && appData.markers) return;
+ if(appData.testLegality && !*engineVariant && appData.markers) return;
MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
return;
}
Don't use it. */
cps->sendTime = 0;
}
+ if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
+ if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
+ sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1 ) return;
+ }
/*
* If chess program startup fails, exit with an error message.
if (!ignore) {
ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
+ int solved = 0;
buf1[0] = NULLCHAR;
if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
&plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
+ char score_buf[MSG_SIZ];
+
+ if(nodes>>32 == u64Const(0xFFFFFFFF)) // [HGM] negative node count read
+ nodes += u64Const(0x100000000);
if (plyext != ' ' && plyext != '\t') {
time *= 100;
if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
+ if(*bestMove) { // rememer time best EPD move was first found
+ int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
+ ChessMove mt; char *p = bestMove;
+ int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
+ solved = 0;
+ while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
+ if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
+ solvingTime = (solvingTime < 0 ? time : solvingTime);
+ solved = 1;
+ break;
+ }
+ while(*p && *p != ' ') p++;
+ while(*p == ' ') p++;
+ }
+ if(!solved) solvingTime = -1;
+ }
+ if(*avoidMove && !solved) {
+ int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
+ ChessMove mt; char *p = avoidMove, solved = 1;
+ int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
+ while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
+ if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
+ solved = 0; solvingTime = -2;
+ break;
+ }
+ while(*p && *p != ' ') p++;
+ while(*p == ' ') p++;
+ }
+ if(solved && !*bestMove) solvingTime = (solvingTime < 0 ? time : solvingTime);
+ }
+
if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
char buf[MSG_SIZ];
FILE *f;
[AS] Protect the thinkOutput buffer from overflow... this
is only useful if buf1 hasn't overflowed first!
*/
- snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
+ if((gameMode == AnalyzeMode && appData.whitePOV || appData.scoreWhite) && !WhiteOnMove(forwardMostMove)) curscore *= -1;
+ if(curscore >= MATE_SCORE)
+ snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
+ else if(curscore <= -MATE_SCORE)
+ snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
+ else
+ snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
+ snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
plylev,
(gameMode == TwoMachinesPlay ?
ToUpper(cps->twoMachinesColor[0]) : ' '),
- ((double) curscore) / 100.0,
+ score_buf,
prefixHint ? lastHint : "",
prefixHint ? " " : "" );
void
ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
{
- ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
+ ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, berolina = 0;
int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
/* [HGM] compute & store e.p. status and castling rights for new position */
/* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
- oldEP = (signed char)board[EP_STATUS];
+ oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
board[EP_STATUS] = EP_NONE;
+ board[EP_FILE] = board[EP_RANK] = 100;
if (fromY == DROP_RANK) {
/* must be first */
// ChessSquare victim;
int i;
- if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
+ if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
// victim = board[killY][killX],
+ killed = board[killY][killX],
board[killY][killX] = EmptySquare,
board[EP_STATUS] = EP_CAPTURE;
+ if( kill2X >= 0 && kill2Y >= 0)
+ killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
+ }
if( board[toY][toX] != EmptySquare ) {
board[EP_STATUS] = EP_CAPTURE;
}
}
- if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
- if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
- board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
- } else
- if( board[fromY][fromX] == WhitePawn ) {
+ pawn = board[fromY][fromX];
+ if( pawn == WhiteLance || pawn == BlackLance ) {
+ if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
+ if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
+ else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
+ }
+ }
+ if( pawn == WhitePawn ) {
if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
board[EP_STATUS] = EP_PAWN_MOVE;
- if( toY-fromY==2) {
+ if( toY-fromY>=2) {
+ board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
gameInfo.variant != VariantBerolina || toX < fromX)
board[EP_STATUS] = toX | berolina;
board[EP_STATUS] = toX;
}
} else
- if( board[fromY][fromX] == BlackPawn ) {
+ if( pawn == BlackPawn ) {
if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
board[EP_STATUS] = EP_PAWN_MOVE;
- if( toY-fromY== -2) {
+ if( toY-fromY<= -2) {
+ board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
gameInfo.variant != VariantBerolina || toX < fromX)
board[EP_STATUS] = toX | berolina;
}
}
+ if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
+ if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
+ if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
+ if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
+
for(i=0; i<nrCastlingRights; i++) {
if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
board[CASTLING][i] == toX && castlingRank[i] == toY
if(toY == BOARD_HEIGHT-1) board[VIRGIN][toX] &= ~VIRGIN_B;
}
- if (fromX == toX && fromY == toY) return;
+ if (fromX == toX && fromY == toY && killX < 0) return;
piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
if(gameInfo.variant == VariantKnightmate)
king += (int) WhiteUnicorn - (int) WhiteKing;
+ if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
+ && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) { // and tramples own
+ board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
+ board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
+ board[EP_STATUS] = EP_NONE; // capture was fake!
+ } else
+ if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
+ board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
+ board[toY][toX] = piece;
+ board[EP_STATUS] = EP_NONE; // capture was fake!
+ } else
/* Code added by Tord: */
/* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
+ board[EP_STATUS] = EP_NONE; // capture was fake!
board[fromY][fromX] = EmptySquare;
board[toY][toX] = EmptySquare;
if((toX > fromX) != (piece == WhiteRook)) {
}
} else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
+ board[EP_STATUS] = EP_NONE;
board[fromY][fromX] = EmptySquare;
board[toY][toX] = EmptySquare;
if((toX > fromX) != (piece == BlackRook)) {
}
/* End of code added by Tord */
+ } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
+ board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
+ board[toY][toX] = piece;
} else if (board[fromY][fromX] == king
&& fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
&& toY == fromY && toX > fromX+1) {
+ for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++)
+ ; // castle with nearest piece
+ board[fromY][toX-1] = board[fromY][rookX];
+ board[fromY][rookX] = EmptySquare;
board[fromY][fromX] = EmptySquare;
board[toY][toX] = king;
- board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
- board[fromY][BOARD_RGHT-1] = EmptySquare;
} else if (board[fromY][fromX] == king
&& fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
&& toY == fromY && toX < fromX-1) {
+ for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
+ ; // castle with nearest piece
+ board[fromY][toX+1] = board[fromY][rookX];
+ board[fromY][rookX] = EmptySquare;
board[fromY][fromX] = EmptySquare;
board[toY][toX] = king;
- board[toY][toX+1] = board[fromY][BOARD_LEFT];
- board[fromY][BOARD_LEFT] = EmptySquare;
} else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
&& toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
) {
/* white pawn promotion */
board[toY][toX] = CharToPiece(ToUpper(promoChar));
- if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
- board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
+ 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)
+ && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
&& (toX != fromX)
&& gameInfo.variant != VariantXiangqi
&& gameInfo.variant != VariantBerolina
- && (board[fromY][fromX] == WhitePawn)
+ && (pawn == WhitePawn)
&& (board[toY][toX] == EmptySquare)) {
board[fromY][fromX] = EmptySquare;
- board[toY][toX] = WhitePawn;
- captured = board[toY - 1][toX];
- board[toY - 1][toX] = EmptySquare;
+ board[toY][toX] = piece;
+ if(toY == epRank - 128 + 1)
+ captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
+ else
+ captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
} else if ((fromY == BOARD_HEIGHT-4)
&& (toX == fromX)
&& gameInfo.variant == VariantBerolina
} else if (board[fromY][fromX] == king
&& fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
&& toY == fromY && toX > fromX+1) {
+ for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++)
+ ;
+ board[fromY][toX-1] = board[fromY][rookX];
+ board[fromY][rookX] = EmptySquare;
board[fromY][fromX] = EmptySquare;
board[toY][toX] = king;
- board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
- board[fromY][BOARD_RGHT-1] = EmptySquare;
} else if (board[fromY][fromX] == king
&& fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
&& toY == fromY && toX < fromX-1) {
+ for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
+ ;
+ board[fromY][toX+1] = board[fromY][rookX];
+ board[fromY][rookX] = EmptySquare;
board[fromY][fromX] = EmptySquare;
board[toY][toX] = king;
- board[toY][toX+1] = board[fromY][BOARD_LEFT];
- board[fromY][BOARD_LEFT] = EmptySquare;
} else if (fromY == 7 && fromX == 3
&& board[fromY][fromX] == BlackKing
&& toY == 7 && toX == 5) {
) {
/* black pawn promotion */
board[toY][toX] = CharToPiece(ToLower(promoChar));
- if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
- board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
+ 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)
+ && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
&& (toX != fromX)
&& gameInfo.variant != VariantXiangqi
&& gameInfo.variant != VariantBerolina
- && (board[fromY][fromX] == BlackPawn)
+ && (pawn == BlackPawn)
&& (board[toY][toX] == EmptySquare)) {
board[fromY][fromX] = EmptySquare;
- board[toY][toX] = BlackPawn;
- captured = board[toY + 1][toX];
- board[toY + 1][toX] = EmptySquare;
+ board[toY][toX] = piece;
+ if(toY == epRank - 128 - 1)
+ captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
+ else
+ captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
} else if ((fromY == 3)
&& (toX == fromX)
&& gameInfo.variant == VariantBerolina
p = (int) captured;
if (p >= (int) BlackPawn) {
p -= (int)BlackPawn;
- if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
- /* in Shogi restore piece to its original first */
- captured = (ChessSquare) (DEMOTED captured);
- p = DEMOTED p;
+ if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
+ /* Restore shogi-promoted piece to its original first */
+ captured = (ChessSquare) (DEMOTED(captured));
+ p = DEMOTED(p);
}
p = PieceToNumber((ChessSquare)p);
if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
} else {
p -= (int)WhitePawn;
- if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
- captured = (ChessSquare) (DEMOTED captured);
- p = DEMOTED p;
+ if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
+ captured = (ChessSquare) (DEMOTED(captured));
+ p = DEMOTED(p);
}
p = PieceToNumber((ChessSquare)p);
if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
} else
if(promoChar == '+') {
/* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
- board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
+ board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
} else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
- if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
- && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
+ 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)
ChessSquare p = boards[forwardMostMove][toY][toX];
// forwardMostMove++; // [HGM] bare: moved downstream
+ if(kill2X >= 0) x = kill2X, y = kill2Y; else
if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
(void) CoordsToAlgebraic(boards[forwardMostMove],
PosFlags(forwardMostMove),
- fromY, fromX, y, x, promoChar,
+ fromY, fromX, y, x, (killX < 0)*promoChar,
s);
+ if(kill2X >= 0 && kill2Y >= 0)
+ sprintf(s + strlen(s), "x%c%d", killX + AAA, killY + ONE - '0'); // 2nd leg of 3-leg move is always capture
if(killX >= 0 && killY >= 0)
- sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
+ sprintf(s + strlen(s), "%c%c%d%c", p == EmptySquare || toX == fromX && toY == fromY || toX== kill2X && toY == kill2Y ? '-' : 'x',
+ toX + AAA, toY + ONE - '0', promoChar);
if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
int timeLeft; static int lastLoadFlag=0; int king, piece;
currentMove = forwardMostMove;
}
- killX = killY = -1; // [HGM] lion: used up
+ killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
if (instant) return;
SendToProgram(buf, cps);
}
+ setboardSpoiledMachineBlack = FALSE;
SendToProgram(cps->initString, cps);
if (gameInfo.variant != VariantNormal &&
gameInfo.variant != VariantLoadable
b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
+
if (b == NULL) {
- DisplayFatalError(variantError, 0, 1);
+ VariantClass v;
+ char c, *q = cps->variants, *p = strchr(q, ',');
+ if(p) *p = NULLCHAR;
+ v = StringToVariant(q);
+ DisplayError(variantError, 0);
+ if(v != VariantUnknown && cps == &first) {
+ int w, h, s;
+ if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
+ appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
+ ASSIGN(appData.variant, q);
+ Reset(TRUE, FALSE);
+ }
+ if(p) *p = ',';
return;
}
p++; q++;
}
if(*p) { // difference
- while(*p && *p++ != '\n');
- while(*q && *q++ != '\n');
+ while(*p && *p++ != '\n')
+ ;
+ while(*q && *q++ != '\n')
+ ;
changed = nPlayers;
changes = 1 + (strcmp(p, q) != 0);
}
SWAP(accumulateTC, h)
SWAP(drawDepth, h)
SWAP(host, p)
+ SWAP(pseudo, h)
}
int
res = LoadGameOrPosition(matchGame); // setup game
appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
if(!res) return; // abort when bad game/pos file
+ if(appData.epd) {// in EPD mode we make sure first engine is to move
+ firstWhite = !(forwardMostMove & 1);
+ first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
+ second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
+ }
TwoMachinesEvent();
}
result, resultDetails ? resultDetails : "(null)", whosays);
}
- fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
+ fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move the user is entering. // [HGM] lion
if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
&& gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
&& gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
&& result != GameIsDrawn)
- { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
+ { int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
int p = (signed char)boards[forwardMostMove][i][j] - color;
if(p >= 0 && p <= (int)WhiteKing) k++;
+ oppoKings += (p + color == WhiteKing + BlackPawn - color);
}
if (appData.debugMode) {
fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
result, resultDetails ? resultDetails : "(null)", whosays, k, color);
}
- if(k <= 1) {
+ if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
result = GameIsDrawn;
snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
resultDetails = buf;
return;
} else {
gameMode = nextGameMode;
+ if(appData.epd) {
+ snprintf(buf, MSG_SIZ, "-------------------------------------- ");
+ OutputKibitz(2, buf);
+ snprintf(buf, MSG_SIZ, _("Average solving time %4.2f sec (total time %4.2f sec) "), totalTime/(100.*first.matchWins), totalTime/100.);
+ OutputKibitz(2, buf);
+ snprintf(buf, MSG_SIZ, _("%d avoid-moves played "), second.matchWins);
+ if(second.matchWins) OutputKibitz(2, buf);
+ snprintf(buf, MSG_SIZ, _("Solved %d out of %d (%3.1f%%) "), first.matchWins, nextGame-1, first.matchWins*100./(nextGame-1));
+ OutputKibitz(2, buf);
+ }
snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
first.tidy, second.tidy,
first.matchWins, second.matchWins,
fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
redraw, init, gameMode);
}
+ pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
+ deadRanks = 0; // assume entire board is used
+ for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
CleanupTail(); // [HGM] vari: delete any stored variations
CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
pausing = pauseExamInvalid = FALSE;
lastHint[0] = NULLCHAR;
ClearGameInfo(&gameInfo);
gameInfo.variant = StringToVariant(appData.variant);
- if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
+ if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) {
+ gameInfo.variant = VariantUnknown;
+ strncpy(engineVariant, appData.variant, MSG_SIZ);
+ }
ics_user_moved = ics_clock_paused = FALSE;
ics_getting_history = H_FALSE;
ics_gamenum = -1;
ClearPremoveHighlights();
gotPremove = FALSE;
alarmSounded = FALSE;
- killX = killY = -1; // [HGM] lion
+ killX = killY = kill2X = kill2Y = -1; // [HGM] lion
GameEnds(EndOfFile, NULL, GE_PLAYER);
if(appData.serverMovesName != NULL) {
HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
+ if(moveList[currentMove][4] == ';') { // multi-leg
+ killX = moveList[currentMove][5] - AAA;
+ killY = moveList[currentMove][6] - ONE;
+ }
AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
+ killX = killY = -1;
if (appData.highlightLastMove) {
SetHighlights(fromX, fromY, toX, toY);
case BlackASideCastleFR:
/* POP Fabien */
if (appData.debugMode)
- fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
+ fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
fromX = currentMoveString[0] - AAA;
fromY = currentMoveString[1] - ONE;
toX = currentMoveString[2] - AAA;
toY = currentMoveString[3] - ONE;
promoChar = currentMoveString[4];
- if(promoChar == ';') promoChar = NULLCHAR;
+ if(promoChar == ';') promoChar = currentMoveString[7];
break;
case WhiteDrop:
if (appData.debugMode)
fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
yy_text, currentMoveString);
- fromX = currentMoveString[0] - AAA;
- fromY = currentMoveString[1] - ONE;
+ if(currentMoveString[1] == '@') {
+ fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
+ fromY = DROP_RANK;
+ } else {
+ fromX = currentMoveString[0] - AAA;
+ fromY = currentMoveString[1] - ONE;
+ }
toX = currentMoveString[2] - AAA;
toY = currentMoveString[3] - ONE;
promoChar = currentMoveString[4];
thinkOutput[0] = NULLCHAR;
MakeMove(fromX, fromY, toX, toY, promoChar);
- killX = killY = -1; // [HGM] lion: used up
+ killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
currentMove = forwardMostMove;
return TRUE;
}
int
QuickScan (Board board, Move *move)
{ // reconstruct game,and compare all positions in it
- int cnt=0, stretch=0, total = MakePieceList(board, counts);
+ int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
do {
int piece = move->piece;
int to = move->to, from = pieceList[piece];
+ if(found < 0) { // if already found just scan to game end for final piece count
+ if(QuickCompare(soughtBoard, minSought, maxSought) ||
+ appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
+ flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
+ appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
+ ) {
+ static int lastCounts[EmptySquare+1];
+ int i;
+ if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
+ if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
+ } else stretch = 0;
+ if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
+ if(found >= 0 && !appData.minPieces) return found;
+ }
if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
- if(!piece) return -1;
+ if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
piece = (++move)->piece;
from = pieceList[piece];
}
}
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
+ if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
quickBoard[from] = 0;
aftercastle:
quickBoard[to] = piece;
pieceList[piece] = to;
cnt++; turn ^= 3;
- if(QuickCompare(soughtBoard, minSought, maxSought) ||
- appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
- flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
- appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
- ) {
- static int lastCounts[EmptySquare+1];
- int i;
- if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
- if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
- } else stretch = 0;
- if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
move++;
} while(1);
}
char buf[MSG_SIZ];
int gn = gameNumber;
ListGame *lg = NULL;
- int numPGNTags = 0;
+ int numPGNTags = 0, i;
int err, pos = -1;
GameMode oldGameMode;
- VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
+ VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
+ char oldName[MSG_SIZ];
+
+ safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
if (appData.debugMode)
fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
if (gameMode != BeginningOfGame) {
Reset(FALSE, TRUE);
}
- killX = killY = -1; // [HGM] lion: in case we did not Reset
+ killX = killY = kill2X = kill2Y = -1; // [HGM] lion: in case we did not Reset
gameFileFP = f;
if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
if (appData.debugMode)
fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
+ for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; } // reset VariantMen
+
if (cm == XBoardGame) {
/* Skip any header junk before position diagram and/or move 1 */
for (;;) {
gameInfo.event = StrSave(yy_text);
}
- startedFromSetupPosition = FALSE;
+ startedFromSetupPosition = startedFromPositionFile; // [HGM]
while (cm == PGNTag) {
if (appData.debugMode)
fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
if (!err) numPGNTags++;
/* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
- if(gameInfo.variant != oldVariant) {
+ if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
InitPosition(TRUE);
return FALSE;
}
CopyBoard(boards[0], initial_position);
+ if(*engineVariant || gameInfo.variant == VariantFairy) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
+ CopyBoard(initialPosition, initial_position);
if (blackPlaysFirst) {
currentMove = forwardMostMove = backwardMostMove = 1;
CopyBoard(boards[1], initial_position);
StartChessProgram(&first);
}
InitChessProgram(&first, FALSE);
+ if(gameInfo.variant == VariantUnknown && *oldName) {
+ safeStrCpy(engineVariant, oldName, MSG_SIZ);
+ gameInfo.variant = v;
+ }
SendToProgram("force\n", &first);
if (startedFromSetupPosition) {
SendBoard(&first, forwardMostMove);
DisplayError(_("Position not found in file"), 0);
return FALSE;
}
- // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
- fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
+ // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
+ fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
if (pn >= 2) {
if (fenMode || line[0] == '#') pn--;
}
if (fenMode) {
+ char *p;
if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
DisplayError(_("Bad FEN position in file"), 0);
return FALSE;
}
+ if((strchr(line, ';')) && (p = strstr(line, " bm "))) { // EPD with best move
+ sscanf(p+4, "%[^;]", bestMove);
+ } else *bestMove = NULLCHAR;
+ if((strchr(line, ';')) && (p = strstr(line, " am "))) { // EPD with avoid move
+ sscanf(p+4, "%[^;]", avoidMove);
+ } else *avoidMove = NULLCHAR;
} else {
(void) fgets(line, MSG_SIZ, f);
(void) fgets(line, MSG_SIZ, f);
}
}
-/* Save game in PGN style and close the file */
-int
-SaveGamePGN (FILE *f)
+/* Save game in PGN style */
+static void
+SaveGamePGN2 (FILE *f)
{
int i, offset, linelen, newblock;
// char *movetext;
snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
}
+ if(appData.cumulativeTimePGN) {
+ snprintf(buf, MSG_SIZ, " %+ld", timeRemaining[i & 1][i+1]/1000);
+ }
+
snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
pvInfoList[i].score >= 0 ? "+" : "",
pvInfoList[i].score / 100.0,
} else {
fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
}
+}
+/* Save game in PGN style and close the file */
+int
+SaveGamePGN (FILE *f)
+{
+ SaveGamePGN2(f);
fclose(f);
lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
return TRUE;
return;
}
+ if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
if (telnetISR != NULL) {
ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
return;
}
+ if(!appData.epd) {
if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
- startingEngine = FALSE;
+ startingEngine = matchMode = FALSE;
DisplayError("second engine does not play this", 0);
+ gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
+ EditGameEvent(); // switch back to EditGame mode
return;
}
ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
return;
}
+ }
GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
if(appData.matchPause>10000 || appData.matchPause<10)
appData.matchPause = 10000; /* [HGM] make pause adjustable */
// we are now committed to starting the game
stalling = 0;
DisplayMessage("", "");
+ if(!appData.epd) {
if (startedFromSetupPosition) {
SendBoard(&second, backwardMostMove);
if (appData.debugMode) {
for (i = backwardMostMove; i < forwardMostMove; i++) {
SendMoveToProgram(i, &second);
}
+ }
gameMode = TwoMachinesPlay;
pausing = startingEngine = FALSE;
snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
SendToProgram(buf, &first);
}
+ if(!appData.epd) {
SendToProgram(second.computerString, &second);
if (second.sendName) {
snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
SendToProgram(buf, &second);
}
+ }
ResetClocks();
if (!first.sendTime || !second.sendTime) {
case MachinePlaysBlack:
case BeginningOfGame:
SendToProgram("force\n", &first);
+ if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
+ if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
+ char buf[MSG_SIZ];
+ abortEngineThink = TRUE;
+ snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
+ SendToProgram(buf, &first);
+ DisplayMessage("Aborting engine think", "");
+ FreezeUI();
+ }
+ }
SetUserThinkingEnables();
break;
case PlayFromGameFile:
SetGameInfo();
}
-
void
EditPositionEvent ()
{
+ int i;
if (gameMode == EditPosition) {
EditGameEvent();
return;
gameMode = EditPosition;
ModeHighlight();
SetGameInfo();
+ CopyBoard(rightsBoard, nullBoard);
if (currentMove > 0)
CopyBoard(boards[0], boards[currentMove]);
+ for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
+ rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
blackPlaysFirst = !WhiteOnMove(currentMove);
ResetClocks();
startedFromSetupPosition = TRUE;
InitChessProgram(&first, FALSE);
if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
+ int r, f;
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 ? 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 ? 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;
+ for(f=0; f<=nrCastlingRights; f++) boards[0][CASTLING][f] = NoRights;
+ for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // first pass: Kings & e.p.
+ if(rightsBoard[r][f]) {
+ ChessSquare p = boards[0][r][f];
+ if(p == (blackPlaysFirst ? WhitePawn : BlackPawn)) boards[0][EP_STATUS] = f;
+ else if(p == king) boards[0][CASTLING][2] = f;
+ else if(p == WHITE_TO_BLACK king) boards[0][CASTLING][5] = f;
+ else rightsBoard[r][f] = 2; // mark for second pass
+ }
+ }
+ for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // second pass: Rooks
+ if(rightsBoard[r][f] == 2) {
+ ChessSquare p = boards[0][r][f];
+ if(p == WhiteRook) boards[0][CASTLING][(f < boards[0][CASTLING][2])] = f; else
+ if(p == BlackRook) boards[0][CASTLING][(f < boards[0][CASTLING][5])+3] = f;
}
}
}
ChessSquare piece = boards[0][y][x];
static Board erasedBoard, currentBoard, menuBoard, nullBoard;
static int lastVariant;
+ int baseRank = BOARD_HEIGHT-1, hasRights = 0;
if (gameMode != EditPosition && gameMode != IcsExamining) return;
switch (selection) {
case ClearBoard:
+ fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
+ MarkTargetSquares(1);
CopyBoard(currentBoard, boards[0]);
CopyBoard(menuBoard, initialPosition);
if (gameMode == IcsExamining && ics_type == ICS_FICS) {
AAA + x, ONE + y);
SendToICS(buf);
}
- } else {
+ } else if(boards[0][y][x] != DarkSquare) {
if(boards[0][y][x] != p) nonEmpty++;
boards[0][y][x] = p;
}
}
- menuBoard[1][x] = menuBoard[BOARD_HEIGHT-2][x] = p;
}
+ CopyBoard(rightsBoard, nullBoard);
if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
- for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates
- ChessSquare p = menuBoard[0][x];
- for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[0][y] == p) menuBoard[0][y] = EmptySquare;
- p = menuBoard[BOARD_HEIGHT-1][x];
- for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[BOARD_HEIGHT-1][y] == p) menuBoard[BOARD_HEIGHT-1][y] = EmptySquare;
+ int r, i;
+ for(r = 0; r < BOARD_HEIGHT; r++) {
+ for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates
+ ChessSquare p = menuBoard[r][x];
+ for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
+ }
}
+ menuBoard[CASTLING][0] = menuBoard[CASTLING][3] = NoRights; // h-side Rook was deleted
DisplayMessage("Clicking clock again restores position", "");
if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
if(!nonEmpty) { // asked to clear an empty board
} else
CopyBoard(erasedBoard, currentBoard);
+ for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
+ rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
}
}
if (gameMode == EditPosition) {
case PromotePiece:
if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
piece >= (int)BlackPawn && piece < (int)BlackMan ) {
- selection = (ChessSquare) (PROMOTED piece);
+ selection = (ChessSquare) (PROMOTED(piece));
} else if(piece == EmptySquare) selection = WhiteSilver;
else selection = (ChessSquare)((int)piece - 1);
goto defaultlabel;
case DemotePiece:
if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
piece > (int)BlackMan && piece <= (int)BlackKing ) {
- selection = (ChessSquare) (DEMOTED piece);
+ selection = (ChessSquare) (DEMOTED(piece));
} else if(piece == EmptySquare) selection = BlackSilver;
else selection = (ChessSquare)((int)piece + 1);
goto defaultlabel;
selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
goto defaultlabel;
+ case WhiteRook:
+ baseRank = 0;
+ case BlackRook:
+ if(y == baseRank && (x == BOARD_LEFT || x == BOARD_RGHT-1 || appData.fischerCastling)) hasRights = 1;
+ if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
+ goto defaultlabel;
+
case WhiteKing:
+ baseRank = 0;
case BlackKing:
if(gameInfo.variant == VariantXiangqi)
selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
if(gameInfo.variant == VariantKnightmate)
selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
+ if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
default:
defaultlabel:
if (gameMode == IcsExamining) {
PieceToChar(selection), AAA + x, ONE + y);
SendToICS(buf);
} else {
+ rightsBoard[y][x] = hasRights;
if(x < BOARD_LEFT || x >= BOARD_RGHT) {
int n;
if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
if (gameMode == EditPosition || gameMode == IcsExamining) {
if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
SetBlackToPlayEvent();
- } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
+ } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
+ gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
} else if (shiftKey) {
AdjustClock(which, -1);
if (gameMode == EditPosition || gameMode == IcsExamining) {
if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
SetWhiteToPlayEvent();
- } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
+ } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
+ gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
} else if (shiftKey) {
AdjustClock(which, -1);
seekGraphUp = FALSE;
MarkTargetSquares(1);
+ fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
if (gameMode == PlayFromGameFile && !pausing)
PauseEvent();
fromX = moveList[target - 1][0] - AAA;
fromY = moveList[target - 1][1] - ONE;
if (target == currentMove + 1) {
+ if(moveList[target - 1][4] == ';') { // multi-leg
+ killX = moveList[target - 1][5] - AAA;
+ killY = moveList[target - 1][6] - ONE;
+ }
AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
+ killX = killY = -1;
}
if (appData.highlightLastMove) {
SetHighlights(fromX, fromY, toX, toY);
if (gameMode == EditPosition) return;
seekGraphUp = FALSE;
MarkTargetSquares(1);
+ fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
if (currentMove <= backwardMostMove) {
ClearHighlights();
DrawPosition(full_redraw, boards[currentMove]);
hintRequested = TRUE;
}
+int
+SaveSelected (FILE *g, int dummy, char *dummy2)
+{
+ ListGame * lg = (ListGame *) gameList.head;
+ int nItem, cnt=0;
+ FILE *f;
+
+ if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
+ DisplayError(_("Game list not loaded or empty"), 0);
+ return 0;
+ }
+
+ creatingBook = TRUE; // suppresses stuff during load game
+
+ /* Get list size */
+ for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
+ if(lg->position >= 0) { // selected?
+ LoadGame(f, nItem, "", TRUE);
+ SaveGamePGN2(g); // leaves g open
+ cnt++; DoEvents();
+ }
+ lg = (ListGame *) lg->node.succ;
+ }
+
+ fclose(g);
+ creatingBook = FALSE;
+
+ return cnt;
+}
+
void
CreateBookEvent ()
{
/* Get list size */
for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
- LoadGame(f, nItem, "", TRUE);
- AddGameToBook(TRUE);
+ if(lg->position >= 0) {
+ LoadGame(f, nItem, "", TRUE);
+ AddGameToBook(TRUE);
+ DoEvents();
+ }
lg = (ListGame *) lg->node.succ;
}
for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
char c = PieceToChar(boards[move][i][j]);
- fputc(c == 'x' ? '.' : c, fp);
+ fputc(c == '?' ? '.' : c, fp);
fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
}
}
pvInfoList[index-1].score = score;
pvInfoList[index-1].time = 10*time; // centi-sec
if(*sep == '}') *sep = 0; else *--sep = '{';
- if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
+ if(p != text) {
+ while(*p++ = *sep++)
+ ;
+ sep = text;
+ } // squeeze out space between PV and comment, and return both
}
return sep;
}
v = StringToVariant(s);
if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
+ if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
+ !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
+ !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
+ !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
}
if(p) *p++ = ',';
char *p, *q, buf[MSG_SIZ];
int n, min = (-1)<<31, max = 1<<31, def;
+ opt->target = &opt->value; // OK for spin/slider and checkbox
if(p = strstr(opt->name, " -spin ")) {
if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
if(max < min) max = min; // enforce consistency
} else if((p = strstr(opt->name, " -string "))) {
opt->textValue = p+9;
opt->type = TextBox;
+ opt->target = &opt->textValue;
} else if((p = strstr(opt->name, " -file "))) {
// for now -file is a synonym for -string, to already provide compatibility with future polyglots
- opt->textValue = p+7;
+ opt->target = opt->textValue = p+7;
opt->type = FileName; // FileName;
+ opt->target = &opt->textValue;
} else if((p = strstr(opt->name, " -path "))) {
// for now -file is a synonym for -string, to already provide compatibility with future polyglots
- opt->textValue = p+7;
+ opt->target = opt->textValue = p+7;
opt->type = PathName; // PathName;
+ opt->target = &opt->textValue;
} else if(p = strstr(opt->name, " -check ")) {
if(sscanf(p, " -check %d", &def) < 1) return FALSE;
opt->value = (def != 0);
(cb == TwoMachinesEventIfReady)) {
CancelDelayedEvent();
ScheduleDelayedEvent(cb, val ? 1 : 3600000);
- }
+ } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
cps->initDone = val;
- if(val) cps->reload = FALSE;
+ if(val) cps->reload = FALSE, RefreshSettingsDialog(cps, val);
}
/* Parse feature command from engine */
#define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
+static int timeSuffix; // [HGM] This should realy be a passed parameter, but it has to pass through too many levels for my laziness...
+
/* Decrement running clock by amount of time that has passed */
void
DecrementClocks ()
{
- long timeRemaining;
+ long tRemaining;
long lastTickLength, fudge;
TimeMark now;
if (WhiteOnMove(forwardMostMove)) {
if(whiteNPS >= 0) lastTickLength = 0;
- timeRemaining = whiteTimeRemaining -= lastTickLength;
- if(timeRemaining < 0 && !appData.icsActive) {
+ tRemaining = whiteTimeRemaining -= lastTickLength;
+ if( tRemaining < 0 && !appData.icsActive) {
GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
whiteStartMove = forwardMostMove; whiteTC = nextSession;
- lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
+ lastWhite= tRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
}
}
+ if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[0][forwardMostMove-1] - tRemaining;
DisplayWhiteClock(whiteTimeRemaining - fudge,
WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
+ timeSuffix = 0;
} else {
if(blackNPS >= 0) lastTickLength = 0;
- timeRemaining = blackTimeRemaining -= lastTickLength;
- if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
+ tRemaining = blackTimeRemaining -= lastTickLength;
+ if( tRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
if(suddenDeath) {
blackStartMove = forwardMostMove;
- lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
+ lastBlack = tRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
}
}
+ if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[1][forwardMostMove-1] - tRemaining;
DisplayBlackClock(blackTimeRemaining - fudge,
!WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
+ timeSuffix = 0;
}
if (CheckFlags()) return;
}
tickStartTM = now;
- intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
+ intendedTickLength = NextTickLength( tRemaining - fudge) + fudge;
StartClockTimer(intendedTickLength);
/* if the time remaining has fallen below the alarm threshold, sound the
((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
)) return;
- if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
+ if (alarmSounded && ( tRemaining > appData.icsAlarmTime)) {
alarmSounded = FALSE;
- } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
+ } else if (!alarmSounded && ( tRemaining <= appData.icsAlarmTime)) {
PlayAlarmSound();
alarmSounded = TRUE;
}
{
long second, minute, hour, day;
char *sign = "";
- static char buf[32];
+ static char buf[40], moveTime[8];
if (ms > 0 && ms <= 9900) {
/* convert milliseconds to tenths, rounding up */
minute = second / 60;
second = second % 60;
+ if(timeSuffix) snprintf(moveTime, 8, " (%d)", timeSuffix/1000); // [HGM] kludge alert; fraction contains move time
+ else *moveTime = NULLCHAR;
+
if (day > 0)
- snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
- sign, day, hour, minute, second);
+ snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld%s ",
+ sign, day, hour, minute, second, moveTime);
else if (hour > 0)
- snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
+ snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld%s ", sign, hour, minute, second, moveTime);
else
- snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
+ snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld%s ", sign, minute, second, moveTime);
return buf;
}
PositionToFEN (int move, char *overrideCastling, int moveCounts)
{
int i, j, fromX, fromY, toX, toY;
- int whiteToPlay;
+ int whiteToPlay, haveRights = nrCastlingRights;
char buf[MSG_SIZ];
char *p, *q;
int emptycount;
p = buf;
/* Piece placement data */
- for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
+ for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) {
if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
emptycount = 0;
for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
if(PieceToChar(piece) == '+') {
/* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
*p++ = '+';
- piece = (ChessSquare)(DEMOTED piece);
+ piece = (ChessSquare)(CHUDEMOTED(piece));
}
*p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
+ if(*p = PieceSuffix(piece)) p++;
if(p[-1] == '~') {
/* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
- p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
+ p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
*p++ = '~';
}
}
*p++ = whiteToPlay ? 'w' : 'b';
*p++ = ' ';
+ if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
+ haveRights = 0; q = p;
+ for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
+ piece = boards[move][0][i];
+ if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
+ if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
+ }
+ }
+ for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
+ piece = boards[move][BOARD_HEIGHT-1][i];
+ if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
+ if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
+ }
+ }
+ if(p == q) *p++ = '-';
+ *p++ = ' ';
+ }
+
if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
- while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
+ while(*p++ = *q++)
+ ;
+ if(q != overrideCastling+1) p[-1] = ' '; else --p;
} else {
- if(nrCastlingRights) {
+ if(haveRights) {
+ int handW=0, handB=0;
+ if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
+ for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
+ for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
+ }
q = p;
if(appData.fischerCastling) {
+ if(handW) { // in shuffle S-Chess simply dump all virgin pieces
+ for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
+ if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
+ } else {
/* [HGM] write directly from rights */
if(boards[move][CASTLING][2] != NoRights &&
boards[move][CASTLING][0] != NoRights )
if(boards[move][CASTLING][2] != NoRights &&
boards[move][CASTLING][1] != NoRights )
*p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
+ }
+ if(handB) {
+ for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
+ if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
+ } else {
if(boards[move][CASTLING][5] != NoRights &&
boards[move][CASTLING][3] != NoRights )
*p++ = boards[move][CASTLING][3] + AAA;
if(boards[move][CASTLING][5] != NoRights &&
boards[move][CASTLING][4] != NoRights )
*p++ = boards[move][CASTLING][4] + AAA;
+ }
} else {
/* [HGM] write true castling rights */
if( nrCastlingRights == 6 ) {
int q, k=0;
- if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
+ if(boards[move][CASTLING][0] != NoRights &&
boards[move][CASTLING][2] != NoRights ) k = 1, *p++ = 'K';
- q = (boards[move][CASTLING][1] == BOARD_LEFT &&
+ q = (boards[move][CASTLING][1] != NoRights &&
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(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
+ for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; 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 &&
+ if(boards[move][CASTLING][3] != NoRights &&
boards[move][CASTLING][5] != NoRights ) k = 1, *p++ = 'k';
- q = (boards[move][CASTLING][4] == BOARD_LEFT &&
+ q = (boards[move][CASTLING][4] != NoRights &&
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(handB) {
+ for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
}
Boolean
ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
{
- int i, j, k, w=0, subst=0, shuffle=0;
+ int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
char *p, c;
int emptycount, virgin[BOARD_FILES];
- ChessSquare piece;
+ ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
p = fen;
+ for(i=1; i<=deadRanks; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) board[BOARD_HEIGHT-i][j] = DarkSquare;
+
/* Piece placement data */
- for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
+ for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) {
j = 0;
for (;;) {
if (*p == '/' || *p == ' ' || *p == '[' ) {
while (emptycount--)
board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
if (*p == '/') p++;
- else if(autoSize) { // we stumbled unexpectedly into end of board
+ else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
}
appData.NrRanks = gameInfo.boardHeight - i; i=0;
}
break;
-#if(BOARD_FILES >= 10)
+#if(BOARD_FILES >= 10)*0
} else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
p++; emptycount=10;
if (j + emptycount > gameInfo.boardWidth) return FALSE;
if (i != 0 && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
} else if (*p == '+' || isalpha(*p)) {
+ char *q, *s = SUFFIXES;
if (j >= gameInfo.boardWidth) return FALSE;
if(*p=='+') {
- piece = CharToPiece(*++p);
+ char c = *++p;
+ if(q = strchr(s, p[1])) p++;
+ piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
if(piece == EmptySquare) return FALSE; /* unknown piece */
- piece = (ChessSquare) (CHUPROMOTED piece ); p++;
+ piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
- } else piece = CharToPiece(*p++);
+ } else {
+ char c = *p++;
+ if(q = strchr(s, *p)) p++;
+ piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
+ }
if(piece==EmptySquare) return FALSE; /* unknown piece */
if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
- piece = (ChessSquare) (PROMOTED piece);
+ piece = (ChessSquare) (PROMOTED(piece));
if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
p++;
}
board[i][(j++)+gameInfo.holdingsWidth] = piece;
+ if(piece == king) wKingRank = i;
+ if(piece == WHITE_TO_BLACK king) bKingRank = i;
} else {
return FALSE;
}
}
while (*p == '/' || *p == ' ') p++;
- if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
+ if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
/* [HGM] by default clear Crazyhouse holdings, if present */
if(gameInfo.holdingsWidth) {
/* [HGM] We NO LONGER ignore the rest of the FEN notation */
/* return the extra info in global variiables */
+ while(*p==' ') p++;
+
+ if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
+ if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
+ if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
+ }
+
/* set defaults in case FEN is incomplete */
board[EP_STATUS] = EP_UNKNOWN;
+ board[TOUCHED_W] = board[TOUCHED_B] = 0;
for(i=0; i<nrCastlingRights; i++ ) {
board[CASTLING][i] =
appData.fischerCastling ? NoRights : initialRights[i];
&& board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
FENrulePlies = 0;
- while(*p==' ') p++;
+ if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
+ char *q = p;
+ int w=0, b=0;
+ while(isalpha(*p)) {
+ if(isupper(*p)) w |= 1 << (*p++ - 'A');
+ if(islower(*p)) b |= 1 << (*p++ - 'a');
+ }
+ if(*p == '-') p++;
+ if(p != q) {
+ board[TOUCHED_W] = ~w;
+ board[TOUCHED_B] = ~b;
+ while(*p == ' ') p++;
+ }
+ } else
+
if(nrCastlingRights) {
int fischer = 0;
if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
- if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
- if(board[0 ][i] == WhiteKing) whiteKingFile = i;
+ if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
+ if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
}
if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
- if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
- && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
- if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
- && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
+ if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
+ && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
+ if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
+ && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
switch(c) {
case'K':
- for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
+ for(i=BOARD_RGHT-1; board[castlingRank[2]][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(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
break;
case'Q':
- for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
+ for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[2]][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(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
break;
case'k':
- for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
+ for(i=BOARD_RGHT-1; board[castlingRank[5]][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(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
break;
case'q':
- for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
+ for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][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;