X-Git-Url: http://winboard.nl/cgi-bin?a=blobdiff_plain;f=backend.c;h=7a3c73160ae32ea3080d4e0b5eca3f27d98352c3;hb=32a64dc5d127fcec56f269a290c3740f2a31da51;hp=d8c5410abb167662e268ce5c7adf7d2f59c49585;hpb=993c315fcddbf1717850eb3a4fa3748f69050c4b;p=xboard.git diff --git a/backend.c b/backend.c index d8c5410..7a3c731 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 * @@ -190,7 +190,7 @@ 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)); @@ -242,6 +242,12 @@ 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 partnerBoardValid = 0; +char partnerStatus[MSG_SIZ]; +Boolean partnerUp; +Boolean originalFlip; +Boolean twoBoards = 0; 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 */ @@ -2064,6 +2070,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; @@ -2312,12 +2516,16 @@ read_from_ics(isr, closure, data, count, error) sprintf(str, "/set-quietly interface %s\n/set-quietly style 12\n", programVersion); + if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph + strcat(str, "/set-2 51 1\n/set seek 1\n"); } else if (ics_type == ICS_CHESSNET) { sprintf(str, "/style 12\n"); } else { strcpy(str, "alias $ @\n$set interface "); strcat(str, programVersion); strcat(str, "\n$iset startpos 1\n$iset ms 1\n"); + if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph + strcat(str, "$iset seekremove 1\n$set seek 1\n"); #ifdef WIN32 strcat(str, "$iset nohighlight 1\n"); #endif @@ -2338,6 +2546,7 @@ read_from_ics(isr, closure, data, count, error) sprintf(mess, "%s%s", talker, parse); OutputChatMessage(chattingPartner, mess); chattingPartner = -1; + next_out = i+1; // [HGM] suppress printing in ICS window } else if(!suppressKibitz) // [HGM] kibitz AppendComment(forwardMostMove, StripHighlight(parse), TRUE); @@ -2361,12 +2570,12 @@ 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); SendToPlayer(tmp, strlen(tmp)); } + next_out = i+1; // [HGM] suppress printing in ICS window } started = STARTED_NONE; } else { @@ -2382,6 +2591,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 */ @@ -2439,6 +2649,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] == ':') { @@ -2447,7 +2701,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 @@ -2456,6 +2709,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] @@ -2470,22 +2725,32 @@ read_from_ics(isr, closure, data, count, error) } continue; } else - if(looking_at(buf, &i, "kibitzed to *\n") && atoi(star_match[0])) { + 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])); - looking_at(buf, &i, "*% "); // eat prompt + 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; @@ -2493,27 +2758,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 @@ -2707,6 +2986,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; } @@ -2874,6 +3155,11 @@ 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; @@ -3069,10 +3355,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 @@ -3179,6 +3465,7 @@ read_from_ics(isr, closure, data, count, error) ZippyGameStart(whitename, blackname); } #endif /*ZIPPY*/ + partnerBoardValid = FALSE; // [HGM] bughouse continue; } @@ -3219,6 +3506,7 @@ read_from_ics(isr, closure, data, count, error) Reset(TRUE, TRUE); } #endif /*ZIPPY*/ + if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard); continue; } @@ -3315,8 +3603,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 @@ -3365,9 +3653,22 @@ 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(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw + if(partnerUp) DrawPosition(FALSE, partnerBoard); + if(twoBoards) { partnerUp = 0; flipView = !flipView; DrawPosition(TRUE, boards[currentMove]); } // [HGM] dual: redraw own + } } /* Suppress following prompt */ if (looking_at(buf, &i, "*% ")) { @@ -3517,6 +3818,28 @@ 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 */ + 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;; + } + } + if(appData.dualBoard) { twoBoards = partnerUp = 1; flipView = !flipView; InitDrawingSizes(-2,0); } // [HGM] dual + CopyBoard(partnerBoard, board); + if(partnerUp) DrawPosition(FALSE, partnerBoard); + if(twoBoards) { partnerUp = 0; flipView = !flipView; DrawPosition(TRUE, boards[currentMove]); } // [HGM] dual: redraw own game! + sprintf(partnerStatus, "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(partnerStatus, ""); + partnerBoardValid = TRUE; + return; + } + /* Modify behavior for initial board display on move listing of wild games. */ @@ -4035,7 +4358,7 @@ ParseBoard12(string) } } - + /* Display the board */ if (!pausing && !appData.noGUI) { @@ -4045,7 +4368,10 @@ 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 + if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view + 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)) || @@ -4250,6 +4576,73 @@ SendMoveToICS(moveType, fromX, fromY, toX, toY) } void +UploadGameEvent() +{ // [HGM] upload: send entire stored game to ICS as long-algebraic moves. + int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm + static char *castlingStrings[4] = { "none", "kside", "qside", "both" }; + if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) { + DisplayError("You cannot do this while you are playing or observing", 0); + return; + } + if(gameMode != IcsExamining) { // is this ever not the case? + char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0; + + if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant + sprintf(command, "match %s", ics_handle); + } else { // on FICS we must first go to general examine mode + strcpy(command, "examine\nbsetup"); // and specify variant within it with bsetups + } + if(gameInfo.variant != VariantNormal) { + // try figure out wild number, as xboard names are not always valid on ICS + for(i=1; i<=36; i++) { + sprintf(buf, "wild/%d", i); + if(StringToVariant(buf) == gameInfo.variant) break; + } + if(i<=36 && ics_type == ICS_ICC) sprintf(buf, "%s w%d\n", command, i); + else if(i == 22) sprintf(buf, "%s fr\n", command); + else sprintf(buf, "%s %s\n", command, VariantName(gameInfo.variant)); + } else sprintf(buf, "%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine + SendToICS(ics_prefix); + SendToICS(buf); + if(startedFromSetupPosition || backwardMostMove != 0) { + fen = PositionToFEN(backwardMostMove, NULL); + if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything + sprintf(buf, "loadfen %s\n", fen); + SendToICS(buf); + } else { // FICS: everything has to set by separate bsetup commands + p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board + sprintf(buf, "bsetup fen %s\n", fen); + SendToICS(buf); + if(!WhiteOnMove(backwardMostMove)) { + SendToICS("bsetup tomove black\n"); + } + i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL); + sprintf(buf, "bsetup wcastle %s\n", castlingStrings[i]); + SendToICS(buf); + i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL); + sprintf(buf, "bsetup bcastle %s\n", castlingStrings[i]); + SendToICS(buf); + i = boards[backwardMostMove][EP_STATUS]; + if(i >= 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; @@ -4465,7 +4878,13 @@ fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+ moveList[endPV-1][1] = fromY + ONE; moveList[endPV-1][2] = toX + AAA; moveList[endPV-1][3] = toY + ONE; - parseList[endPV-1][0] = NULLCHAR; + 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 @@ -4480,16 +4899,19 @@ 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; - index = startPV; - while(buf[index] && buf[index] != '\n') 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); + ParsePV(buf+startPV, FALSE); *start = startPV; *end = index-1; return TRUE; } @@ -4499,7 +4921,7 @@ 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. + ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array. return TRUE; } @@ -5087,6 +5509,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) { @@ -5157,7 +5581,7 @@ HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice) *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); @@ -5298,6 +5722,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; @@ -5741,6 +6223,12 @@ void LeftClick(ClickType clickType, int xPix, int yPix) static int second = 0, promotionChoice = 0; char promoChoice = NULLCHAR; + if(appData.seekGraph && appData.icsActive && loggedOn && + (gameMode == BeginningOfGame || gameMode == IcsIdle)) { + SeekGraphClick(clickType, xPix, yPix, 0); + return; + } + if (clickType == Press) ErrorPopDown(); MarkTargetSquares(1); @@ -5781,7 +6269,10 @@ void LeftClick(ClickType clickType, int xPix, int yPix) || x == BOARD_RGHT+1 && y >= 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)) { @@ -5796,6 +6287,7 @@ void LeftClick(ClickType clickType, int xPix, int yPix) } } return; + } } /* fromX != -1 */ @@ -5821,6 +6313,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 { @@ -5833,6 +6326,7 @@ void LeftClick(ClickType clickType, int xPix, int yPix) DragPieceBegin(xPix, yPix); } return; + } } // ignore clicks on holdings if(x < BOARD_LEFT || x >= BOARD_RGHT) return; @@ -5891,13 +6385,13 @@ void LeftClick(ClickType clickType, int xPix, int yPix) if(x == BOARD_LEFT-2 && piece >= BlackPawn) { n = PieceToNumber(piece - (int)BlackPawn); - if(n > gameInfo.holdingsSize) { n = 0; piece = 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; } + if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; } boards[currentMove][n][BOARD_WIDTH-1] = piece; boards[currentMove][n][BOARD_WIDTH-2]++; } @@ -5941,6 +6435,87 @@ 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(!partnerBoardValid) return -2; // suppress display of uninitialized boards + if( appData.dualBoard) return -2; // [HGM] dual: is already displayed + if(action == Press) { + originalFlip = flipView; + flipView = !flipView; // temporarily flip board to see game from partners perspective + DrawPosition(TRUE, partnerBoard); + DisplayMessage(partnerStatus, ""); + partnerUp = TRUE; + } else if(action == Release) { + flipView = originalFlip; + 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; @@ -6553,39 +7128,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) */ @@ -6619,6 +7166,40 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. 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 */ @@ -6895,9 +7476,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); @@ -7970,8 +8551,8 @@ MakeMove(fromX, fromY, toX, toY, promoChar) } 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; @@ -8323,12 +8904,15 @@ GameEnds(result, resultDetails, whosays) if(endingGame) return; /* [HGM] crash: forbid recursion */ endingGame = 1; + if(twoBoards) { twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0); } // [HGM] dual if (appData.debugMode) { fprintf(debugFP, "GameEnds(%d, %s, %d)\n", 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 @@ -11622,13 +12206,13 @@ EditPositionMenuEvent(selection, x, y) int n; if(x == BOARD_LEFT-2 && selection >= BlackPawn) { n = PieceToNumber(selection - BlackPawn); - if(n > gameInfo.holdingsSize) { n = 0; 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; } + if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; } boards[0][n][BOARD_WIDTH-1] = selection; boards[0][n][BOARD_WIDTH-2]++; } @@ -12121,9 +12705,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) { @@ -13638,7 +14222,7 @@ DecrementClocks() from the color that is *not* on move now. */ void -SwitchClocks() +SwitchClocks(int newMoveNr) { long lastTickLength; TimeMark now; @@ -13648,7 +14232,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 */ @@ -13665,6 +14249,7 @@ SwitchClocks() } flagged = CheckFlags(); } + forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled CheckTimeControl(); if (flagged || !appData.clockMode) return; @@ -14483,7 +15068,7 @@ PushTail(int firstMove, int lastMove) } storedGames++; - forwardMostMove = currentMove; // truncte game so we can start variation + forwardMostMove = firstMove; // truncate game so we can start variation if(storedGames == 1) GreyRevert(FALSE); } @@ -14495,6 +15080,7 @@ PopTail(Boolean annotate) if(appData.icsActive) return FALSE; // only in local mode if(!storedGames) return FALSE; // sanity + CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open storedGames--; ToNrEvent(savedFirst[storedGames]); // sets currentMove @@ -14508,11 +15094,12 @@ PopTail(Boolean annotate) sprintf(moveBuf, " %d. %s", i+2>>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); +}