X-Git-Url: http://winboard.nl/cgi-bin?a=blobdiff_plain;f=backend.c;h=5fbe8f01e662d5a98369cbc4e398837bcd7cd383;hb=29df666d997ad990de5376c962b154b26eb73f2f;hp=caff1c132398bd7b9f5ecbacd6b73ab12790a3c0;hpb=05bc30b15e31c427ce208495a889e9ff36e6642b;p=xboard.git diff --git a/backend.c b/backend.c index caff1c1..5fbe8f0 100644 --- a/backend.c +++ b/backend.c @@ -1,6 +1,6 @@ /* * backend.c -- Common back end for X and Windows NT versions of - * XBoard $Id$ + * XBoard $Id: backend.c,v 2.6 2003/11/28 09:37:36 mann Exp $ * * Copyright 1991 by Digital Equipment Corporation, Maynard, Massachusetts. * Enhancements Copyright 1992-2001 Free Software Foundation, Inc. @@ -47,8 +47,21 @@ * * See the file ChangeLog for a revision history. */ +/* [AS] Also useful here for debugging */ +#ifdef WIN32 +#include + +#define DoSleep( n ) if( (n) != 0 ) Sleep( (n) ); + +#else + +#define DoSleep( n ) + +#endif + #include "config.h" +#include #include #include #include @@ -116,7 +129,7 @@ typedef struct { /* Search stats from chessprogram */ typedef struct { - char movelist[MSG_SIZ]; /* Last PV we were sent */ + char movelist[2*MSG_SIZ]; /* Last PV we were sent */ int depth; /* Current search depth */ int nr_moves; /* Total nr of root moves */ int moves_left; /* Moves remaining to be searched */ @@ -203,6 +216,8 @@ void InitBackEnd3 P((void)); void FeatureDone P((ChessProgramState* cps, int val)); void InitChessProgram P((ChessProgramState *cps)); +void GetInfoFromComment( int, char * ); + extern int tinyLayout, smallLayout; static ChessProgramStats programStats; @@ -245,6 +260,35 @@ static ChessProgramStats programStats; #define TN_SGA 0003 #define TN_PORT 23 +/* [AS] */ +static char * safeStrCpy( char * dst, const char * src, size_t count ) +{ + assert( dst != NULL ); + assert( src != NULL ); + assert( count > 0 ); + + strncpy( dst, src, count ); + dst[ count-1 ] = '\0'; + return dst; +} + +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; +} + /* Fake up flags for now, as we aren't keeping track of castling availability yet */ int @@ -275,6 +319,15 @@ PosFlags(index) FILE *gameFileFP, *debugFP; +/* + [AS] Note: sometimes, the sscanf() function is used to parse the input + into a fixed-size buffer. Because of this, we must be prepared to + receive strings as long as the size of the input buffer, which is currently + set to 4K for Windows and 8K for the rest. + So, we must either allocate sufficiently large buffers here, or + reduce the size of the input buffer in the input reading part. +*/ + char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ]; char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ]; char thinkOutput1[MSG_SIZ*10]; @@ -317,12 +370,17 @@ InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL; GameMode gameMode = BeginningOfGame; char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2]; char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES]; +ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */ +int hiddenThinkOutputState = 0; /* [AS] */ +int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */ +int adjudicateLossPlies = 6; char white_holding[64], black_holding[64]; TimeMark lastNodeCountTime; long lastNodeCount=0; int have_sent_ICS_logon = 0; int movesPerSession; long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement; +long timeControl_2; /* [AS] Allow separate time controls */ long timeRemaining[2][MAX_MOVES]; int matchGame = 0; TimeMark programStartTime; @@ -446,6 +504,15 @@ InitBackEnd1() appData.zippyTalk = appData.zippyPlay = FALSE; } + /* [AS] Initialize pv info list */ + { + int i; + + for( i=0; i PROTOVER || appData.firstProtocolVersion < 1) { char buf[MSG_SIZ]; @@ -586,7 +668,7 @@ InitBackEnd1() switch (variant) { case VariantBughouse: /* need four players and two boards */ case VariantKriegspiel: /* need to hide pieces and move details */ - case VariantFischeRandom: /* castling doesn't work, shuffle not done */ + /* case VariantFischeRandom: (Fabien: moved below) */ sprintf(buf, "Variant %s supported only in ICS mode", appData.variant); DisplayFatalError(buf, 0, 2); return; @@ -609,6 +691,7 @@ InitBackEnd1() case VariantNormal: /* definitely works! */ case VariantWildCastle: /* pieces not automatically shuffled */ case VariantNoCastle: /* pieces not automatically shuffled */ + case VariantFischeRandom: /* Fabien: pieces not automatically shuffled */ case VariantCrazyhouse: /* holdings not shown, offboard interposition not understood */ case VariantLosers: /* should work except for win condition, @@ -626,12 +709,73 @@ InitBackEnd1() } } +int NextIntegerFromString( char ** str, long * value ) +{ + int result = -1; + char * s = *str; + + while( *s == ' ' || *s == '\t' ) { + s++; + } + + *value = 0; + + if( *s >= '0' && *s <= '9' ) { + while( *s >= '0' && *s <= '9' ) { + *value = *value * 10 + (*s - '0'); + s++; + } + + result = 0; + } + + *str = s; + + return result; +} + +int NextTimeControlFromString( char ** str, long * value ) +{ + long temp; + int result = NextIntegerFromString( str, &temp ); + + if( result == 0 ) { + *value = temp * 60; /* Minutes */ + if( **str == ':' ) { + (*str)++; + result = NextIntegerFromString( str, &temp ); + *value += temp; /* Seconds */ + } + } + + return result; +} + +int GetTimeControlForWhite() +{ + int result = timeControl; + + return result; +} + +int GetTimeControlForBlack() +{ + int result = timeControl; + + if( timeControl_2 > 0 ) { + result = timeControl_2; + } + + return result; +} + int ParseTimeControl(tc, ti, mps) char *tc; int ti; int mps; { +#if 0 int matched, min, sec; matched = sscanf(tc, "%d:%d", &min, &sec); @@ -642,6 +786,38 @@ ParseTimeControl(tc, ti, mps) } else { return FALSE; } +#else + long tc1; + long tc2; + + 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; + } + + timeControl = tc1 * 1000; +#endif if (ti >= 0) { timeIncrement = ti * 1000; /* convert to ms */ @@ -1129,7 +1305,7 @@ StringToVariant(e) } else if ((i = 4, p = StrCaseStr(e, "wild")) || (i = 1, p = StrCaseStr(e, "w"))) { p += i; - while (*p && (isspace(*p) || *p == '(')) p++; + while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++; if (isdigit(*p)) { wnum = atoi(p); } else { @@ -1422,6 +1598,8 @@ static int player1Rating = -1; static int player2Rating = -1; /*----------------------------*/ +ColorClass curColor = ColorNormal; + void read_from_ics(isr, closure, data, count, error) InputSourceRef isr; @@ -1445,7 +1623,6 @@ read_from_ics(isr, closure, data, count, error) static int parse_pos = 0; static char buf[BUF_SIZE + 1]; static int firstTime = TRUE, intfSet = FALSE; - static ColorClass curColor = ColorNormal; static ColorClass prevColor = ColorNormal; static int savingComment = FALSE; char str[500]; @@ -1455,6 +1632,16 @@ read_from_ics(isr, closure, data, count, error) int tkind; char *p; +#ifdef WIN32 + if (appData.debugMode) { + if (!error) { + fprintf(debugFP, " 0) { /* If last read ended with a partial line that we couldn't parse, prepend it to the new read and try again. */ @@ -2301,7 +2488,9 @@ read_from_ics(isr, closure, data, count, error) } /* Improved generic start/end-of-game messages */ - if (looking_at(buf, &i, "{Game * (* vs. *) *}*")) { + if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) || + (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){ + /* If tkind == 0: */ /* star_match[0] is the game number */ /* [1] is the white player's name */ /* [2] is the black player's name */ @@ -2312,10 +2501,21 @@ read_from_ics(isr, closure, data, count, error) /* [3] begins with "Creating" or "Continuing" */ /* [4] is " *" or empty (don't care). */ int gamenum = atoi(star_match[0]); - char *why = star_match[3]; - char *endtoken = star_match[4]; + char *whitename, *blackname, *why, *endtoken; ChessMove endtype = (ChessMove) 0; + if (tkind == 0) { + whitename = star_match[1]; + blackname = star_match[2]; + why = star_match[3]; + endtoken = star_match[4]; + } else { + whitename = star_match[1]; + blackname = star_match[3]; + why = star_match[5]; + endtoken = star_match[6]; + } + /* Game start messages */ if (strncmp(why, "Creating ", 9) == 0 || strncmp(why, "Continuing ", 11) == 0) { @@ -2323,7 +2523,7 @@ read_from_ics(isr, closure, data, count, error) strcpy(gs_kind, strchr(why, ' ') + 1); #if ZIPPY if (appData.zippyPlay) { - ZippyGameStart(star_match[1], star_match[2]); + ZippyGameStart(whitename, blackname); } #endif /*ZIPPY*/ continue; @@ -2702,10 +2902,12 @@ ParseBoard12(string) gameInfo.white = StrSave(white); gameInfo.black = StrSave(black); timeControl = basetime * 60 * 1000; + timeControl_2 = 0; timeIncrement = increment * 1000; movesPerSession = 0; gameInfo.timeControl = TimeControlTagValue(); gameInfo.variant = StringToVariant(gameInfo.event); + gameInfo.outOfBook = NULL; /* Do we have the ratings? */ if (strcmp(player1Name, white) == 0 && @@ -3041,7 +3243,25 @@ SendMoveToProgram(moveNum, cps) } SendToProgram(buf, cps); } else { - SendToProgram(moveList[moveNum], cps); + /* Added by Tord: Send castle moves in "O-O" in FRC games if required by + * the engine. It would be nice to have a better way to identify castle + * moves here. */ + if(gameInfo.variant == VariantFischeRandom && cps->useOOCastle) { + int fromX = moveList[moveNum][0] - 'a'; + int fromY = moveList[moveNum][1] - '1'; + int toX = moveList[moveNum][2] - 'a'; + int toY = moveList[moveNum][3] - '1'; + if((boards[currentMove][fromY][fromX] == WhiteKing + && boards[currentMove][toY][toX] == WhiteRook) + || (boards[currentMove][fromY][fromX] == BlackKing + && boards[currentMove][toY][toX] == BlackRook)) { + if(toX > fromX) SendToProgram("O-O\n", cps); + else SendToProgram("O-O-O\n", cps); + } + else SendToProgram(moveList[moveNum], cps); + } + else SendToProgram(moveList[moveNum], cps); + /* End of additions by Tord */ } } @@ -3062,12 +3282,20 @@ SendMoveToICS(moveType, fromX, fromY, toX, toY) case BlackKingSideCastle: case WhiteQueenSideCastleWild: case BlackQueenSideCastleWild: + /* PUSH Fabien */ + case WhiteHSideCastleFR: + case BlackHSideCastleFR: + /* POP Fabien */ sprintf(user_move, "o-o\n"); break; case WhiteQueenSideCastle: case BlackQueenSideCastle: case WhiteKingSideCastleWild: case BlackKingSideCastleWild: + /* PUSH Fabien */ + case WhiteASideCastleFR: + case BlackASideCastleFR: + /* POP Fabien */ sprintf(user_move, "o-o-o\n"); break; case WhitePromotionQueen: @@ -3167,6 +3395,12 @@ ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar) case WhiteQueenSideCastleWild: case BlackKingSideCastleWild: case BlackQueenSideCastleWild: + /* Code added by Tord: */ + case WhiteHSideCastleFR: + case WhiteASideCastleFR: + case BlackHSideCastleFR: + case BlackASideCastleFR: + /* End of code added by Tord */ case IllegalMove: /* bug or odd chess variant */ *fromX = currentMoveString[0] - 'a'; *fromY = currentMoveString[1] - '1'; @@ -3213,12 +3447,107 @@ ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar) } } +/* [AS] FRC game initialization */ +static int FindEmptySquare( Board board, int n ) +{ + int i = 0; + + while( 1 ) { + while( board[0][i] != EmptySquare ) i++; + if( n == 0 ) + break; + n--; + i++; + } + + return i; +} + +static void ShuffleFRC( Board board ) +{ + int i; + + srand( time(0) ); + + for( i=0; i<8; i++ ) { + board[0][i] = EmptySquare; + } + + board[0][(rand() % 4)*2 ] = WhiteBishop; /* On dark square */ + board[0][(rand() % 4)*2+1] = WhiteBishop; /* On lite square */ + board[0][FindEmptySquare(board, rand() % 6)] = WhiteQueen; + board[0][FindEmptySquare(board, rand() % 5)] = WhiteKnight; + board[0][FindEmptySquare(board, rand() % 4)] = WhiteKnight; + board[0][FindEmptySquare(board, 0)] = WhiteRook; + board[0][FindEmptySquare(board, 0)] = WhiteKing; + board[0][FindEmptySquare(board, 0)] = WhiteRook; + + for( i=0; i<8; i++ ) { + board[7][i] = board[0][i] + BlackPawn - WhitePawn; + } +} + +static unsigned char FRC_KnightTable[10] = { + 0x00, 0x01, 0x02, 0x03, 0x11, 0x12, 0x13, 0x22, 0x23, 0x33 +}; + +static void SetupFRC( Board board, int pos_index ) +{ + int i; + unsigned char knights; + + /* Bring the position index into a safe range (just in case...) */ + if( pos_index < 0 ) pos_index = 0; + + pos_index %= 960; + + /* Clear the board */ + for( i=0; i<8; i++ ) { + board[0][i] = EmptySquare; + } + + /* Place bishops and queen */ + board[0][ (pos_index % 4)*2 + 1 ] = WhiteBishop; /* On lite square */ + pos_index /= 4; + + board[0][ (pos_index % 4)*2 ] = WhiteBishop; /* On dark square */ + pos_index /= 4; + + board[0][ FindEmptySquare(board, pos_index % 6) ] = WhiteQueen; + pos_index /= 6; + + /* Place knigths */ + knights = FRC_KnightTable[ pos_index ]; + + board[0][ FindEmptySquare(board, knights / 16) ] = WhiteKnight; + board[0][ FindEmptySquare(board, knights % 16) ] = WhiteKnight; + + /* Place rooks and king */ + board[0][ FindEmptySquare(board, 0) ] = WhiteRook; + board[0][ FindEmptySquare(board, 0) ] = WhiteKing; + board[0][ FindEmptySquare(board, 0) ] = WhiteRook; + + /* Mirror piece placement for black */ + for( i=0; i<8; i++ ) { + board[7][i] = board[0][i] + BlackPawn - WhitePawn; + } +} void InitPosition(redraw) int redraw; { currentMove = forwardMostMove = backwardMostMove = 0; + + /* [AS] Initialize pv info list */ + { + int i; + + for( i=0; iuseSetboard) { - char* fen = PositionToFEN(moveNum); + char* fen = PositionToFEN(moveNum, cps->useFEN960); sprintf(message, "setboard %s\n", fen); SendToProgram(message, cps); free(fen); @@ -3705,6 +4040,17 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar) } } +void SendProgramStatsToFrontend( ChessProgramState * cps ) +{ + SetProgramStats( cps == &first ? 0 : 1, + programStats.depth, + programStats.nodes, + programStats.score, + programStats.time, + programStats.movelist, + lastHint ); +} + void HandleMachineMove(message, cps) char *message; @@ -3748,11 +4094,9 @@ HandleMachineMove(message, cps) /* * Look for machine move. */ - if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && - strcmp(buf2, "...") == 0) || - (sscanf(message, "%s %s", buf1, machineMove) == 2 && - strcmp(buf1, "move") == 0)) { - + if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) || + (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) + { /* This method is only useful on engines that support ping */ if (cps->lastPing != cps->lastPong) { if (gameMode == BeginningOfGame) { @@ -3821,7 +4165,11 @@ HandleMachineMove(message, cps) /* Machine move could not be parsed; ignore it. */ sprintf(buf1, "Illegal move \"%s\" from %s machine", machineMove, cps->which); - /*!!if (appData.debugMode)*/ DisplayError(buf1, 0); + DisplayError(buf1, 0); + if (gameMode == TwoMachinesPlay) { + GameEnds(machineWhite ? BlackWins : WhiteWins, + "Forfeit due to illegal move", GE_XBOARD); + } return; } @@ -3845,8 +4193,53 @@ HandleMachineMove(message, cps) strcat(machineMove, "\n"); strcpy(moveList[forwardMostMove], machineMove); + /* [AS] Save move info and clear stats for next move */ + pvInfoList[ forwardMostMove ].score = programStats.score; + pvInfoList[ forwardMostMove ].depth = programStats.depth; + pvInfoList[ forwardMostMove ].time = -1; + ClearProgramStats(); + thinkOutput[0] = NULLCHAR; + hiddenThinkOutputState = 0; + MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/ + /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */ + if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) { + int count = 0; + + while( count < adjudicateLossPlies ) { + int score = pvInfoList[ forwardMostMove - count - 1 ].score; + + if( count & 1 ) { + score = -score; /* Flip score for winning side */ + } + + if( score > adjudicateLossThreshold ) { + break; + } + + count++; + } + + if( count >= adjudicateLossPlies ) { + ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ + + GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, + "Xboard adjudication", + GE_XBOARD ); + + return; + } + } + + if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) { + ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ + + GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD ); + + return; + } + if (gameMode == TwoMachinesPlay) { if (cps->other->sendTime) { SendTimeRemaining(cps->other, @@ -3864,15 +4257,18 @@ HandleMachineMove(message, cps) } ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ + if (!pausing && appData.ringBellAfterMoves) { RingBell(); } + /* * Reenable menu items that were disabled while * machine was thinking */ if (gameMode != TwoMachinesPlay) SetUserThinkingEnables(); + return; } @@ -3890,7 +4286,7 @@ HandleMachineMove(message, cps) * Look for communication commands */ if (!strncmp(message, "telluser ", 9)) { - DisplayInformation(message + 9); + DisplayNote(message + 9); return; } if (!strncmp(message, "tellusererror ", 14)) { @@ -3904,7 +4300,7 @@ HandleMachineMove(message, cps) SendToICS(buf1); } } else { - DisplayInformation(message + 13); + DisplayNote(message + 13); } return; } @@ -3924,7 +4320,7 @@ HandleMachineMove(message, cps) SendToICS(buf1); } } else { - DisplayInformation(message + 8); + DisplayNote(message + 8); } return; } @@ -4016,14 +4412,16 @@ HandleMachineMove(message, cps) DisplayError(buf2, 0); return; } - if (StrStr(message, "st")) { + if (StrStr(message, "(no matching move)st")) { + /* Special kludge for GNU Chess 4 only */ cps->stKludge = TRUE; SendTimeControl(cps, movesPerSession, timeControl, timeIncrement, appData.searchDepth, searchTime); return; } - if (StrStr(message, "sd")) { + if (StrStr(message, "(no matching move)sd")) { + /* Special kludge for GNU Chess 4 only */ cps->sdKludge = TRUE; SendTimeControl(cps, movesPerSession, timeControl, timeIncrement, appData.searchDepth, @@ -4034,12 +4432,16 @@ HandleMachineMove(message, cps) 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 (gameMode == PlayFromGameFile) { /* Stop reading this game file */ @@ -4282,7 +4684,7 @@ HandleMachineMove(message, cps) /* * Look for thinking output */ - if (appData.showThinking) { + if ( appData.showThinking) { int plylev, mvleft, mvtot, curscore, time; char mvname[MOVE_LEN]; unsigned long nodes; @@ -4304,8 +4706,7 @@ HandleMachineMove(message, cps) case AnalyzeFile: break; case TwoMachinesPlay: - if ((cps->twoMachinesColor[0] == 'w') != - WhiteOnMove(forwardMostMove)) { + if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) { ignore = TRUE; } break; @@ -4322,13 +4723,35 @@ HandleMachineMove(message, cps) if (plyext != ' ' && plyext != '\t') { time *= 100; } + + /* [AS] Negate score if machine is playing black and reporting absolute scores */ + if( cps->scoreIsAbsolute && + ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) + { + curscore = -curscore; + } + + programStats.depth = plylev; programStats.nodes = nodes; programStats.time = time; programStats.score = curscore; - strcpy(programStats.movelist, buf1); programStats.got_only_move = 0; + /* Buffer overflow protection */ + if (buf1[0] != NULLCHAR) { + if (strlen(buf1) >= sizeof(programStats.movelist) + && appData.debugMode) { + fprintf(debugFP, + "PV is too long; using the first %d bytes.\n", + sizeof(programStats.movelist) - 1); + } + + safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) ); + } else { + sprintf(programStats.movelist, " no PV\n"); + } + if (programStats.seen_stat) { programStats.ok_to_send = 1; } @@ -4341,16 +4764,34 @@ HandleMachineMove(message, cps) programStats.line_is_book = 0; } - sprintf(thinkOutput, "[%d]%c%+.2f %s%s%s", + SendProgramStatsToFrontend( cps ); + + /* + [AS] Protect the thinkOutput buffer from overflow... this + is only useful if buf1 hasn't overflowed first! + */ + sprintf(thinkOutput, "[%d]%c%+.2f %s%s", plylev, (gameMode == TwoMachinesPlay ? ToUpper(cps->twoMachinesColor[0]) : ' '), ((double) curscore) / 100.0, prefixHint ? lastHint : "", - prefixHint ? " " : "", buf1); + prefixHint ? " " : "" ); + + if( buf1[0] != NULLCHAR ) { + unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1; - if (currentMove == forwardMostMove || - gameMode == AnalyzeMode || gameMode == AnalyzeFile) { + if( strlen(buf1) > max_len ) { + if( appData.debugMode) { + fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n"); + } + buf1[max_len+1] = '\0'; + } + + strcat( thinkOutput, buf1 ); + } + + if (currentMove == forwardMostMove || gameMode == AnalyzeMode || gameMode == AnalyzeFile) { DisplayMove(currentMove - 1); DisplayAnalysis(); } @@ -4375,8 +4816,9 @@ HandleMachineMove(message, cps) isn't searching, so stats won't change) */ programStats.line_is_book = 1; - if (currentMove == forwardMostMove || gameMode==AnalyzeMode || - gameMode == AnalyzeFile) { + SendProgramStatsToFrontend( cps ); + + if (currentMove == forwardMostMove || gameMode==AnalyzeMode || gameMode == AnalyzeFile) { DisplayMove(currentMove - 1); DisplayAnalysis(); } @@ -4399,6 +4841,9 @@ HandleMachineMove(message, cps) programStats.nr_moves = mvtot; strcpy(programStats.move_name, mvname); programStats.ok_to_send = 1; + + SendProgramStatsToFrontend( cps ); + DisplayAnalysis(); return; @@ -4414,20 +4859,65 @@ HandleMachineMove(message, cps) } else if (thinkOutput[0] != NULLCHAR && strncmp(message, " ", 4) == 0) { + unsigned message_len; + p = message; while (*p && *p == ' ') p++; + + message_len = strlen( p ); + + /* [AS] Avoid buffer overflow */ + if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) { strcat(thinkOutput, " "); strcat(thinkOutput, p); + } + + if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) { strcat(programStats.movelist, " "); strcat(programStats.movelist, p); - if (currentMove == forwardMostMove || gameMode==AnalyzeMode || - gameMode == AnalyzeFile) { + } + + if (currentMove == forwardMostMove || gameMode==AnalyzeMode || gameMode == AnalyzeFile) { DisplayMove(currentMove - 1); DisplayAnalysis(); } return; } } + else { + buf1[0] = NULLCHAR; + + if (sscanf(message, "%d%c %d %d %lu %[^\n]\n", + &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) + { + if (plyext != ' ' && plyext != '\t') { + time *= 100; + } + + /* [AS] Negate score if machine is playing black and reporting absolute scores */ + if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) { + curscore = -curscore; + } + + programStats.depth = plylev; + programStats.nodes = nodes; + programStats.time = time; + programStats.score = curscore; + programStats.got_only_move = 0; + programStats.movelist[0] = '\0'; + + if (buf1[0] != NULLCHAR) { + safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) ); + } + + programStats.ok_to_send = 0; + programStats.line_is_book = 0; + programStats.nr_moves = 0; + programStats.moves_left = 0; + + SendProgramStatsToFrontend( cps ); + } + } } } @@ -4495,6 +4985,12 @@ ParseGameHistory(game) case WhiteQueenSideCastleWild: case BlackKingSideCastleWild: case BlackQueenSideCastleWild: + /* PUSH Fabien */ + case WhiteHSideCastleFR: + case WhiteASideCastleFR: + case BlackHSideCastleFR: + case BlackASideCastleFR: + /* POP Fabien */ case IllegalMove: /* maybe suicide chess, etc. */ fromX = currentMoveString[0] - 'a'; fromY = currentMoveString[1] - '1'; @@ -4534,7 +5030,7 @@ ParseGameHistory(game) } return; case ElapsedTime: - if (boardIndex > 0) { + if (boardIndex > (blackPlaysFirst ? 1 : 0)) { strcat(parseList[boardIndex-1], " "); strcat(parseList[boardIndex-1], yy_text); } @@ -4615,6 +5111,30 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board) board[toY][toX] = (ChessSquare) fromX; } else if (fromX == toX && fromY == toY) { return; + } + + /* Code added by Tord: */ + /* FRC castling assumed when king captures friendly rook. */ + else if (board[fromY][fromX] == WhiteKing && + board[toY][toX] == WhiteRook) { + board[fromY][fromX] = EmptySquare; + board[toY][toX] = EmptySquare; + if(toX > fromX) { + board[0][6] = WhiteKing; board[0][5] = WhiteRook; + } else { + board[0][2] = WhiteKing; board[0][3] = WhiteRook; + } + } else if (board[fromY][fromX] == BlackKing && + board[toY][toX] == BlackRook) { + board[fromY][fromX] = EmptySquare; + board[toY][toX] = EmptySquare; + if(toX > fromX) { + board[7][6] = BlackKing; board[7][5] = BlackRook; + } else { + board[7][2] = BlackKing; board[7][3] = BlackRook; + } + /* End of code added by Tord */ + } else if (fromY == 0 && fromX == 4 && board[fromY][fromX] == WhiteKing && toY == 0 && toX == 6) { @@ -4806,6 +5326,7 @@ ShowMove(fromX, fromY, toX, toY) } if (instant) return; + DisplayMove(currentMove - 1); DrawPosition(FALSE, boards[currentMove]); DisplayBothClocks(); @@ -4939,6 +5460,23 @@ NextMatchGame P((void)) TwoMachinesEventIfReady(); } +void UserAdjudicationEvent( int result ) +{ + ChessMove gameResult = GameIsDrawn; + + if( result > 0 ) { + gameResult = WhiteWins; + } + else if( result < 0 ) { + gameResult = BlackWins; + } + + if( gameMode == TwoMachinesPlay ) { + GameEnds( gameResult, "User adjudication", GE_XBOARD ); + } +} + + void GameEnds(result, resultDetails, whosays) ChessMove result; @@ -5118,7 +5656,9 @@ GameEnds(result, resultDetails, whosays) if (first.pr != NoProc) { ExitAnalyzeMode(); + DoSleep( appData.delayBeforeQuit ); SendToProgram("quit\n", &first); + DoSleep( appData.delayAfterQuit ); DestroyChildProcess(first.pr, first.useSigterm); } first.pr = NoProc; @@ -5141,7 +5681,9 @@ GameEnds(result, resultDetails, whosays) second.isr = NULL; if (second.pr != NoProc) { + DoSleep( appData.delayBeforeQuit ); SendToProgram("quit\n", &second); + DoSleep( appData.delayAfterQuit ); DestroyChildProcess(second.pr, second.useSigterm); } second.pr = NoProc; @@ -5335,6 +5877,10 @@ AutoPlayOneMove() if (currentMove >= forwardMostMove) { gameMode = EditGame; ModeHighlight(); + + /* [AS] Clear current move marker at the end of a game */ + /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */ + return FALSE; } @@ -5348,6 +5894,9 @@ AutoPlayOneMove() } else { fromX = moveList[currentMove][0] - 'a'; fromY = moveList[currentMove][1] - '1'; + + HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */ + AnimateMove(boards[currentMove], fromX, fromY, toX, toY); if (appData.highlightLastMove) { @@ -5427,6 +5976,12 @@ LoadGameOneMove(readAhead) case WhiteQueenSideCastleWild: case BlackKingSideCastleWild: case BlackQueenSideCastleWild: + /* PUSH Fabien */ + case WhiteHSideCastleFR: + case WhiteASideCastleFR: + case BlackHSideCastleFR: + case BlackASideCastleFR: + /* POP Fabien */ if (appData.debugMode) fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString); fromX = currentMoveString[0] - 'a'; @@ -5881,13 +6436,13 @@ LoadGame(f, gameNumber, title, useList) * A game that starts with one of the latter two patterns * will also have a move number 1, possibly * following a position diagram. + * 5-4-02: Let's try being more lenient and allowing a game to + * start with an unnumbered move. Does that break anything? */ cm = lastLoadGameStart = (ChessMove) 0; - yyskipmoves = TRUE; while (gn > 0) { yyboardindex = forwardMostMove; cm = (ChessMove) yylex(); - yyskipmoves = FALSE; switch (cm) { case (ChessMove) 0: if (cmailMsgLoaded) { @@ -5896,7 +6451,6 @@ LoadGame(f, gameNumber, title, useList) Reset(TRUE, TRUE); DisplayError("Game not found in file", 0); } - yyskipmoves = FALSE; return FALSE; case GNUChessGame: @@ -5959,11 +6513,19 @@ LoadGame(f, gameNumber, title, useList) } break; + case NormalMove: + /* Only a NormalMove can be at the start of a game + * without a position diagram. */ + if (lastLoadGameStart == (ChessMove) 0) { + gn--; + lastLoadGameStart = MoveNumberOne; + } + break; + default: break; } } - yyskipmoves = FALSE; if (appData.debugMode) fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm); @@ -6056,10 +6618,12 @@ LoadGame(f, gameNumber, title, useList) gameInfo.variant = StringToVariant(gameInfo.event); } if (!matchMode) { + if( appData.autoDisplayTags ) { tags = PGNTags(&gameInfo); TagsPopUp(tags, CmailMsg()); free(tags); } + } } else { /* Make something up, but don't display it now */ SetGameInfo(); @@ -6144,7 +6708,8 @@ LoadGame(f, gameNumber, title, useList) cm = (ChessMove) yylex(); } - if (cm == (ChessMove) 0 || cm == WhiteWins || cm == BlackWins || + if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) || + cm == WhiteWins || cm == BlackWins || cm == GameIsDrawn || cm == GameUnfinished) { DisplayMessage("", "No moves in game"); if (cmailMsgLoaded) { @@ -6322,6 +6887,7 @@ LoadPosition(f, positionNumber, title) while (pn > 0) { /* skip postions before number pn */ if (fgets(line, MSG_SIZ, f) == NULL) { + Reset(TRUE, TRUE); DisplayError("Position not found in file", 0); return FALSE; } @@ -6459,6 +7025,80 @@ SavePart(str) #define PGN_MAX_LINE 75 +#define PGN_SIDE_WHITE 0 +#define PGN_SIDE_BLACK 1 + +static int FindFirstMoveOutOfBook( int side ) +{ + int result = -1; + + if( backwardMostMove == 0 && ! startedFromSetupPosition) { + int index = backwardMostMove; + int has_book_hit = 0; + + if( (index % 2) != side ) { + index++; + } + + while( index < forwardMostMove ) { + /* Check to see if engine is in book */ + int depth = pvInfoList[index].depth; + int score = pvInfoList[index].score; + int in_book = 0; + + if( depth == 0 ) { + in_book = 1; /* Yace */ + } + if( score == 0 ) { + if( depth <= 1 || depth == 63 /* Zappa */ ) { + in_book = 1; + } + } + + has_book_hit += in_book; + + if( ! in_book ) { + result = index; + + break; + } + + index += 2; + } + } + + return result; +} + +void GetOutOfBookInfo( char * buf ) +{ + int oob[2]; + int i; + int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */ + + oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE ); + oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK ); + + *buf = '\0'; + + if( oob[0] >= 0 || oob[1] >= 0 ) { + for( i=0; i<2; i++ ) { + int idx = oob[i]; + + if( idx >= 0 ) { + if( i > 0 && oob[0] >= 0 ) { + strcat( buf, " " ); + } + + sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" ); + sprintf( buf+strlen(buf), "%s%.2f", + pvInfoList[idx].score >= 0 ? "+" : "", + pvInfoList[idx].score / 100.0 ); + } + } + } +} + /* Save game in PGN style and close the file */ int SaveGamePGN(f) @@ -6469,24 +7109,38 @@ SaveGamePGN(f) char *movetext; char numtext[32]; int movelen, numlen, blank; + char move_buffer[100]; /* [AS] Buffer for move+PV info */ + offset = backwardMostMove & (~1L); /* output move numbers start at 1 */ + tm = time((time_t *) NULL); PrintPGNTags(f, &gameInfo); if (backwardMostMove > 0 || startedFromSetupPosition) { - char *fen = PositionToFEN(backwardMostMove); + char *fen = PositionToFEN(backwardMostMove, 1); fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen); fprintf(f, "\n{--------------\n"); PrintPosition(f, backwardMostMove); fprintf(f, "--------------}\n"); free(fen); - } else { + } + else { + /* [AS] Out of book annotation */ + if( appData.saveOutOfBookInfo ) { + char buf[64]; + + GetOutOfBookInfo( buf ); + + if( buf[0] != '\0' ) { + fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); + } + } + fprintf(f, "\n"); } i = backwardMostMove; - offset = backwardMostMove & (~1L); /* output move numbers start at 1 */ linelen = 0; newblock = TRUE; @@ -6528,6 +7182,17 @@ SaveGamePGN(f) /* Get move */ movetext = SavePart(parseList[i]); + + /* [AS] Add PV info if present */ + if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) { + sprintf( move_buffer, "%s {%s%.2f/%d}", + movetext, + pvInfoList[i].score >= 0 ? "+" : "", + pvInfoList[i].score / 100.0, + pvInfoList[i].depth ); + movetext = move_buffer; + } + movelen = strlen(movetext); /* Print move */ @@ -6689,7 +7354,7 @@ SavePosition(f, dummy, dummy2) PrintPosition(f, currentMove); fprintf(f, "--------------]\n"); } else { - fen = PositionToFEN(currentMove); + fen = PositionToFEN(currentMove, 1); fprintf(f, "%s\n", fen); free(fen); } @@ -6701,6 +7366,7 @@ void ReloadCmailMsgEvent(unregister) int unregister; { +#if !WIN32 static char *inFilename = NULL; static char *outFilename; int i; @@ -6759,6 +7425,7 @@ ReloadCmailMsgEvent(unregister) /* Load first game in the file or popup game menu */ LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE); +#endif /* !WIN32 */ return; } @@ -6856,6 +7523,7 @@ RegisterMove() void MailMoveEvent() { +#if !WIN32 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1"; FILE *commandOutput; char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ]; @@ -6889,7 +7557,7 @@ MailMoveEvent() || (nCmailMovesRegistered + nCmailResults == nCmailGames)) { sprintf(string, partCommandString, appData.debugMode ? " -v" : "", appData.cmailGameName); - commandOutput = popen(string, "r"); + commandOutput = popen(string, "rb"); if (commandOutput == NULL) { DisplayError("Failed to invoke cmail", 0); @@ -6937,11 +7605,15 @@ MailMoveEvent() } return; +#endif /* !WIN32 */ } char * CmailMsg() { +#if WIN32 + return NULL; +#else int prependComma = 0; char number[5]; char string[MSG_SIZ]; /* Space for game-list */ @@ -7010,8 +7682,8 @@ CmailMsg() } } } - return cmailMsg; +#endif /* WIN32 */ } void @@ -7068,12 +7740,17 @@ ExitEvent(status) /* Kill off chess programs */ if (first.pr != NoProc) { ExitAnalyzeMode(); + + DoSleep( appData.delayBeforeQuit ); SendToProgram("quit\n", &first); - DestroyChildProcess(first.pr, first.useSigterm); + DoSleep( appData.delayAfterQuit ); + DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ ); } if (second.pr != NoProc) { + DoSleep( appData.delayBeforeQuit ); SendToProgram("quit\n", &second); - DestroyChildProcess(second.pr, second.useSigterm); + DoSleep( appData.delayAfterQuit ); + DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ ); } if (first.isr != NULL) { RemoveInputSource(first.isr); @@ -7688,6 +8365,7 @@ EditPositionDone() gameMode = EditGame; ModeHighlight(); HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1); + ClearHighlights(); /* [AS] */ } /* Pause for `ms' milliseconds */ @@ -8195,6 +8873,8 @@ void BackwardInner(target) int target; { + int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */ + if (appData.debugMode) fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n", target, currentMove, forwardMostMove); @@ -8202,7 +8882,7 @@ BackwardInner(target) if (gameMode == EditPosition) return; if (currentMove <= backwardMostMove) { ClearHighlights(); - DrawPosition(FALSE, boards[currentMove]); + DrawPosition(full_redraw, boards[currentMove]); return; } if (gameMode == PlayFromGameFile && !pausing) @@ -8243,7 +8923,7 @@ BackwardInner(target) } DisplayBothClocks(); DisplayMove(currentMove - 1); - DrawPosition(FALSE, boards[currentMove]); + DrawPosition(full_redraw, boards[currentMove]); HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1); if (commentList[currentMove] != NULL) { DisplayComment(currentMove - 1, commentList[currentMove]); @@ -8335,7 +9015,7 @@ RetractMoveEvent() DisplayBothClocks(); DisplayMove(currentMove - 1); ClearHighlights();/*!! could figure this out*/ - DrawPosition(FALSE, boards[currentMove]); + DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */ SendToProgram("remove\n", &first); /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */ break; @@ -8569,7 +9249,7 @@ SetGameInfo() switch (gameMode) { case MachinePlaysWhite: - gameInfo.event = StrSave("Computer chess game"); + gameInfo.event = StrSave( appData.pgnEventHeader ); gameInfo.site = StrSave(HostName()); gameInfo.date = PGNDate(); gameInfo.round = StrSave("-"); @@ -8579,7 +9259,7 @@ SetGameInfo() break; case MachinePlaysBlack: - gameInfo.event = StrSave("Computer chess game"); + gameInfo.event = StrSave( appData.pgnEventHeader ); gameInfo.site = StrSave(HostName()); gameInfo.date = PGNDate(); gameInfo.round = StrSave("-"); @@ -8589,7 +9269,7 @@ SetGameInfo() break; case TwoMachinesPlay: - gameInfo.event = StrSave("Computer chess game"); + gameInfo.event = StrSave( appData.pgnEventHeader ); gameInfo.site = StrSave(HostName()); gameInfo.date = PGNDate(); if (matchGame > 0) { @@ -8672,6 +9352,21 @@ ReplaceComment(index, text) } void +CrushCRs(text) + char *text; +{ + char *p = text; + char *q = text; + char ch; + + do { + ch = *p++; + if (ch == '\r') continue; + *q++ = ch; + } while (ch != '\0'); +} + +void AppendComment(index, text) int index; char *text; @@ -8679,6 +9374,9 @@ AppendComment(index, text) int oldlen, len; char *old; + GetInfoFromComment( index, text ); + + CrushCRs(text); while (*text == '\n') text++; len = strlen(text); while (len > 0 && text[len - 1] == '\n') len--; @@ -8702,6 +9400,78 @@ AppendComment(index, text) } } +static char * FindStr( char * text, char * sub_text ) +{ + char * result = strstr( text, sub_text ); + + if( result != NULL ) { + result += strlen( sub_text ); + } + + return result; +} + +/* [AS] Try to extract PV info from PGN comment */ +void GetInfoFromComment( int index, char * text ) +{ + if( text != NULL && index > 0 ) { + int score = 0; + int depth = 0; + int time = -1; + char * s_eval = FindStr( text, "[%eval " ); + char * s_emt = FindStr( text, "[%emt " ); + + if( s_eval != NULL || s_emt != NULL ) { + /* New style */ + char delim; + + if( s_eval != NULL ) { + if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) { + return; + } + + if( delim != ']' ) { + return; + } + } + + if( s_emt != NULL ) { + } + } + else { + /* We expect something like: [+|-]nnn.nn/dd */ + char * sep = strchr( text, '/' ); + int score_lo = 0; + + if( sep == NULL || sep < (text+4) ) { + return; + } + + if( sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) { + return; + } + + if( score_lo < 0 || score_lo >= 100 ) { + return; + } + + score = score >= 0 ? score*100 + score_lo : score*100 - score_lo; + } + + if( depth <= 0 ) { + return; + } + + if( time < 0 ) { + time = -1; + } + + pvInfoList[index-1].depth = depth; + pvInfoList[index-1].score = score; + pvInfoList[index-1].time = time; + } +} + void SendToProgram(message, cps) char *message; @@ -8754,6 +9524,13 @@ ReceiveFromProgram(isr, closure, message, count, error) "Error reading from %s chess program (%s)", cps->which, cps->program); RemoveInputSource(cps->isr); + + /* [AS] Program is misbehaving badly... kill it */ + if( count == -2 ) { + DestroyChildProcess( cps->pr, 9 ); + cps->pr = NoProc; + } + DisplayFatalError(buf, error, 1); } GameEnds((ChessMove) 0, NULL, GE_PLAYER); @@ -8785,6 +9562,12 @@ SendTimeControl(cps, mps, tc, inc, sd, st) char buf[MSG_SIZ]; int seconds = (tc / 1000) % 60; + if( timeControl_2 > 0 ) { + if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) { + tc = timeControl_2; + } + } + if (st > 0) { /* Set exact time per move, normally using st command */ if (cps->stKludge) { @@ -8975,6 +9758,10 @@ ParseFeatures(args, cps) FeatureDone(cps, val); continue; } + /* Added by Tord: */ + if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue; + if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue; + /* End of additions by Tord */ /* unknown feature: complain and skip */ q = p; @@ -9077,13 +9864,29 @@ DisplayMove(moveNumber) if (moveNumber == forwardMostMove - 1 || gameMode == AnalyzeMode || gameMode == AnalyzeFile) { - strcpy(cpThinkOutput, thinkOutput); - if (strchr(cpThinkOutput, '\n')) + safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)); + + if (strchr(cpThinkOutput, '\n')) { *strchr(cpThinkOutput, '\n') = NULLCHAR; + } } else { *cpThinkOutput = NULLCHAR; } + /* [AS] Hide thinking from human user */ + if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) { + *cpThinkOutput = NULLCHAR; + if( thinkOutput[0] != NULLCHAR ) { + int i; + + for( i=0; i<=hiddenThinkOutputState; i++ ) { + cpThinkOutput[i] = '.'; + } + cpThinkOutput[i] = NULLCHAR; + hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3; + } + } + if (moveNumber == forwardMostMove - 1 && gameInfo.resultDetails != NULL) { if (gameInfo.resultDetails[0] == NULLCHAR) { @@ -9134,6 +9937,7 @@ void DisplayAnalysis() { char buf[MSG_SIZ]; + char lst[MSG_SIZ / 2]; double nps; static char *xtra[] = { "", " (--)", " (++)" }; int h, m, s, cs; @@ -9143,8 +9947,10 @@ DisplayAnalysis() } if (programStats.got_only_move) { - strcpy(buf, programStats.movelist); + safeStrCpy(buf, programStats.movelist, sizeof(buf)); } else { + safeStrCpy( lst, programStats.movelist, sizeof(lst)); + nps = (((double)programStats.nodes) / (((double)programStats.time)/100.0)); @@ -9161,8 +9967,8 @@ DisplayAnalysis() programStats.depth, programStats.nr_moves-programStats.moves_left, programStats.nr_moves, programStats.move_name, - ((float)programStats.score)/100.0, programStats.movelist, - only_one_move(programStats.movelist)? + ((float)programStats.score)/100.0, lst, + only_one_move(lst)? xtra[programStats.got_fail] : "", programStats.nodes, (int)nps, h, m, s, cs); } else { @@ -9170,8 +9976,8 @@ DisplayAnalysis() programStats.depth, programStats.nr_moves-programStats.moves_left, programStats.nr_moves, ((float)programStats.score)/100.0, - programStats.movelist, - only_one_move(programStats.movelist)? + lst, + only_one_move(lst)? xtra[programStats.got_fail] : "", programStats.nodes, (int)nps, h, m, s, cs); } @@ -9179,8 +9985,8 @@ DisplayAnalysis() sprintf(buf, "depth=%d %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d", programStats.depth, ((float)programStats.score)/100.0, - programStats.movelist, - only_one_move(programStats.movelist)? + lst, + only_one_move(lst)? xtra[programStats.got_fail] : "", programStats.nodes, (int)nps, h, m, s, cs); } @@ -9195,6 +10001,7 @@ DisplayComment(moveNumber, text) { char title[MSG_SIZ]; + if( appData.autoDisplayComment ) { if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) { strcpy(title, "Comment"); } else { @@ -9204,6 +10011,7 @@ DisplayComment(moveNumber, text) } CommentPopUp(title, text); + } } /* This routine sends a ^C interrupt to gnuchess, to awaken it if it @@ -9310,11 +10118,11 @@ CheckTimeControl() switch ((forwardMostMove + 1) % (movesPerSession * 2)) { case 0: /* White made time control */ - whiteTimeRemaining += timeControl; + whiteTimeRemaining += GetTimeControlForWhite(); break; case 1: /* Black made time control */ - blackTimeRemaining += timeControl; + blackTimeRemaining += GetTimeControlForBlack(); break; default: break; @@ -9418,7 +10226,8 @@ ResetClocks() if (appData.icsActive) { whiteTimeRemaining = blackTimeRemaining = 0; } else { - whiteTimeRemaining = blackTimeRemaining = timeControl; + whiteTimeRemaining = GetTimeControlForWhite(); + blackTimeRemaining = GetTimeControlForBlack(); } if (whiteFlag || blackFlag) { DisplayTitle(""); @@ -9737,8 +10546,9 @@ PGNDate() char * -PositionToFEN(move) +PositionToFEN(move, useFEN960) int move; + int useFEN960; { int i, j, fromX, fromY, toX, toY; int whiteToPlay; @@ -9776,7 +10586,66 @@ PositionToFEN(move) *p++ = whiteToPlay ? 'w' : 'b'; *p++ = ' '; - /* !!We don't keep track of castling availability, so fake it */ + /* HACK: we don't keep track of castling availability, so fake it! */ + + /* PUSH Fabien & Tord */ + + /* Declare all potential FRC castling rights (conservative) */ + /* outermost rook on each side of the king */ + + if( gameInfo.variant == VariantFischeRandom ) { + int fk, fr; + + q = p; + + /* White castling rights */ + + for (fk = 1; fk < 7; fk++) { + + if (boards[move][0][fk] == WhiteKing) { + + for (fr = 7; fr > fk; fr--) { /* H side */ + if (boards[move][0][fr] == WhiteRook) { + *p++ = useFEN960 ? 'A' + fr : 'K'; + break; + } + } + + for (fr = 0; fr < fk; fr++) { /* A side */ + if (boards[move][0][fr] == WhiteRook) { + *p++ = useFEN960 ? 'A' + fr : 'Q'; + break; + } + } + } + } + + /* Black castling rights */ + + for (fk = 1; fk < 7; fk++) { + + if (boards[move][7][fk] == BlackKing) { + + for (fr = 7; fr > fk; fr--) { /* H side */ + if (boards[move][7][fr] == BlackRook) { + *p++ = useFEN960 ? 'a' + fr : 'k'; + break; + } + } + + for (fr = 0; fr < fk; fr++) { /* A side */ + if (boards[move][7][fr] == BlackRook) { + *p++ = useFEN960 ? 'a' + fr : 'q'; + break; + } + } + } + } + + if (q == p) *p++ = '-'; /* No castling rights */ + *p++ = ' '; + } + else { q = p; if (boards[move][0][4] == WhiteKing) { if (boards[move][0][7] == WhiteRook) *p++ = 'K'; @@ -9788,6 +10657,9 @@ PositionToFEN(move) } if (q == p) *p++ = '-'; *p++ = ' '; + } + + /* POP Fabien & Tord */ /* En passant target square */ if (move > backwardMostMove) { @@ -9809,7 +10681,7 @@ PositionToFEN(move) *p++ = '-'; } - /* !!We don't keep track of halfmove clock for 50-move rule */ + /* We don't keep track of halfmove clock for 50-move rule */ strcpy(p, " 0 "); p += 3; @@ -9865,6 +10737,7 @@ ParseFEN(board, blackPlaysFirst, fen) default: return FALSE; } + /* !!We ignore the rest of the FEN notation */ return TRUE; }