X-Git-Url: http://winboard.nl/cgi-bin?a=blobdiff_plain;f=backend.c;h=f806abe3d1dfc9cfabf13ffe32ef510b8cd9ac4b;hb=bb1c0f39816daca49f22a751768be6cf9f4fc9a2;hp=dd4b0be443c032c5f6a2cbe890427f97d42fda23;hpb=3994c15346431e4174170507360a676052456d80;p=xboard.git diff --git a/backend.c b/backend.c index dd4b0be..f806abe 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 * @@ -167,6 +167,7 @@ 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((Boolean fakeRights)); void PrintOpponents P((FILE *fp)); @@ -187,8 +188,9 @@ void DisplayMove P((int moveNumber)); void ParseGameHistory P((char *game)); void ParseBoard12 P((char *string)); +void KeepAlive P((void)); void StartClocks P((void)); -void SwitchClocks P((void)); +void SwitchClocks P((int nr)); void StopClocks P((void)); void ResetClocks P((void)); char *PGNDate P((void)); @@ -235,18 +237,24 @@ 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 */ +Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */ +Boolean partnerUp; 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 @@ -353,6 +361,7 @@ PosFlags(index) case VariantNoCastle: case VariantShatranj: case VariantCourier: + case VariantMakruk: flags &= ~F_ALL_CASTLE_OK; break; default: @@ -486,10 +495,10 @@ ChessSquare KnightmateArray[2][BOARD_FILES] = { }; ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */ - { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen, + { 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_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */ @@ -499,6 +508,13 @@ ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatr 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_FILES>=10) ChessSquare ShogiArray[2][BOARD_FILES] = { @@ -754,7 +770,7 @@ InitBackEnd1() /* [HGM] time odds: set factor for each machine */ first.timeOdds = appData.firstTimeOdds; second.timeOdds = appData.secondTimeOdds; - { int norm = 1; + { float norm = 1; if(appData.timeOddsMode) { norm = first.timeOdds; if(norm > second.timeOdds) norm = second.timeOdds; @@ -885,6 +901,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 */ @@ -1109,6 +1126,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 { @@ -1428,6 +1447,8 @@ read_from_player(isr, closure, message, count, error) void KeepAlive() { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out + if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1); + connectionAlive = FALSE; // only sticks if no response to 'date' command. SendToICS("date\n"); if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000); } @@ -2045,6 +2066,204 @@ static int player2Rating = -1; ColorClass curColor = ColorNormal; int suppressKibitz = 0; +// [HGM] seekgraph +Boolean soughtPending = FALSE; +Boolean seekGraphUp; +#define MAX_SEEK_ADS 200 +#define SQUARE 0x80 +char *seekAdList[MAX_SEEK_ADS]; +int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS]; +float tcList[MAX_SEEK_ADS]; +char colorList[MAX_SEEK_ADS]; +int nrOfSeekAds = 0; +int minRating = 1010, maxRating = 2800; +int hMargin = 10, vMargin = 20, h, w; +extern int squareSize, lineGap; + +void +PlotSeekAd(int i) +{ + int x, y, color = 0, r = ratingList[i]; float tc = tcList[i]; + xList[i] = yList[i] = -100; // outside graph, so cannot be clicked + if(r < minRating+100 && r >=0 ) r = minRating+100; + if(r > maxRating) r = maxRating; + if(tc < 1.) tc = 1.; + if(tc > 95.) tc = 95.; + x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + 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; + } + 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 + sprintf(buf, "play %d\n", seekNrList[closest]); + SendToICS(ics_prefix); + SendToICS(buf); + return TRUE; // let incoming board of started game pop down the graph + } 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; @@ -2082,6 +2301,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, " 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; @@ -2344,10 +2571,11 @@ read_from_ics(isr, closure, data, count, error) sprintf(tmp, _("your opponent kibitzes: %s"), parse); SendToPlayer(tmp, strlen(tmp)); } + next_out = i+1; // [HGM] suppress printing in ICS window } started = STARTED_NONE; } else { - /* Don't match patterns against characters in chatter */ + /* Don't match patterns against characters in comment */ i++; continue; } @@ -2359,6 +2587,7 @@ read_from_ics(isr, closure, data, count, error) continue; } started = STARTED_NONE; + if(suppressKibitz) next_out = i+1; } /* Kludge to deal with rcmd protocol */ @@ -2416,6 +2645,50 @@ read_from_ics(isr, closure, data, count, error) continue; } + oldi = i; + // [HGM] seekgraph: recognize sought lines and end-of-sought message + if(appData.seekGraph) { + if(soughtPending && MatchSoughtLine(buf+i)) { + i = strstr(buf+i, "rated") - buf; + if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out); + 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 + if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any + if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out); + next_out = i; // suppress + continue; + } + if(looking_at(buf, &i, "\nAds 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 + if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out); + next_out = i; + continue; + } + } + } + /* skip formula vars */ if (started == STARTED_NONE && buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') { @@ -2424,7 +2697,6 @@ read_from_ics(isr, closure, data, count, error) continue; } - oldi = i; // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window if (appData.autoKibitz && started == STARTED_NONE && !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze @@ -2433,6 +2705,8 @@ read_from_ics(isr, closure, data, count, error) (StrStr(star_match[0], gameInfo.white) == star_match[0] || StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent suppressKibitz = TRUE; + if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out); + next_out = i; if((StrStr(star_match[0], gameInfo.white) == star_match[0] && (gameMode == IcsPlayingWhite)) || (StrStr(star_match[0], gameInfo.black) == star_match[0] @@ -2447,20 +2721,32 @@ 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, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") || + looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n")) + && atoi(star_match[0])) { + // suppress the acknowledgements of our own autoKibitz + char *p; + if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out); + if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS + SendToPlayer(star_match[0], strlen(star_match[0])); + if(looking_at(buf, &i, "*% ")) // eat prompt + suppressKibitz = FALSE; + next_out = i; + continue; } } // [HGM] kibitz: end of patch -//if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i); - // [HGM] chat: intercept tells by users for which we have an open chat window channel = -1; if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || looking_at(buf, &i, "* whispers:") || + looking_at(buf, &i, "* shouts:") || + looking_at(buf, &i, "* c-shouts:") || + looking_at(buf, &i, "--> * ") || looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) || - looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) { + looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) || + looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) || + looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) { int p; sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle chattingPartner = -1; @@ -2468,27 +2754,41 @@ 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') // shout, c-shout or it; look if there is a 'shouts' chatbox + for(p=0; p') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); } + else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); } + else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); } chattingPartner = p; break; } } if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual for(p=0; p 0 && buf[oldi-1] == '\n') oldi--; + if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out); started = STARTED_COMMENT; parse_pos = 0; parse[0] = NULLCHAR; - savingComment = TRUE; + savingComment = 3 + chattingPartner; // counts as TRUE suppressKibitz = TRUE; + continue; } } // [HGM] chat: end of patch @@ -2682,6 +2982,8 @@ read_from_ics(isr, closure, data, count, error) memcpy(parse, &buf[oldi], parse_pos); parse[parse_pos] = NULLCHAR; started = STARTED_COMMENT; + if(savingComment >= 3) // [HGM] chat: continuation of line for chat box + chattingPartner = savingComment - 3; // kludge to remember the box } else { started = STARTED_CHATTER; } @@ -2849,7 +3151,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: @@ -3042,10 +3351,10 @@ read_from_ics(isr, closure, data, count, error) looking_at(buf, &i, "It is not your move")) { /* Illegal move */ if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved - currentMove = --forwardMostMove; + currentMove = forwardMostMove-1; DisplayMove(currentMove - 1); /* before DMError */ DrawPosition(FALSE, boards[currentMove]); - SwitchClocks(); + SwitchClocks(forwardMostMove-1); // [HGM] race DisplayBothClocks(); } DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg @@ -3146,6 +3455,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); @@ -3275,6 +3585,7 @@ read_from_ics(isr, closure, data, count, error) while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines if (looking_at(buf, &i, "*% ")) { savingComment = FALSE; + suppressKibitz = 0; } } next_out = i; @@ -3286,8 +3597,8 @@ read_from_ics(isr, closure, data, count, error) if (appData.debugMode) fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n", parse, currentMove); - if (sscanf(parse, " game %d", &gamenum) == 1 && - gamenum == ics_gamenum) { + if (sscanf(parse, " game %d", &gamenum) == 1) { + if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game if (gameInfo.variant == VariantNormal) { /* [HGM] We seem to switch variant during a game! * Presumably no holdings were displayed, so we have @@ -3336,14 +3647,26 @@ read_from_ics(isr, closure, data, count, error) gameInfo.white, white_holding, gameInfo.black, black_holding); } - + if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured... DrawPosition(FALSE, boards[currentMove]); DisplayTitle(str); + } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background + sscanf(parse, "game %d white [%s black [%s <- %s", + &gamenum, white_holding, black_holding, + new_piece); + white_holding[strlen(white_holding)-1] = NULLCHAR; + black_holding[strlen(black_holding)-1] = NULLCHAR; + /* [HGM] copy holdings to partner-board holdings area */ + CopyHoldings(partnerBoard, white_holding, WhitePawn); + CopyHoldings(partnerBoard, black_holding, BlackPawn); + if(partnerUp) DrawPosition(FALSE, partnerBoard); + } } /* 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; } @@ -3353,12 +3676,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, @@ -3486,6 +3810,26 @@ ParseBoard12(string) break; } + if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) + && newGameMode == IcsObserving && appData.bgObserve) { + // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */ + char buf[MSG_SIZ]; + for (k = 0; k < ranks; k++) { + for (j = 0; j < files; j++) + board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]); + if(gameInfo.holdingsWidth > 1) { + board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare; + board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;; + } + } + CopyBoard(partnerBoard, board); + if(partnerUp) DrawPosition(FALSE, partnerBoard); + sprintf(buf, "W: %d:%d B: %d:%d (%d-%d) %c", white_time/60000, (white_time%60000)/1000, + (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play); + DisplayMessage(buf, ""); + return; + } + /* Modify behavior for initial board display on move listing of wild games. */ @@ -3692,16 +4036,16 @@ 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--) + for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--) if(board[0][i] == WhiteRook) j = i; initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j); - for(i=BOARD_LEFT, j= -1; i=BOARD_LEFT; i--) + for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--) if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i; initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j); @@ -3711,6 +4055,11 @@ ParseBoard12(string) for(k=BOARD_LEFT; k= 0) { // set e.p. + sprintf(buf, "bsetup eppos %c\n", i+AAA); + SendToICS(buf); + } + bsetup++; + } + } + if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal) + SendToICS("bsetup done\n"); // switch to normal examining. + } + for(i = backwardMostMove; i 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; + if(storeComments) + CoordsToAlgebraic(boards[endPV - 1], + PosFlags(endPV - 1), + fromY, fromX, toY, toX, promoChar, + parseList[endPV - 1]); + else + 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; + char *p; + + 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; + if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3; + index = startPV; + do{ while(buf[index] && buf[index] != '\n') index++; + } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line + buf[index] = 0; + ParsePV(buf+startPV, FALSE); + *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], FALSE); // 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. @@ -4667,6 +5216,12 @@ InitPosition(redraw) nrCastlingRights = 0; SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); break; + case VariantMakruk: + pieces = makrukArray; + nrCastlingRights = 0; + startedFromSetupPosition = TRUE; + SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk"); + break; case VariantTwoKings: pieces = twoKingsArray; break; @@ -4726,7 +5281,7 @@ InitPosition(redraw) break; case VariantFairy: pieces = fairyArray; - SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); + SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); break; case VariantGreat: pieces = GreatArray; @@ -4778,6 +5333,7 @@ InitPosition(redraw) 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) @@ -4942,6 +5498,8 @@ SendBoard(cps, moveNum) setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */ } +static int autoQueen; // [HGM] oneclick + int HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice) { @@ -4963,6 +5521,8 @@ HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice) if(gameInfo.variant == VariantShogi) { promotionZoneSize = 3; highestPromotingPiece = (int)WhiteFerz; + } else if(gameInfo.variant == VariantMakruk) { + promotionZoneSize = 3; } // next weed out all moves that do not touch the promotion zone at all @@ -5006,11 +5566,11 @@ HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice) } // we either have a choice what to promote to, or (in Shogi) whether to promote - if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) { + if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) { *promoChoice = PieceToChar(BlackFerz); // no choice return FALSE; } - if(appData.alwaysPromoteToQueen) { // predetermined + if(autoQueen) { // predetermined if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers) *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want else *promoChoice = PieceToChar(BlackQueen); @@ -5151,6 +5711,64 @@ OKToStartUserMove(x, y) return TRUE; } +Boolean +OnlyMove(int *x, int *y, Boolean captures) { + 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 || + cl.kind == AmbiguousMove && captures && cl.captures == 1 || + cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen || + cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight || + cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) { + 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 || + cl.kind == AmbiguousMove && captures && cl.captures == 1 || + cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen || + cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight || + cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) { + fromX = cl.ff; + fromY = cl.rf; + *x = cl.ft; + *y = cl.rt; + autoQueen = TRUE; // act as if autoQueen on when we click to-square + return TRUE; + } + return FALSE; +} + FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL; int lastLoadGameNumber = 0, lastLoadPositionNumber = 0; int lastLoadGameUseList = FALSE; @@ -5278,6 +5896,18 @@ 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; } @@ -5303,8 +5933,6 @@ 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), fromY, fromX, toY, toX, promoChar); @@ -5419,6 +6047,8 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar) 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; @@ -5443,6 +6073,12 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar) 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; } @@ -5494,6 +6130,8 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar) 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? @@ -5530,6 +6168,43 @@ 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= gameInfo.holdingsSize) ) return; + autoQueen = appData.alwaysPromoteToQueen; + if (fromX == -1) { + if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) { 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); @@ -5590,6 +6276,7 @@ void LeftClick(ClickType clickType, int xPix, int yPix) } } return; + } } /* fromX != -1 */ @@ -5615,6 +6302,7 @@ void LeftClick(ClickType clickType, int xPix, int yPix) !(fromP == BlackKing && toP == BlackRook && frc))) { /* Clicked again on same color piece -- changed his mind */ second = (x == fromX && y == fromY); + if(!second || !OnlyMove(&x, &y, TRUE)) { if (appData.highlightDragging) { SetHighlights(x, y, -1, -1); } else { @@ -5623,9 +6311,11 @@ void LeftClick(ClickType clickType, int xPix, int yPix) 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; @@ -5675,11 +6365,30 @@ void LeftClick(ClickType clickType, int xPix, int yPix) appData.animate = FALSE; } - // moves into holding are invalid for now (later perhaps allow in EditPosition) + // 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, NULL); + DrawPosition(TRUE, boards[currentMove]); return; } @@ -5715,6 +6424,76 @@ void LeftClick(ClickType clickType, int xPix, int yPix) } } +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; + } + + if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) + && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game + if(action == Press) { flipView = !flipView; DrawPosition(TRUE, partnerBoard); partnerUp = TRUE; } else + if(action == Release) { flipView = !flipView; DrawPosition(TRUE, boards[currentMove]); partnerUp = FALSE; } + 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; @@ -5736,311 +6515,24 @@ void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cp stats.an_move_count = cpstats->nr_moves; } - SetProgramStats( &stats ); -} + if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each -char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial) -{ // [HGM] book: this routine intercepts moves to simulate book replies - char *bookHit = NULL; - - //first determine if the incoming move brings opponent into his book - if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI)) - bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move - if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)"); - if(bookHit != NULL && !cps->bookSuspend) { - // make sure opponent is not going to reply after receiving move to book position - SendToProgram("force\n", cps); - cps->bookSuspend = TRUE; // flag indicating it has to be restarted - } - if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move - // now arrange restart after book miss - if(bookHit) { - // after a book hit we never send 'go', and the code after the call to this routine - // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove'). - char buf[MSG_SIZ]; - if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :( - sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it - SendToProgram(buf, cps); - if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go' - } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine - SendToProgram("go\n", cps); - cps->bookSuspend = FALSE; // after a 'go' we are never suspended - } else { // 'go' might be sent based on 'firstMove' after this routine returns - if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return - SendToProgram("go\n", cps); - cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss - } - return bookHit; // notify caller of hit, so it can take action to send move to opponent -} - -char *savedMessage; -ChessProgramState *savedState; -void DeferredBookMove(void) -{ - if(savedState->lastPing != savedState->lastPong) - ScheduleDelayedEvent(DeferredBookMove, 10); - else - HandleMachineMove(savedMessage, savedState); + SetProgramStats( &stats ); } -void -HandleMachineMove(message, cps) - char *message; - ChessProgramState *cps; -{ - char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ]; - char realname[MSG_SIZ]; - int fromX, fromY, toX, toY; - ChessMove moveType; - char promoChar; - char *p; - 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 - */ - while (*message == '\007') message++; - - /* - * [HGM] engine debug message: ignore lines starting with '#' character - */ - if(cps->debug && *message == '#') return; - - /* - * Look for book output - */ - if (cps == &first && bookRequested) { - if (message[0] == '\t' || message[0] == ' ') { - /* Part of the book output is here; append it */ - strcat(bookOutput, message); - strcat(bookOutput, " \n"); - return; - } else if (bookOutput[0] != NULLCHAR) { - /* All of book output has arrived; display it */ - char *p = bookOutput; - while (*p != NULLCHAR) { - if (*p == '\t') *p = ' '; - p++; - } - DisplayInformation(bookOutput); - bookRequested = FALSE; - /* Fall through to parse the current output */ - } - } - - /* - * Look for machine move. - */ - if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) || - (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) - { - /* This method is only useful on engines that support ping */ - if (cps->lastPing != cps->lastPong) { - if (gameMode == BeginningOfGame) { - /* Extra move from before last new; ignore */ - if (appData.debugMode) { - fprintf(debugFP, "Ignoring extra move from %s\n", cps->which); - } - } else { - if (appData.debugMode) { - fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n", - cps->which, gameMode); - } - - SendToProgram("undo\n", cps); - } - return; - } - - switch (gameMode) { - case BeginningOfGame: - /* Extra move from before last reset; ignore */ - if (appData.debugMode) { - fprintf(debugFP, "Ignoring extra move from %s\n", cps->which); - } - return; - - case EndOfGame: - case IcsIdle: - default: - /* Extra move after we tried to stop. The mode test is - not a reliable way of detecting this problem, but it's - the best we can do on engines that don't support ping. - */ - if (appData.debugMode) { - fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n", - cps->which, gameMode); - } - SendToProgram("undo\n", cps); - return; - - case MachinePlaysWhite: - case IcsPlayingWhite: - machineWhite = TRUE; - break; - - case MachinePlaysBlack: - case IcsPlayingBlack: - machineWhite = FALSE; - break; - - case TwoMachinesPlay: - machineWhite = (cps->twoMachinesColor[0] == 'w'); - break; - } - if (WhiteOnMove(forwardMostMove) != machineWhite) { - if (appData.debugMode) { - fprintf(debugFP, - "Ignoring move out of turn by %s, gameMode %d" - ", forwardMost %d\n", - cps->which, gameMode, forwardMostMove); - } - return; - } - - if (appData.debugMode) { int f = forwardMostMove; - fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f, - boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2], - boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]); - } - if(cps->alphaRank) AlphaRank(machineMove, 4); - if (!ParseOneMove(machineMove, forwardMostMove, &moveType, - &fromX, &fromY, &toX, &toY, &promoChar)) { - /* Machine move could not be parsed; ignore it. */ - sprintf(buf1, _("Illegal move \"%s\" from %s machine"), - machineMove, cps->which); - DisplayError(buf1, 0); - sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d", - machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType); - if (gameMode == TwoMachinesPlay) { - GameEnds(machineWhite ? BlackWins : WhiteWins, - buf1, GE_XBOARD); - } - return; - } - - /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */ - /* So we have to redo legality test with true e.p. status here, */ - /* to make sure an illegal e.p. capture does not slip through, */ - /* to cause a forfeit on a justified illegal-move complaint */ - /* of the opponent. */ - if( gameMode==TwoMachinesPlay && appData.testLegality - && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */ - ) { - ChessMove moveType; - moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove), - fromY, fromX, toY, toX, promoChar); - if (appData.debugMode) { - int i; - for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ", - boards[forwardMostMove][CASTLING][i], castlingRank[i]); - fprintf(debugFP, "castling rights\n"); - } - if(moveType == IllegalMove) { - sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c", - machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0); - GameEnds(machineWhite ? BlackWins : WhiteWins, - buf1, GE_XBOARD); - return; - } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom) - /* [HGM] Kludge to handle engines that send FRC-style castling - when they shouldn't (like TSCP-Gothic) */ - switch(moveType) { - case WhiteASideCastleFR: - case BlackASideCastleFR: - toX+=2; - currentMoveString[2]++; - break; - case WhiteHSideCastleFR: - case BlackHSideCastleFR: - toX--; - currentMoveString[2]--; - break; - default: ; // nothing to do, but suppresses warning of pedantic compilers - } - } - hintRequested = FALSE; - lastHint[0] = NULLCHAR; - bookRequested = FALSE; - /* Program may be pondering now */ - cps->maybeThinking = TRUE; - 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) */ - if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) { - int count = 0; - - while( count < adjudicateLossPlies ) { - int score = pvInfoList[ forwardMostMove - count - 1 ].score; - - if( count & 1 ) { - score = -score; /* Flip score for winning side */ - } - - if( score > adjudicateLossThreshold ) { - break; - } - - count++; - } - - if( count >= adjudicateLossPlies ) { - ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ - - GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, - "Xboard adjudication", - GE_XBOARD ); - - return; - } - } - - if( gameMode == TwoMachinesPlay ) { - // [HGM] some adjudications useful with buggy engines - int k, count = 0; static int bare = 1; - if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { - - +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, @@ -6100,12 +6592,13 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. if(gameInfo.variant == VariantAtomic && NrK < 2) { // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated - if(appData.checkMates) { - SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move + if(canAdjudicate && appData.checkMates) { + if(engineOpponent) + SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, "Xboard adjudication: King destroyed", GE_XBOARD ); - return; + return 1; } } @@ -6113,24 +6606,26 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. if( NrW == 1 || NrPieces - NrW == 1) { if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first) boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable - if(appData.checkMates) { - SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move + if(canAdjudicate && appData.checkMates) { + if(engineOpponent) + SendMoveToProgram(forwardMostMove-1, engineOpponent); // 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; + return 1; } } else if( gameInfo.variant == VariantShatranj && --bare < 0) { /* bare King */ boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm - if(appData.checkMates) { + if(canAdjudicate && appData.checkMates) { /* but only adjudicate if adjudication enabled */ - SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move + if(engineOpponent) + SendMoveToProgram(forwardMostMove-1, engineOpponent); // 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; + return 1; } } } else bare = 1; @@ -6185,11 +6680,12 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. 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 */ + 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; + return 1; } /* Next absolutely insufficient mating material. */ @@ -6202,13 +6698,15 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. /* always flag draws, for judging claims */ boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW; - if(appData.materialDraws) { + if(canAdjudicate && 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 */ + 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; + return 1; } } @@ -6219,26 +6717,30 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. || NrWN==2 || NrBN==2 /* KNNK */ || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */ ) ) { - if(--moveCount < 0 && appData.trivialDraws) + if(canAdjudicate && --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 */ + 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; + return 1; } } else moveCount = 6; } - } + } - if (appData.debugMode) { int i; + 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; @@ -6266,11 +6768,13 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] ) rights++; } - if( rights == 0 && ++count > appData.drawRepeats-2 + if( canAdjudicate && 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 */ + 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 @@ -6286,7 +6790,7 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, "Xboard adjudication: perpetual checking", GE_XBOARD ); - return; + 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. @@ -6297,7 +6801,7 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, "Xboard adjudication: perpetual chasing", GE_XBOARD ); - return; + return 1; } if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet break; // Abort repetition-checking loop. @@ -6305,7 +6809,7 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. // 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; + return 1; } if( rights == 0 && count > 1 ) /* occurred 2 or more times before */ boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW; @@ -6324,12 +6828,14 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. if( count >= 100) boards[forwardMostMove][EP_STATUS] = 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 */ + 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; + return 1; } /* if draw offer is pending, treat it as a draw claim @@ -6337,7 +6843,7 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. * claim draws before making their move to avoid a race * condition occurring after their move */ - if( cps->other->offeredDraw || cps->offeredDraw ) { + 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"; @@ -6345,27 +6851,333 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. 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 ) { - SendToProgram("force\n", cps->other); // suppress reply - SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */ - GameEnds( GameIsDrawn, p, GE_XBOARD ); + 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*/ - return; + GameEnds( GameIsDrawn, p, GE_XBOARD ); + return 1; } } - - 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 */ + 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; + 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; + + //first determine if the incoming move brings opponent into his book + if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI)) + bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move + if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)"); + if(bookHit != NULL && !cps->bookSuspend) { + // make sure opponent is not going to reply after receiving move to book position + SendToProgram("force\n", cps); + cps->bookSuspend = TRUE; // flag indicating it has to be restarted + } + if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move + // now arrange restart after book miss + if(bookHit) { + // after a book hit we never send 'go', and the code after the call to this routine + // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove'). + char buf[MSG_SIZ]; + if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :( + sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it + SendToProgram(buf, cps); + if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go' + } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine + SendToProgram("go\n", cps); + cps->bookSuspend = FALSE; // after a 'go' we are never suspended + } else { // 'go' might be sent based on 'firstMove' after this routine returns + if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return + SendToProgram("go\n", cps); + cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss + } + return bookHit; // notify caller of hit, so it can take action to send move to opponent +} + +char *savedMessage; +ChessProgramState *savedState; +void DeferredBookMove(void) +{ + if(savedState->lastPing != savedState->lastPong) + ScheduleDelayedEvent(DeferredBookMove, 10); + else + HandleMachineMove(savedMessage, savedState); +} + +void +HandleMachineMove(message, cps) + char *message; + ChessProgramState *cps; +{ + char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ]; + char realname[MSG_SIZ]; + int fromX, fromY, toX, toY; + ChessMove moveType; + char promoChar; + char *p; + 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 + */ + while (*message == '\007') message++; + + /* + * [HGM] engine debug message: ignore lines starting with '#' character + */ + if(cps->debug && *message == '#') return; + + /* + * Look for book output + */ + if (cps == &first && bookRequested) { + if (message[0] == '\t' || message[0] == ' ') { + /* Part of the book output is here; append it */ + strcat(bookOutput, message); + strcat(bookOutput, " \n"); + return; + } else if (bookOutput[0] != NULLCHAR) { + /* All of book output has arrived; display it */ + char *p = bookOutput; + while (*p != NULLCHAR) { + if (*p == '\t') *p = ' '; + p++; + } + DisplayInformation(bookOutput); + bookRequested = FALSE; + /* Fall through to parse the current output */ + } + } + + /* + * Look for machine move. + */ + if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) || + (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) + { + /* This method is only useful on engines that support ping */ + if (cps->lastPing != cps->lastPong) { + if (gameMode == BeginningOfGame) { + /* Extra move from before last new; ignore */ + if (appData.debugMode) { + fprintf(debugFP, "Ignoring extra move from %s\n", cps->which); + } + } else { + if (appData.debugMode) { + fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n", + cps->which, gameMode); + } + + SendToProgram("undo\n", cps); + } + return; + } + + switch (gameMode) { + case BeginningOfGame: + /* Extra move from before last reset; ignore */ + if (appData.debugMode) { + fprintf(debugFP, "Ignoring extra move from %s\n", cps->which); + } + return; + + case EndOfGame: + case IcsIdle: + default: + /* Extra move after we tried to stop. The mode test is + not a reliable way of detecting this problem, but it's + the best we can do on engines that don't support ping. + */ + if (appData.debugMode) { + fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n", + cps->which, gameMode); + } + SendToProgram("undo\n", cps); + return; + + case MachinePlaysWhite: + case IcsPlayingWhite: + machineWhite = TRUE; + break; + + case MachinePlaysBlack: + case IcsPlayingBlack: + machineWhite = FALSE; + break; + + case TwoMachinesPlay: + machineWhite = (cps->twoMachinesColor[0] == 'w'); + break; + } + if (WhiteOnMove(forwardMostMove) != machineWhite) { + if (appData.debugMode) { + fprintf(debugFP, + "Ignoring move out of turn by %s, gameMode %d" + ", forwardMost %d\n", + cps->which, gameMode, forwardMostMove); + } + return; + } + + if (appData.debugMode) { int f = forwardMostMove; + fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f, + boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2], + boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]); + } + if(cps->alphaRank) AlphaRank(machineMove, 4); + if (!ParseOneMove(machineMove, forwardMostMove, &moveType, + &fromX, &fromY, &toX, &toY, &promoChar)) { + /* Machine move could not be parsed; ignore it. */ + sprintf(buf1, _("Illegal move \"%s\" from %s machine"), + machineMove, cps->which); + DisplayError(buf1, 0); + sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d", + machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType); + if (gameMode == TwoMachinesPlay) { + GameEnds(machineWhite ? BlackWins : WhiteWins, + buf1, GE_XBOARD); + } + return; + } + + /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */ + /* So we have to redo legality test with true e.p. status here, */ + /* to make sure an illegal e.p. capture does not slip through, */ + /* to cause a forfeit on a justified illegal-move complaint */ + /* of the opponent. */ + if( gameMode==TwoMachinesPlay && appData.testLegality + && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */ + ) { + ChessMove moveType; + moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove), + fromY, fromX, toY, toX, promoChar); + if (appData.debugMode) { + int i; + for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ", + boards[forwardMostMove][CASTLING][i], castlingRank[i]); + fprintf(debugFP, "castling rights\n"); + } + if(moveType == IllegalMove) { + sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c", + machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0); + GameEnds(machineWhite ? BlackWins : WhiteWins, + buf1, GE_XBOARD); + return; + } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom) + /* [HGM] Kludge to handle engines that send FRC-style castling + when they shouldn't (like TSCP-Gothic) */ + switch(moveType) { + case WhiteASideCastleFR: + case BlackASideCastleFR: + toX+=2; + currentMoveString[2]++; + break; + case WhiteHSideCastleFR: + case BlackHSideCastleFR: + toX--; + currentMoveString[2]--; + break; + default: ; // nothing to do, but suppresses warning of pedantic compilers + } + } + hintRequested = FALSE; + lastHint[0] = NULLCHAR; + bookRequested = FALSE; + /* Program may be pondering now */ + cps->maybeThinking = TRUE; + if (cps->sendTime == 2) cps->sendTime = 1; + if (cps->offeredDraw) cps->offeredDraw--; + + /* currentMoveString is set as a side-effect of ParseOneMove */ + strcpy(machineMove, currentMoveString); + strcat(machineMove, "\n"); + strcpy(moveList[forwardMostMove], machineMove); + + MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/ + + /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */ + if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) { + int count = 0; + + while( count < adjudicateLossPlies ) { + int score = pvInfoList[ forwardMostMove - count - 1 ].score; + + if( count & 1 ) { + score = -score; /* Flip score for winning side */ + } + + if( score > adjudicateLossThreshold ) { + break; + } + + count++; + } + + if( count >= adjudicateLossPlies ) { + ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ + + GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, + "Xboard adjudication", + GE_XBOARD ); + + return; + } } + if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends + +#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]; + + 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 + + /* [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) { /* [HGM] relaying draw offers moved to after reception of move */ @@ -6642,9 +7454,9 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. gameMode = EditGame; ModeHighlight(); } - currentMove = --forwardMostMove; + currentMove = forwardMostMove-1; DisplayMove(currentMove-1); /* before DisplayMoveError */ - SwitchClocks(); + SwitchClocks(forwardMostMove-1); // [HGM] race DisplayBothClocks(); sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"), parseList[currentMove], cps->which); @@ -7355,6 +8167,7 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board) Board board; { ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0; + int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1; /* [HGM] compute & store e.p. status and castling rights for new position */ /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */ @@ -7401,7 +8214,7 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board) } /* [HGM] In Shatranj and Courier all promotions are to Ferz */ - if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier) + if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk) && promoChar != 0) promoChar = PieceToChar(WhiteFerz); if (fromX == toX && fromY == toY) return; @@ -7452,7 +8265,7 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board) board[toY][toX+1] = board[fromY][BOARD_LEFT]; board[fromY][BOARD_LEFT] = EmptySquare; } else if (board[fromY][fromX] == WhitePawn - && toY == BOARD_HEIGHT-1 + && toY >= BOARD_HEIGHT-promoRank && gameInfo.variant != VariantXiangqi ) { /* white pawn promotion */ @@ -7516,13 +8329,13 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board) 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 */ @@ -7709,14 +8522,15 @@ MakeMove(fromX, fromY, toX, toY, promoChar) 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]); ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]); - forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board - SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it ! + // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board + SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside timeRemaining[0][forwardMostMove] = whiteTimeRemaining; timeRemaining[1][forwardMostMove] = blackTimeRemaining; gameInfo.result = GameUnfinished; @@ -8074,6 +8888,8 @@ GameEnds(result, resultDetails, whosays) result, resultDetails ? resultDetails : "(null)", whosays); } + fromX = fromY = -1; // [HGM] abort any move the user is entering. + if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) { /* If we are playing on ICS, the server decides when the game is over, but the engine can offer to draw, claim @@ -11305,9 +12121,20 @@ EditPositionMenuEvent(selection, x, y) case EmptySquare: if (gameMode == IcsExamining) { + if (x < BOARD_LEFT || x >= 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]); } @@ -11333,7 +12160,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; @@ -11346,12 +12174,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; } @@ -11512,6 +12356,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 && @@ -11837,9 +12682,9 @@ ToNrEvent(int to) } void -RevertEvent() +RevertEvent(Boolean annotate) { - if(PopTail(TRUE)) { // [HGM] vari: restore old game tail + if(PopTail(annotate)) { // [HGM] vari: restore old game tail return; } if (gameMode != IcsExamining) { @@ -12616,7 +13461,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; @@ -13308,12 +14153,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; @@ -13354,7 +14199,7 @@ DecrementClocks() from the color that is *not* on move now. */ void -SwitchClocks() +SwitchClocks(int newMoveNr) { long lastTickLength; TimeMark now; @@ -13364,7 +14209,7 @@ SwitchClocks() if (StopClockTimer() && appData.clockMode) { lastTickLength = SubtractTimeMarks(&now, &tickStartTM); - if (WhiteOnMove(forwardMostMove)) { + if (!WhiteOnMove(forwardMostMove)) { if(blackNPS >= 0) lastTickLength = 0; blackTimeRemaining -= lastTickLength; /* [HGM] PGNtime: save time for PGN file if engine did not give it */ @@ -13381,6 +14226,7 @@ SwitchClocks() } flagged = CheckFlags(); } + forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled CheckTimeControl(); if (flagged || !appData.clockMode) return; @@ -13743,7 +14589,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; @@ -13918,10 +14764,12 @@ ParseFEN(board, blackPlaysFirst, fen) } /* assume possible unless obviously impossible */ 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]] != WhiteKing) board[CASTLING][2] = 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]] != BlackKing) board[CASTLING][5] = 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++; @@ -13936,12 +14784,18 @@ ParseFEN(board, blackPlaysFirst, fen) (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) && ( *p >= '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--); @@ -13949,7 +14803,7 @@ ParseFEN(board, blackPlaysFirst, fen) board[CASTLING][2] = whiteKingFile; break; case'Q': - for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i>1, SavePart(parseList[i])); else sprintf(moveBuf, " %s", SavePart(parseList[i])); strcat(buf, moveBuf); + if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); } if(!--cnt) { strcat(buf, "\n"); cnt = 10; } } strcat(buf, ")"); } - for(i=1; i 0 && --level == 0 && p-text > index && end == NULL) end = p-1; + } + if(*p == wait) wait = NULLCHAR; // closing ]} found + p++; + } + if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click + if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start); + end[1] = NULLCHAR; // clip off comment beyond variation + ToNrEvent(currentMove-1); + PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game + // kludge: use ParsePV() to append variation to game + move = currentMove; + ParsePV(start, TRUE); + forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did + ClearPremoveHighlights(); + CommentPopDown(); + ToNrEvent(currentMove+1); +}