X-Git-Url: http://winboard.nl/cgi-bin?a=blobdiff_plain;f=backend.c;h=95563f0c6a5967b29115ab45d6e0d64991c159f5;hb=6a5089a4fa20889fe9292d2187cf676fe2338303;hp=2438d49e0b09c5b8474f864680e197c0da375483;hpb=9b1fabe6a863409e11c421e6dd1fe674743cbd1c;p=xboard.git diff --git a/backend.c b/backend.c index 2438d49..95563f0 100644 --- a/backend.c +++ b/backend.c @@ -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,13 @@ 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 */ +int partnerHighlight[2]; +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 */ @@ -1101,6 +1108,11 @@ InitBackEnd3 P((void)) InitChessProgram(&first, startedFromSetupPosition); + if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */ + free(programVersion); + programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy)); + sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy); + } if (appData.icsActive) { #ifdef WIN32 @@ -1339,6 +1351,19 @@ establish() } } +void EscapeExpand(char *p, char *q) +{ // [HGM] initstring: routine to shape up string arguments + while(*p++ = *q++) if(p[-1] == '\\') + switch(*q++) { + case 'n': p[-1] = '\n'; break; + case 'r': p[-1] = '\r'; break; + case 't': p[-1] = '\t'; break; + case '\\': p[-1] = '\\'; break; + case 0: *p = 0; return; + default: p[-1] = q[-1]; break; + } +} + void show_bytes(fp, buf, count) FILE *fp; @@ -1971,7 +1996,7 @@ void VariantSwitch(Board board, VariantClass newVariant) { int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j; - Board oldBoard; + static Board oldBoard; startedFromPositionFile = FALSE; if(gameInfo.variant == newVariant) return; @@ -2087,7 +2112,7 @@ PlotSeekAd(int i) if(r > maxRating) r = maxRating; if(tc < 1.) tc = 1.; if(tc > 95.) tc = 95.; - x = (w-hMargin)* log(tc)/log(100.) + hMargin; + 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; @@ -2199,7 +2224,7 @@ DrawSeekGraph() } DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4); for(i=1; i<100; i+=(i<10?1:5)) { - int xx = (w-hMargin)* log((double)i)/log(100.) + hMargin; + int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin; DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks if(i<=5 || (i>40 ? i%20 : i%10) == 0) { char buf[MSG_SIZ]; @@ -2235,15 +2260,16 @@ int SeekGraphClick(ClickType click, int x, int y, int moving) DisplayMessage(second ? "!" : "", seekAdList[closest]); lastSecond = second; displayed = closest; } - sprintf(buf, "play %d\n", seekNrList[closest]); if(click == Press) { if(moving == 2) zList[closest] = 100; // right-click; push to back on press lastDown = closest; return TRUE; } // on press 'hit', only show info if(moving == 2) return TRUE; // ignore right up-clicks on dot + sprintf(buf, "play %d\n", seekNrList[closest]); SendToICS(ics_prefix); - SendToICS(buf); // should this be "sought all"? + 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 @@ -2539,6 +2565,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); @@ -2562,12 +2589,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 { @@ -2583,6 +2610,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 */ @@ -2640,10 +2668,12 @@ 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; @@ -2663,16 +2693,19 @@ read_from_ics(isr, closure, data, count, error) 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, "Ads removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) { + 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; } @@ -2687,15 +2720,16 @@ 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 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) { - if(looking_at(buf, &i, "* kibitzes: ") && + if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) && (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] @@ -2710,24 +2744,33 @@ 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, "* kibitzes:") || + 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; @@ -2736,26 +2779,56 @@ read_from_ics(isr, closure, data, count, error) for(p=0; p') {// shout, c-shout or it; look if there is a 'shouts' chatbox + if(buf[i-8] == '-' && buf[i-3] == 't') + 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 = 3 + chattingPartner; // counts as TRUE suppressKibitz = TRUE; + continue; } } // [HGM] chat: end of patch @@ -3318,10 +3391,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 @@ -3428,6 +3501,7 @@ read_from_ics(isr, closure, data, count, error) ZippyGameStart(whitename, blackname); } #endif /*ZIPPY*/ + partnerBoardValid = FALSE; // [HGM] bughouse continue; } @@ -3468,6 +3542,7 @@ read_from_ics(isr, closure, data, count, error) Reset(TRUE, TRUE); } #endif /*ZIPPY*/ + if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard); continue; } @@ -3564,8 +3639,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 @@ -3614,9 +3689,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; } + } } /* Suppress following prompt */ if (looking_at(buf, &i, "*% ")) { @@ -3766,6 +3854,38 @@ ParseBoard12(string) break; } + if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) + && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) { + // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */ + char *toSqr; + 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(toSqr = strchr(str, '/')) { // extract highlights from long move + partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board + partnerBoard[EP_STATUS-4] = toSqr[2] - ONE; + } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1; + if(toSqr = strchr(str, '-')) { + partnerBoard[EP_STATUS-1] = toSqr[1] - AAA; + partnerBoard[EP_STATUS-2] = toSqr[2] - ONE; + } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1; + if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); } + if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual + if(partnerUp) DrawPosition(FALSE, partnerBoard); + if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual + sprintf(partnerStatus, "W: %d:%02d B: %d:%02d (%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. */ @@ -4295,6 +4415,7 @@ ParseBoard12(string) ClearPremoveHighlights(); 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); @@ -4501,6 +4622,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; @@ -4716,7 +4924,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 @@ -4731,16 +4945,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; } @@ -4750,7 +4967,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; } @@ -5552,7 +5769,7 @@ OKToStartUserMove(x, y) } Boolean -OnlyMove(int *x, int *y) { +OnlyMove(int *x, int *y, Boolean captures) { DisambiguateClosure cl; if (appData.zippyPlay) return FALSE; switch(gameMode) { @@ -5576,6 +5793,7 @@ OnlyMove(int *x, int *y) { 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) { @@ -5594,6 +5812,7 @@ OnlyMove(int *x, int *y) { 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) { @@ -6047,7 +6266,7 @@ void LeftClick(ClickType clickType, int xPix, int yPix) { int x, y; Boolean saveAnimate; - static int second = 0, promotionChoice = 0; + static int second = 0, promotionChoice = 0, dragging = 0; char promoChoice = NULLCHAR; if(appData.seekGraph && appData.icsActive && loggedOn && @@ -6099,7 +6318,7 @@ void LeftClick(ClickType clickType, int xPix, int yPix) autoQueen = appData.alwaysPromoteToQueen; if (fromX == -1) { - if(!appData.oneClick || !OnlyMove(&x, &y)) { + if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) { if (clickType == Press) { /* First square */ if (OKToStartUserMove(x, y)) { @@ -6107,11 +6326,14 @@ void LeftClick(ClickType clickType, int xPix, int yPix) fromY = y; second = 0; MarkTargetSquares(0); - DragPieceBegin(xPix, yPix); + DragPieceBegin(xPix, yPix); dragging = 1; if (appData.highlightDragging) { SetHighlights(x, y, -1, -1); } } + } else if(dragging) { // [HGM] from-square must have been reset due to game end since last press + DragPieceEnd(xPix, yPix); dragging = 0; + DrawPosition(FALSE, NULL); } return; } @@ -6140,6 +6362,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 { @@ -6147,18 +6370,19 @@ void LeftClick(ClickType clickType, int xPix, int yPix) } if (OKToStartUserMove(x, y)) { fromX = x; - fromY = y; + fromY = y; dragging = 1; MarkTargetSquares(0); DragPieceBegin(xPix, yPix); } return; + } } // ignore clicks on holdings if(x < BOARD_LEFT || x >= BOARD_RGHT) return; } if (clickType == Release && x == fromX && y == fromY) { - DragPieceEnd(xPix, yPix); + DragPieceEnd(xPix, yPix); dragging = 0; if (appData.animateDragging) { /* Undo animation damage if any */ DrawPosition(FALSE, NULL); @@ -6196,7 +6420,7 @@ void LeftClick(ClickType clickType, int xPix, int yPix) } else { ClearHighlights(); } - DragPieceEnd(xPix, yPix); + DragPieceEnd(xPix, yPix); dragging = 0; /* Don't animate move and drag both */ appData.animate = FALSE; } @@ -6270,6 +6494,24 @@ int RightClick(ClickType action, int x, int y, int *fromX, int *fromY) 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 @@ -6398,8 +6640,10 @@ Adjudicate(ChessProgramState *cps) case BlackRook: NrBR++; break; case WhiteQueen: + case WhiteCannon: NrWQ++; break; case BlackQueen: + case BlackCannon: NrBQ++; break; case EmptySquare: break; @@ -6521,7 +6765,10 @@ Adjudicate(ChessProgramState *cps) if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 || - NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color + NrPieces == NrBB+NrWB+2 && bishopsColor != 3) // [HGM] all Bishops (Ferz!) same color + || gameInfo.variant == VariantXiangqi && + (NrPieces == 3 && (NrWQ==1 && NrWB==0 && NrBB<2 || NrBQ==1 && NrBB==0 && NrWB<2) || + NrPieces == 4 && NrWQ==1 && NrBQ==1 && NrWB==0 && NrBB==0)) { /* KBK, KNK, KK of KBKB with like Bishops */ /* always flag draws, for judging claims */ @@ -6546,7 +6793,7 @@ Adjudicate(ChessProgramState *cps) || NrWN==2 || NrBN==2 /* KNNK */ || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */ ) ) { - if(canAdjudicate && --moveCount < 0 && appData.trivialDraws) + if(--moveCount < 0 && appData.trivialDraws && canAdjudicate) { /* if the first 3 moves do not show a tactical win, declare draw */ if(engineOpponent) { SendToProgram("force\n", engineOpponent); // suppress reply @@ -6597,14 +6844,11 @@ Adjudicate(ChessProgramState *cps) boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] ) rights++; } - if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2 + if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate && appData.drawRepeats > 1) { /* adjudicate after user-specified nr of repeats */ - if(engineOpponent) { - SendToProgram("force\n", engineOpponent); // suppress reply - SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */ - } - ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ + int result = GameIsDrawn; + char *details = "XBoard adjudication: repetition draw"; if(gameInfo.variant == VariantXiangqi && appData.testLegality) { // [HGM] xiangqi: check for forbidden perpetuals int m, ourPerpetual = 1, hisPerpetual = 1; @@ -6617,27 +6861,31 @@ Adjudicate(ChessProgramState *cps) if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n", ourPerpetual, hisPerpetual); if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit - GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, - "Xboard adjudication: perpetual checking", GE_XBOARD ); - return 1; - } - if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet + result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; + details = "Xboard adjudication: perpetual checking"; + } else + if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet break; // (or we would have caught him before). Abort repetition-checking loop. + } else // Now check for perpetual chases if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase hisPerpetual = PerpetualChase(k, forwardMostMove); ourPerpetual = PerpetualChase(k+1, forwardMostMove); if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit - GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, - "Xboard adjudication: perpetual chasing", GE_XBOARD ); - return 1; - } + result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; + details = "Xboard adjudication: perpetual chasing"; + } else if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet break; // Abort repetition-checking loop. } // if neither of us is checking or chasing all the time, or both are, it is draw } - GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD ); + 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( result, details, GE_XBOARD ); return 1; } if( rights == 0 && count > 1 ) /* occurred 2 or more times before */ @@ -6654,6 +6902,17 @@ Adjudicate(ChessProgramState *cps) if( count == backwardMostMove ) count -= initialRulePlies; count = forwardMostMove - count; + if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) { + // adjust reversible move counter for checks in Xiangqi + int i = forwardMostMove - count, inCheck = 0, lastCheck; + if(i < backwardMostMove) i = backwardMostMove; + while(i <= forwardMostMove) { + lastCheck = inCheck; // check evasion does not count + inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK); + if(inCheck || lastCheck) count--; // check does not count + i++; + } + } if( count >= 100) boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW; /* this is used to judge if draw claims are legal */ @@ -6722,8 +6981,7 @@ char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial) // 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 + sprintf(buf, "%s%s\n", (cps->useUsermove ? "usermove " : ""), 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 @@ -6940,6 +7198,11 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h strcat(machineMove, "\n"); strcpy(moveList[forwardMostMove], machineMove); + /* [AS] Save move info*/ + pvInfoList[ forwardMostMove ].score = programStats.score; + pvInfoList[ forwardMostMove ].depth = programStats.depth; + pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats + MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/ /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */ @@ -6999,10 +7262,7 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. } #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 + /* [AS] Clear stats for next move */ ClearProgramStats(); thinkOutput[0] = NULLCHAR; hiddenThinkOutputState = 0; @@ -7108,11 +7368,13 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. * Look for communication commands */ if (!strncmp(message, "telluser ", 9)) { + EscapeExpand(message+9, message+9); // [HGM] esc: allow escape sequences in popup box DisplayNote(message + 9); return; } if (!strncmp(message, "tellusererror ", 14)) { cps->userError = 1; + EscapeExpand(message+14, message+14); // [HGM] esc: allow escape sequences in popup box DisplayError(message + 14, 0); return; } @@ -7283,9 +7545,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); @@ -7566,6 +7828,7 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. } if (!ignore) { + ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines buf1[0] = NULLCHAR; if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n", &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) { @@ -7587,11 +7850,11 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. } - programStats.depth = plylev; - programStats.nodes = nodes; - programStats.time = time; - programStats.score = curscore; - programStats.got_only_move = 0; + tempStats.depth = plylev; + tempStats.nodes = nodes; + tempStats.time = time; + tempStats.score = curscore; + tempStats.got_only_move = 0; if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */ int ticklen; @@ -7608,31 +7871,34 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. /* Buffer overflow protection */ if (buf1[0] != NULLCHAR) { - if (strlen(buf1) >= sizeof(programStats.movelist) + if (strlen(buf1) >= sizeof(tempStats.movelist) && appData.debugMode) { fprintf(debugFP, "PV is too long; using the first %u bytes.\n", - (unsigned) sizeof(programStats.movelist) - 1); + (unsigned) sizeof(tempStats.movelist) - 1); } - safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) ); + safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist) ); } else { - sprintf(programStats.movelist, " no PV\n"); + sprintf(tempStats.movelist, " no PV\n"); } - if (programStats.seen_stat) { - programStats.ok_to_send = 1; + if (tempStats.seen_stat) { + tempStats.ok_to_send = 1; } - if (strchr(programStats.movelist, '(') != NULL) { - programStats.line_is_book = 1; - programStats.nr_moves = 0; - programStats.moves_left = 0; + if (strchr(tempStats.movelist, '(') != NULL) { + tempStats.line_is_book = 1; + tempStats.nr_moves = 0; + tempStats.moves_left = 0; } else { - programStats.line_is_book = 0; + tempStats.line_is_book = 0; } - SendProgramStatsToFrontend( cps, &programStats ); + if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0) + programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line + + SendProgramStatsToFrontend( cps, &tempStats ); /* [AS] Protect the thinkOutput buffer from overflow... this @@ -8331,7 +8597,7 @@ MakeMove(fromX, fromY, toX, toY, promoChar) if( (boards[forwardMostMove][fromY][fromX] == WhitePawn || boards[forwardMostMove][fromY][fromX] == BlackPawn ) && boards[forwardMostMove][toY][toX] == EmptySquare - && fromX != toX ) + && fromX != toX && fromY != toY) fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY); // promotion suffix if(promoChar != NULLCHAR) @@ -8358,8 +8624,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; @@ -8711,12 +8977,17 @@ GameEnds(result, resultDetails, whosays) if(endingGame) return; /* [HGM] crash: forbid recursion */ endingGame = 1; - + if(twoBoards) { // [HGM] dual: switch back to one board + twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0); + DrawPosition(TRUE, partnerBoard); // observed game becomes foreground + } 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 @@ -9979,7 +10250,9 @@ LoadGame(f, gameNumber, title, useList) if (numPGNTags > 0){ char *tags; if (gameInfo.variant == VariantNormal) { - gameInfo.variant = StringToVariant(gameInfo.event); + VariantClass v = StringToVariant(gameInfo.event); + // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag + if(v < VariantShogi) gameInfo.variant = v; } if (!matchMode) { if( appData.autoDisplayTags ) { @@ -12509,9 +12782,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) { @@ -13166,8 +13439,10 @@ ReceiveFromProgram(isr, closure, message, count, error) sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 && sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 && sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 && - sscanf(message, "pong %c", &c)!=1 && start != '#') - { quote = "# "; print = (appData.engineComments == 2); } + sscanf(message, "pong %c", &c)!=1 && start != '#') { + quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### "; + print = (appData.engineComments >= 2); + } message[0] = start; // restore original message } if(print) { @@ -14026,7 +14301,7 @@ DecrementClocks() from the color that is *not* on move now. */ void -SwitchClocks() +SwitchClocks(int newMoveNr) { long lastTickLength; TimeMark now; @@ -14036,7 +14311,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 */ @@ -14053,6 +14328,7 @@ SwitchClocks() } flagged = CheckFlags(); } + forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled CheckTimeControl(); if (flagged || !appData.clockMode) return; @@ -14871,7 +15147,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); } @@ -14883,6 +15159,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 @@ -14896,11 +15173,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); +} +