X-Git-Url: http://winboard.nl/cgi-bin?a=blobdiff_plain;f=backend.c;h=8a47479ff637f2c1d2aa1a9fd72f72adb0f3203e;hb=ff23924264802924788f2a15c73a8d058e90432e;hp=88848ef2d6dbf97b68932aa125c6672f72589e74;hpb=07596a90fd52fa895bab836cf22e83c6c3f37e62;p=xboard.git diff --git a/backend.c b/backend.c index 88848ef..8a47479 100644 --- a/backend.c +++ b/backend.c @@ -5,7 +5,7 @@ * 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 Free Software Foundation, Inc. * * Enhancements Copyright 2005 Alessandro Scotti * @@ -295,7 +295,9 @@ ChessSquare promoSweep = EmptySquare, defaultPromoChoice; int promoDefaultAltered; int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */ static int initPing = -1; -static Boolean pieceDefs; +int border; /* [HGM] width of board rim, needed to size seek graph */ +char bestMove[MSG_SIZ]; +int solvingTime, totalTime; /* States for ics_getting_history */ #define H_FALSE 0 @@ -441,6 +443,7 @@ char *currentDebugFile; // [HGM] debug split: to remember name 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; @@ -848,7 +851,7 @@ InitEngine (ChessProgramState *cps, int n) 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; @@ -1201,6 +1204,11 @@ InitBackEnd1 () 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 */ @@ -1213,7 +1221,6 @@ InitBackEnd1 () 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 */ @@ -1734,6 +1741,7 @@ InitBackEnd3 P((void)) if(!blackPlaysFirst) { startedFromPositionFile = TRUE; CopyBoard(filePosition, boards[0]); + CopyBoard(initialPosition, boards[0]); } } if (initialMode == AnalyzeMode) { @@ -2104,14 +2112,14 @@ StringToVariant (char *e) 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++ != '_'); } @@ -2121,7 +2129,7 @@ StringToVariant (char *e) } else for (i=0; 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; @@ -2717,8 +2725,8 @@ DrawSeekGraph () { 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); @@ -4882,13 +4890,13 @@ ParseBoard12 (char *string) 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. */ @@ -5137,10 +5145,17 @@ SendMoveToProgram (int moveNum, ChessProgramState *cps) 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]; + 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 + snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\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'); SendToProgram(buf, cps); } else if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed @@ -5217,7 +5232,7 @@ SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char 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)); @@ -5323,10 +5338,11 @@ UploadGameEvent () 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 @@ -5336,7 +5352,10 @@ CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char 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 + killX, ONE + killY); + } } else { sprintf(move, "%c%c%c%c%c\n", AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar); @@ -5364,7 +5383,7 @@ static ClickType lastClickType; int Partner (ChessSquare *p) { // change piece into promotion partner if one shogi-promotes to the other - int stride = gameInfo.variant == VariantChu ? 22 : 11; + int stride = gameInfo.variant == VariantChu ? WhiteTokin : 11; ChessSquare partner; partner = (*p/stride & 1 ? *p - stride : *p + stride); if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0; @@ -5387,11 +5406,13 @@ Sweep (int step) 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] ? !strchr(promoRestrict, ToUpper(PieceToChar(promoSweep))) : // if choice set available, use it + promoSweep == pawn || appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess && (promoSweep == WhiteLion || promoSweep == BlackLion))); if(toX >= 0) { @@ -5515,6 +5536,12 @@ ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fro 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; @@ -5541,6 +5568,7 @@ ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fro *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; @@ -5636,21 +5664,26 @@ ParsePV (char *pv, Boolean storeComments, Boolean atEnd) } 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; inrOptions; i++) - if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin) - return i; + for(i=0; inrOptions; 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); @@ -5664,14 +5697,22 @@ LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane) 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; @@ -5719,6 +5760,16 @@ void 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; @@ -5747,6 +5798,17 @@ MovePV (int x, int y, int h) { // 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; @@ -5928,23 +5990,40 @@ SetUpShuffle (Board board, int number) } 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 != '/' && !strchr(escapes, *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; + int result = FALSE; int NrPieces, offs; - 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, j = 0; /* [HGM] Accept even length from 12 to 88 */ for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.'; - for( i=0; ialphaRank) { /* [HGM] shogi: translate coords */ message[1] = BOARD_RGHT - 1 - j + '1'; @@ -6375,12 +6461,12 @@ SendBoard (ChessProgramState *cps, int moveNum) && ((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'; @@ -6510,8 +6596,10 @@ DefaultPromoChoice (int white) { 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) @@ -6544,9 +6632,9 @@ HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, i 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 = (p >= WhiteTokin || PieceToChar(piece + WhiteTokin) != '+') ? WhitePawn : WhiteTokin-1; + } 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; @@ -6603,7 +6691,7 @@ HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, i // 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++); @@ -6942,14 +7030,14 @@ UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar) /* 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 q, p = boards[0][rf][ff]; if(p >= BlackPawn) p = BLACK_TO_WHITE p; - if(CHUPROMOTED p < BlackPawn) p = q = CHUPROMOTED boards[0][rf][ff]; + if(CHUPROMOTED(p) < BlackPawn) p = q = CHUPROMOTED(boards[0][rf][ff]); else p = CHUDEMOTED (q = boards[0][rf][ff]); if(PieceToChar(q) == '+') gatingPiece = p; } @@ -6985,7 +7073,7 @@ UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar) // 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; } @@ -6995,6 +7083,8 @@ UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar) 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 (moveType == IllegalMove || moveType == ImpossibleMove) { @@ -7013,6 +7103,7 @@ UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar) 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", fromX + AAA, fromY + ONE - '0', killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0'); snprintf(buf, MSG_SIZ, " 0.0%% 1 %s\n", move); AddBookMove(buf); addToBookFlag = FALSE; @@ -7229,7 +7320,7 @@ MarkByFEN(char *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 @@ -7258,11 +7349,11 @@ Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VO { 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 : rt == killY && ft == killX || legNr & 2)) (*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), 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; @@ -7314,11 +7405,11 @@ CanPromote (ChessSquare piece, int y) 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 == VariantMakruk) 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 @@ -7354,12 +7445,15 @@ void ReportClick(char *action, int x, int y) 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= 0) { @@ -7386,6 +7473,20 @@ LeftClick (ClickType clickType, int xPix, int yPix) 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 @@ -7466,7 +7567,7 @@ LeftClick (ClickType clickType, int xPix, int yPix) 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")); } @@ -7494,6 +7595,7 @@ LeftClick (ClickType clickType, int xPix, int yPix) 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) && @@ -7525,12 +7627,12 @@ LeftClick (ClickType clickType, int xPix, int yPix) 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 } } @@ -7542,7 +7644,13 @@ LeftClick (ClickType clickType, int xPix, int yPix) 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 @@ -7556,10 +7664,9 @@ LeftClick (ClickType clickType, int xPix, int yPix) /* 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); @@ -7575,7 +7682,8 @@ LeftClick (ClickType clickType, int xPix, int yPix) 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); @@ -7610,8 +7718,9 @@ LeftClick (ClickType clickType, int xPix, int yPix) 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 Sweep(0); // Pawn that is going to promote: preview promotion piece sweepSelecting = 1; DisplayMessage("", _("Pull pawn backwards to under-promote")); @@ -7627,6 +7736,7 @@ LeftClick (ClickType clickType, int xPix, int yPix) } } 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; if (appData.animate || appData.highlightLastMove) { SetHighlights(fromX, fromY, toX, toY); } else { @@ -7646,7 +7756,9 @@ LeftClick (ClickType clickType, int xPix, int yPix) 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 { + if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill + else { + kill2X = killX; kill2Y = killY; killX = x; killY = y; //remeber this square as intermediate ReportClick("put", x, y); // and inform engine ReportClick("lift", x, y); @@ -7693,7 +7805,9 @@ LeftClick (ClickType clickType, int xPix, int yPix) if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece)); - if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) { + if(legal[toY][toX] == 2) promoChoice = ToLower(PieceToChar(defaultPromoChoice)); // highlight-induced promotion + + 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) { @@ -8037,7 +8151,7 @@ Adjudicate (ChessProgramState *cps) // 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; @@ -8479,7 +8593,7 @@ DeferredBookMove (void) 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) @@ -8562,24 +8676,27 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h 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 { + switch (gameMode) { case BeginningOfGame: /* Extra move from before last reset; ignore */ @@ -8625,21 +8742,28 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h } 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 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) + safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off + 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(*p == *buf) // if first-leg to not equal to second-leg from first leg says unmodified (assume it ia 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; @@ -8651,8 +8775,8 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h 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, buf1, GE_XBOARD); @@ -8717,6 +8841,17 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h free(fen); GameEnds(GameUnfinished, NULL, GE_XBOARD); } + if(appData.epd) { + if(solvingTime >= 0) { + snprintf(buf1, MSG_SIZ, "%d. %4.2fs\n", matchGame, solvingTime/100.); + totalTime += solvingTime; first.matchWins++; + } else { + snprintf(buf1, MSG_SIZ, "%d. wrong (%s)\n", matchGame, parseList[backwardMostMove]); + 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 && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) { @@ -8728,7 +8863,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h if( count & 1 ) { score = -score; /* Flip score for winning side */ } -printf("score=%d count=%d\n",score,count); + if( score > appData.adjudicateLossThreshold ) { break; } @@ -8867,10 +9002,9 @@ printf("score=%d count=%d\n",score,count); 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); + s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES); ASSIGN(appData.pieceToCharTable, buf); } - if(startedFromSetupPosition) return; dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName); if(dummy >= 3) { while(message[s] && message[s++] != ' '); @@ -8879,17 +9013,41 @@ printf("score=%d count=%d\n",score,count); 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 %c %s", &promoChar, buf1) == 2) { - ChessSquare piece = CharToPiece(promoChar); - if(piece < EmptySquare && !appData.testLegality) { ASSIGN(pieceDesc[piece], buf1); pieceDefs = TRUE; } + if(sscanf(message, "piece %s %s", buf2, buf1) == 2) { + ChessSquare piece = WhitePawn; + char *p=message+6, *q, *s = SUFFIXES, ID = *p; + if(*p == '+') piece = CHUPROMOTED(WhitePawn), ID = *++p; + if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++; + piece += CharToPiece(ID & 255) - WhitePawn; + 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 = PieceToChar(forwardMostMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict)); + Sweep(0); return; } /* [HGM] Allow engine to set up a position. Don't ask me why one would @@ -9008,10 +9166,15 @@ printf("score=%d count=%d\n",score,count); } 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; } @@ -9426,6 +9589,7 @@ printf("score=%d count=%d\n",score,count); 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); @@ -9448,6 +9612,14 @@ printf("score=%d count=%d\n",score,count); 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; + int ok = ParseOneMove(bestMove, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1); + ok &= ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2); + solvingTime = (ok && ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2 ? time : -1); + } + if(serverMoves && (time > 100 || time == 0 && plylev > 7)) { char buf[MSG_SIZ]; FILE *f; @@ -9518,11 +9690,17 @@ printf("score=%d count=%d\n",score,count); [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(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 ? " " : "" ); @@ -9864,15 +10042,16 @@ ParseGameHistory (char *game) 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 */ @@ -9885,10 +10064,14 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board) // 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; @@ -9899,14 +10082,18 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board) } } - 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; @@ -9915,10 +10102,11 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board) 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; @@ -9928,6 +10116,11 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board) } } + if(fromY == 0) board[TOUCHED_W] |= 1<= 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)) { @@ -9961,6 +10166,7 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board) } } 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)) { @@ -9970,40 +10176,47 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board) } /* 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 @@ -10020,17 +10233,19 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board) } 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) { @@ -10051,20 +10266,22 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board) ) { /* 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 @@ -10124,10 +10341,10 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board) 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; } @@ -10135,9 +10352,9 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board) 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; } @@ -10165,13 +10382,13 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board) } 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) @@ -10463,7 +10680,19 @@ InitChessProgram (ChessProgramState *cps, int setup) 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; } @@ -11255,16 +11484,17 @@ GameEnds (ChessMove result, char *resultDetails, int whosays) && 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= 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; @@ -11851,7 +12081,7 @@ LoadGameOneMove (ChessMove readAhead) 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; @@ -11978,8 +12208,13 @@ LoadGameOneMove (ChessMove readAhead) 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]; @@ -12596,7 +12831,10 @@ LoadGame (FILE *f, int gameNumber, char *title, int useList) int numPGNTags = 0; 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); @@ -12803,7 +13041,7 @@ LoadGame (FILE *f, int gameNumber, char *title, int useList) gameInfo.event = StrSave(yy_text); } - startedFromSetupPosition = FALSE; + startedFromSetupPosition = startedFromPositionFile; // [HGM] while (cm == PGNTag) { if (appData.debugMode) fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text); @@ -12811,7 +13049,7 @@ LoadGame (FILE *f, int gameNumber, char *title, int useList) 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); @@ -12830,6 +13068,8 @@ LoadGame (FILE *f, int gameNumber, char *title, int useList) return FALSE; } CopyBoard(boards[0], initial_position); + if(*engineVariant) // [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); @@ -12952,6 +13192,10 @@ LoadGame (FILE *f, int gameNumber, char *title, int useList) 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); @@ -13144,8 +13388,8 @@ LoadPosition (FILE *f, int positionNumber, char *title) 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--; @@ -13161,10 +13405,14 @@ LoadPosition (FILE *f, int positionNumber, char *title) } if (fenMode) { + char *p; if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) { DisplayError(_("Bad FEN position in file"), 0); return FALSE; } + if((p = strstr(line, ";")) && (p = strstr(p+1, "bm "))) { // EPD with best move + sscanf(p+3, "%s", bestMove); + } else *bestMove = NULLCHAR; } else { (void) fgets(line, MSG_SIZ, f); (void) fgets(line, MSG_SIZ, f); @@ -14622,8 +14870,10 @@ TwoMachinesEvent P((void)) 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; } @@ -14785,6 +15035,16 @@ EditGameEvent () 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: @@ -15030,6 +15290,8 @@ EditPositionMenuEvent (ChessSquare selection, int x, int y) switch (selection) { case ClearBoard: + fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress + MarkTargetSquares(1); CopyBoard(currentBoard, boards[0]); CopyBoard(menuBoard, initialPosition); if (gameMode == IcsExamining && ics_type == ICS_FICS) { @@ -15049,7 +15311,7 @@ EditPositionMenuEvent (ChessSquare selection, int x, int y) 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; } @@ -15116,7 +15378,7 @@ EditPositionMenuEvent (ChessSquare selection, int x, int y) 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; @@ -15124,7 +15386,7 @@ EditPositionMenuEvent (ChessSquare selection, int x, int y) 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; @@ -15481,6 +15743,7 @@ ForwardInner (int target) seekGraphUp = FALSE; MarkTargetSquares(1); + fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress if (gameMode == PlayFromGameFile && !pausing) PauseEvent(); @@ -15599,6 +15862,7 @@ BackwardInner (int target) if (gameMode == EditPosition) return; seekGraphUp = FALSE; MarkTargetSquares(1); + fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress if (currentMove <= backwardMostMove) { ClearHighlights(); DrawPosition(full_redraw, boards[currentMove]); @@ -17701,7 +17965,7 @@ char * 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; @@ -17728,12 +17992,13 @@ PositionToFEN (int move, char *overrideCastling, int moveCounts) if(PieceToChar(piece) == '+') { /* [HGM] write promoted pieces as '+' (Shogi) */ *p++ = '+'; - piece = (ChessSquare)(CHUDEMOTED piece); + piece = (ChessSquare)(CHUDEMOTED(piece)); } *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece)); + if(*p = PieceSuffix(piece)) p++; if(p[-1] == '~') { /* [HGM] flag promoted pieces as '~' (Crazyhouse) */ - p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED piece)); + p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece))); *p++ = '~'; } } @@ -17774,12 +18039,39 @@ PositionToFEN (int move, char *overrideCastling, int moveCounts) *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<=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<=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 ) @@ -17787,36 +18079,40 @@ PositionToFEN (int move, char *overrideCastling, int moveCounts) 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_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_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; } @@ -17887,10 +18183,10 @@ PositionToFEN (int move, char *overrideCastling, int moveCounts) 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; @@ -17904,7 +18200,7 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize) 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= 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; } @@ -17959,7 +18264,7 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize) } 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) { @@ -18051,8 +18356,16 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize) /* [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> 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; @@ -18103,7 +18430,7 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize) if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1; break; case'Q': - for(i=BOARD_LEFT; i>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; @@ -18119,7 +18446,7 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize) if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1; break; case'q': - for(i=BOARD_LEFT; i