/*
* 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.
*
* See the file ChangeLog for a revision history. */
+/* [AS] Also useful here for debugging */
+#ifdef WIN32
+#include <windows.h>
+
+#define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
+
+#else
+
+#define DoSleep( n )
+
+#endif
+
#include "config.h"
+#include <assert.h>
#include <stdio.h>
#include <ctype.h>
#include <errno.h>
void FeatureDone P((ChessProgramState* cps, int val));
void InitChessProgram P((ChessProgramState *cps));
+void GetInfoFromComment( int, char * );
+
extern int tinyLayout, smallLayout;
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
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];
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;
appData.zippyTalk = appData.zippyPlay = FALSE;
}
+ /* [AS] Initialize pv info list */
+ {
+ int i;
+
+ for( i=0; i<MAX_MOVES; i++ ) {
+ pvInfoList[i].depth = 0;
+ }
+ }
+
/*
* Parse timeControl resource
*/
}
}
+ /* [AS] Adjudication threshold */
+ adjudicateLossThreshold = appData.adjudicateLossThreshold;
+
first.which = "first";
second.which = "second";
first.maybeThinking = second.maybeThinking = FALSE;
first.analyzing = second.analyzing = FALSE;
first.initDone = second.initDone = FALSE;
+ /* New features added by Tord: */
+ first.useFEN960 = FALSE; second.useFEN960 = FALSE;
+ first.useOOCastle = TRUE; second.useOOCastle = TRUE;
+ /* End of new features added by Tord. */
+ first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
+ second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
+
if (appData.firstProtocolVersion > PROTOVER ||
appData.firstProtocolVersion < 1) {
char buf[MSG_SIZ];
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;
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,
}
}
+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);
} 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 */
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 &&
}
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 */
}
}
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:
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';
}
}
+/* [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; i<MAX_MOVES; i++ ) {
+ pvInfoList[i].depth = 0;
+ }
+ }
+
switch (gameInfo.variant) {
default:
CopyBoard(boards[0], initialPosition);
break;
case VariantFischeRandom:
CopyBoard(boards[0], initialPosition);
- /* !!shuffle according to FR rules */
+ if( appData.defaultFrcPosition < 0 ) {
+ ShuffleFRC( boards[0] );
+ }
+ else {
+ SetupFRC( boards[0], appData.defaultFrcPosition );
+ }
break;
}
+
if (redraw)
- DrawPosition(FALSE, boards[currentMove]);
+ DrawPosition(TRUE, boards[currentMove]);
}
void
char message[MSG_SIZ];
if (cps->useSetboard) {
- char* fen = PositionToFEN(moveNum);
+ char* fen = PositionToFEN(moveNum, cps->useFEN960);
sprintf(message, "setboard %s\n", fen);
SendToProgram(message, cps);
free(fen);
}
}
+void SendProgramStatsToFrontend( ChessProgramState * cps )
+{
+ SetProgramStats( cps == &first ? 0 : 1,
+ programStats.depth,
+ programStats.nodes,
+ programStats.score,
+ programStats.time,
+ programStats.movelist );
+}
+
void
HandleMachineMove(message, cps)
char *message;
/*
* 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) {
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,
}
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;
}
/*
* Look for thinking output
*/
- if (appData.showThinking) {
+ if ( appData.showThinking) {
int plylev, mvleft, mvtot, curscore, time;
char mvname[MOVE_LEN];
unsigned long nodes;
case AnalyzeFile:
break;
case TwoMachinesPlay:
- if ((cps->twoMachinesColor[0] == 'w') !=
- WhiteOnMove(forwardMostMove)) {
+ if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
ignore = TRUE;
}
break;
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;
"PV is too long; using the first %d bytes.\n",
sizeof(programStats.movelist) - 1);
}
- strncpy(programStats.movelist, buf1,
- sizeof(programStats.movelist));
- programStats.movelist[sizeof(programStats.movelist) - 1]
- = NULLCHAR;
+
+ safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
} else {
sprintf(programStats.movelist, " no PV\n");
}
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 (currentMove == forwardMostMove ||
- gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
+ if( buf1[0] != NULLCHAR ) {
+ unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
+
+ 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();
}
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();
}
programStats.nr_moves = mvtot;
strcpy(programStats.move_name, mvname);
programStats.ok_to_send = 1;
+
+ SendProgramStatsToFrontend( cps );
+
DisplayAnalysis();
return;
} 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();
}
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';
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) {
}
if (instant) return;
+
DisplayMove(currentMove - 1);
DrawPosition(FALSE, boards[currentMove]);
DisplayBothClocks();
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;
if (first.pr != NoProc) {
ExitAnalyzeMode();
+ DoSleep( appData.delayBeforeQuit );
SendToProgram("quit\n", &first);
+ DoSleep( appData.delayAfterQuit );
DestroyChildProcess(first.pr, first.useSigterm);
}
first.pr = NoProc;
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;
if (currentMove >= forwardMostMove) {
gameMode = EditGame;
ModeHighlight();
+
+ /* [AS] Clear current move marker at the end of a game */
+ /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
+
return FALSE;
}
} 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) {
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';
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();
#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)
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;
/* 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 */
PrintPosition(f, currentMove);
fprintf(f, "--------------]\n");
} else {
- fen = PositionToFEN(currentMove);
+ fen = PositionToFEN(currentMove, 1);
fprintf(f, "%s\n", fen);
free(fen);
}
/* 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);
gameMode = EditGame;
ModeHighlight();
HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
+ ClearHighlights(); /* [AS] */
}
/* Pause for `ms' milliseconds */
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);
if (gameMode == EditPosition) return;
if (currentMove <= backwardMostMove) {
ClearHighlights();
- DrawPosition(FALSE, boards[currentMove]);
+ DrawPosition(full_redraw, boards[currentMove]);
return;
}
if (gameMode == PlayFromGameFile && !pausing)
}
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]);
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;
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("-");
break;
case MachinePlaysBlack:
- gameInfo.event = StrSave("Computer chess game");
+ gameInfo.event = StrSave( appData.pgnEventHeader );
gameInfo.site = StrSave(HostName());
gameInfo.date = PGNDate();
gameInfo.round = StrSave("-");
break;
case TwoMachinesPlay:
- gameInfo.event = StrSave("Computer chess game");
+ gameInfo.event = StrSave( appData.pgnEventHeader );
gameInfo.site = StrSave(HostName());
gameInfo.date = PGNDate();
if (matchGame > 0) {
int oldlen, len;
char *old;
+ GetInfoFromComment( index, text );
+
CrushCRs(text);
while (*text == '\n') text++;
len = strlen(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;
"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);
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) {
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;
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) {
DisplayAnalysis()
{
char buf[MSG_SIZ];
+ char lst[MSG_SIZ / 2];
double nps;
static char *xtra[] = { "", " (--)", " (++)" };
int h, m, s, cs;
}
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));
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 {
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);
}
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);
}
{
char title[MSG_SIZ];
+ if( appData.autoDisplayComment ) {
if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
strcpy(title, "Comment");
} else {
}
CommentPopUp(title, text);
+ }
}
/* This routine sends a ^C interrupt to gnuchess, to awaken it if it
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;
if (appData.icsActive) {
whiteTimeRemaining = blackTimeRemaining = 0;
} else {
- whiteTimeRemaining = blackTimeRemaining = timeControl;
+ whiteTimeRemaining = GetTimeControlForWhite();
+ blackTimeRemaining = GetTimeControlForBlack();
}
if (whiteFlag || blackFlag) {
DisplayTitle("");
char *
-PositionToFEN(move)
+PositionToFEN(move, useFEN960)
int move;
+ int useFEN960;
{
int i, j, fromX, fromY, toX, toY;
int whiteToPlay;
*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';
}
if (q == p) *p++ = '-';
*p++ = ' ';
+ }
+
+ /* POP Fabien & Tord */
/* En passant target square */
if (move > backwardMostMove) {
*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;
default:
return FALSE;
}
+
/* !!We ignore the rest of the FEN notation */
return TRUE;
}