X-Git-Url: http://winboard.nl/cgi-bin?a=blobdiff_plain;f=backend.c;h=a54c6ccc3c5f674b65ba32c6e732c4a689db678a;hb=5cd55bddca592918f38deff675d05b650a71412e;hp=1f424e3ea2803c3a34051250ab9d012da86203fa;hpb=3fe0d6330195d298ec4ccc074c91a06a73211d60;p=xboard.git diff --git a/backend.c b/backend.c index 1f424e3..a54c6cc 100644 --- a/backend.c +++ b/backend.c @@ -5,7 +5,7 @@ * Massachusetts. * * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006, - * 2007, 2008, 2009 Free Software Foundation, Inc. + * 2007, 2008, 2009, 2010 Free Software Foundation, Inc. * * Enhancements Copyright 2005 Alessandro Scotti * @@ -160,15 +160,16 @@ int LoadGameFromFile P((char *filename, int n, char *title, int useList)); int LoadPositionFromFile P((char *filename, int n, char *title)); int SavePositionToFile P((char *filename)); void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar, - Board board, char *castle, char *ep)); + Board board)); void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar)); void ShowMove P((int fromX, int fromY, int toX, int toY)); int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY, /*char*/int promoChar)); void BackwardInner P((int target)); void ForwardInner P((int target)); +int Adjudicate P((ChessProgramState *cps)); void GameEnds P((ChessMove result, char *resultDetails, int whosays)); -void EditPositionDone P((void)); +void EditPositionDone P((Boolean fakeRights)); void PrintOpponents P((FILE *fp)); void PrintPosition P((FILE *fp, int move)); void StartChessProgram P((ChessProgramState *cps)); @@ -184,10 +185,10 @@ void FeedMovesToProgram P((ChessProgramState *cps, int upto)); void ResurrectChessProgram P((void)); void DisplayComment P((int moveNumber, char *text)); void DisplayMove P((int moveNumber)); -void DisplayAnalysis P((void)); void ParseGameHistory P((char *game)); void ParseBoard12 P((char *string)); +void KeepAlive P((void)); void StartClocks P((void)); void SwitchClocks P((void)); void StopClocks P((void)); @@ -236,6 +237,8 @@ extern char installDir[MSG_SIZ]; extern int tinyLayout, smallLayout; ChessProgramStats programStats; +char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */ +int endPV = -1; static int exiting = 0; /* [HGM] moved to top */ static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/; int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */ @@ -243,11 +246,13 @@ char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */ VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */ int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */ +Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */ int opponentKibitzes; int lastSavedGame; /* [HGM] save: ID of game */ char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */ extern int chatCount; int chattingPartner; +char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */ /* States for ics_getting_history */ #define H_FALSE 0 @@ -354,6 +359,7 @@ PosFlags(index) case VariantNoCastle: case VariantShatranj: case VariantCourier: + case VariantMakruk: flags &= ~F_ALL_CASTLE_OK; break; default: @@ -442,84 +448,102 @@ AppData appData; Board boards[MAX_MOVES]; /* [HGM] Following 7 needed for accurate legality tests: */ -signed char epStatus[MAX_MOVES]; -signed char castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1 -signed char castlingRank[BOARD_SIZE]; // and corresponding ranks -signed char initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE]; +signed char castlingRank[BOARD_FILES]; // and corresponding ranks +signed char initialRights[BOARD_FILES]; int nrCastlingRights; // For TwoKings, or to implement castling-unknown status int initialRulePlies, FENrulePlies; -char FENepStatus; FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option) int loadFlag = 0; int shuffleOpenings; int mute; // mute all sounds -ChessSquare FIDEArray[2][BOARD_SIZE] = { +// [HGM] vari: next 12 to save and restore variations +#define MAX_VARIATIONS 10 +int framePtr = MAX_MOVES-1; // points to free stack entry +int storedGames = 0; +int savedFirst[MAX_VARIATIONS]; +int savedLast[MAX_VARIATIONS]; +int savedFramePtr[MAX_VARIATIONS]; +char *savedDetails[MAX_VARIATIONS]; +ChessMove savedResult[MAX_VARIATIONS]; + +void PushTail P((int firstMove, int lastMove)); +Boolean PopTail P((Boolean annotate)); +void CleanupTail P((void)); + +ChessSquare FIDEArray[2][BOARD_FILES] = { { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing, WhiteBishop, WhiteKnight, WhiteRook }, { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackKing, BlackBishop, BlackKnight, BlackRook } }; -ChessSquare twoKingsArray[2][BOARD_SIZE] = { +ChessSquare twoKingsArray[2][BOARD_FILES] = { { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing, WhiteKing, WhiteKnight, WhiteRook }, { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackKing, BlackKing, BlackKnight, BlackRook } }; -ChessSquare KnightmateArray[2][BOARD_SIZE] = { +ChessSquare KnightmateArray[2][BOARD_FILES] = { { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen, WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook }, { BlackRook, BlackMan, BlackBishop, BlackQueen, BlackUnicorn, BlackBishop, BlackMan, BlackRook } }; -ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */ - { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen, +ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */ + { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing, WhiteBishop, WhiteKnight, WhiteRook }, - { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen, - BlackKing, BlackBishop, BlackKnight, BlackRook } + { BlackLance, BlackAlfil, BlackMarshall, BlackAngel, + BlackKing, BlackMarshall, BlackAlfil, BlackLance } }; -ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */ +ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */ { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook }, { BlackRook, BlackKnight, BlackAlfil, BlackKing, BlackFerz, BlackAlfil, BlackKnight, BlackRook } }; +ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */ + { WhiteRook, WhiteKnight, WhiteMan, WhiteKing, + WhiteFerz, WhiteMan, WhiteKnight, WhiteRook }, + { BlackRook, BlackKnight, BlackMan, BlackFerz, + BlackKing, BlackMan, BlackKnight, BlackRook } +}; + -#if (BOARD_SIZE>=10) -ChessSquare ShogiArray[2][BOARD_SIZE] = { +#if (BOARD_FILES>=10) +ChessSquare ShogiArray[2][BOARD_FILES] = { { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir, WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen }, { BlackQueen, BlackKnight, BlackFerz, BlackWazir, BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen } }; -ChessSquare XiangqiArray[2][BOARD_SIZE] = { +ChessSquare XiangqiArray[2][BOARD_FILES] = { { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz, WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook }, { BlackRook, BlackKnight, BlackAlfil, BlackFerz, BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook } }; -ChessSquare CapablancaArray[2][BOARD_SIZE] = { +ChessSquare CapablancaArray[2][BOARD_FILES] = { { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook }, { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook } }; -ChessSquare GreatArray[2][BOARD_SIZE] = { +ChessSquare GreatArray[2][BOARD_FILES] = { { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon }, { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon }, }; -ChessSquare JanusArray[2][BOARD_SIZE] = { +ChessSquare JanusArray[2][BOARD_FILES] = { { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook }, { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, @@ -527,7 +551,7 @@ ChessSquare JanusArray[2][BOARD_SIZE] = { }; #ifdef GOTHIC -ChessSquare GothicArray[2][BOARD_SIZE] = { +ChessSquare GothicArray[2][BOARD_FILES] = { { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook }, { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, @@ -538,7 +562,7 @@ ChessSquare GothicArray[2][BOARD_SIZE] = { #endif // !GOTHIC #ifdef FALCON -ChessSquare FalconArray[2][BOARD_SIZE] = { +ChessSquare FalconArray[2][BOARD_FILES] = { { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook }, { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, @@ -548,23 +572,23 @@ ChessSquare FalconArray[2][BOARD_SIZE] = { #define FalconArray CapablancaArray #endif // !FALCON -#else // !(BOARD_SIZE>=10) +#else // !(BOARD_FILES>=10) #define XiangqiPosition FIDEArray #define CapablancaArray FIDEArray #define GothicArray FIDEArray #define GreatArray FIDEArray -#endif // !(BOARD_SIZE>=10) +#endif // !(BOARD_FILES>=10) -#if (BOARD_SIZE>=12) -ChessSquare CourierArray[2][BOARD_SIZE] = { +#if (BOARD_FILES>=12) +ChessSquare CourierArray[2][BOARD_FILES] = { { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing, WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook }, { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing, BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook } }; -#else // !(BOARD_SIZE>=12) +#else // !(BOARD_FILES>=12) #define CourierArray CapablancaArray -#endif // !(BOARD_SIZE>=12) +#endif // !(BOARD_FILES>=12) Board initialPosition; @@ -608,7 +632,7 @@ InitBackEnd1() ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options GetTimeMark(&programStartTime); - srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level + srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level ClearProgramStats(); programStats.ok_to_send = 1; @@ -645,10 +669,10 @@ InitBackEnd1() { int i, j; - for( i=0; i second.timeOdds) norm = second.timeOdds; @@ -795,7 +819,8 @@ InitBackEnd1() if (appData.icsActive) { appData.clockMode = TRUE; /* changes dynamically in ICS mode */ - } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) { +// } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) { + } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode appData.clockMode = FALSE; first.sendTime = second.sendTime = 0; } @@ -874,6 +899,7 @@ InitBackEnd1() case VariantAtomic: /* should work except for win condition */ case Variant3Check: /* should work except for win condition */ case VariantShatranj: /* should work except for all win conditions */ + case VariantMakruk: /* should work except for daw countdown */ case VariantBerolina: /* might work if TestLegality is off */ case VariantCapaRandom: /* should work */ case VariantJanus: /* should work */ @@ -1043,6 +1069,7 @@ InitBackEnd2() fprintf(debugFP, "%s\n", programVersion); } + set_cont_sequence(appData.wrapContSeq); if (appData.matchGames > 0) { appData.matchMode = TRUE; } else if (appData.matchMode) { @@ -1097,6 +1124,8 @@ InitBackEnd3 P((void)) AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR); fromUserISR = AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR); + if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command + ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000); } else if (appData.noChessProgram) { SetNCPMode(); } else { @@ -1192,10 +1221,9 @@ InitBackEnd3 P((void)) } /* [HGM] loadPos: make that every new game uses the setup */ /* from file as long as we do not switch variant */ - if(!blackPlaysFirst) { int i; + if(!blackPlaysFirst) { startedFromPositionFile = TRUE; CopyBoard(filePosition, boards[0]); - for(i=0; i= BlackPawn ) { holdingsColumn = 0; @@ -1932,7 +1964,6 @@ CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece) board[holdingsStartRow+j*direction][holdingsColumn] = piece; board[holdingsStartRow+j*direction][countsColumn]++; } - } @@ -1940,8 +1971,7 @@ void VariantSwitch(Board board, VariantClass newVariant) { int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j; - int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove; -// Board tempBoard; int saveCastling[BOARD_SIZE], saveEP; + Board oldBoard; startedFromPositionFile = FALSE; if(gameInfo.variant == newVariant) return; @@ -1958,59 +1988,65 @@ VariantSwitch(Board board, VariantClass newVariant) * case we want to add those holdings to the already received position. */ - - if (appData.debugMode) { - fprintf(debugFP, "Switch board from %s to %s\n", - VariantName(gameInfo.variant), VariantName(newVariant)); - setbuf(debugFP, NULL); - } - shuffleOpenings = 0; /* [HGM] shuffle */ - gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */ - switch(newVariant) { - case VariantShogi: - newWidth = 9; newHeight = 9; - gameInfo.holdingsSize = 7; - case VariantBughouse: - case VariantCrazyhouse: - newHoldingsWidth = 2; break; - default: - newHoldingsWidth = gameInfo.holdingsSize = 0; - } - - if(newWidth != gameInfo.boardWidth || - newHeight != gameInfo.boardHeight || - newHoldingsWidth != gameInfo.holdingsWidth ) { - - /* shift position to new playing area, if needed */ - if(newHoldingsWidth > gameInfo.holdingsWidth) { - for(i=0; i=BOARD_LEFT; j--) - board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] = - board[i][j]; - for(i=0; i gameInfo.holdingsWidth) { + for(i=0; i=BOARD_LEFT; j--) + board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] = + board[i][j]; + for(i=0; i=0 ) r = minRating+100; + if(r > maxRating) r = maxRating; + if(tc < 1.) tc = 1.; + if(tc > 95.) tc = 95.; + x = (w-hMargin)* log(tc)/log(100.) + hMargin; + y = ((double)r - minRating)/(maxRating - minRating) + * (h-vMargin-squareSize/8-1) + vMargin; + if(ratingList[i] < 0) y = vMargin + squareSize/4; + if(strstr(seekAdList[i], " u ")) color = 1; + if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color + !strstr(seekAdList[i], "bullet") && + !strstr(seekAdList[i], "blitz") && + !strstr(seekAdList[i], "standard") ) color = 2; + if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares + DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color); +} + +void +AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot) +{ + char buf[MSG_SIZ], *ext = ""; + VariantClass v = StringToVariant(type); + if(strstr(type, "wild")) { + ext = type + 4; // append wild number + if(v == VariantFischeRandom) type = "chess960"; else + if(v == VariantLoadable) type = "setup"; else + type = VariantName(v); + } + sprintf(buf, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext); + if(nrOfSeekAds < MAX_SEEK_ADS-1) { + if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]); + ratingList[nrOfSeekAds] = -1; // for if seeker has no rating + sscanf(rating, "%d", &ratingList[nrOfSeekAds]); + tcList[nrOfSeekAds] = base + (2./3.)*inc; + seekNrList[nrOfSeekAds] = nr; + zList[nrOfSeekAds] = 0; + seekAdList[nrOfSeekAds++] = StrSave(buf); + if(plot) PlotSeekAd(nrOfSeekAds-1); + } +} + +void +EraseSeekDot(int i) +{ + int x = xList[i], y = yList[i], d=squareSize/4, k; + DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1); + if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1); + // now replot every dot that overlapped + for(k=0; k x-d && yy <= y+d && yy > y-d) + DrawSeekDot(xx, yy, colorList[k]); + } +} + +void +RemoveSeekAd(int nr) +{ + int i; + for(i=0; i=minRating && i40 ? i%20 : i%10) == 0) { + char buf[MSG_SIZ]; + sprintf(buf, "%d", i); + DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2); + } + } + for(i=0; i0) zList[i] *= 0.8; // age priority + } + if(dist < 120) { + char buf[MSG_SIZ]; + second = (second > 1); + if(displayed != closest || second != lastSecond) { + DisplayMessage(second ? "!" : "", seekAdList[closest]); + lastSecond = second; displayed = closest; + } + sprintf(buf, "play %d\n", seekNrList[closest]); + if(click == Press) { + if(moving == 2) zList[closest] = 100; // right-click; push to back on press + lastDown = closest; + return TRUE; + } // on press 'hit', only show info + if(moving == 2) return TRUE; // ignore right up-clicks on dot + SendToICS(ics_prefix); + SendToICS(buf); // should this be "sought all"? + } else if(click == Release) { // release 'miss' is ignored + zList[lastDown] = 100; // make future selection of the rejected ad more difficult + if(moving == 2) { // right up-click + nrOfSeekAds = 0; // refresh graph + soughtPending = TRUE; + SendToICS(ics_prefix); + SendToICS("sought\n"); // should this be "sought all"? + } + return TRUE; + } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; } + // press miss or release hit 'pop down' seek graph + seekGraphUp = FALSE; + DrawPosition(TRUE, NULL); + } + return TRUE; +} + void read_from_ics(isr, closure, data, count, error) InputSourceRef isr; @@ -2035,7 +2269,7 @@ read_from_ics(isr, closure, data, count, error) int count; int error; { -#define BUF_SIZE 8192 +#define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */ #define STARTED_NONE 0 #define STARTED_MOVES 1 #define STARTED_BOARD 2 @@ -2052,6 +2286,8 @@ read_from_ics(isr, closure, data, count, error) static int firstTime = TRUE, intfSet = FALSE; static ColorClass prevColor = ColorNormal; static int savingComment = FALSE; + static int cmatch = 0; // continuation sequence match + char *bp; char str[500]; int i, oldi; int buf_len; @@ -2062,6 +2298,8 @@ read_from_ics(isr, closure, data, count, error) char talker[MSG_SIZ]; // [HGM] chat int channel; + connectionAlive = TRUE; // [HGM] alive: I think, therefore I am... + if (appData.debugMode) { if (!error) { fprintf(debugFP, " 0) { /* If last read ended with a partial line that we couldn't parse, @@ -2082,21 +2321,70 @@ read_from_ics(isr, closure, data, count, error) buf[i] = buf[leftover_start + i]; } - /* Copy in new characters, removing nulls and \r's */ - buf_len = leftover_len; - for (i = 0; i < count; i++) { - if (data[i] != NULLCHAR && data[i] != '\r') - buf[buf_len++] = data[i]; - if(!appData.noJoin && buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' && - buf[buf_len-3]==' ' && buf[buf_len-2]==' ' && buf[buf_len-1]==' ') { - buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous - if(buf_len == 0 || buf[buf_len-1] != ' ') - buf[buf_len++] = ' '; // add space (assumes ICS does not break lines within word) - } - } + /* copy new characters into the buffer */ + bp = buf + leftover_len; + buf_len=leftover_len; + for (i=0; i MSG_SIZ - 30) // defuse unreasonably long input { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; } parse[parse_pos] = NULLCHAR; // try to be smart: if it does not look like search info, it should go to // ICS interaction window after all, not to engine-output window. - for(i=0; i= '0' && parse[i] <= '9'); - nrAlph += (parse[i] >= 'a' && parse[i] <= 'z'); - nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z'); + for(j=0; j= '0' && parse[j] <= '9'); + nrAlph += (parse[j] >= 'a' && parse[j] <= 'z'); + nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z'); } if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info int depth=0; float score; @@ -2274,6 +2562,7 @@ read_from_ics(isr, closure, data, count, error) pvInfoList[forwardMostMove-1].score = 100*score; } OutputKibitz(suppressKibitz, parse); + next_out = i+1; // [HGM] suppress printing in ICS window } else { char tmp[MSG_SIZ]; sprintf(tmp, _("your opponent kibitzes: %s"), parse); @@ -2282,7 +2571,7 @@ read_from_ics(isr, closure, data, count, error) } started = STARTED_NONE; } else { - /* Don't match patterns against characters in chatter */ + /* Don't match patterns against characters in comment */ i++; continue; } @@ -2351,6 +2640,45 @@ read_from_ics(isr, closure, data, count, error) continue; } + // [HGM] seekgraph: recognize sought lines and end-of-sought message + if(appData.seekGraph) { + if(soughtPending && MatchSoughtLine(buf+i)) { + i = strstr(buf+i, "rated") - buf; + next_out = leftover_start = i; + started = STARTED_CHATTER; + suppressKibitz = TRUE; + continue; + } + if((gameMode == IcsIdle || gameMode == BeginningOfGame) + && looking_at(buf, &i, "* ads displayed")) { + soughtPending = FALSE; + seekGraphUp = TRUE; + DrawSeekGraph(); + continue; + } + if(appData.autoRefresh) { + if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) { + int s = (ics_type == ICS_ICC); // ICC format differs + if(seekGraphUp) + AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]), + star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE); + looking_at(buf, &i, "*% "); // eat prompt + next_out = i; // suppress + continue; + } + if(looking_at(buf, &i, "Ads removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) { + char *p = star_match[0]; + while(*p) { + if(seekGraphUp) RemoveSeekAd(atoi(p)); + while(*p && *p++ != ' '); // next + } + looking_at(buf, &i, "*% "); // eat prompt + next_out = i; + continue; + } + } + } + /* skip formula vars */ if (started == STARTED_NONE && buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') { @@ -2382,9 +2710,13 @@ read_from_ics(isr, closure, data, count, error) } continue; } else - if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz - started = STARTED_CHATTER; - suppressKibitz = TRUE; + if(looking_at(buf, &i, "kibitzed to *\n") && atoi(star_match[0])) { + // suppress the acknowledgements of our own autoKibitz + char *p; + if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS + SendToPlayer(star_match[0], strlen(star_match[0])); + looking_at(buf, &i, "*% "); // eat prompt + next_out = i; } } // [HGM] kibitz: end of patch @@ -2403,14 +2735,14 @@ read_from_ics(isr, closure, data, count, error) if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel for(p=0; p= 3) // [HGM] chat: continuation of line for chat box + chattingPartner = savingComment - 3; // kludge to remember the box } else { started = STARTED_CHATTER; } @@ -2784,7 +3118,14 @@ read_from_ics(isr, closure, data, count, error) if (looking_at(buf, &i, "% ") || ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE) && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book + if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line + soughtPending = FALSE; + seekGraphUp = TRUE; + DrawSeekGraph(); + } + if(suppressKibitz) next_out = i; savingComment = FALSE; + suppressKibitz = 0; switch (started) { case STARTED_MOVES: case STARTED_MOVES_NOHIDE: @@ -2852,7 +3193,7 @@ read_from_ics(isr, closure, data, count, error) currentMove = forwardMostMove; ClearHighlights();/*!!could figure this out*/ flipView = appData.flipView; - DrawPosition(FALSE, boards[currentMove]); + DrawPosition(TRUE, boards[currentMove]); DisplayBothClocks(); sprintf(str, "%s vs. %s", gameInfo.white, gameInfo.black); @@ -3081,6 +3422,7 @@ read_from_ics(isr, closure, data, count, error) strncmp(why, "Continuing ", 11) == 0) { gs_gamenum = gamenum; strcpy(gs_kind, strchr(why, ' ') + 1); + VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board #if ZIPPY if (appData.zippyPlay) { ZippyGameStart(whitename, blackname); @@ -3183,16 +3525,14 @@ read_from_ics(isr, closure, data, count, error) if (currentMove == 0 && gameMode == IcsPlayingWhite && appData.premoveWhite) { - sprintf(str, "%s%s\n", ics_prefix, - appData.premoveWhiteText); + sprintf(str, "%s\n", appData.premoveWhiteText); if (appData.debugMode) fprintf(debugFP, "Sending premove:\n"); SendToICS(str); } else if (currentMove == 1 && gameMode == IcsPlayingBlack && appData.premoveBlack) { - sprintf(str, "%s%s\n", ics_prefix, - appData.premoveBlackText); + sprintf(str, "%s\n", appData.premoveBlackText); if (appData.debugMode) fprintf(debugFP, "Sending premove:\n"); SendToICS(str); @@ -3209,8 +3549,10 @@ read_from_ics(isr, closure, data, count, error) /* Usually suppress following prompt */ if (!(forwardMostMove == 0 && gameMode == IcsExamining)) { + while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines if (looking_at(buf, &i, "*% ")) { savingComment = FALSE; + suppressKibitz = 0; } } next_out = i; @@ -3230,7 +3572,13 @@ read_from_ics(isr, closure, data, count, error) * to move the position two files to the right to * create room for them! */ - VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */ + VariantClass newVariant; + switch(gameInfo.boardWidth) { // base guess on board width + case 9: newVariant = VariantShogi; break; + case 10: newVariant = VariantGreat; break; + default: newVariant = VariantCrazyhouse; break; + } + VariantSwitch(boards[currentMove], newVariant); /* temp guess */ /* Get a move list just to see the header, which will tell us whether this is really bug or zh */ if (ics_getting_history == H_FALSE) { @@ -3246,8 +3594,9 @@ read_from_ics(isr, closure, data, count, error) white_holding[strlen(white_holding)-1] = NULLCHAR; black_holding[strlen(black_holding)-1] = NULLCHAR; /* [HGM] copy holdings to board holdings area */ - CopyHoldings(boards[currentMove], white_holding, WhitePawn); - CopyHoldings(boards[currentMove], black_holding, BlackPawn); + CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn); + CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn); + boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set #if ZIPPY if (appData.zippyPlay && first.initDone) { ZippyHoldings(white_holding, black_holding, @@ -3271,7 +3620,9 @@ read_from_ics(isr, closure, data, count, error) } /* Suppress following prompt */ if (looking_at(buf, &i, "*% ")) { + if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures savingComment = FALSE; + suppressKibitz = 0; } next_out = i; } @@ -3281,12 +3632,13 @@ read_from_ics(isr, closure, data, count, error) i++; /* skip unparsed character and loop back */ } - if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window - started != STARTED_HOLDINGS && i > next_out) { - SendToPlayer(&buf[next_out], i - next_out); + if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz +// started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i? +// SendToPlayer(&buf[next_out], i - next_out); + started != STARTED_HOLDINGS && leftover_start > next_out) { + SendToPlayer(&buf[next_out], leftover_start - next_out); next_out = i; } - suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above leftover_len = buf_len - leftover_start; /* if buffer ends with something we couldn't parse, @@ -3340,6 +3692,7 @@ ParseBoard12(string) char promoChar; int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */ char *bookHit = NULL; // [HGM] book + Boolean weird = FALSE, reqFlag = FALSE; fromX = fromY = toX = toY = -1; @@ -3355,6 +3708,7 @@ ParseBoard12(string) while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) { if(string[i] == ' ') { ranks++; files = 0; } else files++; + if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies i++; } for(j = 0; j = MAX_MOVES) { + if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"), 0, 1); return; @@ -3444,6 +3798,27 @@ ParseBoard12(string) ics_getting_history = H_FALSE; return; } + + if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || + weird && (int)gameInfo.variant <= (int)VariantShogi) { + /* [HGM] We seem to have switched variant unexpectedly + * Try to guess new variant from board size + */ + VariantClass newVariant = VariantFairy; // if 8x8, but fairies present + if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else + if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else + if(ranks == 8 && files == 12) newVariant = VariantCourier; else + if(ranks == 9 && files == 9) newVariant = VariantShogi; else + if(!weird) newVariant = VariantNormal; + VariantSwitch(boards[currentMove], newVariant); /* temp guess */ + /* Get a move list just to see the header, which + will tell us whether this is really bug or zh */ + if (ics_getting_history == H_FALSE) { + ics_getting_history = H_REQUESTED; reqFlag = TRUE; + sprintf(str, "%smoves %d\n", ics_prefix, gamenum); + SendToICS(str); + } + } /* Take action if this is the first board of a new game, or of a different game than is currently being displayed. */ @@ -3452,15 +3827,15 @@ ParseBoard12(string) /* Forget the old game and get the history (if any) of the new one */ if (gameMode != BeginningOfGame) { - Reset(FALSE, TRUE); + Reset(TRUE, TRUE); } newGame = TRUE; if (appData.autoRaiseBoard) BoardToTop(); prevMove = -3; if (gamenum == -1) { newGameMode = IcsIdle; - } else if (moveNum > 0 && newGameMode != IcsIdle && - appData.getMoveList) { + } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle && + appData.getMoveList && !reqFlag) { /* Need to get game history */ ics_getting_history = H_REQUESTED; sprintf(str, "%smoves %d\n", ics_prefix, gamenum); @@ -3496,7 +3871,7 @@ ParseBoard12(string) timeIncrement = increment * 1000; movesPerSession = 0; gameInfo.timeControl = TimeControlTagValue(); - VariantSwitch(board, StringToVariant(gameInfo.event) ); + VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) ); if (appData.debugMode) { fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event); fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant)); @@ -3576,6 +3951,7 @@ ParseBoard12(string) } } CopyBoard(boards[moveNum], board); + boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set if (moveNum == 0) { startedFromSetupPosition = !CompareBoards(board, initialPosition); @@ -3596,40 +3972,45 @@ ParseBoard12(string) if(moveNum == 0 || gameInfo.variant != VariantFischeRandom) { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing; - for(i=BOARD_LEFT, j= -1; i=BOARD_LEFT; i--) + initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j); + for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--) if(board[0][i] == WhiteRook) j = i; - initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j); - for(i=BOARD_LEFT, j= -1; i=BOARD_LEFT; i--) + initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j); + for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--) if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i; - initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j); + initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j); if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; } for(k=BOARD_LEFT; k forwardMostMove; - /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */ - if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) { - takeback = forwardMostMove - moveNum; - for (i = 0; i < takeback; i++) { - if (appData.debugMode) fprintf(debugFP, "take back move\n"); - SendToProgram("undo\n", &first); - } - } - if (newGame) { forwardMostMove = backwardMostMove = currentMove = moveNum; if (gameMode == IcsExamining && moveNum == 0) { @@ -3663,6 +4035,20 @@ ParseBoard12(string) } } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) { +#if ZIPPY + /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */ + /* [HGM] applied this also to an engine that is silently watching */ + if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone && + (gameMode == IcsObserving || gameMode == IcsExamining) && + gameInfo.variant == currentlyInitializedVariant) { + takeback = forwardMostMove - moveNum; + for (i = 0; i < takeback; i++) { + if (appData.debugMode) fprintf(debugFP, "take back move\n"); + SendToProgram("undo\n", &first); + } + } +#endif + forwardMostMove = moveNum; if (!pausing || currentMove > forwardMostMove) currentMove = forwardMostMove; @@ -3673,12 +4059,20 @@ ParseBoard12(string) forwardMostMove = pauseExamForwardMostMove; return; } - forwardMostMove = backwardMostMove = currentMove = moveNum; if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) { +#if ZIPPY + if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) { + // [HGM] when we will receive the move list we now request, it will be + // fed to the engine from the first move on. So if the engine is not + // in the initial position now, bring it there. + InitChessProgram(&first, 0); + } +#endif ics_getting_history = H_REQUESTED; sprintf(str, "%smoves %d\n", ics_prefix, gamenum); SendToICS(str); } + forwardMostMove = backwardMostMove = currentMove = moveNum; } /* Update the clocks */ @@ -3706,7 +4100,8 @@ ParseBoard12(string) if (appData.debugMode) { if (appData.debugMode) { int f = forwardMostMove; fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f, - castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]); + boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2], + boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]); } fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str); fprintf(debugFP, "moveNum = %d\n", moveNum); @@ -3752,11 +4147,10 @@ ParseBoard12(string) // end of long SAN patch if (valid) { (void) CoordsToAlgebraic(boards[moveNum - 1], - PosFlags(moveNum - 1), EP_UNKNOWN, + PosFlags(moveNum - 1), fromY, fromX, toY, toX, promoChar, parseList[moveNum-1]); - switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN, - castlingRights[moveNum]) ) { + switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) { case MT_NONE: case MT_STALEMATE: default: @@ -3890,7 +4284,7 @@ ParseBoard12(string) } } - + /* Display the board */ if (!pausing && !appData.noGUI) { @@ -3900,7 +4294,9 @@ ParseBoard12(string) ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove)))) ClearPremoveHighlights(); - DrawPosition(FALSE, boards[currentMove]); + j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph + DrawPosition(j, boards[currentMove]); + DisplayMove(moveNum - 1); if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) || @@ -4072,7 +4468,7 @@ SendMoveToICS(moveType, fromX, fromY, toX, toY) case BlackPromotionChancellor: case WhitePromotionArchbishop: case BlackPromotionArchbishop: - if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) + if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) sprintf(user_move, "%c%c%c%c=%c\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY, PieceToChar(WhiteFerz)); @@ -4251,7 +4647,8 @@ ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar) if (appData.testLegality) { return (*moveType != IllegalMove); } else { - return !(fromX == fromY && toX == toY); + return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && + WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn); } case WhiteDrop: @@ -4286,6 +4683,108 @@ ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar) } } + +void +ParsePV(char *pv) +{ // Parse a string of PV moves, and append to current game, behind forwardMostMove + int fromX, fromY, toX, toY; char promoChar; + ChessMove moveType; + Boolean valid; + int nr = 0; + + endPV = forwardMostMove; + do { + while(*pv == ' ') pv++; + if(*pv == '(') pv++; // first (ponder) move can be in parentheses + valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar); +if(appData.debugMode){ +fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv); +} + if(!valid && nr == 0 && + ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ + nr++; moveType = Comment; // First move has been played; kludge to make sure we continue + } + while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators + if(moveType == Comment) { valid++; continue; } // allow comments in PV + nr++; + if(endPV+1 > framePtr) break; // no space, truncate + if(!valid) break; + endPV++; + CopyBoard(boards[endPV], boards[endPV-1]); + ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]); + moveList[endPV-1][0] = fromX + AAA; + moveList[endPV-1][1] = fromY + ONE; + moveList[endPV-1][2] = toX + AAA; + moveList[endPV-1][3] = toY + ONE; + parseList[endPV-1][0] = NULLCHAR; + } while(valid); + currentMove = endPV; + if(currentMove == forwardMostMove) ClearPremoveHighlights(); else + SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE, + moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE); + DrawPosition(TRUE, boards[currentMove]); +} + +static int lastX, lastY; + +Boolean +LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end) +{ + int startPV; + + if(index < 0 || index >= strlen(buf)) return FALSE; // sanity + lastX = x; lastY = y; + while(index > 0 && buf[index-1] != '\n') index--; // beginning of line + startPV = index; + while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index; + index = startPV; + while(buf[index] && buf[index] != '\n') index++; + buf[index] = 0; + ParsePV(buf+startPV); + *start = startPV; *end = index-1; + return TRUE; +} + +Boolean +LoadPV(int x, int y) +{ // called on right mouse click to load PV + int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w')); + lastX = x; lastY = y; + ParsePV(lastPV[which]); // load the PV of the thinking engine in the boards array. + return TRUE; +} + +void +UnLoadPV() +{ + if(endPV < 0) return; + endPV = -1; + currentMove = forwardMostMove; + ClearPremoveHighlights(); + DrawPosition(TRUE, boards[currentMove]); +} + +void +MovePV(int x, int y, int h) +{ // step through PV based on mouse coordinates (called on mouse move) + int margin = h>>3, step = 0; + + if(endPV < 0) return; + // we must somehow check if right button is still down (might be released off board!) + if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else + if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else + if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1; + if(!step) return; + lastX = x; lastY = y; + if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0; + currentMove += step; + if(currentMove == forwardMostMove) ClearPremoveHighlights(); else + SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE, + moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE); + DrawPosition(FALSE, boards[currentMove]); +} + + // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants. // All positions will have equal probability, but the current method will not provide a unique // numbering scheme for arrays that contain 3 or more pieces of the same kind. @@ -4409,12 +4908,12 @@ void SetUpShuffle(Board board, int number) // Last King gets castling rights while(piecesLeft[(int)WhiteUnicorn]) { i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY); - initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i; + initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i; } while(piecesLeft[(int)WhiteKing]) { i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY); - initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i; + initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i; } @@ -4429,9 +4928,9 @@ void SetUpShuffle(Board board, int number) if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights if(first) { first=0; - initialRights[1] = initialRights[4] = castlingRights[0][1] = castlingRights[0][4] = i; + initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i; } - initialRights[0] = initialRights[3] = castlingRights[0][0] = castlingRights[0][3] = i; + initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i; } } for(i=BOARD_LEFT; i>1; - castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1; - castlingRights[0][4] = initialRights[4] = BOARD_LEFT; - castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1; + initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1; + initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT; + initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1; + initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1; + initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT; + initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1; break; case VariantFalcon: pieces = FalconArray; @@ -4607,7 +5114,6 @@ InitPosition(redraw) gameInfo.boardWidth = 12; nrCastlingRights = 0; SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); - for(i=0; i BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE) - DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2); + if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES) + DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2); pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */ if(pawnRow < 1) pawnRow = 1; + if(gameInfo.variant == VariantMakruk) pawnRow = 2; /* User pieceToChar list overrules defaults */ if(appData.pieceToCharTable != NULL) @@ -4712,12 +5218,12 @@ InitPosition(redraw) /* Variants with other castling rights must set them themselves above */ nrCastlingRights = 6; - castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1; - castlingRights[0][1] = initialRights[1] = BOARD_LEFT; - castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1; - castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1; - castlingRights[0][4] = initialRights[4] = BOARD_LEFT; - castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1; + initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1; + initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT; + initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1; + initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1; + initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT; + initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1; } if(gameInfo.variant == VariantSuper) Prelude(initialPosition); @@ -4738,7 +5244,7 @@ InitPosition(redraw) /* [HGM] loadPos: use PositionFile for every new game */ CopyBoard(initialPosition, filePosition); for(i=0; i= BOARD_RGHT) return FALSE; // drop + if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings + + if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions + !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move + return FALSE; - if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || - !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE; - /* [HGM] Note to self: line above also weeds out drops */ piece = boards[currentMove][fromY][fromX]; if(gameInfo.variant == VariantShogi) { promotionZoneSize = 3; - highestPromotingPiece = (int)WhiteKing; - /* [HGM] Should be Silver = Ferz, really, but legality testing is off, - and if in normal chess we then allow promotion to King, why not - allow promotion of other piece in Shogi? */ + highestPromotingPiece = (int)WhiteFerz; + } else if(gameInfo.variant == VariantMakruk) { + promotionZoneSize = 3; } + + // next weed out all moves that do not touch the promotion zone at all if((int)piece >= BlackPawn) { if(toY >= promotionZoneSize && fromY >= promotionZoneSize) return FALSE; @@ -4859,7 +5372,61 @@ IsPromotion(fromX, fromY, toX, toY) if( toY < BOARD_HEIGHT - promotionZoneSize && fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE; } - return ( (int)piece <= highestPromotingPiece ); + + if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece + + // weed out mandatory Shogi promotions + if(gameInfo.variant == VariantShogi) { + if(piece >= BlackPawn) { + if(toY == 0 && piece == BlackPawn || + toY == 0 && piece == BlackQueen || + toY <= 1 && piece == BlackKnight) { + *promoChoice = '+'; + return FALSE; + } + } else { + if(toY == BOARD_HEIGHT-1 && piece == WhitePawn || + toY == BOARD_HEIGHT-1 && piece == WhiteQueen || + toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) { + *promoChoice = '+'; + return FALSE; + } + } + } + + // weed out obviously illegal Pawn moves + if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) { + if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide + if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep + if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep + if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE; + // note we are not allowed to test for valid (non-)capture, due to premove + } + + // 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) { + *promoChoice = PieceToChar(BlackFerz); // no choice + return FALSE; + } + if(appData.alwaysPromoteToQueen) { // predetermined + if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers) + *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want + else *promoChoice = PieceToChar(BlackQueen); + return FALSE; + } + + // suppress promotion popup on illegal moves that are not premoves + premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) || + gameMode == IcsPlayingBlack && WhiteOnMove(currentMove); + if(appData.testLegality && !premove) { + moveType = LegalityTest(boards[currentMove], PosFlags(currentMove), + fromY, fromX, toY, toX, NULLCHAR); + if(moveType != WhitePromotionQueen && moveType != BlackPromotionQueen && + moveType != WhitePromotionKnight && moveType != BlackPromotionKnight) + return FALSE; + } + + return TRUE; } int @@ -4946,12 +5513,6 @@ OKToStartUserMove(x, y) /* Could disallow this or prompt for confirmation */ cmailOldMove = -1; } - if (currentMove < forwardMostMove) { - /* Discarding moves */ - /* Could prompt for confirmation here, - but I don't think that's such a good idea */ - forwardMostMove = currentMove; - } break; case BeginningOfGame: @@ -4980,6 +5541,7 @@ OKToStartUserMove(x, y) break; } if (currentMove != forwardMostMove && gameMode != AnalyzeMode + && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode && gameMode != AnalyzeFile && gameMode != Training) { DisplayMoveError(_("Displayed position is not current")); return FALSE; @@ -4987,6 +5549,55 @@ OKToStartUserMove(x, y) return TRUE; } +Boolean +OnlyMove(int *x, int *y) { + DisambiguateClosure cl; + if (appData.zippyPlay) return FALSE; + switch(gameMode) { + case MachinePlaysBlack: + case IcsPlayingWhite: + case BeginningOfGame: + if(!WhiteOnMove(currentMove)) return FALSE; + break; + case MachinePlaysWhite: + case IcsPlayingBlack: + if(WhiteOnMove(currentMove)) return FALSE; + break; + default: + return FALSE; + } + cl.pieceIn = EmptySquare; + cl.rfIn = *y; + cl.ffIn = *x; + cl.rtIn = -1; + cl.ftIn = -1; + cl.promoCharIn = NULLCHAR; + Disambiguate(boards[currentMove], PosFlags(currentMove), &cl); + if(cl.kind == NormalMove) { + fromX = cl.ff; + fromY = cl.rf; + *x = cl.ft; + *y = cl.rt; + return TRUE; + } + if(cl.kind != ImpossibleMove) return FALSE; + cl.pieceIn = EmptySquare; + cl.rfIn = -1; + cl.ffIn = -1; + cl.rtIn = *y; + cl.ftIn = *x; + cl.promoCharIn = NULLCHAR; + Disambiguate(boards[currentMove], PosFlags(currentMove), &cl); + if(cl.kind == NormalMove) { + fromX = cl.ff; + fromY = cl.rf; + *x = cl.ft; + *y = cl.rt; + return TRUE; + } + return FALSE; +} + FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL; int lastLoadGameNumber = 0, lastLoadPositionNumber = 0; int lastLoadGameUseList = FALSE; @@ -5002,29 +5613,6 @@ UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn) ChessMove moveType; ChessSquare pdown, pup; - if (fromX < 0 || fromY < 0) return ImpossibleMove; - - /* [HGM] suppress all moves into holdings area and guard band */ - if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 ) - return ImpossibleMove; - - /* [HGM] moved to here from winboard.c */ - /* note: capture of own piece can be legal as drag-drop premove. For click-click it is selection of new piece. */ - pdown = boards[currentMove][fromY][fromX]; - pup = boards[currentMove][toY][toX]; - if ( gameMode != EditPosition && !captureOwn && - (WhitePawn <= pdown && pdown < BlackPawn && - WhitePawn <= pup && pup < BlackPawn || - BlackPawn <= pdown && pdown < EmptySquare && - BlackPawn <= pup && pup < EmptySquare - ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) && - (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0|| - pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 || - pup == WhiteKing && pdown == WhiteRook && fromY == 0 && toY == 0|| // also allow RxK - pup == BlackKing && pdown == BlackRook && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 ) - ) ) - return Comment; - /* Check if the user is playing in turn. This is complicated because we let the user "pick up" a piece before it is his turn. So the piece he tried to pick up may have been captured by the time he puts it down! @@ -5137,12 +5725,28 @@ UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn) return AmbiguousMove; } else if (toX >= 0 && toY >= 0) { boards[0][toY][toX] = boards[0][fromY][fromX]; + if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings + if(boards[0][fromY][0] != EmptySquare) { + if(boards[0][fromY][1]) boards[0][fromY][1]--; + if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare; + } + } else + if(fromX == BOARD_RGHT+1) { + if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) { + if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--; + if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; + } + } else boards[0][fromY][fromX] = EmptySquare; return AmbiguousMove; } return ImpossibleMove; } + if(toX < 0 || toY < 0) return ImpossibleMove; + pdown = boards[currentMove][fromY][fromX]; + pup = boards[currentMove][toY][toX]; + /* [HGM] If move started in holdings, it means a drop */ if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { if( pup != EmptySquare ) return ImpossibleMove; @@ -5158,11 +5762,8 @@ UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn) return WhiteDrop; /* Not needed to specify white or black yet */ } - userOfferedDraw = FALSE; - /* [HGM] always test for legality, to get promotion info */ moveType = LegalityTest(boards[currentMove], PosFlags(currentMove), - epStatus[currentMove], castlingRights[currentMove], fromY, fromX, toY, toX, promoChar); /* [HGM] but possibly ignore an IllegalMove result */ if (appData.testLegality) { @@ -5171,7 +5772,7 @@ UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn) return ImpossibleMove; } } -if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar); + return moveType; /* [HGM] in stead of calling FinishMove directly, this function is made into one that returns an OK move type if FinishMove @@ -5190,7 +5791,7 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar) /*char*/int promoChar; { char *bookHit = 0; -if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar); + if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { // [HGM] superchess: suppress promotions to non-available piece int k = PieceToNumber(CharToPiece(ToUpper(promoChar))); @@ -5205,13 +5806,12 @@ if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", move move type in caller when we know the move is a legal promotion */ if(moveType == NormalMove && promoChar) moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar); -if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar); + /* [HGM] convert drag-and-drop piece drops to standard form */ - if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { + if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){ moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop; if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]); -// fromX = boards[currentMove][fromY][fromX]; // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings @@ -5220,7 +5820,7 @@ if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", move } /* [HGM] The following if has been moved here from - UserMoveEvent(). Because it seemed to belon here (why not allow + UserMoveEvent(). Because it seemed to belong here (why not allow piece drops in training games?), and because it can only be performed after it is known to what we promote. */ if (gameMode == Training) { @@ -5229,9 +5829,9 @@ if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", move * If they don't match, display an error message. */ int saveAnimate; - Board testBoard; char testRights[BOARD_SIZE]; char testStatus; + Board testBoard; CopyBoard(testBoard, boards[currentMove]); - ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus); + ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard); if (CompareBoards(testBoard, boards[currentMove+1])) { ForwardInner(currentMove+1); @@ -5260,8 +5860,9 @@ if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", move /* Ok, now we know that the move is good, so we can kill the previous line in Analysis Mode */ - if (gameMode == AnalyzeMode && currentMove < forwardMostMove) { - forwardMostMove = currentMove; + if ((gameMode == AnalyzeMode || gameMode == EditGame) + && currentMove < forwardMostMove) { + PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game } /* If we need the chess program but it's dead, restart it */ @@ -5275,6 +5876,8 @@ if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", move MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/ + if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end + if (gameMode == BeginningOfGame) { if (appData.noChessProgram) { gameMode = EditGame; @@ -5294,11 +5897,17 @@ if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", move } ModeHighlight(); } -if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar); + /* Relay move to ICS or chess engine */ if (appData.icsActive) { if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsExamining) { + if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) { + SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS + SendToICS("draw "); + SendMoveToICS(moveType, fromX, fromY, toX, toY); + } + // also send plain move, in case ICS does not understand atomic claims SendMoveToICS(moveType, fromX, fromY, toX, toY); ics_user_moved = 1; } @@ -5322,8 +5931,7 @@ if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", move switch (gameMode) { case EditGame: - switch (MateTest(boards[currentMove], PosFlags(currentMove), - EP_UNKNOWN, castlingRights[currentMove]) ) { + switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) { case MT_NONE: case MT_CHECK: break; @@ -5351,6 +5959,8 @@ if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", move break; } + userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw + if(bookHit) { // [HGM] book: simulate book reply static char bookMove[MSG_SIZ]; // a bit generous? @@ -5387,6 +5997,321 @@ if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", move FinishMove(moveType, fromX, fromY, toX, toY, promoChar); } +void +Mark(board, flags, kind, rf, ff, rt, ft, closure) + Board board; + int flags; + ChessMove kind; + int rf, ff, rt, ft; + VOIDSTAR closure; +{ + typedef char Markers[BOARD_RANKS][BOARD_FILES]; + Markers *m = (Markers *) closure; + if(rf == fromY && ff == fromX) + (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare + || kind == WhiteCapturesEnPassant + || kind == BlackCapturesEnPassant); + else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3; +} + +void +MarkTargetSquares(int clear) +{ + int x, y; + if(!appData.markers || !appData.highlightDragging || + !appData.testLegality || gameMode == EditPosition) return; + if(clear) { + for(x=0; x1) capt++; + if(capt) + for(x=0; x= 0) { + y = BOARD_HEIGHT - 1 - y; + } + if (flipView && x >= 0) { + x = BOARD_WIDTH - 1 - x; + } + + if(promotionChoice) { // we are waiting for a click to indicate promotion piece + if(clickType == Release) return; // ignore upclick of click-click destination + promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel + if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y); + if(gameInfo.holdingsWidth && + (WhiteOnMove(currentMove) + ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0 + : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) { + // click in right holdings, for determining promotion piece + ChessSquare p = boards[currentMove][y][x]; + if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p); + if(p != EmptySquare) { + FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p))); + fromX = fromY = -1; + return; + } + } + DrawPosition(FALSE, boards[currentMove]); + return; + } + + /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */ + if(clickType == Press + && ( x == BOARD_LEFT-1 || x == BOARD_RGHT + || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize + || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) ) + return; + + if (fromX == -1) { + if(!appData.oneClick || !OnlyMove(&x, &y)) { + if (clickType == Press) { + /* First square */ + if (OKToStartUserMove(x, y)) { + fromX = x; + fromY = y; + second = 0; + MarkTargetSquares(0); + DragPieceBegin(xPix, yPix); + if (appData.highlightDragging) { + SetHighlights(x, y, -1, -1); + } + } + } + return; + } + } + + /* fromX != -1 */ + if (clickType == Press && gameMode != EditPosition) { + ChessSquare fromP; + ChessSquare toP; + int frc; + + // ignore off-board to clicks + if(y < 0 || x < 0) return; + + /* Check if clicking again on the same color piece */ + fromP = boards[currentMove][fromY][fromX]; + toP = boards[currentMove][y][x]; + frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom; + if ((WhitePawn <= fromP && fromP <= WhiteKing && + WhitePawn <= toP && toP <= WhiteKing && + !(fromP == WhiteKing && toP == WhiteRook && frc) && + !(fromP == WhiteRook && toP == WhiteKing && frc)) || + (BlackPawn <= fromP && fromP <= BlackKing && + BlackPawn <= toP && toP <= BlackKing && + !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling + !(fromP == BlackKing && toP == BlackRook && frc))) { + /* Clicked again on same color piece -- changed his mind */ + second = (x == fromX && y == fromY); + if (appData.highlightDragging) { + SetHighlights(x, y, -1, -1); + } else { + ClearHighlights(); + } + if (OKToStartUserMove(x, y)) { + fromX = x; + fromY = y; + MarkTargetSquares(0); + DragPieceBegin(xPix, yPix); + } + return; + } + // ignore clicks on holdings + if(x < BOARD_LEFT || x >= BOARD_RGHT) return; + } + + if (clickType == Release && x == fromX && y == fromY) { + DragPieceEnd(xPix, yPix); + if (appData.animateDragging) { + /* Undo animation damage if any */ + DrawPosition(FALSE, NULL); + } + if (second) { + /* Second up/down in same square; just abort move */ + second = 0; + fromX = fromY = -1; + ClearHighlights(); + gotPremove = 0; + ClearPremoveHighlights(); + } else { + /* First upclick in same square; start click-click mode */ + SetHighlights(x, y, -1, -1); + } + return; + } + + /* we now have a different from- and (possibly off-board) to-square */ + /* Completed move */ + toX = x; + toY = y; + saveAnimate = appData.animate; + if (clickType == Press) { + /* Finish clickclick move */ + if (appData.animate || appData.highlightLastMove) { + SetHighlights(fromX, fromY, toX, toY); + } else { + ClearHighlights(); + } + } else { + /* Finish drag move */ + if (appData.highlightLastMove) { + SetHighlights(fromX, fromY, toX, toY); + } else { + ClearHighlights(); + } + DragPieceEnd(xPix, yPix); + /* Don't animate move and drag both */ + appData.animate = FALSE; + } + + // moves into holding are invalid for now (except in EditPosition, adapting to-square) + if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) { + ChessSquare piece = boards[currentMove][fromY][fromX]; + if(gameMode == EditPosition && piece != EmptySquare && + fromX >= BOARD_LEFT && fromX < BOARD_RGHT) { + int n; + + if(x == BOARD_LEFT-2 && piece >= BlackPawn) { + n = PieceToNumber(piece - (int)BlackPawn); + if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; } + boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece; + boards[currentMove][BOARD_HEIGHT-1 - n][1]++; + } else + if(x == BOARD_RGHT+1 && piece < BlackPawn) { + n = PieceToNumber(piece); + if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; } + boards[currentMove][n][BOARD_WIDTH-1] = piece; + boards[currentMove][n][BOARD_WIDTH-2]++; + } + boards[currentMove][fromY][fromX] = EmptySquare; + } + ClearHighlights(); + fromX = fromY = -1; + DrawPosition(TRUE, boards[currentMove]); + return; + } + + // off-board moves should not be highlighted + if(x < 0 || x < 0) ClearHighlights(); + + if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) { + SetHighlights(fromX, fromY, toX, toY); + if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { + // [HGM] super: promotion to captured piece selected from holdings + ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX]; + promotionChoice = TRUE; + // kludge follows to temporarily execute move on display, without promoting yet + boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank + boards[currentMove][toY][toX] = p; + DrawPosition(FALSE, boards[currentMove]); + boards[currentMove][fromY][fromX] = p; // take back, but display stays + boards[currentMove][toY][toX] = q; + DisplayMessage("Click in holdings to choose piece", ""); + return; + } + PromotionPopUp(); + } else { + UserMoveEvent(fromX, fromY, toX, toY, promoChoice); + if (!appData.highlightLastMove || gotPremove) ClearHighlights(); + if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY); + fromX = fromY = -1; + } + appData.animate = saveAnimate; + if (appData.animate || appData.animateDragging) { + /* Undo animation damage if needed */ + DrawPosition(FALSE, NULL); + } +} + +int RightClick(ClickType action, int x, int y, int *fromX, int *fromY) +{ // front-end-free part taken out of PieceMenuPopup + int whichMenu; int xSqr, ySqr; + + if(seekGraphUp) { // [HGM] seekgraph + if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss + if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit + return -2; + } + + xSqr = EventToSquare(x, BOARD_WIDTH); + ySqr = EventToSquare(y, BOARD_HEIGHT); + if (action == Release) UnLoadPV(); // [HGM] pv + if (action != Press) return -2; // return code to be ignored + switch (gameMode) { + case IcsExamining: + if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1; + case EditPosition: + if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1; + if (xSqr < 0 || ySqr < 0) return -1; + whichMenu = 0; // edit-position menu + break; + case IcsObserving: + if(!appData.icsEngineAnalyze) return -1; + case IcsPlayingWhite: + case IcsPlayingBlack: + if(!appData.zippyPlay) goto noZip; + case AnalyzeMode: + case AnalyzeFile: + case MachinePlaysWhite: + case MachinePlaysBlack: + case TwoMachinesPlay: // [HGM] pv: use for showing PV + if (!appData.dropMenu) { + LoadPV(x, y); + return 2; // flag front-end to grab mouse events + } + if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode || + gameMode == AnalyzeFile || gameMode == IcsObserving) return -1; + case EditGame: + noZip: + if (xSqr < 0 || ySqr < 0) return -1; + if (!appData.dropMenu || appData.testLegality && + gameInfo.variant != VariantBughouse && + gameInfo.variant != VariantCrazyhouse) return -1; + whichMenu = 1; // drop menu + break; + default: + return -1; + } + + if (((*fromX = xSqr) < 0) || + ((*fromY = ySqr) < 0)) { + *fromX = *fromY = -1; + return -1; + } + if (flipView) + *fromX = BOARD_WIDTH - 1 - *fromX; + else + *fromY = BOARD_HEIGHT - 1 - *fromY; + + return whichMenu; +} + void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats ) { // char * hint = lastHint; @@ -5408,9 +6333,365 @@ void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cp stats.an_move_count = cpstats->nr_moves; } + if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each + SetProgramStats( &stats ); } +int +Adjudicate(ChessProgramState *cps) +{ // [HGM] some adjudications useful with buggy engines + // [HGM] adjudicate: made into separate routine, which now can be called after every move + // In any case it determnes if the game is a claimable draw (filling in EP_STATUS). + // Actually ending the game is now based on the additional internal condition canAdjudicate. + // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed. + int k, count = 0; static int bare = 1; + ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first)); + Boolean canAdjudicate = !appData.icsActive; + + // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops + if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { + if( appData.testLegality ) + { /* [HGM] Some more adjudications for obstinate engines */ + int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0, + NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0, + NrPieces=0, NrPawns=0, PawnAdvance=0, i, j; + static int moveCount = 6; + ChessMove result; + char *reason = NULL; + + /* Count what is on board. */ + for(i=0; i 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, + "Xboard adjudication: Bare king", GE_XBOARD ); + return 1; + } + } + } else bare = 1; + + + // don't wait for engine to announce game end if we can judge ourselves + switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) { + case MT_CHECK: + if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time + int i, checkCnt = 0; // (should really be done by making nr of checks part of game state) + for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) { + if(MateTest(boards[i], PosFlags(i)) == MT_CHECK) + checkCnt++; + if(checkCnt >= 2) { + reason = "Xboard adjudication: 3rd check"; + boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; + break; + } + } + } + case MT_NONE: + default: + break; + case MT_STALEMATE: + case MT_STAINMATE: + reason = "Xboard adjudication: Stalemate"; + if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt + boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw + if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers: + boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win + else if(gameInfo.variant == VariantSuicide) // in suicide it depends + boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE : + ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ? + EP_CHECKMATE : EP_WINS); + else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi) + boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses + } + break; + case MT_CHECKMATE: + reason = "Xboard adjudication: Checkmate"; + boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE); + break; + } + + switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) { + case EP_STALEMATE: + result = GameIsDrawn; break; + case EP_CHECKMATE: + result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break; + case EP_WINS: + result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break; + default: + result = (ChessMove) 0; + } + if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested + if(engineOpponent) + SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */ + ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ + GameEnds( result, reason, GE_XBOARD ); + return 1; + } + + /* Next absolutely insufficient mating material. */ + if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && + gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible + (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 || + NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color + { /* KBK, KNK, KK of KBKB with like Bishops */ + + /* always flag draws, for judging claims */ + boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW; + + if(canAdjudicate && appData.materialDraws) { + /* but only adjudicate them if adjudication enabled */ + if(engineOpponent) { + SendToProgram("force\n", engineOpponent); // suppress reply + SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */ + } + ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ + GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD ); + return 1; + } + } + + /* Then some trivial draws (only adjudicate, cannot be claimed) */ + if(NrPieces == 4 && + ( NrWR == 1 && NrBR == 1 /* KRKR */ + || NrWQ==1 && NrBQ==1 /* KQKQ */ + || NrWN==2 || NrBN==2 /* KNNK */ + || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */ + ) ) { + if(canAdjudicate && --moveCount < 0 && appData.trivialDraws) + { /* if the first 3 moves do not show a tactical win, declare draw */ + if(engineOpponent) { + SendToProgram("force\n", engineOpponent); // suppress reply + SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */ + } + ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ + GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD ); + return 1; + } + } else moveCount = 6; + } + } + + if (appData.debugMode) { int i; + fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n", + forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS], + appData.drawRepeats); + for( i=forwardMostMove; i>=backwardMostMove; i-- ) + fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]); + + } + + // Repetition draws and 50-move rule can be applied independently of legality testing + + /* Check for rep-draws */ + count = 0; + for(k = forwardMostMove-2; + k>=backwardMostMove && k>=forwardMostMove-100 && + (signed char)boards[k][EP_STATUS] < EP_UNKNOWN && + (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE; + k-=2) + { int rights=0; + if(CompareBoards(boards[k], boards[forwardMostMove])) { + /* compare castling rights */ + if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] && + (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) ) + rights++; /* King lost rights, while rook still had them */ + if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */ + if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] || + boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] ) + rights++; /* but at least one rook lost them */ + } + if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] && + (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) ) + rights++; + if( boards[forwardMostMove][CASTLING][5] != NoRights ) { + if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] || + boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] ) + rights++; + } + if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2 + && appData.drawRepeats > 1) { + /* adjudicate after user-specified nr of repeats */ + if(engineOpponent) { + SendToProgram("force\n", engineOpponent); // suppress reply + SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */ + } + ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ + if(gameInfo.variant == VariantXiangqi && appData.testLegality) { + // [HGM] xiangqi: check for forbidden perpetuals + int m, ourPerpetual = 1, hisPerpetual = 1; + for(m=forwardMostMove; m>k; m-=2) { + if(MateTest(boards[m], PosFlags(m)) != MT_CHECK) + ourPerpetual = 0; // the current mover did not always check + if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK) + hisPerpetual = 0; // the opponent did not always check + } + if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n", + ourPerpetual, hisPerpetual); + if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit + GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, + "Xboard adjudication: perpetual checking", GE_XBOARD ); + return 1; + } + if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet + break; // (or we would have caught him before). Abort repetition-checking loop. + // Now check for perpetual chases + if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase + hisPerpetual = PerpetualChase(k, forwardMostMove); + ourPerpetual = PerpetualChase(k+1, forwardMostMove); + if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit + GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, + "Xboard adjudication: perpetual chasing", GE_XBOARD ); + return 1; + } + if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet + break; // Abort repetition-checking loop. + } + // if neither of us is checking or chasing all the time, or both are, it is draw + } + GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD ); + return 1; + } + if( rights == 0 && count > 1 ) /* occurred 2 or more times before */ + boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW; + } + } + + /* Now we test for 50-move draws. Determine ply count */ + count = forwardMostMove; + /* look for last irreversble move */ + while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove ) + count--; + /* if we hit starting position, add initial plies */ + if( count == backwardMostMove ) + count -= initialRulePlies; + count = forwardMostMove - count; + if( count >= 100) + boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW; + /* this is used to judge if draw claims are legal */ + if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) { + if(engineOpponent) { + SendToProgram("force\n", engineOpponent); // suppress reply + SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */ + } + ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ + GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD ); + return 1; + } + + /* if draw offer is pending, treat it as a draw claim + * when draw condition present, to allow engines a way to + * claim draws before making their move to avoid a race + * condition occurring after their move + */ + if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) { + char *p = NULL; + if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW) + p = "Draw claim: 50-move rule"; + if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW) + p = "Draw claim: 3-fold repetition"; + if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW) + p = "Draw claim: insufficient mating material"; + if( p != NULL && canAdjudicate) { + if(engineOpponent) { + SendToProgram("force\n", engineOpponent); // suppress reply + SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */ + } + ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ + GameEnds( GameIsDrawn, p, GE_XBOARD ); + return 1; + } + } + + if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) { + if(engineOpponent) { + SendToProgram("force\n", engineOpponent); // suppress reply + SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */ + } + ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ + GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD ); + return 1; + } + return 0; +} + char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial) { // [HGM] book: this routine intercepts moves to simulate book replies char *bookHit = NULL; @@ -5469,6 +6750,8 @@ HandleMachineMove(message, cps) int machineWhite; char *bookHit; + cps->userError = 0; + FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit /* * Kludge to ignore BEL characters @@ -5574,7 +6857,8 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h if (appData.debugMode) { int f = forwardMostMove; fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f, - castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]); + boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2], + boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]); } if(cps->alphaRank) AlphaRank(machineMove, 4); if (!ParseOneMove(machineMove, forwardMostMove, &moveType, @@ -5602,12 +6886,11 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h ) { ChessMove moveType; moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove), - epStatus[forwardMostMove], castlingRights[forwardMostMove], fromY, fromX, toY, toX, promoChar); if (appData.debugMode) { int i; for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ", - castlingRights[forwardMostMove][i], castlingRank[i]); + boards[forwardMostMove][CASTLING][i], castlingRank[i]); fprintf(debugFP, "castling rights\n"); } if(moveType == IllegalMove) { @@ -5641,39 +6924,11 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h if (cps->sendTime == 2) cps->sendTime = 1; if (cps->offeredDraw) cps->offeredDraw--; -#if ZIPPY - if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) && - first.initDone) { - SendMoveToICS(moveType, fromX, fromY, toX, toY); - ics_user_moved = 1; - if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */ - char buf[3*MSG_SIZ]; - - sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n", - programStats.score / 100., - programStats.depth, - programStats.time / 100., - (unsigned int)programStats.nodes, - (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 /* currentMoveString is set as a side-effect of ParseOneMove */ strcpy(machineMove, currentMoveString); strcat(machineMove, "\n"); strcpy(moveList[forwardMostMove], machineMove); - /* [AS] Save move info and clear stats for next move */ - pvInfoList[ forwardMostMove ].score = programStats.score; - pvInfoList[ forwardMostMove ].depth = programStats.depth; - pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats - ClearProgramStats(); - thinkOutput[0] = NULLCHAR; - hiddenThinkOutputState = 0; - MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/ /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */ @@ -5705,339 +6960,41 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. } } - if( gameMode == TwoMachinesPlay ) { - // [HGM] some adjudications useful with buggy engines - int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1; - if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { - + if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends - if( appData.testLegality ) - { /* [HGM] Some more adjudications for obstinate engines */ - int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0, - NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0, - NrPieces=0, NrPawns=0, PawnAdvance=0, i, j; - static int moveCount = 6; - ChessMove result; - char *reason = NULL; - - /* Count what is on board. */ - for(i=0; iother); // make sure opponent gets move - ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ - GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, - "Xboard adjudication: King destroyed", GE_XBOARD ); - return; - } - } - - /* Bare King in Shatranj (loses) or Losers (wins) */ - if( NrW == 1 || NrPieces - NrW == 1) { - if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first) - epStatus[forwardMostMove] = EP_WINS; // mark as win, so it becomes claimable - if(appData.checkMates) { - SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move - ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ - GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, - "Xboard adjudication: Bare king", GE_XBOARD ); - return; - } - } else - if( gameInfo.variant == VariantShatranj && --bare < 0) - { /* bare King */ - epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm - if(appData.checkMates) { - /* but only adjudicate if adjudication enabled */ - SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move - ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ - GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, - "Xboard adjudication: Bare king", GE_XBOARD ); - return; - } - } - } else bare = 1; - - - // don't wait for engine to announce game end if we can judge ourselves - switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile, - castlingRights[forwardMostMove]) ) { - case MT_CHECK: - if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time - int i, checkCnt = 0; // (should really be done by making nr of checks part of game state) - for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) { - if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK) - checkCnt++; - if(checkCnt >= 2) { - reason = "Xboard adjudication: 3rd check"; - epStatus[forwardMostMove] = EP_CHECKMATE; - break; - } - } - } - case MT_NONE: - default: - break; - case MT_STALEMATE: - case MT_STAINMATE: - reason = "Xboard adjudication: Stalemate"; - if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt - epStatus[forwardMostMove] = EP_STALEMATE; // default result for stalemate is draw - if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers: - epStatus[forwardMostMove] = EP_WINS; // in these variants stalemated is always a win - else if(gameInfo.variant == VariantSuicide) // in suicide it depends - epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE : - ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ? - EP_CHECKMATE : EP_WINS); - else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi) - epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses - } - break; - case MT_CHECKMATE: - reason = "Xboard adjudication: Checkmate"; - epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE); - break; - } - - switch(i = epStatus[forwardMostMove]) { - case EP_STALEMATE: - result = GameIsDrawn; break; - case EP_CHECKMATE: - result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break; - case EP_WINS: - result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break; - default: - result = (ChessMove) 0; - } - if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested - SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */ - ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ - GameEnds( result, reason, GE_XBOARD ); - return; - } - - /* Next absolutely insufficient mating material. */ - if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && - gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible - (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 || - NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color - { /* KBK, KNK, KK of KBKB with like Bishops */ - - /* always flag draws, for judging claims */ - epStatus[forwardMostMove] = EP_INSUF_DRAW; - - if(appData.materialDraws) { - /* but only adjudicate them if adjudication enabled */ - SendToProgram("force\n", cps->other); // suppress reply - SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */ - ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ - GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD ); - return; - } - } - - /* Then some trivial draws (only adjudicate, cannot be claimed) */ - if(NrPieces == 4 && - ( NrWR == 1 && NrBR == 1 /* KRKR */ - || NrWQ==1 && NrBQ==1 /* KQKQ */ - || NrWN==2 || NrBN==2 /* KNNK */ - || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */ - ) ) { - if(--moveCount < 0 && appData.trivialDraws) - { /* if the first 3 moves do not show a tactical win, declare draw */ - SendToProgram("force\n", cps->other); // suppress reply - SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */ - ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ - GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD ); - return; - } - } else moveCount = 6; - } - } - - if (appData.debugMode) { int i; - fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n", - forwardMostMove, backwardMostMove, epStatus[backwardMostMove], - appData.drawRepeats); - for( i=forwardMostMove; i>=backwardMostMove; i-- ) - fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]); - +#if ZIPPY + if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) && + first.initDone) { + if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) { + SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS + SendToICS("draw "); + SendMoveToICS(moveType, fromX, fromY, toX, toY); } + SendMoveToICS(moveType, fromX, fromY, toX, toY); + ics_user_moved = 1; + if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */ + char buf[3*MSG_SIZ]; - /* Check for rep-draws */ - count = 0; - for(k = forwardMostMove-2; - k>=backwardMostMove && k>=forwardMostMove-100 && - epStatus[k] < EP_UNKNOWN && - epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE; - k-=2) - { int rights=0; - if(CompareBoards(boards[k], boards[forwardMostMove])) { - /* compare castling rights */ - if( castlingRights[forwardMostMove][2] != castlingRights[k][2] && - (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) ) - rights++; /* King lost rights, while rook still had them */ - if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */ - if( castlingRights[forwardMostMove][0] != castlingRights[k][0] || - castlingRights[forwardMostMove][1] != castlingRights[k][1] ) - rights++; /* but at least one rook lost them */ - } - if( castlingRights[forwardMostMove][5] != castlingRights[k][5] && - (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) ) - rights++; - if( castlingRights[forwardMostMove][5] >= 0 ) { - if( castlingRights[forwardMostMove][3] != castlingRights[k][3] || - castlingRights[forwardMostMove][4] != castlingRights[k][4] ) - rights++; - } - if( rights == 0 && ++count > appData.drawRepeats-2 - && appData.drawRepeats > 1) { - /* adjudicate after user-specified nr of repeats */ - SendToProgram("force\n", cps->other); // suppress reply - SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */ - ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ - if(gameInfo.variant == VariantXiangqi && appData.testLegality) { - // [HGM] xiangqi: check for forbidden perpetuals - int m, ourPerpetual = 1, hisPerpetual = 1; - for(m=forwardMostMove; m>k; m-=2) { - if(MateTest(boards[m], PosFlags(m), - EP_NONE, castlingRights[m]) != MT_CHECK) - ourPerpetual = 0; // the current mover did not always check - if(MateTest(boards[m-1], PosFlags(m-1), - EP_NONE, castlingRights[m-1]) != MT_CHECK) - hisPerpetual = 0; // the opponent did not always check - } - if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n", - ourPerpetual, hisPerpetual); - if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit - GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, - "Xboard adjudication: perpetual checking", GE_XBOARD ); - return; - } - if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet - break; // (or we would have caught him before). Abort repetition-checking loop. - // Now check for perpetual chases - if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase - hisPerpetual = PerpetualChase(k, forwardMostMove); - ourPerpetual = PerpetualChase(k+1, forwardMostMove); - if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit - GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, - "Xboard adjudication: perpetual chasing", GE_XBOARD ); - return; - } - if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet - break; // Abort repetition-checking loop. - } - // if neither of us is checking or chasing all the time, or both are, it is draw - } - GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD ); - return; - } - if( rights == 0 && count > 1 ) /* occurred 2 or more times before */ - epStatus[forwardMostMove] = EP_REP_DRAW; - } - } - - /* Now we test for 50-move draws. Determine ply count */ - count = forwardMostMove; - /* look for last irreversble move */ - while( epStatus[count] <= EP_NONE && count > backwardMostMove ) - count--; - /* if we hit starting position, add initial plies */ - if( count == backwardMostMove ) - count -= initialRulePlies; - count = forwardMostMove - count; - if( count >= 100) - epStatus[forwardMostMove] = EP_RULE_DRAW; - /* this is used to judge if draw claims are legal */ - if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) { - SendToProgram("force\n", cps->other); // suppress reply - SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */ - ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ - GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD ); - return; - } - - /* if draw offer is pending, treat it as a draw claim - * when draw condition present, to allow engines a way to - * claim draws before making their move to avoid a race - * condition occurring after their move - */ - if( cps->other->offeredDraw || cps->offeredDraw ) { - char *p = NULL; - if(epStatus[forwardMostMove] == EP_RULE_DRAW) - p = "Draw claim: 50-move rule"; - if(epStatus[forwardMostMove] == EP_REP_DRAW) - p = "Draw claim: 3-fold repetition"; - if(epStatus[forwardMostMove] == EP_INSUF_DRAW) - p = "Draw claim: insufficient mating material"; - if( p != NULL ) { - SendToProgram("force\n", cps->other); // suppress reply - SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */ - GameEnds( GameIsDrawn, p, GE_XBOARD ); - ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ - return; - } - } - - - if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) { - SendToProgram("force\n", cps->other); // suppress reply - SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */ - ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ - - GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD ); + sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n", + programStats.score / 100., + programStats.depth, + programStats.time / 100., + (unsigned int)programStats.nodes, + (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 - return; - } - } + /* [AS] Save move info and clear stats for next move */ + pvInfoList[ forwardMostMove-1 ].score = programStats.score; + pvInfoList[ forwardMostMove-1 ].depth = programStats.depth; + pvInfoList[ forwardMostMove-1 ].time = programStats.time; // [HGM] PGNtime: take time from engine stats + ClearProgramStats(); + thinkOutput[0] = NULLCHAR; + hiddenThinkOutputState = 0; bookHit = NULL; if (gameMode == TwoMachinesPlay) { @@ -6118,7 +7075,7 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. * want this, I was asked to put it in, and obliged. */ if (!strncmp(message, "setboard ", 9)) { - Board initial_position; int i; + Board initial_position; GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD); @@ -6126,12 +7083,9 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. DisplayError(_("Bad FEN received from engine"), 0); return ; } else { - Reset(FALSE, FALSE); + Reset(TRUE, FALSE); CopyBoard(boards[0], initial_position); initialRulePlies = FENrulePlies; - epStatus[0] = FENepStatus; - for( i=0; iuserError = 1; DisplayError(message + 14, 0); return; } @@ -6369,7 +7324,7 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. if (ParseOneMove(buf1, forwardMostMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) { (void) CoordsToAlgebraic(boards[forwardMostMove], - PosFlags(forwardMostMove), EP_UNKNOWN, + PosFlags(forwardMostMove), fromY, fromX, toY, toX, promoChar, buf1); snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1); DisplayInformation(buf2); @@ -6610,7 +7565,12 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. /* [AS] Negate score if machine is playing black and reporting absolute scores */ if( cps->scoreIsAbsolute && - ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) + ( gameMode == MachinePlaysBlack || + gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' || + gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV + (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) && + !WhiteOnMove(currentMove) + ) ) { curscore = -curscore; } @@ -6627,9 +7587,12 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. if(cps->nps == 0) ticklen = 10*time; // use engine reported time else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time - if(WhiteOnMove(forwardMostMove)) + if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite || + gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen; - else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen; + if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack || + gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) + blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen; } /* Buffer overflow protection */ @@ -6637,8 +7600,8 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. if (strlen(buf1) >= sizeof(programStats.movelist) && appData.debugMode) { fprintf(debugFP, - "PV is too long; using the first %d bytes.\n", - sizeof(programStats.movelist) - 1); + "PV is too long; using the first %u bytes.\n", + (unsigned) sizeof(programStats.movelist) - 1); } safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) ); @@ -6688,7 +7651,6 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. if (currentMove == forwardMostMove || gameMode == AnalyzeMode || gameMode == AnalyzeFile || appData.icsEngineAnalyze) { DisplayMove(currentMove - 1); - DisplayAnalysis(); } return; @@ -6716,7 +7678,6 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. if (currentMove == forwardMostMove || gameMode==AnalyzeMode || gameMode == AnalyzeFile || appData.icsEngineAnalyze) { DisplayMove(currentMove - 1); - DisplayAnalysis(); } return; } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s", @@ -6741,7 +7702,6 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. SendProgramStatsToFrontend( cps, &programStats ); - DisplayAnalysis(); return; } else if (strncmp(message,"++",2) == 0) { @@ -6777,7 +7737,6 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. if (currentMove == forwardMostMove || gameMode==AnalyzeMode || gameMode == AnalyzeFile || appData.icsEngineAnalyze) { DisplayMove(currentMove - 1); - DisplayAnalysis(); } return; } @@ -6992,18 +7951,15 @@ ParseGameHistory(game) return; } (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex), - EP_UNKNOWN, fromY, fromX, toY, toX, promoChar, + fromY, fromX, toY, toX, promoChar, parseList[boardIndex]); CopyBoard(boards[boardIndex + 1], boards[boardIndex]); - {int i; for(i=0; iBOARD_LEFT && board[toY][toX-1] == BlackPawn && gameInfo.variant != VariantBerolina || toX < fromX) - *ep = toX | berolina; + board[EP_STATUS] = toX | berolina; if(toX fromX) - *ep = toX; + board[EP_STATUS] = toX; } } else if( board[fromY][fromX] == BlackPawn ) { if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers - *ep = EP_PAWN_MOVE; + board[EP_STATUS] = EP_PAWN_MOVE; if( toY-fromY== -2) { if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn && gameInfo.variant != VariantBerolina || toX < fromX) - *ep = toX | berolina; + board[EP_STATUS] = toX | berolina; if(toX fromX) - *ep = toX; + board[EP_STATUS] = toX; } } for(i=0; i= BOARD_HEIGHT-promoRank && gameInfo.variant != VariantXiangqi ) { /* white pawn promotion */ @@ -7192,13 +8147,13 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep) board[fromY][0] = EmptySquare; board[toY][2] = BlackRook; } else if (board[fromY][fromX] == BlackPawn - && toY == 0 + && toY < promoRank && gameInfo.variant != VariantXiangqi ) { /* black pawn promotion */ - board[0][toX] = CharToPiece(ToLower(promoChar)); - if (board[0][toX] == EmptySquare) { - board[0][toX] = BlackQueen; + board[toY][toX] = CharToPiece(ToLower(promoChar)); + if (board[toY][toX] == EmptySquare) { + board[toY][toX] = BlackQueen; } if(gameInfo.variant==VariantBughouse || gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */ @@ -7249,14 +8204,20 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep) p = (int) fromX; if(p < (int) BlackPawn) { /* white drop */ p -= (int)WhitePawn; + p = PieceToNumber((ChessSquare)p); if(p >= gameInfo.holdingsSize) p = 0; - if(--board[p][BOARD_WIDTH-2] == 0) + if(--board[p][BOARD_WIDTH-2] <= 0) board[p][BOARD_WIDTH-1] = EmptySquare; + if((int)board[p][BOARD_WIDTH-2] < 0) + board[p][BOARD_WIDTH-2] = 0; } else { /* black drop */ p -= (int)BlackPawn; + p = PieceToNumber((ChessSquare)p); if(p >= gameInfo.holdingsSize) p = 0; - if(--board[BOARD_HEIGHT-1-p][1] == 0) + if(--board[BOARD_HEIGHT-1-p][1] <= 0) board[BOARD_HEIGHT-1-p][0] = EmptySquare; + if((int)board[BOARD_HEIGHT-1-p][1] < 0) + board[BOARD_HEIGHT-1-p][1] = 0; } } if (captured != EmptySquare && gameInfo.holdingsSize > 0 @@ -7290,7 +8251,6 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep) board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured; } } - } else if (gameInfo.variant == VariantAtomic) { if (captured != EmptySquare) { int y, x; @@ -7375,19 +8335,18 @@ MakeMove(fromX, fromY, toX, toY, promoChar) fflush(serverMoves); } - if (forwardMostMove+1 >= MAX_MOVES) { + if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"), 0, 1); return; } + UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this if (commentList[forwardMostMove+1] != NULL) { free(commentList[forwardMostMove+1]); commentList[forwardMostMove+1] = NULL; } CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]); - {int i; for(i=0; i EP_DRAWS + if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS && (forwardMostMove <= backwardMostMove || - epStatus[forwardMostMove-1] > EP_DRAWS || + (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS || (claimer=='b')==(forwardMostMove&1)) ) { /* [HGM] verify: draws that were not flagged are false claims */ @@ -7849,7 +8806,7 @@ GameEnds(result, resultDetails, whosays) && result != GameIsDrawn) { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn); for(j=BOARD_LEFT; j= 0 && p <= (int)WhiteKing) k++; } if (appData.debugMode) { @@ -8161,6 +9118,7 @@ Reset(redraw, init) fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n", redraw, init, gameMode); } + CleanupTail(); // [HGM] vari: delete any stored variations pausing = pauseExamInvalid = FALSE; startedFromSetupPosition = blackPlaysFirst = FALSE; firstMove = TRUE; @@ -8328,14 +9286,9 @@ LoadGameOneMove(readAhead) if (appData.debugMode) fprintf(debugFP, "Parsed Comment: %s\n", yy_text); p = yy_text; - if (*p == '{' || *p == '[' || *p == '(') { - p[strlen(p) - 1] = NULLCHAR; - p++; - } /* append the comment but don't display it */ - while (*p == '\n') p++; - AppendComment(currentMove, p); + AppendComment(currentMove, p, FALSE); return TRUE; case WhiteCapturesEnPassant: @@ -8422,8 +9375,7 @@ LoadGameOneMove(readAhead) case (ChessMove) 0: /* end of file */ if (appData.debugMode) fprintf(debugFP, "Parser hit end of file\n"); - switch (MateTest(boards[currentMove], PosFlags(currentMove), - EP_UNKNOWN, castlingRights[currentMove]) ) { + switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) { case MT_NONE: case MT_CHECK: break; @@ -8458,8 +9410,7 @@ LoadGameOneMove(readAhead) /* Reached start of next game in file */ if (appData.debugMode) fprintf(debugFP, "Parsed start of next game: %s\n", yy_text); - switch (MateTest(boards[currentMove], PosFlags(currentMove), - EP_UNKNOWN, castlingRights[currentMove]) ) { + switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) { case MT_NONE: case MT_CHECK: break; @@ -8618,8 +9569,7 @@ MakeRegisteredMove() MakeMove(fromX, fromY, toX, toY, promoChar); ShowMove(fromX, fromY, toX, toY); - switch (MateTest(boards[currentMove], PosFlags(currentMove), - EP_UNKNOWN, castlingRights[currentMove]) ) { + switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) { case MT_NONE: case MT_CHECK: break; @@ -8989,9 +9939,8 @@ LoadGame(f, gameNumber, title, useList) /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */ { int i; initialRulePlies = FENrulePlies; - epStatus[forwardMostMove] = FENepStatus; for( i=0; i< nrCastlingRights; i++ ) - initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i]; + initialRights[i] = initial_position[CASTLING][i]; } yyboardindex = forwardMostMove; free(gameInfo.fen); @@ -9007,12 +9956,7 @@ LoadGame(f, gameNumber, title, useList) if (appData.debugMode) fprintf(debugFP, "Parsed Comment: %s\n", yy_text); p = yy_text; - if (*p == '{' || *p == '[' || *p == '(') { - p[strlen(p) - 1] = NULLCHAR; - p++; - } - while (*p == '\n') p++; - AppendComment(currentMove, p); + AppendComment(currentMove, p, FALSE); yyboardindex = forwardMostMove; cm = (ChessMove) yylex(); } @@ -9113,12 +10057,7 @@ LoadGame(f, gameNumber, title, useList) if (appData.debugMode) fprintf(debugFP, "Parsed Comment: %s\n", yy_text); p = yy_text; - if (*p == '{' || *p == '[' || *p == '(') { - p[strlen(p) - 1] = NULLCHAR; - p++; - } - while (*p == '\n') p++; - AppendComment(currentMove, p); + AppendComment(currentMove, p, FALSE); yyboardindex = forwardMostMove; cm = (ChessMove) yylex(); } @@ -9340,17 +10279,11 @@ LoadPosition(f, positionNumber, title) currentMove = forwardMostMove = backwardMostMove = 0; DisplayMessage("", _("White to play")); } - /* [HGM] copy FEN attributes as well */ - { int i; - initialRulePlies = FENrulePlies; - epStatus[forwardMostMove] = FENepStatus; - for( i=0; i< nrCastlingRights; i++ ) - castlingRights[forwardMostMove][i] = FENcastlingRights[i]; - } + initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */ SendBoard(&first, forwardMostMove); if (appData.debugMode) { int i, j; - for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");} + for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");} for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n"); fprintf(debugFP, "Load Position\n"); } @@ -9570,7 +10503,7 @@ SaveGamePGN(f) /* Print comments preceding this move */ if (commentList[i] != NULL) { if (linelen > 0) fprintf(f, "\n"); - fprintf(f, "{\n%s}\n", commentList[i]); + fprintf(f, "%s", commentList[i]); linelen = 0; newblock = TRUE; } @@ -9623,17 +10556,9 @@ SaveGamePGN(f) /* [AS] Add PV info if present */ if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) { /* [HGM] add time */ - char buf[MSG_SIZ]; int seconds = 0; + char buf[MSG_SIZ]; int seconds; - if(i >= backwardMostMove) { - if(WhiteOnMove(i)) - seconds = timeRemaining[0][i] - timeRemaining[0][i+1] - + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds); - else - seconds = timeRemaining[1][i] - timeRemaining[1][i+1] - + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds); - } - seconds = (seconds+50)/100; // deci-seconds, rounded to nearest + seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest if( seconds <= 0) buf[0] = 0; else if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else { @@ -9673,7 +10598,7 @@ SaveGamePGN(f) /* Print comments after last move */ if (commentList[i] != NULL) { - fprintf(f, "{\n%s}\n", commentList[i]); + fprintf(f, "%s\n", commentList[i]); } /* Print result */ @@ -9762,7 +10687,7 @@ SaveGame(f, dummy, dummy2) int dummy; char *dummy2; { - if (gameMode == EditPosition) EditPositionDone(); + if (gameMode == EditPosition) EditPositionDone(TRUE); lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving if (appData.oldSaveStyle) return SaveGameOldStyle(f); @@ -9803,6 +10728,7 @@ SavePosition(f, dummy, dummy2) time_t tm; char *fen; + if (gameMode == EditPosition) EditPositionDone(TRUE); if (appData.oldSaveStyle) { tm = time((time_t *) NULL); @@ -10324,8 +11250,7 @@ AnalyzeModeEvent() first.analyzing = TRUE; /*first.maybeThinking = TRUE;*/ first.maybeThinking = FALSE; /* avoid killing GNU Chess */ - AnalysisPopUp(_("Analysis"), - _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis.")); + EngineOutputPopUp(); } if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode; pausing = FALSE; @@ -10351,8 +11276,7 @@ AnalyzeFileEvent() first.analyzing = TRUE; /*first.maybeThinking = TRUE;*/ first.maybeThinking = FALSE; /* avoid killing GNU Chess */ - AnalysisPopUp(_("Analysis"), - _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis.")); + EngineOutputPopUp(); } gameMode = AnalyzeFile; pausing = FALSE; @@ -10382,7 +11306,7 @@ MachineWhiteEvent() EditGameEvent(); if (gameMode == EditPosition) - EditPositionDone(); + EditPositionDone(TRUE); if (!WhiteOnMove(currentMove)) { DisplayError(_("It is not White's turn"), 0); @@ -10463,7 +11387,7 @@ MachineBlackEvent() EditGameEvent(); if (gameMode == EditPosition) - EditPositionDone(); + EditPositionDone(TRUE); if (WhiteOnMove(currentMove)) { DisplayError(_("It is not Black's turn"), 0); @@ -10570,7 +11494,7 @@ TwoMachinesEvent P((void)) if (gameMode != EditGame) return; break; case EditPosition: - EditPositionDone(); + EditPositionDone(TRUE); break; case AnalyzeMode: case AnalyzeFile: @@ -10581,7 +11505,8 @@ TwoMachinesEvent P((void)) break; } - forwardMostMove = currentMove; +// forwardMostMove = currentMove; + TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this... ResurrectChessProgram(); /* in case first program isn't running */ if (second.pr == NULL) { @@ -10662,7 +11587,9 @@ TwoMachinesEvent P((void)) strcpy(bookMove, "move "); strcat(bookMove, bookHit); - HandleMachineMove(bookMove, &first); + savedMessage = bookMove; // args for deferred call + savedState = onmove; + ScheduleDelayedEvent(DeferredBookMove, 1); } } @@ -10706,7 +11633,7 @@ IcsClientEvent() break; case EditPosition: - EditPositionDone(); + EditPositionDone(TRUE); break; case AnalyzeMode: @@ -10747,7 +11674,7 @@ EditGameEvent() } break; case EditPosition: - EditPositionDone(); + EditPositionDone(TRUE); break; case AnalyzeMode: case AnalyzeFile: @@ -10850,37 +11777,34 @@ ExitAnalyzeMode() SendToProgram("exit\n", &first); first.analyzing = FALSE; } - AnalysisPopDown(); thinkOutput[0] = NULLCHAR; } void -EditPositionDone() +EditPositionDone(Boolean fakeRights) { int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing; startedFromSetupPosition = TRUE; InitChessProgram(&first, FALSE); - castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1; + if(fakeRights) { // [HGM] suppress this if we just pasted a FEN. + boards[0][EP_STATUS] = EP_NONE; + boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1; if(boards[0][0][BOARD_WIDTH>>1] == king) { - castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1; - castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1; - } else castlingRights[0][2] = -1; + boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : 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) { - castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1; - castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1; - } else castlingRights[0][5] = -1; + boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights; + boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights; + } else boards[0][CASTLING][5] = NoRights; + } SendToProgram("force\n", &first); if (blackPlaysFirst) { strcpy(moveList[0], ""); strcpy(parseList[0], ""); currentMove = forwardMostMove = backwardMostMove = 1; CopyBoard(boards[1], boards[0]); - /* [HGM] copy rights as well, as this code is also used after pasting a FEN */ - { int i; - epStatus[1] = epStatus[0]; - for(i=0; i= BOARD_RGHT) break; // [HGM] holdings sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y); SendToICS(buf); } else { + if(x < BOARD_LEFT || x >= BOARD_RGHT) { + if(x == BOARD_LEFT-2) { + if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break; + boards[0][y][1] = 0; + } else + if(x == BOARD_RGHT+1) { + if(y >= gameInfo.holdingsSize) break; + boards[0][y][BOARD_WIDTH-2] = 0; + } else break; + } boards[0][y][x] = EmptySquare; DrawPosition(FALSE, boards[0]); } @@ -11041,7 +11976,8 @@ EditPositionMenuEvent(selection, x, y) case BlackQueen: if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || - gameInfo.variant == VariantCourier ) + gameInfo.variant == VariantCourier || + gameInfo.variant == VariantMakruk ) selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz); goto defaultlabel; @@ -11054,12 +11990,28 @@ EditPositionMenuEvent(selection, x, y) default: defaultlabel: if (gameMode == IcsExamining) { + if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings sprintf(buf, "%s%c@%c%c\n", ics_prefix, PieceToChar(selection), AAA + x, ONE + y); SendToICS(buf); } else { + if(x < BOARD_LEFT || x >= BOARD_RGHT) { + int n; + if(x == BOARD_LEFT-2 && selection >= BlackPawn) { + n = PieceToNumber(selection - BlackPawn); + if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; } + boards[0][BOARD_HEIGHT-1-n][0] = selection; + boards[0][BOARD_HEIGHT-1-n][1]++; + } else + if(x == BOARD_RGHT+1 && selection < BlackPawn) { + n = PieceToNumber(selection); + if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; } + boards[0][n][BOARD_WIDTH-1] = selection; + boards[0][n][BOARD_WIDTH-2]++; + } + } else boards[0][y][x] = selection; - DrawPosition(FALSE, boards[0]); + DrawPosition(TRUE, boards[0]); } break; } @@ -11148,7 +12100,7 @@ DeclineEvent() StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ? "Black offers a draw" : "White offers a draw")) { #ifdef NOTDEF - AppendComment(cmailOldMove, "Draw declined"); + AppendComment(cmailOldMove, "Draw declined", TRUE); DisplayComment(cmailOldMove - 1, "Draw declined"); #endif /*NOTDEF*/ } else { @@ -11220,6 +12172,7 @@ DrawEvent() SendToICS(ics_prefix); SendToICS("draw\n"); + userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play } else if (cmailMsgLoaded) { if (currentMove == cmailOldMove && commentList[cmailOldMove] != NULL && @@ -11230,7 +12183,7 @@ DrawEvent() } else if (currentMove == cmailOldMove + 1) { char *offer = WhiteOnMove(cmailOldMove) ? "White offers a draw" : "Black offers a draw"; - AppendComment(currentMove, offer); + AppendComment(currentMove, offer, TRUE); DisplayComment(currentMove - 1, offer); cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW; } else { @@ -11506,7 +12459,7 @@ void ToStartEvent() { if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) { - /* to optimze, we temporarily turn off analysis mode while we undo + /* to optimize, we temporarily turn off analysis mode while we undo * all the moves. Otherwise we get analysis output after each undo. */ if (first.analysisSupport) { @@ -11547,6 +12500,9 @@ ToNrEvent(int to) void RevertEvent() { + if(PopTail(TRUE)) { // [HGM] vari: restore old game tail + return; + } if (gameMode != IcsExamining) { DisplayError(_("You are not examining a game"), 0); return; @@ -11644,6 +12600,7 @@ TruncateGameEvent() void TruncateGame() { + CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate if (forwardMostMove > currentMove) { if (gameInfo.resultDetails != NULL) { free(gameInfo.resultDetails); @@ -11701,7 +12658,7 @@ BookEvent() } break; case EditPosition: - EditPositionDone(); + EditPositionDone(TRUE); break; case TwoMachinesPlay: return; @@ -11806,6 +12763,14 @@ SetGameInfo() { /* This routine is used only for certain modes */ VariantClass v = gameInfo.variant; + ChessMove r = GameUnfinished; + char *p = NULL; + + if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame + r = gameInfo.result; + p = gameInfo.resultDetails; + gameInfo.resultDetails = NULL; + } ClearGameInfo(&gameInfo); gameInfo.variant = v; @@ -11858,6 +12823,8 @@ SetGameInfo() gameInfo.round = StrSave("-"); gameInfo.white = StrSave("-"); gameInfo.black = StrSave("-"); + gameInfo.result = r; + gameInfo.resultDetails = p; break; case EditPosition: @@ -11907,10 +12874,23 @@ ReplaceComment(index, text) commentList[index] = NULL; return; } + if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces + *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing + *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?) commentList[index] = (char *) malloc(len + 2); strncpy(commentList[index], text, len); commentList[index][len] = '\n'; commentList[index][len + 1] = NULLCHAR; + } else { + // [HGM] braces: if text does not start with known OK delimiter, put braces around it. + char *p; + commentList[index] = (char *) malloc(len + 6); + strcpy(commentList[index], "{\n"); + strncpy(commentList[index]+2, text, len); + commentList[index][len+2] = NULLCHAR; + while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment + strcat(commentList[index], "\n}\n"); + } } void @@ -11929,13 +12909,15 @@ CrushCRs(text) } void -AppendComment(index, text) +AppendComment(index, text, addBraces) int index; char *text; + Boolean addBraces; // [HGM] braces: tells if we should add {} { int oldlen, len; char *old; +if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP); text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */ CrushCRs(text); @@ -11948,17 +12930,30 @@ AppendComment(index, text) if (commentList[index] != NULL) { old = commentList[index]; oldlen = strlen(old); - commentList[index] = (char *) malloc(oldlen + len + 2); + while(commentList[index][oldlen-1] == '\n') + commentList[index][--oldlen] = NULLCHAR; + commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4 strcpy(commentList[index], old); free(old); - strncpy(&commentList[index][oldlen], text, len); - commentList[index][oldlen + len] = '\n'; - commentList[index][oldlen + len + 1] = NULLCHAR; + // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}" + if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) { + if(addBraces) addBraces = FALSE; else { text++; len--; } + while (*text == '\n') { text++; len--; } + commentList[index][--oldlen] = NULLCHAR; + } + if(addBraces) strcat(commentList[index], "\n{\n"); + else strcat(commentList[index], "\n"); + strcat(commentList[index], text); + if(addBraces) strcat(commentList[index], "\n}\n"); + else strcat(commentList[index], "\n"); } else { - commentList[index] = (char *) malloc(len + 2); - strncpy(commentList[index], text, len); - commentList[index][len] = '\n'; - commentList[index][len + 1] = NULLCHAR; + commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4... + if(addBraces) + strcpy(commentList[index], "{\n"); + else commentList[index][0] = NULLCHAR; + strcat(commentList[index], text); + strcat(commentList[index], "\n"); + if(addBraces) strcat(commentList[index], "}\n"); } } @@ -12002,21 +12997,24 @@ char *GetInfoFromComment( int index, char * text ) if( s_emt != NULL ) { } + return text; } else { /* We expect something like: [+|-]nnn.nn/dd */ int score_lo = 0; + if(*text != '{') return text; // [HGM] braces: must be normal comment + sep = strchr( text, '/' ); if( sep == NULL || sep < (text+4) ) { return text; } time = -1; sec = -1; deci = -1; - if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 && - sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 && - sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 && - sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) { + if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 && + sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 && + sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 && + sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) { return text; } @@ -12051,6 +13049,7 @@ char *GetInfoFromComment( int index, char * text ) pvInfoList[index-1].depth = depth; pvInfoList[index-1].score = score; pvInfoList[index-1].time = 10*time; // centi-sec + if(*sep == '}') *sep = 0; else *--sep = '{'; } return sep; } @@ -12080,15 +13079,15 @@ SendToProgram(message, cps) && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */ sprintf(buf, _("Error writing to %s chess program"), cps->which); if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */ - if(epStatus[forwardMostMove] <= EP_DRAWS) { + if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) { gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */ sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program); } else { gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins; } - gameInfo.resultDetails = buf; + gameInfo.resultDetails = StrSave(buf); } - DisplayFatalError(buf, error, 1); + if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1; } } @@ -12111,16 +13110,16 @@ ReceiveFromProgram(isr, closure, message, count, error) _("Error: %s chess program (%s) exited unexpectedly"), cps->which, cps->program); if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */ - if(epStatus[forwardMostMove] <= EP_DRAWS) { + if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) { gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */ sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program); } else { gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins; } - gameInfo.resultDetails = buf; + gameInfo.resultDetails = StrSave(buf); } RemoveInputSource(cps->isr); - DisplayFatalError(buf, 0, 1); + if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1; } else { sprintf(buf, _("Error reading from %s chess program (%s)"), @@ -12133,7 +13132,7 @@ ReceiveFromProgram(isr, closure, message, count, error) cps->pr = NoProc; } - DisplayFatalError(buf, error, 1); + if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1; } return; } @@ -12278,7 +13277,7 @@ SendTimeRemaining(cps, machineWhite) /* [HGM] translate opponent's time by time-odds factor */ otime = (otime * cps->other->timeOdds) / cps->timeOdds; if (appData.debugMode) { - fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds); + fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds); } if (time <= 0) time = 1; @@ -12524,7 +13523,7 @@ ParseFeatures(args, cps) /* unknown feature: complain and skip */ q = p; while (*q && *q != '=') q++; - sprintf(buf, "rejected %.*s\n", q-p, p); + sprintf(buf, "rejected %.*s\n", (int)(q-p), p); SendToProgram(buf, cps); p = q; if (*p == '=') { @@ -12551,7 +13550,7 @@ PeriodicUpdatesEvent(newState) appData.periodicUpdates=newState; /* Display type changes, so update it now */ - DisplayAnalysis(); +// DisplayAnalysis(); /* Get the ball rolling again... */ if (newState) { @@ -12565,7 +13564,7 @@ PonderNextMoveEvent(newState) int newState; { if (newState == appData.ponderNextMove) return; - if (gameMode == EditPosition) EditPositionDone(); + if (gameMode == EditPosition) EditPositionDone(TRUE); if (newState) { SendToProgram("hard\n", &first); if (gameMode == TwoMachinesPlay) { @@ -12588,7 +13587,7 @@ NewSettingEvent(option, command, value) { char buf[MSG_SIZ]; - if (gameMode == EditPosition) EditPositionDone(); + if (gameMode == EditPosition) EditPositionDone(TRUE); sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value); SendToProgram(buf, &first); if (gameMode == TwoMachinesPlay) { @@ -12607,7 +13606,7 @@ ShowThinkingEvent() if (oldState == newState) return; oldState = newState; - if (gameMode == EditPosition) EditPositionDone(); + if (gameMode == EditPosition) EditPositionDone(TRUE); if (oldState) { SendToProgram("post\n", &first); if (gameMode == TwoMachinesPlay) { @@ -12691,92 +13690,6 @@ DisplayMove(moveNumber) } void -DisplayAnalysisText(text) - char *text; -{ - char buf[MSG_SIZ]; - - if (gameMode == AnalyzeMode || gameMode == AnalyzeFile - || appData.icsEngineAnalyze) { - sprintf(buf, "Analysis (%s)", first.tidy); - AnalysisPopUp(buf, text); - } -} - -static int -only_one_move(str) - char *str; -{ - while (*str && isspace(*str)) ++str; - while (*str && !isspace(*str)) ++str; - if (!*str) return 1; - while (*str && isspace(*str)) ++str; - if (!*str) return 1; - return 0; -} - -void -DisplayAnalysis() -{ - char buf[MSG_SIZ]; - char lst[MSG_SIZ / 2]; - double nps; - static char *xtra[] = { "", " (--)", " (++)" }; - int h, m, s, cs; - - if (programStats.time == 0) { - programStats.time = 1; - } - - if (programStats.got_only_move) { - safeStrCpy(buf, programStats.movelist, sizeof(buf)); - } else { - safeStrCpy( lst, programStats.movelist, sizeof(lst)); - - nps = (u64ToDouble(programStats.nodes) / - ((double)programStats.time /100.0)); - - cs = programStats.time % 100; - s = programStats.time / 100; - h = (s / (60*60)); - s = s - h*60*60; - m = (s/60); - s = s - m*60; - - if (programStats.moves_left > 0 && appData.periodicUpdates) { - if (programStats.move_name[0] != NULLCHAR) { - sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d", - programStats.depth, - programStats.nr_moves-programStats.moves_left, - programStats.nr_moves, programStats.move_name, - ((float)programStats.score)/100.0, lst, - only_one_move(lst)? - xtra[programStats.got_fail] : "", - (u64)programStats.nodes, (int)nps, h, m, s, cs); - } else { - sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d", - programStats.depth, - programStats.nr_moves-programStats.moves_left, - programStats.nr_moves, ((float)programStats.score)/100.0, - lst, - only_one_move(lst)? - xtra[programStats.got_fail] : "", - (u64)programStats.nodes, (int)nps, h, m, s, cs); - } - } else { - sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d", - programStats.depth, - ((float)programStats.score)/100.0, - lst, - only_one_move(lst)? - xtra[programStats.got_fail] : "", - (u64)programStats.nodes, (int)nps, h, m, s, cs); - } - } - DisplayAnalysisText(buf); -} - -void DisplayComment(moveNumber, text) int moveNumber; char *text; @@ -12784,26 +13697,23 @@ DisplayComment(moveNumber, text) char title[MSG_SIZ]; char buf[8000]; // comment can be long! int score, depth; - - if( appData.autoDisplayComment ) { - if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) { - strcpy(title, "Comment"); - } else { - sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1, - WhiteOnMove(moveNumber) ? " " : ".. ", - parseList[moveNumber]); - } - // [HGM] PV info: display PV info together with (or as) comment - if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) { - if(text == NULL) text = ""; - score = pvInfoList[moveNumber].score; - sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100., - depth, (pvInfoList[moveNumber].time+50)/100, text); - text = buf; - } - } else title[0] = 0; - - if (text != NULL) + + if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) { + strcpy(title, "Comment"); + } else { + sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1, + WhiteOnMove(moveNumber) ? " " : ".. ", + parseList[moveNumber]); + } + // [HGM] PV info: display PV info together with (or as) comment + if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) { + if(text == NULL) text = ""; + score = pvInfoList[moveNumber].score; + sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100., + depth, (pvInfoList[moveNumber].time+50)/100, text); + text = buf; + } + if (text != NULL && (appData.autoDisplayComment || commentUp)) CommentPopUp(title, text); } @@ -12894,7 +13804,7 @@ CheckFlags() void CheckTimeControl() { - if (!appData.clockMode || appData.icsActive || + if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode gameMode == PlayFromGameFile || forwardMostMove == 0) return; /* @@ -13020,6 +13930,9 @@ ResetClocks() (void) StopClockTimer(); if (appData.icsActive) { whiteTimeRemaining = blackTimeRemaining = 0; + } else if (searchTime) { + whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds; + blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds; } else { /* [HGM] correct new time quote for time odds */ whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds; blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds; @@ -13056,12 +13969,12 @@ DecrementClocks() if(whiteNPS >= 0) lastTickLength = 0; timeRemaining = whiteTimeRemaining -= lastTickLength; DisplayWhiteClock(whiteTimeRemaining - fudge, - WhiteOnMove(currentMove)); + WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove)); } else { if(blackNPS >= 0) lastTickLength = 0; timeRemaining = blackTimeRemaining -= lastTickLength; DisplayBlackClock(blackTimeRemaining - fudge, - !WhiteOnMove(currentMove)); + !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove)); } if (CheckFlags()) return; @@ -13149,6 +14062,12 @@ SwitchClocks() break; } + if (searchTime) { // [HGM] st: set clock of player that has to move to max time + if(WhiteOnMove(forwardMostMove)) + whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds; + else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds; + } + tickStartTM = now; intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ? whiteTimeRemaining : blackTimeRemaining); @@ -13448,36 +14367,36 @@ PositionToFEN(move, overrideCastling) *p++ = ' '; if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines - while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; + while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p; } else { if(nrCastlingRights) { q = p; if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) { /* [HGM] write directly from rights */ - if(castlingRights[move][2] >= 0 && - castlingRights[move][0] >= 0 ) - *p++ = castlingRights[move][0] + AAA + 'A' - 'a'; - if(castlingRights[move][2] >= 0 && - castlingRights[move][1] >= 0 ) - *p++ = castlingRights[move][1] + AAA + 'A' - 'a'; - if(castlingRights[move][5] >= 0 && - castlingRights[move][3] >= 0 ) - *p++ = castlingRights[move][3] + AAA; - if(castlingRights[move][5] >= 0 && - castlingRights[move][4] >= 0 ) - *p++ = castlingRights[move][4] + AAA; + if(boards[move][CASTLING][2] != NoRights && + boards[move][CASTLING][0] != NoRights ) + *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a'; + if(boards[move][CASTLING][2] != NoRights && + boards[move][CASTLING][1] != NoRights ) + *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a'; + 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 ) { - if(castlingRights[move][0] == BOARD_RGHT-1 && - castlingRights[move][2] >= 0 ) *p++ = 'K'; - if(castlingRights[move][1] == BOARD_LEFT && - castlingRights[move][2] >= 0 ) *p++ = 'Q'; - if(castlingRights[move][3] == BOARD_RGHT-1 && - castlingRights[move][5] >= 0 ) *p++ = 'k'; - if(castlingRights[move][4] == BOARD_LEFT && - castlingRights[move][5] >= 0 ) *p++ = 'q'; + if(boards[move][CASTLING][0] == BOARD_RGHT-1 && + boards[move][CASTLING][2] != NoRights ) *p++ = 'K'; + if(boards[move][CASTLING][1] == BOARD_LEFT && + boards[move][CASTLING][2] != NoRights ) *p++ = 'Q'; + if(boards[move][CASTLING][3] == BOARD_RGHT-1 && + boards[move][CASTLING][5] != NoRights ) *p++ = 'k'; + if(boards[move][CASTLING][4] == BOARD_LEFT && + boards[move][CASTLING][5] != NoRights ) *p++ = 'q'; } } if (q == p) *p++ = '-'; /* No castling rights */ @@ -13485,7 +14404,7 @@ PositionToFEN(move, overrideCastling) } if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi && - gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { + gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { /* En passant target square */ if (move > backwardMostMove) { fromX = moveList[move - 1][0] - AAA; @@ -13504,8 +14423,8 @@ PositionToFEN(move, overrideCastling) } } else if(move == backwardMostMove) { // [HGM] perhaps we should always do it like this, and forget the above? - if(epStatus[move] >= 0) { - *p++ = epStatus[move] + AAA; + if((signed char)boards[move][EP_STATUS] >= 0) { + *p++ = boards[move][EP_STATUS] + AAA; *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3'; } else { *p++ = '-'; @@ -13523,11 +14442,11 @@ PositionToFEN(move, overrideCastling) if (appData.debugMode) { int k; fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove); for(k=backwardMostMove; k<=forwardMostMove; k++) - fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]); + fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]); } - while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++; + while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++; if( j == backwardMostMove ) i += initialRulePlies; sprintf(p, "%d ", i); p += i>=100 ? 4 : i >= 10 ? 3 : 2; @@ -13571,7 +14490,7 @@ ParseFEN(board, blackPlaysFirst, fen) while (emptycount--) board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare; break; -#if(BOARD_SIZE >= 10) +#if(BOARD_FILES >= 10) } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */ p++; emptycount=10; if (j + emptycount > gameInfo.boardWidth) return FALSE; @@ -13653,17 +14572,19 @@ ParseFEN(board, blackPlaysFirst, fen) /* return the extra info in global variiables */ /* set defaults in case FEN is incomplete */ - FENepStatus = EP_UNKNOWN; + board[EP_STATUS] = EP_UNKNOWN; for(i=0; i=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1; - if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1; - if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1; - if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1; - if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1; - if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1; + if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights; + if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights; + if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn + && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights; + if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights; + if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights; + if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn + && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights; FENrulePlies = 0; while(*p==' ') p++; @@ -13671,39 +14592,45 @@ ParseFEN(board, blackPlaysFirst, fen) if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') { /* castling indicator present, so default becomes no castlings */ for(i=0; i= 'a' && *p < 'a' + gameInfo.boardWidth) || ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) { - char c = *p++; int whiteKingFile=-1, blackKingFile=-1; + char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights; for(i=BOARD_LEFT; 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; switch(c) { case'K': for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--); - FENcastlingRights[0] = i != whiteKingFile ? i : -1; - FENcastlingRights[2] = whiteKingFile; + board[CASTLING][0] = i != whiteKingFile ? i : NoRights; + board[CASTLING][2] = whiteKingFile; break; case'Q': - for(i=BOARD_LEFT; board[0][i]!=WhiteRook && iblackKingFile; i--); - FENcastlingRights[3] = i != blackKingFile ? i : -1; - FENcastlingRights[5] = blackKingFile; + board[CASTLING][3] = i != blackKingFile ? i : NoRights; + board[CASTLING][5] = blackKingFile; break; case'q': - for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i= BlackKing ) break; if(c > i) - FENcastlingRights[3] = c; + board[CASTLING][3] = c; else - FENcastlingRights[4] = c; + board[CASTLING][4] = c; } else { /* white rights */ for(i=BOARD_LEFT; i= WhiteKing) break; if(c > i) - FENcastlingRights[0] = c; + board[CASTLING][0] = c; else - FENcastlingRights[1] = c; + board[CASTLING][1] = c; } } } + for(i=0; i= BOARD_RGHT) return TRUE; if(*p >= '0' && *p <='9') *p++; - FENepStatus = c; + board[EP_STATUS] = c; } } @@ -13780,16 +14709,224 @@ EditPositionPasteFEN(char *fen) EditPositionEvent(); blackPlaysFirst = savedBlackPlaysFirst; CopyBoard(boards[0], initial_position); - /* [HGM] copy FEN attributes as well */ - { int i; - initialRulePlies = FENrulePlies; - epStatus[0] = FENepStatus; - for( i=0; i 0) && (len < sizeof(cseq)); + if (ret) + strcpy(cseq, new_seq); + else if (appData.debugMode) + fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1); + return ret; +} + +/* + reformat a source message so words don't cross the width boundary. internal + newlines are not removed. returns the wrapped size (no null character unless + included in source message). If dest is NULL, only calculate the size required + for the dest buffer. lp argument indicats line position upon entry, and it's + passed back upon exit. +*/ +int wrap(char *dest, char *src, int count, int width, int *lp) +{ + int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen; + + cseq_len = strlen(cseq); + old_line = line = *lp; + ansi = len = clen = 0; + + for (i=0; i < count; i++) + { + if (src[i] == '\033') + ansi = 1; + + // if we hit the width, back up + if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ') + { + // store i & len in case the word is too long + old_i = i, old_len = len; + + // find the end of the last word + while (i && src[i] != ' ' && src[i] != '\n') + { + i--; + len--; + } + + // word too long? restore i & len before splitting it + if ((old_i-i+clen) >= width) + { + i = old_i; + len = old_len; + } + + // extra space? + if (i && src[i-1] == ' ') + len--; + + if (src[i] != ' ' && src[i] != '\n') + { + i--; + if (len) + len--; + } + + // now append the newline and continuation sequence + if (dest) + dest[len] = '\n'; + len++; + if (dest) + strncpy(dest+len, cseq, cseq_len); + len += cseq_len; + line = cseq_len; + clen = cseq_len; + continue; + } + + if (dest) + dest[len] = src[i]; + len++; + if (!ansi) + line++; + if (src[i] == '\n') + line = 0; + if (src[i] == 'm') + ansi = 0; + } + if (dest && appData.debugMode) + { + fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ", + count, width, line, len, *lp); + show_bytes(debugFP, src, count); + fprintf(debugFP, "\ndest: "); + show_bytes(debugFP, dest, len); + fprintf(debugFP, "\n"); + } + *lp = dest ? line : old_line; + + return len; +} + +// [HGM] vari: routines for shelving variations + +void +PushTail(int firstMove, int lastMove) +{ + int i, j, nrMoves = lastMove - firstMove; + + if(appData.icsActive) { // only in local mode + forwardMostMove = currentMove; // mimic old ICS behavior + return; + } + if(storedGames >= MAX_VARIATIONS-1) return; + + // push current tail of game on stack + savedResult[storedGames] = gameInfo.result; + savedDetails[storedGames] = gameInfo.resultDetails; + gameInfo.resultDetails = NULL; + savedFirst[storedGames] = firstMove; + savedLast [storedGames] = lastMove; + savedFramePtr[storedGames] = framePtr; + framePtr -= nrMoves; // reserve space for the boards + for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap + CopyBoard(boards[framePtr+i], boards[firstMove+i]); + for(j=0; j>1); + else strcpy(buf, "("); + for(i=currentMove; i>1, SavePart(parseList[i])); + else sprintf(moveBuf, " %s", SavePart(parseList[i])); + strcat(buf, moveBuf); + if(!--cnt) { strcat(buf, "\n"); cnt = 10; } + } + strcat(buf, ")"); + } + for(i=1; i