X-Git-Url: http://winboard.nl/cgi-bin?a=blobdiff_plain;f=backend.c;h=f7c75b68b2634426b6ffbfa54a7199cd4d2f2cd5;hb=e10b34662822bd8dc06680b6b9977966816450de;hp=424463dad2eca1e8477a821e39fb78ca9b85c765;hpb=60876535e02fb87e638a0aa94aa80490093099fc;p=xboard.git diff --git a/backend.c b/backend.c index 424463d..f7c75b6 100644 --- a/backend.c +++ b/backend.c @@ -132,8 +132,13 @@ extern int gettimeofday(struct timeval *, struct timezone *); # define _(s) gettext (s) # define N_(s) gettext_noop (s) #else -# define _(s) (s) -# define N_(s) s +# ifdef WIN32 +# define _(s) T_(s) +# define N_(s) s +# else +# define _(s) (s) +# define N_(s) s +# endif #endif @@ -234,6 +239,7 @@ char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book void ics_update_width P((int new_width)); extern char installDir[MSG_SIZ]; +VariantClass startVariant; /* [HGM] nicks: initial variant */ extern int tinyLayout, smallLayout; ChessProgramStats programStats; @@ -243,7 +249,12 @@ 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 */ @@ -632,6 +643,7 @@ InitBackEnd1() int matched, min, sec; ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options + startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant GetTimeMark(&programStartTime); srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level @@ -707,8 +719,8 @@ InitBackEnd1() /* [AS] Adjudication threshold */ adjudicateLossThreshold = appData.adjudicateLossThreshold; - first.which = "first"; - second.which = "second"; + first.which = _("first"); + second.which = _("second"); first.maybeThinking = second.maybeThinking = FALSE; first.pr = second.pr = NoProc; first.isr = second.isr = NULL; @@ -1103,6 +1115,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 @@ -1341,6 +1358,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; @@ -1973,7 +2003,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; @@ -2181,8 +2211,8 @@ MatchSoughtLine(char *line) int DrawSeekGraph() { - if(!seekGraphUp) return FALSE; int i; + if(!seekGraphUp) return FALSE; h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap; w = BOARD_WIDTH * (squareSize + lineGap) + lineGap; @@ -2701,7 +2731,7 @@ read_from_ics(isr, closure, data, count, error) 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; @@ -2740,6 +2770,7 @@ read_from_ics(isr, closure, data, count, error) 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, "--> * ") || @@ -2759,6 +2790,13 @@ read_from_ics(isr, closure, data, count, error) chattingPartner = p; break; } } else + if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox + for(p=0; p') // shout, c-shout or it; look if there is a 'shouts' chatbox - 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 background @@ -3659,7 +3699,9 @@ read_from_ics(isr, closure, data, count, error) /* [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 */ @@ -3811,9 +3853,9 @@ ParseBoard12(string) } if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) - && newGameMode == IcsObserving && appData.bgObserve) { + && 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 buf[MSG_SIZ]; + 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]); @@ -3823,10 +3865,22 @@ ParseBoard12(string) } } 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); - sprintf(buf, "W: %d:%d B: %d:%d (%d-%d) %c", white_time/60000, (white_time%60000)/1000, + 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(buf, ""); + DisplayMessage(partnerStatus, ""); + partnerBoardValid = TRUE; return; } @@ -4359,6 +4413,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); @@ -4473,7 +4528,7 @@ SendMoveToProgram(moveNum, cps) /* Send 'go' if we are in a mode where machine should play. */ if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) && (gameMode == TwoMachinesPlay || -#ifdef ZIPPY +#if ZIPPY gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite || #endif gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) { @@ -4715,6 +4770,8 @@ AlphaRank(char *move, int n) } } +char yy_textstr[8000]; + /* Parser for moves from gnuchess, ICS, or user typein box */ Boolean ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar) @@ -4727,7 +4784,7 @@ ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar) if (appData.debugMode) { fprintf(debugFP, "move to parse: %s\n", move); } - *moveType = yylexstr(moveNum, move); + *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr); switch (*moveType) { case WhitePromotionChancellor: @@ -4821,15 +4878,15 @@ ParsePV(char *pv, Boolean storeComments) int fromX, fromY, toX, toY; char promoChar; ChessMove moveType; Boolean valid; - int nr = 0, dummy; + int nr = 0; endPV = forwardMostMove; do { - while(*pv == ' ') pv++; - if(nr == 0 && *pv == '(') pv++; // first (ponder) move can be in parentheses + while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace + if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar); if(appData.debugMode){ -fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv); +fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, yy_textstr, pv); } if(!valid && nr == 0 && ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ @@ -4848,30 +4905,13 @@ fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+ strcpy(moveList[endPV-2], "_0_0"); // suppress premove highlight on takeback move } } - if(moveType == Comment) { - // [HGM] vari: try to skip comment - int level = 0; char c, *start = pv, wait = NULLCHAR; - do { - if(!wait) { - if(*pv == '(') level++; else - if(*pv == ')' && level) level--; - } - if(*pv == wait) wait = NULLCHAR; else - if(*pv == '{') wait = '}'; else - if(*pv == '[') wait = ']'; - pv++; - } while(*pv && (wait || level)); - if(storeComments) { - c = *pv; *pv = NULLCHAR; - AppendComment(endPV, start, FALSE); - *pv = c; - } - valid++; // allow comments in PV + pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed + if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move; + if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE); + if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) { + valid++; // allow comments in PV continue; } - if(sscanf(pv, "%d...", &dummy) == 1 || sscanf(pv, "%d.", &dummy) == 1) - while(*pv && *pv++ != ' '); // skip any move numbers - while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators nr++; if(endPV+1 > framePtr) break; // no space, truncate if(!valid) break; @@ -4903,14 +4943,17 @@ 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, FALSE); *start = startPV; *end = index-1; @@ -5215,7 +5258,10 @@ InitPosition(redraw) for(i=0; i 2) return FALSE; // no trivial draws with more than 1 major + if(myPawns == 2 && nMine == 3) // KPP + return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3; + if(myPawns == 1 && nMine == 2) // KP + return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1; + if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP + return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5; + if(myPawns) return FALSE; + if(pCnt[WhiteRook+side]) + return pCnt[BlackRook-side] || + pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) || + pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 || + pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4; + if(pCnt[WhiteCannon+side]) { + if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform + return majorDefense || pCnt[BlackAlfil-side] >= 2; + } + if(pCnt[WhiteKnight+side]) + return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1; + return FALSE; +} + +int +MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor) +{ + VariantClass v = gameInfo.variant; + + if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable + if(v == VariantShatranj) return TRUE; // always winnable through baring + if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE; + if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King + + if(v == VariantXiangqi) { + int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side]; + + nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns + if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!) + if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC + if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R + // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P + if(stale) // we have at least one last-rank P plus perhaps C + return majors // KPKX + || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA + else // KCA*E* + return pCnt[WhiteFerz+side] // KCAK + || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H) + || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX + // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon + + } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings + int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side]; + + if(nMine == 1) return FALSE; // bare King + if(nBishops && bisColor == 3) return TRUE; // There must be a second B/A/F, which can either block (his) or attack (mine) the escape square + nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one + if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils + // by now we have King + 1 piece (or multiple Bishops on the same color) + if(pCnt[WhiteKnight+side]) + return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] + + pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS + || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA) + if(nBishops) + return (pCnt[BlackKnight-side]); // KBKN, KFKN + if(pCnt[WhiteAlfil+side]) + return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates + if(pCnt[WhiteWazir+side]) + return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE + } + + return TRUE; +} + int Adjudicate(ChessProgramState *cps) { // [HGM] some adjudications useful with buggy engines @@ -6547,61 +6704,16 @@ Adjudicate(ChessProgramState *cps) if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { if( appData.testLegality ) { /* [HGM] Some more adjudications for obstinate engines */ - int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0, - NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0, - NrPieces=0, NrPawns=0, PawnAdvance=0, i, j; + int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i; static int moveCount = 6; ChessMove result; char *reason = NULL; /* Count what is on board. */ - for(i=0; i 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, + GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn, "Xboard adjudication: Bare king", GE_XBOARD ); return 1; } @@ -6669,8 +6781,8 @@ Adjudicate(ChessProgramState *cps) if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers: boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win else if(gameInfo.variant == VariantSuicide) // in suicide it depends - boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE : - ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ? + boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE : + ((nrW < nrB) != WhiteOnMove(forwardMostMove) ? EP_CHECKMATE : EP_WINS); else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi) boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses @@ -6701,11 +6813,9 @@ Adjudicate(ChessProgramState *cps) } /* Next absolutely insufficient mating material. */ - if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && - gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible - (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 || - NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color - { /* KBK, KNK, KK of KBKB with like Bishops */ + if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) && + !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor)) + { /* includes KBK, KNK, KK of KBKB with like Bishops */ /* always flag draws, for judging claims */ boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW; @@ -6723,13 +6833,15 @@ Adjudicate(ChessProgramState *cps) } /* Then some trivial draws (only adjudicate, cannot be claimed) */ - if(NrPieces == 4 && - ( NrWR == 1 && NrBR == 1 /* KRKR */ - || NrWQ==1 && NrBQ==1 /* KQKQ */ - || NrWN==2 || NrBN==2 /* KNNK */ - || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */ - ) ) { - if(canAdjudicate && --moveCount < 0 && appData.trivialDraws) + if(gameInfo.variant == VariantXiangqi ? + SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW) + : nrW + nrB == 4 && + ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */ + || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */ + || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */ + || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */ + ) ) { + 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 @@ -6780,14 +6892,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; @@ -6800,27 +6909,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 */ @@ -6837,6 +6950,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 */ @@ -6905,8 +7029,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 @@ -7123,6 +7246,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) */ @@ -7182,10 +7310,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; @@ -7291,11 +7416,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; } @@ -7749,6 +7876,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) { @@ -7770,11 +7898,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; @@ -7791,31 +7919,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 @@ -8514,7 +8645,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) @@ -8890,11 +9021,14 @@ GameEnds(result, resultDetails, whosays) { GameMode nextGameMode; int isIcsGame; - char buf[MSG_SIZ]; + char buf[MSG_SIZ], popupRequested = 0; 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); @@ -9223,13 +9357,12 @@ GameEnds(result, resultDetails, whosays) endingGame = 0; /* [HGM] crash */ return; } else { - char buf[MSG_SIZ]; gameMode = nextGameMode; sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"), first.tidy, second.tidy, first.matchWins, second.matchWins, appData.matchGames - (first.matchWins + second.matchWins)); - DisplayFatalError(buf, 0, 0); + popupRequested++; // [HGM] crash: postpone to after resetting endingGame } } if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && @@ -9238,6 +9371,12 @@ GameEnds(result, resultDetails, whosays) gameMode = nextGameMode; ModeHighlight(); endingGame = 0; /* [HGM] crash */ + if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion. + if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else { + matchMode = FALSE; appData.matchGames = matchGame = 0; + DisplayNote(buf); + } + } } /* Assumes program was just initialized (initString sent). @@ -10102,6 +10241,7 @@ LoadGame(f, gameNumber, title, useList) /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */ if(gameInfo.variant != oldVariant) { startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */ + ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-( InitPosition(TRUE); oldVariant = gameInfo.variant; if (appData.debugMode) @@ -10164,7 +10304,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 ) { @@ -13351,8 +13493,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) { @@ -13713,7 +13857,6 @@ ParseFeatures(args, cps) } continue; } - if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue; /* End of additions by HGM */ /* unknown feature: complain and skip */ @@ -13777,17 +13920,17 @@ PonderNextMoveEvent(newState) } void -NewSettingEvent(option, command, value) +NewSettingEvent(option, feature, command, value) char *command; - int option, value; + int option, value, *feature; { char buf[MSG_SIZ]; if (gameMode == EditPosition) EditPositionDone(TRUE); sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value); - SendToProgram(buf, &first); + if(feature == NULL || *feature) SendToProgram(buf, &first); if (gameMode == TwoMachinesPlay) { - SendToProgram(buf, &second); + if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second); } } @@ -13869,7 +14012,7 @@ DisplayMove(moveNumber) sprintf(res, " %s", PGNResult(gameInfo.result)); } else { sprintf(res, " {%s} %s", - gameInfo.resultDetails, PGNResult(gameInfo.result)); + T_(gameInfo.resultDetails), PGNResult(gameInfo.result)); } } else { res[0] = NULLCHAR; @@ -14661,7 +14804,7 @@ ParseFEN(board, blackPlaysFirst, fen) char *fen; { int i, j; - char *p; + char *p, c; int emptycount; ChessSquare piece; @@ -14754,7 +14897,12 @@ ParseFEN(board, blackPlaysFirst, fen) while(*p == ' ') p++; /* Active color */ - switch (*p++) { + c = *p++; + if(appData.colorNickNames) { + if( c == appData.colorNickNames[0] ) c = 'w'; else + if( c == appData.colorNickNames[1] ) c = 'b'; + } + switch (c) { case 'w': *blackPlaysFirst = FALSE; break; @@ -15161,3 +15309,4 @@ LoadVariation(int index, char *text) CommentPopDown(); ToNrEvent(currentMove+1); } +