X-Git-Url: http://winboard.nl/cgi-bin?a=blobdiff_plain;f=backend.c;h=0bbe876c9aeb5c2ba47a835fa3de4e6210c8cf9b;hb=b62cf5e6c96ca7a64c184b6218c18eb045142a7b;hp=40fb9ce5458e0df6f33160d7392a8b56aa0e1501;hpb=6db08230fafeb8a5fd163689e0225608bac64169;p=xboard.git diff --git a/backend.c b/backend.c old mode 100644 new mode 100755 index 40fb9ce..0bbe876 --- a/backend.c +++ b/backend.c @@ -77,6 +77,7 @@ #if STDC_HEADERS # include # include +# include #else /* not STDC_HEADERS */ # if HAVE_STRING_H # include @@ -147,11 +148,11 @@ void read_from_player P((InputSourceRef isr, VOIDSTAR closure, char *buf, int count, int error)); void read_from_ics P((InputSourceRef isr, VOIDSTAR closure, char *buf, int count, int error)); +void ics_printf P((char *format, ...)); void SendToICS P((char *s)); void SendToICSDelayed P((char *s, long msdelay)); void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY)); -void InitPosition P((int redraw)); void HandleMachineMove P((char *message, ChessProgramState *cps)); int AutoPlayOneMove P((void)); int LoadGameOneMove P((ChessMove readAhead)); @@ -183,7 +184,6 @@ void FeedMovesToProgram P((ChessProgramState *cps, int upto)); void ResurrectChessProgram P((void)); void DisplayComment P((int moveNumber, char *text)); void DisplayMove P((int moveNumber)); -void DisplayAnalysis P((void)); void ParseGameHistory P((char *game)); void ParseBoard12 P((char *string)); @@ -230,6 +230,7 @@ char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comm void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c 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]; extern int tinyLayout, smallLayout; @@ -242,6 +243,10 @@ int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */ int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */ int opponentKibitzes; +int lastSavedGame; /* [HGM] save: ID of game */ +char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */ +extern int chatCount; +int chattingPartner; /* States for ics_getting_history */ #define H_FALSE 0 @@ -296,26 +301,6 @@ static char * safeStrCpy( char * dst, const char * src, size_t count ) return dst; } -#if 0 -//[HGM] for future use? Conditioned out for now to suppress warning. -static char * safeStrCat( char * dst, const char * src, size_t count ) -{ - size_t dst_len; - - assert( dst != NULL ); - assert( src != NULL ); - assert( count > 0 ); - - dst_len = strlen(dst); - - assert( count > dst_len ); /* Buffer size must be greater than current length */ - - safeStrCpy( dst + dst_len, src, count - dst_len ); - - return dst; -} -#endif - /* Some compiler can't cast u64 to double * This function do the job for us: @@ -456,16 +441,17 @@ AppData appData; Board boards[MAX_MOVES]; /* [HGM] Following 7 needed for accurate legality tests: */ -char epStatus[MAX_MOVES]; -char castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1 -char castlingRank[BOARD_SIZE]; // and corresponding ranks -char initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE]; +signed char epStatus[MAX_MOVES]; +signed char castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1 +signed char castlingRank[BOARD_SIZE]; // and corresponding ranks +signed char initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE]; int nrCastlingRights; // For TwoKings, or to implement castling-unknown status int initialRulePlies, FENrulePlies; char FENepStatus; FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option) int loadFlag = 0; int shuffleOpenings; +int mute; // mute all sounds ChessSquare FIDEArray[2][BOARD_SIZE] = { { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, @@ -827,20 +813,9 @@ InitBackEnd1() programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING)); sprintf(programVersion, "%s", PACKAGE_STRING); } else { -#if 0 - char *p, *q; - q = first.program; - while (*q != ' ' && *q != NULLCHAR) q++; - p = q; - while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] backslash added */ - programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING + (q - p)); - sprintf(programVersion, "%s + ", PACKAGE_STRING); - strncat(programVersion, p, q - p); -#else - /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */ - programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy)); - sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy); -#endif + /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */ + programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy)); + sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy); } if (!appData.icsActive) { @@ -1006,71 +981,58 @@ ParseTimeControl(tc, ti, mps) int ti; int mps; { -#if 0 - int matched, min, sec; - - matched = sscanf(tc, "%d:%d", &min, &sec); - if (matched == 1) { - timeControl = min * 60 * 1000; - } else if (matched == 2) { - timeControl = (min * 60 + sec) * 1000; - } else { - return FALSE; - } -#else - long tc1; - long tc2; - char buf[MSG_SIZ]; - - if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0; - if(ti > 0) { - if(mps) - sprintf(buf, "+%d/%s+%d", mps, tc, ti); - else sprintf(buf, "+%s+%d", tc, ti); - } else { - if(mps) + long tc1; + long tc2; + char buf[MSG_SIZ]; + + if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0; + if(ti > 0) { + if(mps) + sprintf(buf, "+%d/%s+%d", mps, tc, ti); + else sprintf(buf, "+%s+%d", tc, ti); + } else { + if(mps) sprintf(buf, "+%d/%s", mps, tc); - else sprintf(buf, "+%s", tc); - } - fullTimeControlString = StrSave(buf); - - if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) { - return FALSE; - } - - if( *tc == '/' ) { - /* Parse second time control */ - tc++; - - if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) { - return FALSE; - } - - if( tc2 == 0 ) { - return FALSE; - } - - timeControl_2 = tc2 * 1000; - } - else { - timeControl_2 = 0; - } - - if( tc1 == 0 ) { - return FALSE; + else sprintf(buf, "+%s", tc); + } + fullTimeControlString = StrSave(buf); + + if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) { + return FALSE; + } + + if( *tc == '/' ) { + /* Parse second time control */ + tc++; + + if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) { + return FALSE; } - - timeControl = tc1 * 1000; -#endif - - if (ti >= 0) { - timeIncrement = ti * 1000; /* convert to ms */ - movesPerSession = 0; - } else { - timeIncrement = 0; - movesPerSession = mps; + + if( tc2 == 0 ) { + return FALSE; } - return TRUE; + + timeControl_2 = tc2 * 1000; + } + else { + timeControl_2 = 0; + } + + if( tc1 == 0 ) { + return FALSE; + } + + timeControl = tc1 * 1000; + + if (ti >= 0) { + timeIncrement = ti * 1000; /* convert to ms */ + movesPerSession = 0; + } else { + timeIncrement = 0; + movesPerSession = mps; + } + return TRUE; } void @@ -1452,6 +1414,26 @@ read_from_player(isr, closure, message, count, error) } void +KeepAlive() +{ // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out + SendToICS("date\n"); + if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000); +} + +/* added routine for printf style output to ics */ +void ics_printf(char *format, ...) +{ + char buffer[MSG_SIZ]; + va_list args; + + va_start(args, format); + vsnprintf(buffer, sizeof(buffer), format, args); + buffer[sizeof(buffer)-1] = '\0'; + SendToICS(buffer); + va_end(args); +} + +void SendToICS(s) char *s; { @@ -1569,6 +1551,10 @@ StringToVariant(e) while( *e++ != '_'); } + if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi + v = VariantNormal; + found = TRUE; + } else for (i=0; i gameInfo.holdingsWidth) { - for(i=0; i=BOARD_LEFT; j--) - board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] = - board[i][j]; - for(i=0; i gameInfo.holdingsWidth) { + for(i=0; i=BOARD_LEFT; j--) + board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] = + board[i][j]; + for(i=0; i= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' && - buf[buf_len-3]==' ' && buf[buf_len-2]==' ' && buf[buf_len-1]==' ') - buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous - } - + /* copy new characters into the buffer */ + bp = buf + leftover_len; + buf_len=leftover_len; + for (i=0; i=0) { + char mess[MSG_SIZ]; + sprintf(mess, "%s%s", talker, parse); + OutputChatMessage(chattingPartner, mess); + chattingPartner = -1; + } else if(!suppressKibitz) // [HGM] kibitz AppendComment(forwardMostMove, StripHighlight(parse)); else { // [HGM kibitz: divert memorized engine kibitz to engine-output window @@ -2396,6 +2424,45 @@ read_from_ics(isr, closure, data, count, error) } } // [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, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) || + looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) { + int p; + sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle + chattingPartner = -1; + + if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel + for(p=0; p= BOARD_RGHT || toY < 0 ) return ImpossibleMove; /* [HGM] moved to here from winboard.c */ - /* note: this code seems to exist for filtering out some obviously illegal premoves */ + /* note: capture of own piece can be legal as drag-drop premove. For click-click it is selection of new piece. */ pdown = boards[currentMove][fromY][fromX]; pup = boards[currentMove][toY][toX]; - if ( gameMode != EditPosition && + if ( gameMode != EditPosition && !captureOwn && (WhitePawn <= pdown && pdown < BlackPawn && WhitePawn <= pup && pup < BlackPawn || BlackPawn <= pdown && pdown < EmptySquare && BlackPawn <= pup && pup < EmptySquare ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) && (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0|| - pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 ) + pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 || + pup == WhiteKing && pdown == WhiteRook && fromY == 0 && toY == 0|| // also allow RxK + pup == BlackKing && pdown == BlackRook && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 ) ) ) - return ImpossibleMove; + return Comment; /* Check if the user is playing in turn. This is complicated because we let the user "pick up" a piece before it is his turn. So the piece he @@ -5160,7 +5200,6 @@ UserMoveTest(fromX, fromY, toX, toY, promoChar) moveType = LegalityTest(boards[currentMove], PosFlags(currentMove), epStatus[currentMove], castlingRights[currentMove], fromY, fromX, toY, toX, promoChar); - /* [HGM] but possibly ignore an IllegalMove result */ if (appData.testLegality) { if (moveType == IllegalMove || moveType == ImpossibleMove) { @@ -5376,9 +5415,11 @@ UserMoveEvent(fromX, fromY, toX, toY, promoChar) FinishMove if the first part succeeded. Calls that do not need to do anything in between, can call this routine the old way. */ - ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar); + ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE); if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar); - if(moveType != ImpossibleMove) + if(moveType == AmbiguousMove) + DrawPosition(FALSE, boards[currentMove]); + else if(moveType != ImpossibleMove && moveType != Comment) FinishMove(moveType, fromX, fromY, toX, toY, promoChar); } @@ -5644,14 +5685,15 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h 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, %.0f nodes, %1.0f knps) PV=%s\n", + sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n", programStats.score / 100., programStats.depth, programStats.time / 100., - u64ToDouble(programStats.nodes), - u64ToDouble(programStats.nodes) / (10*abs(programStats.time) + 1.), + (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 @@ -5895,16 +5937,16 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h } else moveCount = 6; } } -#if 1 - if (appData.debugMode) { int i; - fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n", - forwardMostMove, backwardMostMove, epStatus[backwardMostMove], - appData.drawRepeats); - for( i=forwardMostMove; i>=backwardMostMove; i-- ) - fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]); + + if (appData.debugMode) { int i; + fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n", + forwardMostMove, backwardMostMove, epStatus[backwardMostMove], + appData.drawRepeats); + for( i=forwardMostMove; i>=backwardMostMove; i-- ) + fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]); + + } - } -#endif /* Check for rep-draws */ count = 0; for(k = forwardMostMove-2; @@ -5913,17 +5955,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE; k-=2) { int rights=0; -#if 0 - if (appData.debugMode) { - fprintf(debugFP, " loop\n"); - } -#endif if(CompareBoards(boards[k], boards[forwardMostMove])) { -#if 0 - if (appData.debugMode) { - fprintf(debugFP, "match\n"); - } -#endif /* compare castling rights */ if( castlingRights[forwardMostMove][2] != castlingRights[k][2] && (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) ) @@ -5941,16 +5973,6 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h castlingRights[forwardMostMove][4] != castlingRights[k][4] ) rights++; } -#if 0 - if (appData.debugMode) { - for(i=0; i appData.drawRepeats-2 && appData.drawRepeats > 1) { /* adjudicate after user-specified nr of repeats */ @@ -6123,6 +6145,10 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h cps->useSigint = FALSE; cps->useSigterm = FALSE; } + if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands + ParseFeatures(message+8, cps); + return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands! + } /* [HGM] Allow engine to set up a position. Don't ask me why one would * want this, I was asked to put it in, and obliged. @@ -6227,9 +6253,6 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h return; } } - if (strncmp(message, "feature ", 8) == 0) { - ParseFeatures(message+8, cps); - } if (sscanf(message, "pong %d", &cps->lastPong) == 1) { return; } @@ -6301,17 +6324,30 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h if (gameMode == BeginningOfGame || gameMode == EndOfGame || gameMode == IcsIdle) return; if (forwardMostMove <= backwardMostMove) return; -#if 0 - /* Following removed: it caused a bug where a real illegal move - message in analyze mored would be ignored. */ - if (cps == &first && programStats.ok_to_send == 0) { - /* Bogus message from Crafty responding to "." This filtering - can miss some of the bad messages, but fortunately the bug - is fixed in current Crafty versions, so it doesn't matter. */ - return; - } -#endif if (pausing) PauseEvent(); + if(appData.forceIllegal) { + // [HGM] illegal: machine refused move; force position after move into it + SendToProgram("force\n", cps); + if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks. + // we have a real problem now, as SendBoard will use the a2a3 kludge + // when black is to move, while there might be nothing on a2 or black + // might already have the move. So send the board as if white has the move. + // But first we must change the stm of the engine, as it refused the last move + SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0] + if(WhiteOnMove(forwardMostMove)) { + SendToProgram("a7a6\n", cps); // for the engine black still had the move + SendBoard(cps, forwardMostMove); // kludgeless board + } else { + SendToProgram("a2a3\n", cps); // for the engine white still had the move + CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]); + SendBoard(cps, forwardMostMove+1); // kludgeless board + } + } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly + if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || + gameMode == TwoMachinesPlay) + SendToProgram("go\n", cps); + return; + } else if (gameMode == PlayFromGameFile) { /* Stop reading this game file */ gameMode = EditGame; @@ -6548,13 +6584,6 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h /* [HGM] in two-machine mode we delay relaying draw offer */ /* until after we also have move, to see if it is really claim */ } -#if 0 - else { - if (cps->other->sendDrawOffers) { - SendToProgram("draw\n", cps->other); - } - } -#endif } else if (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack) { if (userOfferedDraw) { @@ -6695,7 +6724,6 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h if (currentMove == forwardMostMove || gameMode == AnalyzeMode || gameMode == AnalyzeFile || appData.icsEngineAnalyze) { DisplayMove(currentMove - 1); - DisplayAnalysis(); } return; @@ -6723,7 +6751,6 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h if (currentMove == forwardMostMove || gameMode==AnalyzeMode || gameMode == AnalyzeFile || appData.icsEngineAnalyze) { DisplayMove(currentMove - 1); - DisplayAnalysis(); } return; } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s", @@ -6748,7 +6775,6 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h SendProgramStatsToFrontend( cps, &programStats ); - DisplayAnalysis(); return; } else if (strncmp(message,"++",2) == 0) { @@ -6784,7 +6810,6 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h if (currentMove == forwardMostMove || gameMode==AnalyzeMode || gameMode == AnalyzeFile || appData.icsEngineAnalyze) { DisplayMove(currentMove - 1); - DisplayAnalysis(); } return; } @@ -7387,9 +7412,6 @@ MakeMove(fromX, fromY, toX, toY, promoChar) 0, 1); return; } - SwitchClocks(); - timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining; - timeRemaining[1][forwardMostMove+1] = blackTimeRemaining; if (commentList[forwardMostMove+1] != NULL) { free(commentList[forwardMostMove+1]); commentList[forwardMostMove+1] = NULL; @@ -7399,6 +7421,9 @@ MakeMove(fromX, fromY, toX, toY, promoChar) ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1], castlingRights[forwardMostMove+1], &epStatus[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 ! + timeRemaining[0][forwardMostMove] = whiteTimeRemaining; + timeRemaining[1][forwardMostMove] = blackTimeRemaining; gameInfo.result = GameUnfinished; if (gameInfo.resultDetails != NULL) { free(gameInfo.resultDetails); @@ -7485,7 +7510,7 @@ void SendEgtPath(ChessProgramState *cps) while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string c = *r; *r = 0; // temporarily null-terminate path info *--q = 0; // strip of trailig ':' from name - sprintf(buf, "egtbpath %s %s\n", name+1, s); + sprintf(buf, "egtpath %s %s\n", name+1, s); *r = c; SendToProgram(buf,cps); // send egtbpath command for this format } @@ -7716,6 +7741,28 @@ void UserAdjudicationEvent( int result ) } +// [HGM] save: calculate checksum of game to make games easily identifiable +int StringCheckSum(char *s) +{ + int i = 0; + if(s==NULL) return 0; + while(*s) i = i*259 + *s++; + return i; +} + +int GameCheckSum() +{ + int i, sum=0; + for(i=backwardMostMove; i1 && sum==0) sum++; // make sure never zero for non-empty game + return sum + StringCheckSum(commentList[i]); +} // end of save patch + void GameEnds(result, resultDetails, whosays) ChessMove result; @@ -7865,7 +7912,9 @@ GameEnds(result, resultDetails, whosays) DisplayMove(currentMove - 1); if (forwardMostMove != 0) { - if (gameMode != PlayFromGameFile && gameMode != EditGame) { + if (gameMode != PlayFromGameFile && gameMode != EditGame + && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates + ) { if (*appData.saveGameFile != NULLCHAR) { SaveGameToFile(appData.saveGameFile, TRUE); } else if (appData.autoSaveGames) { @@ -8190,6 +8239,7 @@ Reset(redraw, init) gameMode = BeginningOfGame; ModeHighlight(); if(appData.icsActive) gameInfo.variant = VariantNormal; + currentMove = forwardMostMove = backwardMostMove = 0; InitPosition(redraw); for (i = 0; i < MAX_MOVES; i++) { if (commentList[i] != NULL) { @@ -8209,6 +8259,7 @@ Reset(redraw, init) DisplayTitle(""); DisplayMessage("", ""); HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1); + lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved } void @@ -9266,26 +9317,8 @@ LoadPosition(f, positionNumber, title) DisplayError(_("Position not found in file"), 0); return FALSE; } -#if 0 - switch (line[0]) { - case '#': case 'x': - default: - fenMode = FALSE; - break; - case 'p': case 'n': case 'b': case 'r': case 'q': case 'k': - case 'P': case 'N': case 'B': case 'R': case 'Q': case 'K': - case '1': case '2': case '3': case '4': case '5': case '6': - case '7': case '8': case '9': - case 'H': case 'A': case 'M': case 'h': case 'a': case 'm': - case 'E': case 'F': case 'G': case 'e': case 'f': case 'g': - case 'C': case 'W': case 'c': case 'w': - fenMode = TRUE; - break; - } -#else // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare; -#endif if (pn >= 2) { if (fenMode || line[0] == '#') pn--; @@ -9598,20 +9631,12 @@ SaveGamePGN(f) fprintf(f, " "); linelen++; } - fprintf(f, numtext); + fprintf(f, "%s", numtext); linelen += numlen; /* Get move */ - strcpy(move_buffer, parseList[i]); // [HGM] pgn: print move via buffer, so it can be edited + strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */ - if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) { - int p = movelen - 1; - if(move_buffer[p] == ' ') p--; - if(move_buffer[p] == ')') { // [HGM] pgn: strip off ICS time if we have extended info - while(p && move_buffer[--p] != '('); - if(p && move_buffer[p-1] == ' ') move_buffer[movelen=p-1] = 0; - } - } /* Print move */ blank = linelen > 0 && movelen > 0; @@ -9624,7 +9649,7 @@ SaveGamePGN(f) fprintf(f, " "); linelen++; } - fprintf(f, move_buffer); + fprintf(f, "%s", move_buffer); linelen += movelen; /* [AS] Add PV info if present */ @@ -9632,7 +9657,6 @@ SaveGamePGN(f) /* [HGM] add time */ char buf[MSG_SIZ]; int seconds = 0; -#if 1 if(i >= backwardMostMove) { if(WhiteOnMove(i)) seconds = timeRemaining[0][i] - timeRemaining[0][i+1] @@ -9642,9 +9666,6 @@ SaveGamePGN(f) + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds); } seconds = (seconds+50)/100; // deci-seconds, rounded to nearest -#else - seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time -#endif if( seconds <= 0) buf[0] = 0; else if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else { @@ -9672,7 +9693,7 @@ SaveGamePGN(f) fprintf(f, " "); linelen++; } - fprintf(f, move_buffer); + fprintf(f, "%s", move_buffer); linelen += movelen; } @@ -9697,6 +9718,7 @@ SaveGamePGN(f) } fclose(f); + lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving return TRUE; } @@ -9773,6 +9795,7 @@ SaveGame(f, dummy, dummy2) char *dummy2; { if (gameMode == EditPosition) EditPositionDone(); + lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving if (appData.oldSaveStyle) return SaveGameOldStyle(f); else @@ -10189,24 +10212,10 @@ ExitEvent(status) if (icsPR != NoProc) { DestroyChildProcess(icsPR, TRUE); } -#if 0 - /* Save game if resource set and not already saved by GameEnds() */ - if ((gameInfo.resultDetails == NULL || errorExitFlag ) - && forwardMostMove > 0) { - if (*appData.saveGameFile != NULLCHAR) { - SaveGameToFile(appData.saveGameFile, TRUE); - } else if (appData.autoSaveGames) { - AutoSaveGame(); - } - if (*appData.savePositionFile != NULLCHAR) { - SavePositionToFile(appData.savePositionFile); - } - } - GameEnds((ChessMove) 0, NULL, GE_PLAYER); -#else + /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */ GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER); -#endif + /* [HGM] crash: the above GameEnds() is a dud if another one was running */ /* make sure this other one finishes before killing it! */ if(endingGame) { int count = 0; @@ -10347,8 +10356,7 @@ AnalyzeModeEvent() first.analyzing = TRUE; /*first.maybeThinking = TRUE;*/ first.maybeThinking = FALSE; /* avoid killing GNU Chess */ - AnalysisPopUp(_("Analysis"), - _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis.")); + EngineOutputPopUp(); } if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode; pausing = FALSE; @@ -10374,8 +10382,7 @@ AnalyzeFileEvent() first.analyzing = TRUE; /*first.maybeThinking = TRUE;*/ first.maybeThinking = FALSE; /* avoid killing GNU Chess */ - AnalysisPopUp(_("Analysis"), - _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis.")); + EngineOutputPopUp(); } gameMode = AnalyzeFile; pausing = FALSE; @@ -10447,6 +10454,7 @@ MachineWhiteEvent() SetMachineThinkingEnables(); first.maybeThinking = TRUE; StartClocks(); + firstMove = FALSE; if (appData.autoFlipView && !flipView) { flipView = !flipView; @@ -10872,15 +10880,25 @@ ExitAnalyzeMode() SendToProgram("exit\n", &first); first.analyzing = FALSE; } - AnalysisPopDown(); thinkOutput[0] = NULLCHAR; } void EditPositionDone() { + int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing; + startedFromSetupPosition = TRUE; InitChessProgram(&first, FALSE); + castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1; + if(boards[0][0][BOARD_WIDTH>>1] == king) { + castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1; + castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1; + } else castlingRights[0][2] = -1; + if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) { + castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1; + castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1; + } else castlingRights[0][5] = -1; SendToProgram("force\n", &first); if (blackPlaysFirst) { strcpy(moveList[0], ""); @@ -12166,7 +12184,8 @@ ReceiveFromProgram(isr, closure, message, count, error) sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 && 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 && start != '#') + 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); } message[0] = start; // restore original message } @@ -12382,9 +12401,27 @@ ParseOption(Option *opt, ChessProgramState *cps) opt->min = min; opt->max = max; opt->type = Spin; - } else if(p = strstr(opt->name, " -string ")) { + } else if((p = strstr(opt->name, " -slider "))) { + // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots + if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE; + if(max < min) max = min; // enforce consistency + if(def < min) def = min; + if(def > max) def = max; + opt->value = def; + opt->min = min; + opt->max = max; + opt->type = Spin; // Slider; + } else if((p = strstr(opt->name, " -string "))) { opt->textValue = p+9; opt->type = TextBox; + } else if((p = strstr(opt->name, " -file "))) { + // for now -file is a synonym for -string, to already provide compatibility with future polyglots + opt->textValue = p+7; + opt->type = TextBox; // FileName; + } else if((p = strstr(opt->name, " -path "))) { + // for now -file is a synonym for -string, to already provide compatibility with future polyglots + opt->textValue = p+7; + opt->type = TextBox; // PathName; } else if(p = strstr(opt->name, " -check ")) { if(sscanf(p, " -check %d", &def) < 1) return FALSE; opt->value = (def != 0); @@ -12392,6 +12429,7 @@ ParseOption(Option *opt, ChessProgramState *cps) } else if(p = strstr(opt->name, " -combo ")) { opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices + if(*q == '*') cps->comboList[cps->comboCnt-1]++; opt->value = n = 0; while(q = StrStr(q, " /// ")) { n++; *q = 0; // count choices, and null-terminate each of them @@ -12497,7 +12535,11 @@ ParseFeatures(args, cps) if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue; if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue; if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) { - ParseOption(&(cps->option[cps->nrOptions++]), cps); // [HGM] options: add option feature + if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature + sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name); + SendToProgram(buf, cps); + continue; + } if(cps->nrOptions >= MAX_OPTIONS) { cps->nrOptions--; sprintf(buf, "%s engine has too many options\n", cps->which); @@ -12538,7 +12580,7 @@ PeriodicUpdatesEvent(newState) appData.periodicUpdates=newState; /* Display type changes, so update it now */ - DisplayAnalysis(); +// DisplayAnalysis(); /* Get the ball rolling again... */ if (newState) { @@ -12678,92 +12720,6 @@ DisplayMove(moveNumber) } void -DisplayAnalysisText(text) - char *text; -{ - char buf[MSG_SIZ]; - - if (gameMode == AnalyzeMode || gameMode == AnalyzeFile - || appData.icsEngineAnalyze) { - sprintf(buf, "Analysis (%s)", first.tidy); - AnalysisPopUp(buf, text); - } -} - -static int -only_one_move(str) - char *str; -{ - while (*str && isspace(*str)) ++str; - while (*str && !isspace(*str)) ++str; - if (!*str) return 1; - while (*str && isspace(*str)) ++str; - if (!*str) return 1; - return 0; -} - -void -DisplayAnalysis() -{ - char buf[MSG_SIZ]; - char lst[MSG_SIZ / 2]; - double nps; - static char *xtra[] = { "", " (--)", " (++)" }; - int h, m, s, cs; - - if (programStats.time == 0) { - programStats.time = 1; - } - - if (programStats.got_only_move) { - safeStrCpy(buf, programStats.movelist, sizeof(buf)); - } else { - safeStrCpy( lst, programStats.movelist, sizeof(lst)); - - nps = (u64ToDouble(programStats.nodes) / - ((double)programStats.time /100.0)); - - cs = programStats.time % 100; - s = programStats.time / 100; - h = (s / (60*60)); - s = s - h*60*60; - m = (s/60); - s = s - m*60; - - if (programStats.moves_left > 0 && appData.periodicUpdates) { - if (programStats.move_name[0] != NULLCHAR) { - sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d", - programStats.depth, - programStats.nr_moves-programStats.moves_left, - programStats.nr_moves, programStats.move_name, - ((float)programStats.score)/100.0, lst, - only_one_move(lst)? - xtra[programStats.got_fail] : "", - (u64)programStats.nodes, (int)nps, h, m, s, cs); - } else { - sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d", - programStats.depth, - programStats.nr_moves-programStats.moves_left, - programStats.nr_moves, ((float)programStats.score)/100.0, - lst, - only_one_move(lst)? - xtra[programStats.got_fail] : "", - (u64)programStats.nodes, (int)nps, h, m, s, cs); - } - } else { - sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d", - programStats.depth, - ((float)programStats.score)/100.0, - lst, - only_one_move(lst)? - xtra[programStats.got_fail] : "", - (u64)programStats.nodes, (int)nps, h, m, s, cs); - } - } - DisplayAnalysisText(buf); -} - -void DisplayComment(moveNumber, text) int moveNumber; char *text; @@ -13489,6 +13445,14 @@ PositionToFEN(move, overrideCastling) } else { *p++ = '-'; } + } else if(move == backwardMostMove) { + // [HGM] perhaps we should always do it like this, and forget the above? + if(epStatus[move] >= 0) { + *p++ = epStatus[move] + AAA; + *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3'; + } else { + *p++ = '-'; + } } else { *p++ = '-'; }