2 * backend.c -- Common back end for X and Windows NT versions of
3 * XBoard $Id: backend.c,v 2.6 2003/11/28 09:37:36 mann Exp $
5 * Copyright 1991 by Digital Equipment Corporation, Maynard, Massachusetts.
6 * Enhancements Copyright 1992-2001 Free Software Foundation, Inc.
8 * The following terms apply to Digital Equipment Corporation's copyright
10 * ------------------------------------------------------------------------
13 * Permission to use, copy, modify, and distribute this software and its
14 * documentation for any purpose and without fee is hereby granted,
15 * provided that the above copyright notice appear in all copies and that
16 * both that copyright notice and this permission notice appear in
17 * supporting documentation, and that the name of Digital not be
18 * used in advertising or publicity pertaining to distribution of the
19 * software without specific, written prior permission.
21 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
22 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
23 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
24 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
25 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
26 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
28 * ------------------------------------------------------------------------
30 * The following terms apply to the enhanced version of XBoard distributed
31 * by the Free Software Foundation:
32 * ------------------------------------------------------------------------
33 * This program is free software; you can redistribute it and/or modify
34 * it under the terms of the GNU General Public License as published by
35 * the Free Software Foundation; either version 2 of the License, or
36 * (at your option) any later version.
38 * This program is distributed in the hope that it will be useful,
39 * but WITHOUT ANY WARRANTY; without even the implied warranty of
40 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
41 * GNU General Public License for more details.
43 * You should have received a copy of the GNU General Public License
44 * along with this program; if not, write to the Free Software
45 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
46 * ------------------------------------------------------------------------
48 * See the file ChangeLog for a revision history. */
50 /* [AS] Also useful here for debugging */
54 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
68 #include <sys/types.h>
75 #else /* not STDC_HEADERS */
78 # else /* not HAVE_STRING_H */
80 # endif /* not HAVE_STRING_H */
81 #endif /* not STDC_HEADERS */
84 # include <sys/fcntl.h>
85 #else /* not HAVE_SYS_FCNTL_H */
88 # endif /* HAVE_FCNTL_H */
89 #endif /* not HAVE_SYS_FCNTL_H */
91 #if TIME_WITH_SYS_TIME
92 # include <sys/time.h>
96 # include <sys/time.h>
102 #if defined(_amigados) && !defined(__GNUC__)
107 extern int gettimeofday(struct timeval *, struct timezone *);
115 #include "frontend.h"
122 #include "backendz.h"
124 /* A point in time */
126 long sec; /* Assuming this is >= 32 bits */
127 int ms; /* Assuming this is >= 16 bits */
130 /* Search stats from chessprogram */
132 char movelist[2*MSG_SIZ]; /* Last PV we were sent */
133 int depth; /* Current search depth */
134 int nr_moves; /* Total nr of root moves */
135 int moves_left; /* Moves remaining to be searched */
136 char move_name[MOVE_LEN]; /* Current move being searched, if provided */
137 unsigned long nodes; /* # of nodes searched */
138 int time; /* Search time (centiseconds) */
139 int score; /* Score (centipawns) */
140 int got_only_move; /* If last msg was "(only move)" */
141 int got_fail; /* 0 - nothing, 1 - got "--", 2 - got "++" */
142 int ok_to_send; /* handshaking between send & recv */
143 int line_is_book; /* 1 if movelist is book moves */
144 int seen_stat; /* 1 if we've seen the stat01: line */
147 int establish P((void));
148 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
149 char *buf, int count, int error));
150 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
151 char *buf, int count, int error));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
156 void InitPosition P((int redraw));
157 void HandleMachineMove P((char *message, ChessProgramState *cps));
158 int AutoPlayOneMove P((void));
159 int LoadGameOneMove P((ChessMove readAhead));
160 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
161 int LoadPositionFromFile P((char *filename, int n, char *title));
162 int SavePositionToFile P((char *filename));
163 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
165 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
166 void ShowMove P((int fromX, int fromY, int toX, int toY));
167 void FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
168 /*char*/int promoChar));
169 void BackwardInner P((int target));
170 void ForwardInner P((int target));
171 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
172 void EditPositionDone P((void));
173 void PrintOpponents P((FILE *fp));
174 void PrintPosition P((FILE *fp, int move));
175 void StartChessProgram P((ChessProgramState *cps));
176 void SendToProgram P((char *message, ChessProgramState *cps));
177 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
178 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
179 char *buf, int count, int error));
180 void SendTimeControl P((ChessProgramState *cps,
181 int mps, long tc, int inc, int sd, int st));
182 char *TimeControlTagValue P((void));
183 void Attention P((ChessProgramState *cps));
184 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
185 void ResurrectChessProgram P((void));
186 void DisplayComment P((int moveNumber, char *text));
187 void DisplayMove P((int moveNumber));
188 void DisplayAnalysis P((void));
190 void ParseGameHistory P((char *game));
191 void ParseBoard12 P((char *string));
192 void StartClocks P((void));
193 void SwitchClocks P((void));
194 void StopClocks P((void));
195 void ResetClocks P((void));
196 char *PGNDate P((void));
197 void SetGameInfo P((void));
198 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 void GetTimeMark P((TimeMark *));
208 long SubtractTimeMarks P((TimeMark *, TimeMark *));
209 int CheckFlags P((void));
210 long NextTickLength P((long));
211 void CheckTimeControl P((void));
212 void show_bytes P((FILE *, char *, int));
213 int string_to_rating P((char *str));
214 void ParseFeatures P((char* args, ChessProgramState *cps));
215 void InitBackEnd3 P((void));
216 void FeatureDone P((ChessProgramState* cps, int val));
217 void InitChessProgram P((ChessProgramState *cps));
219 void GetInfoFromComment( int, char * );
221 extern int tinyLayout, smallLayout;
222 static ChessProgramStats programStats;
224 /* States for ics_getting_history */
226 #define H_REQUESTED 1
227 #define H_GOT_REQ_HEADER 2
228 #define H_GOT_UNREQ_HEADER 3
229 #define H_GETTING_MOVES 4
230 #define H_GOT_UNWANTED_HEADER 5
232 /* whosays values for GameEnds */
239 /* Maximum number of games in a cmail message */
240 #define CMAIL_MAX_GAMES 20
242 /* Different types of move when calling RegisterMove */
244 #define CMAIL_RESIGN 1
246 #define CMAIL_ACCEPT 3
248 /* Different types of result to remember for each game */
249 #define CMAIL_NOT_RESULT 0
250 #define CMAIL_OLD_RESULT 1
251 #define CMAIL_NEW_RESULT 2
253 /* Telnet protocol constants */
264 static char * safeStrCpy( char * dst, const char * src, size_t count )
266 assert( dst != NULL );
267 assert( src != NULL );
270 strncpy( dst, src, count );
271 dst[ count-1 ] = '\0';
275 static char * safeStrCat( char * dst, const char * src, size_t count )
279 assert( dst != NULL );
280 assert( src != NULL );
283 dst_len = strlen(dst);
285 assert( count > dst_len ); /* Buffer size must be greater than current length */
287 safeStrCpy( dst + dst_len, src, count - dst_len );
292 /* Fake up flags for now, as we aren't keeping track of castling
297 int flags = F_ALL_CASTLE_OK;
298 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
299 switch (gameInfo.variant) {
301 case VariantGiveaway:
302 flags |= F_IGNORE_CHECK;
303 flags &= ~F_ALL_CASTLE_OK;
306 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
308 case VariantKriegspiel:
309 flags |= F_KRIEGSPIEL_CAPTURE;
311 case VariantNoCastle:
312 flags &= ~F_ALL_CASTLE_OK;
320 FILE *gameFileFP, *debugFP;
323 [AS] Note: sometimes, the sscanf() function is used to parse the input
324 into a fixed-size buffer. Because of this, we must be prepared to
325 receive strings as long as the size of the input buffer, which is currently
326 set to 4K for Windows and 8K for the rest.
327 So, we must either allocate sufficiently large buffers here, or
328 reduce the size of the input buffer in the input reading part.
331 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
332 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
333 char thinkOutput1[MSG_SIZ*10];
335 ChessProgramState first, second;
337 /* premove variables */
340 int premoveFromX = 0;
341 int premoveFromY = 0;
342 int premovePromoChar = 0;
344 Boolean alarmSounded;
345 /* end premove variables */
347 #define ICS_GENERIC 0
350 #define ICS_CHESSNET 3 /* not really supported */
351 int ics_type = ICS_GENERIC;
352 char *ics_prefix = "$";
354 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
355 int pauseExamForwardMostMove = 0;
356 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
357 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
358 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
359 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
360 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
361 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
362 int whiteFlag = FALSE, blackFlag = FALSE;
363 int userOfferedDraw = FALSE;
364 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
365 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
366 int cmailMoveType[CMAIL_MAX_GAMES];
367 long ics_clock_paused = 0;
368 ProcRef icsPR = NoProc, cmailPR = NoProc;
369 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
370 GameMode gameMode = BeginningOfGame;
371 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
372 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
373 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
374 int hiddenThinkOutputState = 0; /* [AS] */
375 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
376 int adjudicateLossPlies = 6;
377 char white_holding[64], black_holding[64];
378 TimeMark lastNodeCountTime;
379 long lastNodeCount=0;
380 int have_sent_ICS_logon = 0;
382 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
383 long timeControl_2; /* [AS] Allow separate time controls */
384 long timeRemaining[2][MAX_MOVES];
386 TimeMark programStartTime;
387 char ics_handle[MSG_SIZ];
388 int have_set_title = 0;
390 /* animateTraining preserves the state of appData.animate
391 * when Training mode is activated. This allows the
392 * response to be animated when appData.animate == TRUE and
393 * appData.animateDragging == TRUE.
395 Boolean animateTraining;
401 Board boards[MAX_MOVES];
402 Board initialPosition = {
403 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
404 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
405 { WhitePawn, WhitePawn, WhitePawn, WhitePawn,
406 WhitePawn, WhitePawn, WhitePawn, WhitePawn },
407 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
408 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
409 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
410 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
411 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
412 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
413 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
414 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
415 { BlackPawn, BlackPawn, BlackPawn, BlackPawn,
416 BlackPawn, BlackPawn, BlackPawn, BlackPawn },
417 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
418 BlackKing, BlackBishop, BlackKnight, BlackRook }
420 Board twoKingsPosition = {
421 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
422 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
423 { WhitePawn, WhitePawn, WhitePawn, WhitePawn,
424 WhitePawn, WhitePawn, WhitePawn, WhitePawn },
425 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
426 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
427 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
428 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
429 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
430 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
431 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
432 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
433 { BlackPawn, BlackPawn, BlackPawn, BlackPawn,
434 BlackPawn, BlackPawn, BlackPawn, BlackPawn },
435 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
436 BlackKing, BlackKing, BlackKnight, BlackRook }
440 /* Convert str to a rating. Checks for special cases of "----",
441 "++++", etc. Also strips ()'s */
443 string_to_rating(str)
446 while(*str && !isdigit(*str)) ++str;
448 return 0; /* One of the special "no rating" cases */
456 /* Init programStats */
457 programStats.movelist[0] = 0;
458 programStats.depth = 0;
459 programStats.nr_moves = 0;
460 programStats.moves_left = 0;
461 programStats.nodes = 0;
462 programStats.time = 100;
463 programStats.score = 0;
464 programStats.got_only_move = 0;
465 programStats.got_fail = 0;
466 programStats.line_is_book = 0;
472 int matched, min, sec;
474 GetTimeMark(&programStartTime);
477 programStats.ok_to_send = 1;
478 programStats.seen_stat = 0;
481 * Initialize game list
487 * Internet chess server status
489 if (appData.icsActive) {
490 appData.matchMode = FALSE;
491 appData.matchGames = 0;
493 appData.noChessProgram = !appData.zippyPlay;
495 appData.zippyPlay = FALSE;
496 appData.zippyTalk = FALSE;
497 appData.noChessProgram = TRUE;
499 if (*appData.icsHelper != NULLCHAR) {
500 appData.useTelnet = TRUE;
501 appData.telnetProgram = appData.icsHelper;
504 appData.zippyTalk = appData.zippyPlay = FALSE;
507 /* [AS] Initialize pv info list */
511 for( i=0; i<MAX_MOVES; i++ ) {
512 pvInfoList[i].depth = 0;
517 * Parse timeControl resource
519 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
520 appData.movesPerSession)) {
522 sprintf(buf, "bad timeControl option %s", appData.timeControl);
523 DisplayFatalError(buf, 0, 2);
527 * Parse searchTime resource
529 if (*appData.searchTime != NULLCHAR) {
530 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
532 searchTime = min * 60;
533 } else if (matched == 2) {
534 searchTime = min * 60 + sec;
537 sprintf(buf, "bad searchTime option %s", appData.searchTime);
538 DisplayFatalError(buf, 0, 2);
542 /* [AS] Adjudication threshold */
543 adjudicateLossThreshold = appData.adjudicateLossThreshold;
545 first.which = "first";
546 second.which = "second";
547 first.maybeThinking = second.maybeThinking = FALSE;
548 first.pr = second.pr = NoProc;
549 first.isr = second.isr = NULL;
550 first.sendTime = second.sendTime = 2;
551 first.sendDrawOffers = 1;
552 if (appData.firstPlaysBlack) {
553 first.twoMachinesColor = "black\n";
554 second.twoMachinesColor = "white\n";
556 first.twoMachinesColor = "white\n";
557 second.twoMachinesColor = "black\n";
559 first.program = appData.firstChessProgram;
560 second.program = appData.secondChessProgram;
561 first.host = appData.firstHost;
562 second.host = appData.secondHost;
563 first.dir = appData.firstDirectory;
564 second.dir = appData.secondDirectory;
565 first.other = &second;
566 second.other = &first;
567 first.initString = appData.initString;
568 second.initString = appData.secondInitString;
569 first.computerString = appData.firstComputerString;
570 second.computerString = appData.secondComputerString;
571 first.useSigint = second.useSigint = TRUE;
572 first.useSigterm = second.useSigterm = TRUE;
573 first.reuse = appData.reuseFirst;
574 second.reuse = appData.reuseSecond;
575 first.useSetboard = second.useSetboard = FALSE;
576 first.useSAN = second.useSAN = FALSE;
577 first.usePing = second.usePing = FALSE;
578 first.lastPing = second.lastPing = 0;
579 first.lastPong = second.lastPong = 0;
580 first.usePlayother = second.usePlayother = FALSE;
581 first.useColors = second.useColors = TRUE;
582 first.useUsermove = second.useUsermove = FALSE;
583 first.sendICS = second.sendICS = FALSE;
584 first.sendName = second.sendName = appData.icsActive;
585 first.sdKludge = second.sdKludge = FALSE;
586 first.stKludge = second.stKludge = FALSE;
587 TidyProgramName(first.program, first.host, first.tidy);
588 TidyProgramName(second.program, second.host, second.tidy);
589 first.matchWins = second.matchWins = 0;
590 strcpy(first.variants, appData.variant);
591 strcpy(second.variants, appData.variant);
592 first.analysisSupport = second.analysisSupport = 2; /* detect */
593 first.analyzing = second.analyzing = FALSE;
594 first.initDone = second.initDone = FALSE;
596 /* New features added by Tord: */
597 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
598 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
599 /* End of new features added by Tord. */
600 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
601 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
603 if (appData.firstProtocolVersion > PROTOVER ||
604 appData.firstProtocolVersion < 1) {
606 sprintf(buf, "protocol version %d not supported",
607 appData.firstProtocolVersion);
608 DisplayFatalError(buf, 0, 2);
610 first.protocolVersion = appData.firstProtocolVersion;
613 if (appData.secondProtocolVersion > PROTOVER ||
614 appData.secondProtocolVersion < 1) {
616 sprintf(buf, "protocol version %d not supported",
617 appData.secondProtocolVersion);
618 DisplayFatalError(buf, 0, 2);
620 second.protocolVersion = appData.secondProtocolVersion;
623 if (appData.icsActive) {
624 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
625 } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
626 appData.clockMode = FALSE;
627 first.sendTime = second.sendTime = 0;
631 /* Override some settings from environment variables, for backward
632 compatibility. Unfortunately it's not feasible to have the env
633 vars just set defaults, at least in xboard. Ugh.
635 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
640 if (appData.noChessProgram) {
641 programVersion = (char*) malloc(5 + strlen(PRODUCT) + strlen(VERSION)
642 + strlen(PATCHLEVEL));
643 sprintf(programVersion, "%s %s.%s", PRODUCT, VERSION, PATCHLEVEL);
647 while (*q != ' ' && *q != NULLCHAR) q++;
649 while (p > first.program && *(p-1) != '/') p--;
650 programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)
651 + strlen(PATCHLEVEL) + (q - p));
652 sprintf(programVersion, "%s %s.%s + ", PRODUCT, VERSION, PATCHLEVEL);
653 strncat(programVersion, p, q - p);
656 if (!appData.icsActive) {
658 /* Check for variants that are supported only in ICS mode,
659 or not at all. Some that are accepted here nevertheless
660 have bugs; see comments below.
662 VariantClass variant = StringToVariant(appData.variant);
664 case VariantBughouse: /* need four players and two boards */
665 case VariantKriegspiel: /* need to hide pieces and move details */
666 /* case VariantFischeRandom: (Fabien: moved below) */
667 sprintf(buf, "Variant %s supported only in ICS mode", appData.variant);
668 DisplayFatalError(buf, 0, 2);
672 case VariantLoadable:
682 sprintf(buf, "Unknown variant name %s", appData.variant);
683 DisplayFatalError(buf, 0, 2);
686 case VariantNormal: /* definitely works! */
687 case VariantWildCastle: /* pieces not automatically shuffled */
688 case VariantNoCastle: /* pieces not automatically shuffled */
689 case VariantFischeRandom: /* Fabien: pieces not automatically shuffled */
690 case VariantCrazyhouse: /* holdings not shown,
691 offboard interposition not understood */
692 case VariantLosers: /* should work except for win condition,
693 and doesn't know captures are mandatory */
694 case VariantSuicide: /* should work except for win condition,
695 and doesn't know captures are mandatory */
696 case VariantGiveaway: /* should work except for win condition,
697 and doesn't know captures are mandatory */
698 case VariantTwoKings: /* should work */
699 case VariantAtomic: /* should work except for win condition */
700 case Variant3Check: /* should work except for win condition */
701 case VariantShatranj: /* might work if TestLegality is off */
707 int NextIntegerFromString( char ** str, long * value )
712 while( *s == ' ' || *s == '\t' ) {
718 if( *s >= '0' && *s <= '9' ) {
719 while( *s >= '0' && *s <= '9' ) {
720 *value = *value * 10 + (*s - '0');
732 int NextTimeControlFromString( char ** str, long * value )
735 int result = NextIntegerFromString( str, &temp );
738 *value = temp * 60; /* Minutes */
741 result = NextIntegerFromString( str, &temp );
742 *value += temp; /* Seconds */
749 int GetTimeControlForWhite()
751 int result = timeControl;
756 int GetTimeControlForBlack()
758 int result = timeControl;
760 if( timeControl_2 > 0 ) {
761 result = timeControl_2;
768 ParseTimeControl(tc, ti, mps)
774 int matched, min, sec;
776 matched = sscanf(tc, "%d:%d", &min, &sec);
778 timeControl = min * 60 * 1000;
779 } else if (matched == 2) {
780 timeControl = (min * 60 + sec) * 1000;
788 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
793 /* Parse second time control */
796 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
804 timeControl_2 = tc2 * 1000;
814 timeControl = tc1 * 1000;
818 timeIncrement = ti * 1000; /* convert to ms */
822 movesPerSession = mps;
830 if (appData.debugMode) {
831 fprintf(debugFP, "%s\n", programVersion);
834 if (appData.matchGames > 0) {
835 appData.matchMode = TRUE;
836 } else if (appData.matchMode) {
837 appData.matchGames = 1;
840 if (appData.noChessProgram || first.protocolVersion == 1) {
843 /* kludge: allow timeout for initial "feature" commands */
845 DisplayMessage("", "Starting chess program");
846 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
851 InitBackEnd3 P((void))
853 GameMode initialMode;
857 InitChessProgram(&first);
859 if (appData.icsActive) {
862 if (*appData.icsCommPort != NULLCHAR) {
863 sprintf(buf, "Could not open comm port %s",
864 appData.icsCommPort);
866 sprintf(buf, "Could not connect to host %s, port %s",
867 appData.icsHost, appData.icsPort);
869 DisplayFatalError(buf, err, 1);
874 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
876 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
877 } else if (appData.noChessProgram) {
883 if (*appData.cmailGameName != NULLCHAR) {
885 OpenLoopback(&cmailPR);
887 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
891 DisplayMessage("", "");
892 if (StrCaseCmp(appData.initialMode, "") == 0) {
893 initialMode = BeginningOfGame;
894 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
895 initialMode = TwoMachinesPlay;
896 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
897 initialMode = AnalyzeFile;
898 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
899 initialMode = AnalyzeMode;
900 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
901 initialMode = MachinePlaysWhite;
902 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
903 initialMode = MachinePlaysBlack;
904 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
905 initialMode = EditGame;
906 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
907 initialMode = EditPosition;
908 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
909 initialMode = Training;
911 sprintf(buf, "Unknown initialMode %s", appData.initialMode);
912 DisplayFatalError(buf, 0, 2);
916 if (appData.matchMode) {
917 /* Set up machine vs. machine match */
918 if (appData.noChessProgram) {
919 DisplayFatalError("Can't have a match with no chess programs",
925 if (*appData.loadGameFile != NULLCHAR) {
926 if (!LoadGameFromFile(appData.loadGameFile,
927 appData.loadGameIndex,
928 appData.loadGameFile, FALSE)) {
929 DisplayFatalError("Bad game file", 0, 1);
932 } else if (*appData.loadPositionFile != NULLCHAR) {
933 if (!LoadPositionFromFile(appData.loadPositionFile,
934 appData.loadPositionIndex,
935 appData.loadPositionFile)) {
936 DisplayFatalError("Bad position file", 0, 1);
941 } else if (*appData.cmailGameName != NULLCHAR) {
942 /* Set up cmail mode */
943 ReloadCmailMsgEvent(TRUE);
945 /* Set up other modes */
946 if (initialMode == AnalyzeFile) {
947 if (*appData.loadGameFile == NULLCHAR) {
948 DisplayFatalError("AnalyzeFile mode requires a game file", 0, 1);
952 if (*appData.loadGameFile != NULLCHAR) {
953 (void) LoadGameFromFile(appData.loadGameFile,
954 appData.loadGameIndex,
955 appData.loadGameFile, TRUE);
956 } else if (*appData.loadPositionFile != NULLCHAR) {
957 (void) LoadPositionFromFile(appData.loadPositionFile,
958 appData.loadPositionIndex,
959 appData.loadPositionFile);
961 if (initialMode == AnalyzeMode) {
962 if (appData.noChessProgram) {
963 DisplayFatalError("Analysis mode requires a chess engine", 0, 2);
966 if (appData.icsActive) {
967 DisplayFatalError("Analysis mode does not work with ICS mode",0,2);
971 } else if (initialMode == AnalyzeFile) {
972 ShowThinkingEvent(TRUE);
974 AnalysisPeriodicEvent(1);
975 } else if (initialMode == MachinePlaysWhite) {
976 if (appData.noChessProgram) {
977 DisplayFatalError("MachineWhite mode requires a chess engine",
981 if (appData.icsActive) {
982 DisplayFatalError("MachineWhite mode does not work with ICS mode",
987 } else if (initialMode == MachinePlaysBlack) {
988 if (appData.noChessProgram) {
989 DisplayFatalError("MachineBlack mode requires a chess engine",
993 if (appData.icsActive) {
994 DisplayFatalError("MachineBlack mode does not work with ICS mode",
999 } else if (initialMode == TwoMachinesPlay) {
1000 if (appData.noChessProgram) {
1001 DisplayFatalError("TwoMachines mode requires a chess engine",
1005 if (appData.icsActive) {
1006 DisplayFatalError("TwoMachines mode does not work with ICS mode",
1011 } else if (initialMode == EditGame) {
1013 } else if (initialMode == EditPosition) {
1014 EditPositionEvent();
1015 } else if (initialMode == Training) {
1016 if (*appData.loadGameFile == NULLCHAR) {
1017 DisplayFatalError("Training mode requires a game file", 0, 2);
1026 * Establish will establish a contact to a remote host.port.
1027 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1028 * used to talk to the host.
1029 * Returns 0 if okay, error code if not.
1036 if (*appData.icsCommPort != NULLCHAR) {
1037 /* Talk to the host through a serial comm port */
1038 return OpenCommPort(appData.icsCommPort, &icsPR);
1040 } else if (*appData.gateway != NULLCHAR) {
1041 if (*appData.remoteShell == NULLCHAR) {
1042 /* Use the rcmd protocol to run telnet program on a gateway host */
1043 sprintf(buf, "%s %s %s",
1044 appData.telnetProgram, appData.icsHost, appData.icsPort);
1045 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1048 /* Use the rsh program to run telnet program on a gateway host */
1049 if (*appData.remoteUser == NULLCHAR) {
1050 sprintf(buf, "%s %s %s %s %s", appData.remoteShell,
1051 appData.gateway, appData.telnetProgram,
1052 appData.icsHost, appData.icsPort);
1054 sprintf(buf, "%s %s -l %s %s %s %s",
1055 appData.remoteShell, appData.gateway,
1056 appData.remoteUser, appData.telnetProgram,
1057 appData.icsHost, appData.icsPort);
1059 return StartChildProcess(buf, "", &icsPR);
1062 } else if (appData.useTelnet) {
1063 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1066 /* TCP socket interface differs somewhat between
1067 Unix and NT; handle details in the front end.
1069 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1074 show_bytes(fp, buf, count)
1080 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1081 fprintf(fp, "\\%03o", *buf & 0xff);
1090 /* Returns an errno value */
1092 OutputMaybeTelnet(pr, message, count, outError)
1098 char buf[8192], *p, *q, *buflim;
1099 int left, newcount, outcount;
1101 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1102 *appData.gateway != NULLCHAR) {
1103 if (appData.debugMode) {
1104 fprintf(debugFP, ">ICS: ");
1105 show_bytes(debugFP, message, count);
1106 fprintf(debugFP, "\n");
1108 return OutputToProcess(pr, message, count, outError);
1111 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1118 if (appData.debugMode) {
1119 fprintf(debugFP, ">ICS: ");
1120 show_bytes(debugFP, buf, newcount);
1121 fprintf(debugFP, "\n");
1123 outcount = OutputToProcess(pr, buf, newcount, outError);
1124 if (outcount < newcount) return -1; /* to be sure */
1131 } else if (((unsigned char) *p) == TN_IAC) {
1132 *q++ = (char) TN_IAC;
1139 if (appData.debugMode) {
1140 fprintf(debugFP, ">ICS: ");
1141 show_bytes(debugFP, buf, newcount);
1142 fprintf(debugFP, "\n");
1144 outcount = OutputToProcess(pr, buf, newcount, outError);
1145 if (outcount < newcount) return -1; /* to be sure */
1150 read_from_player(isr, closure, message, count, error)
1157 int outError, outCount;
1158 static int gotEof = 0;
1160 /* Pass data read from player on to ICS */
1163 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1164 if (outCount < count) {
1165 DisplayFatalError("Error writing to ICS", outError, 1);
1167 } else if (count < 0) {
1168 RemoveInputSource(isr);
1169 DisplayFatalError("Error reading from keyboard", error, 1);
1170 } else if (gotEof++ > 0) {
1171 RemoveInputSource(isr);
1172 DisplayFatalError("Got end of file from keyboard", 0, 0);
1180 int count, outCount, outError;
1182 if (icsPR == NULL) return;
1185 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1186 if (outCount < count) {
1187 DisplayFatalError("Error writing to ICS", outError, 1);
1191 /* This is used for sending logon scripts to the ICS. Sending
1192 without a delay causes problems when using timestamp on ICC
1193 (at least on my machine). */
1195 SendToICSDelayed(s,msdelay)
1199 int count, outCount, outError;
1201 if (icsPR == NULL) return;
1204 if (appData.debugMode) {
1205 fprintf(debugFP, ">ICS: ");
1206 show_bytes(debugFP, s, count);
1207 fprintf(debugFP, "\n");
1209 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1211 if (outCount < count) {
1212 DisplayFatalError("Error writing to ICS", outError, 1);
1217 /* Remove all highlighting escape sequences in s
1218 Also deletes any suffix starting with '('
1221 StripHighlightAndTitle(s)
1224 static char retbuf[MSG_SIZ];
1227 while (*s != NULLCHAR) {
1228 while (*s == '\033') {
1229 while (*s != NULLCHAR && !isalpha(*s)) s++;
1230 if (*s != NULLCHAR) s++;
1232 while (*s != NULLCHAR && *s != '\033') {
1233 if (*s == '(' || *s == '[') {
1244 /* Remove all highlighting escape sequences in s */
1249 static char retbuf[MSG_SIZ];
1252 while (*s != NULLCHAR) {
1253 while (*s == '\033') {
1254 while (*s != NULLCHAR && !isalpha(*s)) s++;
1255 if (*s != NULLCHAR) s++;
1257 while (*s != NULLCHAR && *s != '\033') {
1265 char *variantNames[] = VARIANT_NAMES;
1270 return variantNames[v];
1274 /* Identify a variant from the strings the chess servers use or the
1275 PGN Variant tag names we use. */
1282 VariantClass v = VariantNormal;
1283 int i, found = FALSE;
1288 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1289 if (StrCaseStr(e, variantNames[i])) {
1290 v = (VariantClass) i;
1297 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1298 || StrCaseStr(e, "wild/fr")) {
1299 v = VariantFischeRandom;
1300 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1301 (i = 1, p = StrCaseStr(e, "w"))) {
1303 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1310 case 0: /* FICS only, actually */
1312 /* Castling legal even if K starts on d-file */
1313 v = VariantWildCastle;
1318 /* Castling illegal even if K & R happen to start in
1319 normal positions. */
1320 v = VariantNoCastle;
1333 /* Castling legal iff K & R start in normal positions */
1339 /* Special wilds for position setup; unclear what to do here */
1340 v = VariantLoadable;
1343 /* Bizarre ICC game */
1344 v = VariantTwoKings;
1347 v = VariantKriegspiel;
1353 v = VariantFischeRandom;
1356 v = VariantCrazyhouse;
1359 v = VariantBughouse;
1365 /* Not quite the same as FICS suicide! */
1366 v = VariantGiveaway;
1372 v = VariantShatranj;
1375 /* Temporary names for future ICC types. The name *will* change in
1376 the next xboard/WinBoard release after ICC defines it. */
1403 /* Found "wild" or "w" in the string but no number;
1404 must assume it's normal chess. */
1408 sprintf(buf, "Unknown wild type %d", wnum);
1409 DisplayError(buf, 0);
1415 if (appData.debugMode) {
1416 fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
1417 e, wnum, VariantName(v));
1422 static int leftover_start = 0, leftover_len = 0;
1423 char star_match[STAR_MATCH_N][MSG_SIZ];
1425 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1426 advance *index beyond it, and set leftover_start to the new value of
1427 *index; else return FALSE. If pattern contains the character '*', it
1428 matches any sequence of characters not containing '\r', '\n', or the
1429 character following the '*' (if any), and the matched sequence(s) are
1430 copied into star_match.
1433 looking_at(buf, index, pattern)
1438 char *bufp = &buf[*index], *patternp = pattern;
1440 char *matchp = star_match[0];
1443 if (*patternp == NULLCHAR) {
1444 *index = leftover_start = bufp - buf;
1448 if (*bufp == NULLCHAR) return FALSE;
1449 if (*patternp == '*') {
1450 if (*bufp == *(patternp + 1)) {
1452 matchp = star_match[++star_count];
1456 } else if (*bufp == '\n' || *bufp == '\r') {
1458 if (*patternp == NULLCHAR)
1463 *matchp++ = *bufp++;
1467 if (*patternp != *bufp) return FALSE;
1474 SendToPlayer(data, length)
1478 int error, outCount;
1479 outCount = OutputToProcess(NoProc, data, length, &error);
1480 if (outCount < length) {
1481 DisplayFatalError("Error writing to display", error, 1);
1486 PackHolding(packed, holding)
1498 switch (runlength) {
1509 sprintf(q, "%d", runlength);
1521 /* Telnet protocol requests from the front end */
1523 TelnetRequest(ddww, option)
1524 unsigned char ddww, option;
1526 unsigned char msg[3];
1527 int outCount, outError;
1529 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1531 if (appData.debugMode) {
1532 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1548 sprintf(buf1, "%d", ddww);
1557 sprintf(buf2, "%d", option);
1560 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1565 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1567 DisplayFatalError("Error writing to ICS", outError, 1);
1574 if (!appData.icsActive) return;
1575 TelnetRequest(TN_DO, TN_ECHO);
1581 if (!appData.icsActive) return;
1582 TelnetRequest(TN_DONT, TN_ECHO);
1585 static int loggedOn = FALSE;
1587 /*-- Game start info cache: --*/
1589 char gs_kind[MSG_SIZ];
1590 static char player1Name[128] = "";
1591 static char player2Name[128] = "";
1592 static int player1Rating = -1;
1593 static int player2Rating = -1;
1594 /*----------------------------*/
1597 read_from_ics(isr, closure, data, count, error)
1604 #define BUF_SIZE 8192
1605 #define STARTED_NONE 0
1606 #define STARTED_MOVES 1
1607 #define STARTED_BOARD 2
1608 #define STARTED_OBSERVE 3
1609 #define STARTED_HOLDINGS 4
1610 #define STARTED_CHATTER 5
1611 #define STARTED_COMMENT 6
1612 #define STARTED_MOVES_NOHIDE 7
1614 static int started = STARTED_NONE;
1615 static char parse[20000];
1616 static int parse_pos = 0;
1617 static char buf[BUF_SIZE + 1];
1618 static int firstTime = TRUE, intfSet = FALSE;
1619 static ColorClass curColor = ColorNormal;
1620 static ColorClass prevColor = ColorNormal;
1621 static int savingComment = FALSE;
1630 if (appData.debugMode) {
1632 fprintf(debugFP, "<ICS: ");
1633 show_bytes(debugFP, data, count);
1634 fprintf(debugFP, "\n");
1640 /* If last read ended with a partial line that we couldn't parse,
1641 prepend it to the new read and try again. */
1642 if (leftover_len > 0) {
1643 for (i=0; i<leftover_len; i++)
1644 buf[i] = buf[leftover_start + i];
1647 /* Copy in new characters, removing nulls and \r's */
1648 buf_len = leftover_len;
1649 for (i = 0; i < count; i++) {
1650 if (data[i] != NULLCHAR && data[i] != '\r')
1651 buf[buf_len++] = data[i];
1654 buf[buf_len] = NULLCHAR;
1655 next_out = leftover_len;
1659 while (i < buf_len) {
1660 /* Deal with part of the TELNET option negotiation
1661 protocol. We refuse to do anything beyond the
1662 defaults, except that we allow the WILL ECHO option,
1663 which ICS uses to turn off password echoing when we are
1664 directly connected to it. We reject this option
1665 if localLineEditing mode is on (always on in xboard)
1666 and we are talking to port 23, which might be a real
1667 telnet server that will try to keep WILL ECHO on permanently.
1669 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
1670 static int remoteEchoOption = FALSE; /* telnet ECHO option */
1671 unsigned char option;
1673 switch ((unsigned char) buf[++i]) {
1675 if (appData.debugMode)
1676 fprintf(debugFP, "\n<WILL ");
1677 switch (option = (unsigned char) buf[++i]) {
1679 if (appData.debugMode)
1680 fprintf(debugFP, "ECHO ");
1681 /* Reply only if this is a change, according
1682 to the protocol rules. */
1683 if (remoteEchoOption) break;
1684 if (appData.localLineEditing &&
1685 atoi(appData.icsPort) == TN_PORT) {
1686 TelnetRequest(TN_DONT, TN_ECHO);
1689 TelnetRequest(TN_DO, TN_ECHO);
1690 remoteEchoOption = TRUE;
1694 if (appData.debugMode)
1695 fprintf(debugFP, "%d ", option);
1696 /* Whatever this is, we don't want it. */
1697 TelnetRequest(TN_DONT, option);
1702 if (appData.debugMode)
1703 fprintf(debugFP, "\n<WONT ");
1704 switch (option = (unsigned char) buf[++i]) {
1706 if (appData.debugMode)
1707 fprintf(debugFP, "ECHO ");
1708 /* Reply only if this is a change, according
1709 to the protocol rules. */
1710 if (!remoteEchoOption) break;
1712 TelnetRequest(TN_DONT, TN_ECHO);
1713 remoteEchoOption = FALSE;
1716 if (appData.debugMode)
1717 fprintf(debugFP, "%d ", (unsigned char) option);
1718 /* Whatever this is, it must already be turned
1719 off, because we never agree to turn on
1720 anything non-default, so according to the
1721 protocol rules, we don't reply. */
1726 if (appData.debugMode)
1727 fprintf(debugFP, "\n<DO ");
1728 switch (option = (unsigned char) buf[++i]) {
1730 /* Whatever this is, we refuse to do it. */
1731 if (appData.debugMode)
1732 fprintf(debugFP, "%d ", option);
1733 TelnetRequest(TN_WONT, option);
1738 if (appData.debugMode)
1739 fprintf(debugFP, "\n<DONT ");
1740 switch (option = (unsigned char) buf[++i]) {
1742 if (appData.debugMode)
1743 fprintf(debugFP, "%d ", option);
1744 /* Whatever this is, we are already not doing
1745 it, because we never agree to do anything
1746 non-default, so according to the protocol
1747 rules, we don't reply. */
1752 if (appData.debugMode)
1753 fprintf(debugFP, "\n<IAC ");
1754 /* Doubled IAC; pass it through */
1758 if (appData.debugMode)
1759 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
1760 /* Drop all other telnet commands on the floor */
1763 if (oldi > next_out)
1764 SendToPlayer(&buf[next_out], oldi - next_out);
1770 /* OK, this at least will *usually* work */
1771 if (!loggedOn && looking_at(buf, &i, "ics%")) {
1775 if (loggedOn && !intfSet) {
1776 if (ics_type == ICS_ICC) {
1778 "/set-quietly interface %s\n/set-quietly style 12\n",
1781 } else if (ics_type == ICS_CHESSNET) {
1782 sprintf(str, "/style 12\n");
1784 strcpy(str, "alias $ @\n$set interface ");
1785 strcat(str, programVersion);
1786 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
1788 strcat(str, "$iset nohighlight 1\n");
1790 strcat(str, "$iset lock 1\n$style 12\n");
1796 if (started == STARTED_COMMENT) {
1797 /* Accumulate characters in comment */
1798 parse[parse_pos++] = buf[i];
1799 if (buf[i] == '\n') {
1800 parse[parse_pos] = NULLCHAR;
1801 AppendComment(forwardMostMove, StripHighlight(parse));
1802 started = STARTED_NONE;
1804 /* Don't match patterns against characters in chatter */
1809 if (started == STARTED_CHATTER) {
1810 if (buf[i] != '\n') {
1811 /* Don't match patterns against characters in chatter */
1815 started = STARTED_NONE;
1818 /* Kludge to deal with rcmd protocol */
1819 if (firstTime && looking_at(buf, &i, "\001*")) {
1820 DisplayFatalError(&buf[1], 0, 1);
1826 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
1829 if (appData.debugMode)
1830 fprintf(debugFP, "ics_type %d\n", ics_type);
1833 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
1834 ics_type = ICS_FICS;
1836 if (appData.debugMode)
1837 fprintf(debugFP, "ics_type %d\n", ics_type);
1840 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
1841 ics_type = ICS_CHESSNET;
1843 if (appData.debugMode)
1844 fprintf(debugFP, "ics_type %d\n", ics_type);
1849 (looking_at(buf, &i, "\"*\" is *a registered name") ||
1850 looking_at(buf, &i, "Logging you in as \"*\"") ||
1851 looking_at(buf, &i, "will be \"*\""))) {
1852 strcpy(ics_handle, star_match[0]);
1856 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
1858 sprintf(buf, "%s@%s", ics_handle, appData.icsHost);
1859 DisplayIcsInteractionTitle(buf);
1860 have_set_title = TRUE;
1863 /* skip finger notes */
1864 if (started == STARTED_NONE &&
1865 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
1866 (buf[i] == '1' && buf[i+1] == '0')) &&
1867 buf[i+2] == ':' && buf[i+3] == ' ') {
1868 started = STARTED_CHATTER;
1873 /* skip formula vars */
1874 if (started == STARTED_NONE &&
1875 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
1876 started = STARTED_CHATTER;
1882 if (appData.zippyTalk || appData.zippyPlay) {
1884 if (ZippyControl(buf, &i) ||
1885 ZippyConverse(buf, &i) ||
1886 (appData.zippyPlay && ZippyMatch(buf, &i))) {
1892 if (/* Don't color "message" or "messages" output */
1893 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
1894 looking_at(buf, &i, "*. * at *:*: ") ||
1895 looking_at(buf, &i, "--* (*:*): ") ||
1896 /* Regular tells and says */
1897 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
1898 looking_at(buf, &i, "* (your partner) tells you: ") ||
1899 looking_at(buf, &i, "* says: ") ||
1900 /* Message notifications (same color as tells) */
1901 looking_at(buf, &i, "* has left a message ") ||
1902 looking_at(buf, &i, "* just sent you a message:\n") ||
1903 /* Whispers and kibitzes */
1904 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
1905 looking_at(buf, &i, "* kibitzes: ") ||
1907 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
1909 if (tkind == 1 && strchr(star_match[0], ':')) {
1910 /* Avoid "tells you:" spoofs in channels */
1913 if (star_match[0][0] == NULLCHAR ||
1914 strchr(star_match[0], ' ') ||
1915 (tkind == 3 && strchr(star_match[1], ' '))) {
1916 /* Reject bogus matches */
1919 if (appData.colorize) {
1920 if (oldi > next_out) {
1921 SendToPlayer(&buf[next_out], oldi - next_out);
1926 Colorize(ColorTell, FALSE);
1927 curColor = ColorTell;
1930 Colorize(ColorKibitz, FALSE);
1931 curColor = ColorKibitz;
1934 p = strrchr(star_match[1], '(');
1941 Colorize(ColorChannel1, FALSE);
1942 curColor = ColorChannel1;
1944 Colorize(ColorChannel, FALSE);
1945 curColor = ColorChannel;
1949 curColor = ColorNormal;
1953 if (started == STARTED_NONE && appData.autoComment &&
1954 (gameMode == IcsObserving ||
1955 gameMode == IcsPlayingWhite ||
1956 gameMode == IcsPlayingBlack)) {
1957 parse_pos = i - oldi;
1958 memcpy(parse, &buf[oldi], parse_pos);
1959 parse[parse_pos] = NULLCHAR;
1960 started = STARTED_COMMENT;
1961 savingComment = TRUE;
1963 started = STARTED_CHATTER;
1964 savingComment = FALSE;
1971 if (looking_at(buf, &i, "* s-shouts: ") ||
1972 looking_at(buf, &i, "* c-shouts: ")) {
1973 if (appData.colorize) {
1974 if (oldi > next_out) {
1975 SendToPlayer(&buf[next_out], oldi - next_out);
1978 Colorize(ColorSShout, FALSE);
1979 curColor = ColorSShout;
1982 started = STARTED_CHATTER;
1986 if (looking_at(buf, &i, "--->")) {
1991 if (looking_at(buf, &i, "* shouts: ") ||
1992 looking_at(buf, &i, "--> ")) {
1993 if (appData.colorize) {
1994 if (oldi > next_out) {
1995 SendToPlayer(&buf[next_out], oldi - next_out);
1998 Colorize(ColorShout, FALSE);
1999 curColor = ColorShout;
2002 started = STARTED_CHATTER;
2006 if (looking_at( buf, &i, "Challenge:")) {
2007 if (appData.colorize) {
2008 if (oldi > next_out) {
2009 SendToPlayer(&buf[next_out], oldi - next_out);
2012 Colorize(ColorChallenge, FALSE);
2013 curColor = ColorChallenge;
2019 if (looking_at(buf, &i, "* offers you") ||
2020 looking_at(buf, &i, "* offers to be") ||
2021 looking_at(buf, &i, "* would like to") ||
2022 looking_at(buf, &i, "* requests to") ||
2023 looking_at(buf, &i, "Your opponent offers") ||
2024 looking_at(buf, &i, "Your opponent requests")) {
2026 if (appData.colorize) {
2027 if (oldi > next_out) {
2028 SendToPlayer(&buf[next_out], oldi - next_out);
2031 Colorize(ColorRequest, FALSE);
2032 curColor = ColorRequest;
2037 if (looking_at(buf, &i, "* (*) seeking")) {
2038 if (appData.colorize) {
2039 if (oldi > next_out) {
2040 SendToPlayer(&buf[next_out], oldi - next_out);
2043 Colorize(ColorSeek, FALSE);
2044 curColor = ColorSeek;
2050 if (looking_at(buf, &i, "\\ ")) {
2051 if (prevColor != ColorNormal) {
2052 if (oldi > next_out) {
2053 SendToPlayer(&buf[next_out], oldi - next_out);
2056 Colorize(prevColor, TRUE);
2057 curColor = prevColor;
2059 if (savingComment) {
2060 parse_pos = i - oldi;
2061 memcpy(parse, &buf[oldi], parse_pos);
2062 parse[parse_pos] = NULLCHAR;
2063 started = STARTED_COMMENT;
2065 started = STARTED_CHATTER;
2070 if (looking_at(buf, &i, "Black Strength :") ||
2071 looking_at(buf, &i, "<<< style 10 board >>>") ||
2072 looking_at(buf, &i, "<10>") ||
2073 looking_at(buf, &i, "#@#")) {
2074 /* Wrong board style */
2076 SendToICS(ics_prefix);
2077 SendToICS("set style 12\n");
2078 SendToICS(ics_prefix);
2079 SendToICS("refresh\n");
2083 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2085 have_sent_ICS_logon = 1;
2089 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2090 (looking_at(buf, &i, "\n<12> ") ||
2091 looking_at(buf, &i, "<12> "))) {
2093 if (oldi > next_out) {
2094 SendToPlayer(&buf[next_out], oldi - next_out);
2097 started = STARTED_BOARD;
2102 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2103 looking_at(buf, &i, "<b1> ")) {
2104 if (oldi > next_out) {
2105 SendToPlayer(&buf[next_out], oldi - next_out);
2108 started = STARTED_HOLDINGS;
2113 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2115 /* Header for a move list -- first line */
2117 switch (ics_getting_history) {
2121 case BeginningOfGame:
2122 /* User typed "moves" or "oldmoves" while we
2123 were idle. Pretend we asked for these
2124 moves and soak them up so user can step
2125 through them and/or save them.
2128 gameMode = IcsObserving;
2131 ics_getting_history = H_GOT_UNREQ_HEADER;
2133 case EditGame: /*?*/
2134 case EditPosition: /*?*/
2135 /* Should above feature work in these modes too? */
2136 /* For now it doesn't */
2137 ics_getting_history = H_GOT_UNWANTED_HEADER;
2140 ics_getting_history = H_GOT_UNWANTED_HEADER;
2145 /* Is this the right one? */
2146 if (gameInfo.white && gameInfo.black &&
2147 strcmp(gameInfo.white, star_match[0]) == 0 &&
2148 strcmp(gameInfo.black, star_match[2]) == 0) {
2150 ics_getting_history = H_GOT_REQ_HEADER;
2153 case H_GOT_REQ_HEADER:
2154 case H_GOT_UNREQ_HEADER:
2155 case H_GOT_UNWANTED_HEADER:
2156 case H_GETTING_MOVES:
2157 /* Should not happen */
2158 DisplayError("Error gathering move list: two headers", 0);
2159 ics_getting_history = H_FALSE;
2163 /* Save player ratings into gameInfo if needed */
2164 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2165 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2166 (gameInfo.whiteRating == -1 ||
2167 gameInfo.blackRating == -1)) {
2169 gameInfo.whiteRating = string_to_rating(star_match[1]);
2170 gameInfo.blackRating = string_to_rating(star_match[3]);
2171 if (appData.debugMode)
2172 fprintf(debugFP, "Ratings from header: W %d, B %d\n",
2173 gameInfo.whiteRating, gameInfo.blackRating);
2178 if (looking_at(buf, &i,
2179 "* * match, initial time: * minute*, increment: * second")) {
2180 /* Header for a move list -- second line */
2181 /* Initial board will follow if this is a wild game */
2183 if (gameInfo.event != NULL) free(gameInfo.event);
2184 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2185 gameInfo.event = StrSave(str);
2186 gameInfo.variant = StringToVariant(gameInfo.event);
2190 if (looking_at(buf, &i, "Move ")) {
2191 /* Beginning of a move list */
2192 switch (ics_getting_history) {
2194 /* Normally should not happen */
2195 /* Maybe user hit reset while we were parsing */
2198 /* Happens if we are ignoring a move list that is not
2199 * the one we just requested. Common if the user
2200 * tries to observe two games without turning off
2203 case H_GETTING_MOVES:
2204 /* Should not happen */
2205 DisplayError("Error gathering move list: nested", 0);
2206 ics_getting_history = H_FALSE;
2208 case H_GOT_REQ_HEADER:
2209 ics_getting_history = H_GETTING_MOVES;
2210 started = STARTED_MOVES;
2212 if (oldi > next_out) {
2213 SendToPlayer(&buf[next_out], oldi - next_out);
2216 case H_GOT_UNREQ_HEADER:
2217 ics_getting_history = H_GETTING_MOVES;
2218 started = STARTED_MOVES_NOHIDE;
2221 case H_GOT_UNWANTED_HEADER:
2222 ics_getting_history = H_FALSE;
2228 if (looking_at(buf, &i, "% ") ||
2229 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2230 && looking_at(buf, &i, "}*"))) {
2231 savingComment = FALSE;
2234 case STARTED_MOVES_NOHIDE:
2235 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2236 parse[parse_pos + i - oldi] = NULLCHAR;
2237 ParseGameHistory(parse);
2239 if (appData.zippyPlay && first.initDone) {
2240 FeedMovesToProgram(&first, forwardMostMove);
2241 if (gameMode == IcsPlayingWhite) {
2242 if (WhiteOnMove(forwardMostMove)) {
2243 if (first.sendTime) {
2244 if (first.useColors) {
2245 SendToProgram("black\n", &first);
2247 SendTimeRemaining(&first, TRUE);
2249 if (first.useColors) {
2250 SendToProgram("white\ngo\n", &first);
2252 SendToProgram("go\n", &first);
2254 first.maybeThinking = TRUE;
2256 if (first.usePlayother) {
2257 if (first.sendTime) {
2258 SendTimeRemaining(&first, TRUE);
2260 SendToProgram("playother\n", &first);
2266 } else if (gameMode == IcsPlayingBlack) {
2267 if (!WhiteOnMove(forwardMostMove)) {
2268 if (first.sendTime) {
2269 if (first.useColors) {
2270 SendToProgram("white\n", &first);
2272 SendTimeRemaining(&first, FALSE);
2274 if (first.useColors) {
2275 SendToProgram("black\ngo\n", &first);
2277 SendToProgram("go\n", &first);
2279 first.maybeThinking = TRUE;
2281 if (first.usePlayother) {
2282 if (first.sendTime) {
2283 SendTimeRemaining(&first, FALSE);
2285 SendToProgram("playother\n", &first);
2294 if (gameMode == IcsObserving && ics_gamenum == -1) {
2295 /* Moves came from oldmoves or moves command
2296 while we weren't doing anything else.
2298 currentMove = forwardMostMove;
2299 ClearHighlights();/*!!could figure this out*/
2300 flipView = appData.flipView;
2301 DrawPosition(FALSE, boards[currentMove]);
2302 DisplayBothClocks();
2303 sprintf(str, "%s vs. %s",
2304 gameInfo.white, gameInfo.black);
2308 /* Moves were history of an active game */
2309 if (gameInfo.resultDetails != NULL) {
2310 free(gameInfo.resultDetails);
2311 gameInfo.resultDetails = NULL;
2314 HistorySet(parseList, backwardMostMove,
2315 forwardMostMove, currentMove-1);
2316 DisplayMove(currentMove - 1);
2317 if (started == STARTED_MOVES) next_out = i;
2318 started = STARTED_NONE;
2319 ics_getting_history = H_FALSE;
2322 case STARTED_OBSERVE:
2323 started = STARTED_NONE;
2324 SendToICS(ics_prefix);
2325 SendToICS("refresh\n");
2334 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2335 started == STARTED_HOLDINGS ||
2336 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2337 /* Accumulate characters in move list or board */
2338 parse[parse_pos++] = buf[i];
2341 /* Start of game messages. Mostly we detect start of game
2342 when the first board image arrives. On some versions
2343 of the ICS, though, we need to do a "refresh" after starting
2344 to observe in order to get the current board right away. */
2345 if (looking_at(buf, &i, "Adding game * to observation list")) {
2346 started = STARTED_OBSERVE;
2350 /* Handle auto-observe */
2351 if (appData.autoObserve &&
2352 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2353 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2355 /* Choose the player that was highlighted, if any. */
2356 if (star_match[0][0] == '\033' ||
2357 star_match[1][0] != '\033') {
2358 player = star_match[0];
2360 player = star_match[2];
2362 sprintf(str, "%sobserve %s\n",
2363 ics_prefix, StripHighlightAndTitle(player));
2366 /* Save ratings from notify string */
2367 strcpy(player1Name, star_match[0]);
2368 player1Rating = string_to_rating(star_match[1]);
2369 strcpy(player2Name, star_match[2]);
2370 player2Rating = string_to_rating(star_match[3]);
2372 if (appData.debugMode)
2374 "Ratings from 'Game notification:' %s %d, %s %d\n",
2375 player1Name, player1Rating,
2376 player2Name, player2Rating);
2381 /* Deal with automatic examine mode after a game,
2382 and with IcsObserving -> IcsExamining transition */
2383 if (looking_at(buf, &i, "Entering examine mode for game *") ||
2384 looking_at(buf, &i, "has made you an examiner of game *")) {
2386 int gamenum = atoi(star_match[0]);
2387 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2388 gamenum == ics_gamenum) {
2389 /* We were already playing or observing this game;
2390 no need to refetch history */
2391 gameMode = IcsExamining;
2393 pauseExamForwardMostMove = forwardMostMove;
2394 } else if (currentMove < forwardMostMove) {
2395 ForwardInner(forwardMostMove);
2398 /* I don't think this case really can happen */
2399 SendToICS(ics_prefix);
2400 SendToICS("refresh\n");
2405 /* Error messages */
2406 if (ics_user_moved) {
2407 if (looking_at(buf, &i, "Illegal move") ||
2408 looking_at(buf, &i, "Not a legal move") ||
2409 looking_at(buf, &i, "Your king is in check") ||
2410 looking_at(buf, &i, "It isn't your turn") ||
2411 looking_at(buf, &i, "It is not your move")) {
2414 if (forwardMostMove > backwardMostMove) {
2415 currentMove = --forwardMostMove;
2416 DisplayMove(currentMove - 1); /* before DMError */
2417 DisplayMoveError("Illegal move (rejected by ICS)");
2418 DrawPosition(FALSE, boards[currentMove]);
2420 DisplayBothClocks();
2426 if (looking_at(buf, &i, "still have time") ||
2427 looking_at(buf, &i, "not out of time") ||
2428 looking_at(buf, &i, "either player is out of time") ||
2429 looking_at(buf, &i, "has timeseal; checking")) {
2430 /* We must have called his flag a little too soon */
2431 whiteFlag = blackFlag = FALSE;
2435 if (looking_at(buf, &i, "added * seconds to") ||
2436 looking_at(buf, &i, "seconds were added to")) {
2437 /* Update the clocks */
2438 SendToICS(ics_prefix);
2439 SendToICS("refresh\n");
2443 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
2444 ics_clock_paused = TRUE;
2449 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
2450 ics_clock_paused = FALSE;
2455 /* Grab player ratings from the Creating: message.
2456 Note we have to check for the special case when
2457 the ICS inserts things like [white] or [black]. */
2458 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
2459 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
2461 0 player 1 name (not necessarily white)
2463 2 empty, white, or black (IGNORED)
2464 3 player 2 name (not necessarily black)
2467 The names/ratings are sorted out when the game
2468 actually starts (below).
2470 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
2471 player1Rating = string_to_rating(star_match[1]);
2472 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
2473 player2Rating = string_to_rating(star_match[4]);
2475 if (appData.debugMode)
2477 "Ratings from 'Creating:' %s %d, %s %d\n",
2478 player1Name, player1Rating,
2479 player2Name, player2Rating);
2484 /* Improved generic start/end-of-game messages */
2485 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
2486 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
2487 /* If tkind == 0: */
2488 /* star_match[0] is the game number */
2489 /* [1] is the white player's name */
2490 /* [2] is the black player's name */
2491 /* For end-of-game: */
2492 /* [3] is the reason for the game end */
2493 /* [4] is a PGN end game-token, preceded by " " */
2494 /* For start-of-game: */
2495 /* [3] begins with "Creating" or "Continuing" */
2496 /* [4] is " *" or empty (don't care). */
2497 int gamenum = atoi(star_match[0]);
2498 char *whitename, *blackname, *why, *endtoken;
2499 ChessMove endtype = (ChessMove) 0;
2502 whitename = star_match[1];
2503 blackname = star_match[2];
2504 why = star_match[3];
2505 endtoken = star_match[4];
2507 whitename = star_match[1];
2508 blackname = star_match[3];
2509 why = star_match[5];
2510 endtoken = star_match[6];
2513 /* Game start messages */
2514 if (strncmp(why, "Creating ", 9) == 0 ||
2515 strncmp(why, "Continuing ", 11) == 0) {
2516 gs_gamenum = gamenum;
2517 strcpy(gs_kind, strchr(why, ' ') + 1);
2519 if (appData.zippyPlay) {
2520 ZippyGameStart(whitename, blackname);
2526 /* Game end messages */
2527 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
2528 ics_gamenum != gamenum) {
2531 while (endtoken[0] == ' ') endtoken++;
2532 switch (endtoken[0]) {
2535 endtype = GameUnfinished;
2538 endtype = BlackWins;
2541 if (endtoken[1] == '/')
2542 endtype = GameIsDrawn;
2544 endtype = WhiteWins;
2547 GameEnds(endtype, why, GE_ICS);
2549 if (appData.zippyPlay && first.initDone) {
2550 ZippyGameEnd(endtype, why);
2551 if (first.pr == NULL) {
2552 /* Start the next process early so that we'll
2553 be ready for the next challenge */
2554 StartChessProgram(&first);
2556 /* Send "new" early, in case this command takes
2557 a long time to finish, so that we'll be ready
2558 for the next challenge. */
2565 if (looking_at(buf, &i, "Removing game * from observation") ||
2566 looking_at(buf, &i, "no longer observing game *") ||
2567 looking_at(buf, &i, "Game * (*) has no examiners")) {
2568 if (gameMode == IcsObserving &&
2569 atoi(star_match[0]) == ics_gamenum)
2574 ics_user_moved = FALSE;
2579 if (looking_at(buf, &i, "no longer examining game *")) {
2580 if (gameMode == IcsExamining &&
2581 atoi(star_match[0]) == ics_gamenum)
2585 ics_user_moved = FALSE;
2590 /* Advance leftover_start past any newlines we find,
2591 so only partial lines can get reparsed */
2592 if (looking_at(buf, &i, "\n")) {
2593 prevColor = curColor;
2594 if (curColor != ColorNormal) {
2595 if (oldi > next_out) {
2596 SendToPlayer(&buf[next_out], oldi - next_out);
2599 Colorize(ColorNormal, FALSE);
2600 curColor = ColorNormal;
2602 if (started == STARTED_BOARD) {
2603 started = STARTED_NONE;
2604 parse[parse_pos] = NULLCHAR;
2605 ParseBoard12(parse);
2608 /* Send premove here */
2609 if (appData.premove) {
2611 if (currentMove == 0 &&
2612 gameMode == IcsPlayingWhite &&
2613 appData.premoveWhite) {
2614 sprintf(str, "%s%s\n", ics_prefix,
2615 appData.premoveWhiteText);
2616 if (appData.debugMode)
2617 fprintf(debugFP, "Sending premove:\n");
2619 } else if (currentMove == 1 &&
2620 gameMode == IcsPlayingBlack &&
2621 appData.premoveBlack) {
2622 sprintf(str, "%s%s\n", ics_prefix,
2623 appData.premoveBlackText);
2624 if (appData.debugMode)
2625 fprintf(debugFP, "Sending premove:\n");
2627 } else if (gotPremove) {
2629 ClearPremoveHighlights();
2630 if (appData.debugMode)
2631 fprintf(debugFP, "Sending premove:\n");
2632 UserMoveEvent(premoveFromX, premoveFromY,
2633 premoveToX, premoveToY,
2638 /* Usually suppress following prompt */
2639 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
2640 if (looking_at(buf, &i, "*% ")) {
2641 savingComment = FALSE;
2645 } else if (started == STARTED_HOLDINGS) {
2647 char new_piece[MSG_SIZ];
2648 started = STARTED_NONE;
2649 parse[parse_pos] = NULLCHAR;
2650 if (appData.debugMode)
2651 fprintf(debugFP, "Parsing holdings: %s\n", parse);
2652 if (sscanf(parse, " game %d", &gamenum) == 1 &&
2653 gamenum == ics_gamenum) {
2654 if (gameInfo.variant == VariantNormal) {
2655 gameInfo.variant = VariantCrazyhouse; /*temp guess*/
2656 /* Get a move list just to see the header, which
2657 will tell us whether this is really bug or zh */
2658 if (ics_getting_history == H_FALSE) {
2659 ics_getting_history = H_REQUESTED;
2660 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
2664 new_piece[0] = NULLCHAR;
2665 sscanf(parse, "game %d white [%s black [%s <- %s",
2666 &gamenum, white_holding, black_holding,
2668 white_holding[strlen(white_holding)-1] = NULLCHAR;
2669 black_holding[strlen(black_holding)-1] = NULLCHAR;
2671 if (appData.zippyPlay && first.initDone) {
2672 ZippyHoldings(white_holding, black_holding,
2676 if (tinyLayout || smallLayout) {
2677 char wh[16], bh[16];
2678 PackHolding(wh, white_holding);
2679 PackHolding(bh, black_holding);
2680 sprintf(str, "[%s-%s] %s-%s", wh, bh,
2681 gameInfo.white, gameInfo.black);
2683 sprintf(str, "%s [%s] vs. %s [%s]",
2684 gameInfo.white, white_holding,
2685 gameInfo.black, black_holding);
2687 DrawPosition(FALSE, NULL);
2690 /* Suppress following prompt */
2691 if (looking_at(buf, &i, "*% ")) {
2692 savingComment = FALSE;
2699 i++; /* skip unparsed character and loop back */
2702 if (started != STARTED_MOVES && started != STARTED_BOARD &&
2703 started != STARTED_HOLDINGS && i > next_out) {
2704 SendToPlayer(&buf[next_out], i - next_out);
2708 leftover_len = buf_len - leftover_start;
2709 /* if buffer ends with something we couldn't parse,
2710 reparse it after appending the next read */
2712 } else if (count == 0) {
2713 RemoveInputSource(isr);
2714 DisplayFatalError("Connection closed by ICS", 0, 0);
2716 DisplayFatalError("Error reading from ICS", error, 1);
2721 /* Board style 12 looks like this:
2723 <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
2725 * The "<12> " is stripped before it gets to this routine. The two
2726 * trailing 0's (flip state and clock ticking) are later addition, and
2727 * some chess servers may not have them, or may have only the first.
2728 * Additional trailing fields may be added in the future.
2731 #define PATTERN "%72c%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
2733 #define RELATION_OBSERVING_PLAYED 0
2734 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
2735 #define RELATION_PLAYING_MYMOVE 1
2736 #define RELATION_PLAYING_NOTMYMOVE -1
2737 #define RELATION_EXAMINING 2
2738 #define RELATION_ISOLATED_BOARD -3
2739 #define RELATION_STARTING_POSITION -4 /* FICS only */
2742 ParseBoard12(string)
2745 GameMode newGameMode;
2746 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
2747 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
2748 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
2749 char to_play, board_chars[72];
2750 char move_str[500], str[500], elapsed_time[500];
2751 char black[32], white[32];
2753 int prevMove = currentMove;
2756 int fromX, fromY, toX, toY;
2759 fromX = fromY = toX = toY = -1;
2763 if (appData.debugMode)
2764 fprintf(debugFP, "Parsing board: %s\n", string);
2766 move_str[0] = NULLCHAR;
2767 elapsed_time[0] = NULLCHAR;
2768 n = sscanf(string, PATTERN, board_chars, &to_play, &double_push,
2769 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
2770 &gamenum, white, black, &relation, &basetime, &increment,
2771 &white_stren, &black_stren, &white_time, &black_time,
2772 &moveNum, str, elapsed_time, move_str, &ics_flip,
2776 sprintf(str, "Failed to parse board string:\n\"%s\"", string);
2777 DisplayError(str, 0);
2781 /* Convert the move number to internal form */
2782 moveNum = (moveNum - 1) * 2;
2783 if (to_play == 'B') moveNum++;
2784 if (moveNum >= MAX_MOVES) {
2785 DisplayFatalError("Game too long; increase MAX_MOVES and recompile",
2791 case RELATION_OBSERVING_PLAYED:
2792 case RELATION_OBSERVING_STATIC:
2793 if (gamenum == -1) {
2794 /* Old ICC buglet */
2795 relation = RELATION_OBSERVING_STATIC;
2797 newGameMode = IcsObserving;
2799 case RELATION_PLAYING_MYMOVE:
2800 case RELATION_PLAYING_NOTMYMOVE:
2802 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
2803 IcsPlayingWhite : IcsPlayingBlack;
2805 case RELATION_EXAMINING:
2806 newGameMode = IcsExamining;
2808 case RELATION_ISOLATED_BOARD:
2810 /* Just display this board. If user was doing something else,
2811 we will forget about it until the next board comes. */
2812 newGameMode = IcsIdle;
2814 case RELATION_STARTING_POSITION:
2815 newGameMode = gameMode;
2819 /* Modify behavior for initial board display on move listing
2822 switch (ics_getting_history) {
2826 case H_GOT_REQ_HEADER:
2827 case H_GOT_UNREQ_HEADER:
2828 /* This is the initial position of the current game */
2829 gamenum = ics_gamenum;
2830 moveNum = 0; /* old ICS bug workaround */
2831 if (to_play == 'B') {
2832 startedFromSetupPosition = TRUE;
2833 blackPlaysFirst = TRUE;
2835 if (forwardMostMove == 0) forwardMostMove = 1;
2836 if (backwardMostMove == 0) backwardMostMove = 1;
2837 if (currentMove == 0) currentMove = 1;
2839 newGameMode = gameMode;
2840 relation = RELATION_STARTING_POSITION; /* ICC needs this */
2842 case H_GOT_UNWANTED_HEADER:
2843 /* This is an initial board that we don't want */
2845 case H_GETTING_MOVES:
2846 /* Should not happen */
2847 DisplayError("Error gathering move list: extra board", 0);
2848 ics_getting_history = H_FALSE;
2852 /* Take action if this is the first board of a new game, or of a
2853 different game than is currently being displayed. */
2854 if (gamenum != ics_gamenum || newGameMode != gameMode ||
2855 relation == RELATION_ISOLATED_BOARD) {
2857 /* Forget the old game and get the history (if any) of the new one */
2858 if (gameMode != BeginningOfGame) {
2862 if (appData.autoRaiseBoard) BoardToTop();
2864 if (gamenum == -1) {
2865 newGameMode = IcsIdle;
2866 } else if (moveNum > 0 && newGameMode != IcsIdle &&
2867 appData.getMoveList) {
2868 /* Need to get game history */
2869 ics_getting_history = H_REQUESTED;
2870 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
2874 /* Initially flip the board to have black on the bottom if playing
2875 black or if the ICS flip flag is set, but let the user change
2876 it with the Flip View button. */
2877 flipView = appData.autoFlipView ?
2878 (newGameMode == IcsPlayingBlack) || ics_flip :
2881 /* Done with values from previous mode; copy in new ones */
2882 gameMode = newGameMode;
2884 ics_gamenum = gamenum;
2885 if (gamenum == gs_gamenum) {
2886 int klen = strlen(gs_kind);
2887 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
2888 sprintf(str, "ICS %s", gs_kind);
2889 gameInfo.event = StrSave(str);
2891 gameInfo.event = StrSave("ICS game");
2893 gameInfo.site = StrSave(appData.icsHost);
2894 gameInfo.date = PGNDate();
2895 gameInfo.round = StrSave("-");
2896 gameInfo.white = StrSave(white);
2897 gameInfo.black = StrSave(black);
2898 timeControl = basetime * 60 * 1000;
2900 timeIncrement = increment * 1000;
2901 movesPerSession = 0;
2902 gameInfo.timeControl = TimeControlTagValue();
2903 gameInfo.variant = StringToVariant(gameInfo.event);
2904 gameInfo.outOfBook = NULL;
2906 /* Do we have the ratings? */
2907 if (strcmp(player1Name, white) == 0 &&
2908 strcmp(player2Name, black) == 0) {
2909 if (appData.debugMode)
2910 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
2911 player1Rating, player2Rating);
2912 gameInfo.whiteRating = player1Rating;
2913 gameInfo.blackRating = player2Rating;
2914 } else if (strcmp(player2Name, white) == 0 &&
2915 strcmp(player1Name, black) == 0) {
2916 if (appData.debugMode)
2917 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
2918 player2Rating, player1Rating);
2919 gameInfo.whiteRating = player2Rating;
2920 gameInfo.blackRating = player1Rating;
2922 player1Name[0] = player2Name[0] = NULLCHAR;
2924 /* Silence shouts if requested */
2925 if (appData.quietPlay &&
2926 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
2927 SendToICS(ics_prefix);
2928 SendToICS("set shout 0\n");
2932 /* Deal with midgame name changes */
2934 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
2935 if (gameInfo.white) free(gameInfo.white);
2936 gameInfo.white = StrSave(white);
2938 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
2939 if (gameInfo.black) free(gameInfo.black);
2940 gameInfo.black = StrSave(black);
2944 /* Throw away game result if anything actually changes in examine mode */
2945 if (gameMode == IcsExamining && !newGame) {
2946 gameInfo.result = GameUnfinished;
2947 if (gameInfo.resultDetails != NULL) {
2948 free(gameInfo.resultDetails);
2949 gameInfo.resultDetails = NULL;
2953 /* In pausing && IcsExamining mode, we ignore boards coming
2954 in if they are in a different variation than we are. */
2955 if (pauseExamInvalid) return;
2956 if (pausing && gameMode == IcsExamining) {
2957 if (moveNum <= pauseExamForwardMostMove) {
2958 pauseExamInvalid = TRUE;
2959 forwardMostMove = pauseExamForwardMostMove;
2964 /* Parse the board */
2965 for (k = 0; k < 8; k++)
2966 for (j = 0; j < 8; j++)
2967 board[k][j] = CharToPiece(board_chars[(7-k)*9 + j]);
2968 CopyBoard(boards[moveNum], board);
2970 startedFromSetupPosition =
2971 !CompareBoards(board, initialPosition);
2974 if (ics_getting_history == H_GOT_REQ_HEADER ||
2975 ics_getting_history == H_GOT_UNREQ_HEADER) {
2976 /* This was an initial position from a move list, not
2977 the current position */
2981 /* Update currentMove and known move number limits */
2982 newMove = newGame || moveNum > forwardMostMove;
2984 forwardMostMove = backwardMostMove = currentMove = moveNum;
2985 if (gameMode == IcsExamining && moveNum == 0) {
2986 /* Workaround for ICS limitation: we are not told the wild
2987 type when starting to examine a game. But if we ask for
2988 the move list, the move list header will tell us */
2989 ics_getting_history = H_REQUESTED;
2990 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
2993 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
2994 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
2995 forwardMostMove = moveNum;
2996 if (!pausing || currentMove > forwardMostMove)
2997 currentMove = forwardMostMove;
2999 /* New part of history that is not contiguous with old part */
3000 if (pausing && gameMode == IcsExamining) {
3001 pauseExamInvalid = TRUE;
3002 forwardMostMove = pauseExamForwardMostMove;
3005 forwardMostMove = backwardMostMove = currentMove = moveNum;
3006 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3007 ics_getting_history = H_REQUESTED;
3008 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3013 /* Update the clocks */
3014 if (strchr(elapsed_time, '.')) {
3016 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3017 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3019 /* Time is in seconds */
3020 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3021 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3026 if (appData.zippyPlay && newGame &&
3027 gameMode != IcsObserving && gameMode != IcsIdle &&
3028 gameMode != IcsExamining)
3029 ZippyFirstBoard(moveNum, basetime, increment);
3032 /* Put the move on the move list, first converting
3033 to canonical algebraic form. */
3035 if (moveNum <= backwardMostMove) {
3036 /* We don't know what the board looked like before
3038 strcpy(parseList[moveNum - 1], move_str);
3039 strcat(parseList[moveNum - 1], " ");
3040 strcat(parseList[moveNum - 1], elapsed_time);
3041 moveList[moveNum - 1][0] = NULLCHAR;
3042 } else if (ParseOneMove(move_str, moveNum - 1, &moveType,
3043 &fromX, &fromY, &toX, &toY, &promoChar)) {
3044 (void) CoordsToAlgebraic(boards[moveNum - 1],
3045 PosFlags(moveNum - 1), EP_UNKNOWN,
3046 fromY, fromX, toY, toX, promoChar,
3047 parseList[moveNum-1]);
3048 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN)){
3054 strcat(parseList[moveNum - 1], "+");
3057 strcat(parseList[moveNum - 1], "#");
3060 strcat(parseList[moveNum - 1], " ");
3061 strcat(parseList[moveNum - 1], elapsed_time);
3062 /* currentMoveString is set as a side-effect of ParseOneMove */
3063 strcpy(moveList[moveNum - 1], currentMoveString);
3064 strcat(moveList[moveNum - 1], "\n");
3065 } else if (strcmp(move_str, "none") == 0) {
3066 /* Again, we don't know what the board looked like;
3067 this is really the start of the game. */
3068 parseList[moveNum - 1][0] = NULLCHAR;
3069 moveList[moveNum - 1][0] = NULLCHAR;
3070 backwardMostMove = moveNum;
3071 startedFromSetupPosition = TRUE;
3072 fromX = fromY = toX = toY = -1;
3074 /* Move from ICS was illegal!? Punt. */
3076 if (appData.testLegality && appData.debugMode) {
3077 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
3078 DisplayError(str, 0);
3081 strcpy(parseList[moveNum - 1], move_str);
3082 strcat(parseList[moveNum - 1], " ");
3083 strcat(parseList[moveNum - 1], elapsed_time);
3084 moveList[moveNum - 1][0] = NULLCHAR;
3085 fromX = fromY = toX = toY = -1;
3089 /* Send move to chess program (BEFORE animating it). */
3090 if (appData.zippyPlay && !newGame && newMove &&
3091 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3093 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3094 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3095 if (moveList[moveNum - 1][0] == NULLCHAR) {
3096 sprintf(str, "Couldn't parse move \"%s\" from ICS",
3098 DisplayError(str, 0);
3100 if (first.sendTime) {
3101 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3103 SendMoveToProgram(moveNum - 1, &first);
3106 if (first.useColors) {
3107 SendToProgram(gameMode == IcsPlayingWhite ?
3109 "black\ngo\n", &first);
3111 SendToProgram("go\n", &first);
3113 first.maybeThinking = TRUE;
3116 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3117 if (moveList[moveNum - 1][0] == NULLCHAR) {
3118 sprintf(str, "Couldn't parse move \"%s\" from ICS", move_str);
3119 DisplayError(str, 0);
3121 SendMoveToProgram(moveNum - 1, &first);
3128 if (moveNum > 0 && !gotPremove) {
3129 /* If move comes from a remote source, animate it. If it
3130 isn't remote, it will have already been animated. */
3131 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3132 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3134 if (!pausing && appData.highlightLastMove) {
3135 SetHighlights(fromX, fromY, toX, toY);
3139 /* Start the clocks */
3140 whiteFlag = blackFlag = FALSE;
3141 appData.clockMode = !(basetime == 0 && increment == 0);
3143 ics_clock_paused = TRUE;
3145 } else if (ticking == 1) {
3146 ics_clock_paused = FALSE;
3148 if (gameMode == IcsIdle ||
3149 relation == RELATION_OBSERVING_STATIC ||
3150 relation == RELATION_EXAMINING ||
3152 DisplayBothClocks();
3156 /* Display opponents and material strengths */
3157 if (gameInfo.variant != VariantBughouse &&
3158 gameInfo.variant != VariantCrazyhouse) {
3159 if (tinyLayout || smallLayout) {
3160 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3161 gameInfo.white, white_stren, gameInfo.black, black_stren,
3162 basetime, increment);
3164 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3165 gameInfo.white, white_stren, gameInfo.black, black_stren,
3166 basetime, increment);
3172 /* Display the board */
3175 if (appData.premove)
3177 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3178 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3179 ClearPremoveHighlights();
3181 DrawPosition(FALSE, boards[currentMove]);
3182 DisplayMove(moveNum - 1);
3183 if (appData.ringBellAfterMoves && !ics_user_moved)
3187 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3194 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3195 ics_getting_history = H_REQUESTED;
3196 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3202 AnalysisPeriodicEvent(force)
3205 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3206 && !force) || !appData.periodicUpdates)
3209 /* Send . command to Crafty to collect stats */
3210 SendToProgram(".\n", &first);
3212 /* Don't send another until we get a response (this makes
3213 us stop sending to old Crafty's which don't understand
3214 the "." command (sending illegal cmds resets node count & time,
3215 which looks bad)) */
3216 programStats.ok_to_send = 0;
3220 SendMoveToProgram(moveNum, cps)
3222 ChessProgramState *cps;
3225 if (cps->useUsermove) {
3226 SendToProgram("usermove ", cps);
3230 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
3231 int len = space - parseList[moveNum];
3232 memcpy(buf, parseList[moveNum], len);
3234 buf[len] = NULLCHAR;
3236 sprintf(buf, "%s\n", parseList[moveNum]);
3238 SendToProgram(buf, cps);
3240 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
3241 * the engine. It would be nice to have a better way to identify castle
3243 if(gameInfo.variant == VariantFischeRandom && cps->useOOCastle) {
3244 int fromX = moveList[moveNum][0] - 'a';
3245 int fromY = moveList[moveNum][1] - '1';
3246 int toX = moveList[moveNum][2] - 'a';
3247 int toY = moveList[moveNum][3] - '1';
3248 if((boards[currentMove][fromY][fromX] == WhiteKing
3249 && boards[currentMove][toY][toX] == WhiteRook)
3250 || (boards[currentMove][fromY][fromX] == BlackKing
3251 && boards[currentMove][toY][toX] == BlackRook)) {
3252 if(toX > fromX) SendToProgram("O-O\n", cps);
3253 else SendToProgram("O-O-O\n", cps);
3255 else SendToProgram(moveList[moveNum], cps);
3257 else SendToProgram(moveList[moveNum], cps);
3258 /* End of additions by Tord */
3263 SendMoveToICS(moveType, fromX, fromY, toX, toY)
3265 int fromX, fromY, toX, toY;
3267 char user_move[MSG_SIZ];
3271 sprintf(user_move, "say Internal error; bad moveType %d (%d,%d-%d,%d)",
3272 (int)moveType, fromX, fromY, toX, toY);
3273 DisplayError(user_move + strlen("say "), 0);
3275 case WhiteKingSideCastle:
3276 case BlackKingSideCastle:
3277 case WhiteQueenSideCastleWild:
3278 case BlackQueenSideCastleWild:
3280 case WhiteHSideCastleFR:
3281 case BlackHSideCastleFR:
3283 sprintf(user_move, "o-o\n");
3285 case WhiteQueenSideCastle:
3286 case BlackQueenSideCastle:
3287 case WhiteKingSideCastleWild:
3288 case BlackKingSideCastleWild:
3290 case WhiteASideCastleFR:
3291 case BlackASideCastleFR:
3293 sprintf(user_move, "o-o-o\n");
3295 case WhitePromotionQueen:
3296 case BlackPromotionQueen:
3297 case WhitePromotionRook:
3298 case BlackPromotionRook:
3299 case WhitePromotionBishop:
3300 case BlackPromotionBishop:
3301 case WhitePromotionKnight:
3302 case BlackPromotionKnight:
3303 case WhitePromotionKing:
3304 case BlackPromotionKing:
3305 sprintf(user_move, "%c%c%c%c=%c\n",
3306 'a' + fromX, '1' + fromY, 'a' + toX, '1' + toY,
3307 PieceToChar(PromoPiece(moveType)));
3311 sprintf(user_move, "%c@%c%c\n",
3312 ToUpper(PieceToChar((ChessSquare) fromX)),
3313 'a' + toX, '1' + toY);
3316 case WhiteCapturesEnPassant:
3317 case BlackCapturesEnPassant:
3318 case IllegalMove: /* could be a variant we don't quite understand */
3319 sprintf(user_move, "%c%c%c%c\n",
3320 'a' + fromX, '1' + fromY, 'a' + toX, '1' + toY);
3323 SendToICS(user_move);
3327 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
3332 if (rf == DROP_RANK) {
3333 sprintf(move, "%c@%c%c\n",
3334 ToUpper(PieceToChar((ChessSquare) ff)), 'a' + ft, '1' + rt);
3336 if (promoChar == 'x' || promoChar == NULLCHAR) {
3337 sprintf(move, "%c%c%c%c\n",
3338 'a' + ff, '1' + rf, 'a' + ft, '1' + rt);
3340 sprintf(move, "%c%c%c%c%c\n",
3341 'a' + ff, '1' + rf, 'a' + ft, '1' + rt, promoChar);
3347 ProcessICSInitScript(f)
3352 while (fgets(buf, MSG_SIZ, f)) {
3353 SendToICSDelayed(buf,(long)appData.msLoginDelay);
3360 /* Parser for moves from gnuchess, ICS, or user typein box */
3362 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
3365 ChessMove *moveType;
3366 int *fromX, *fromY, *toX, *toY;
3369 *moveType = yylexstr(moveNum, move);
3370 switch (*moveType) {
3371 case WhitePromotionQueen:
3372 case BlackPromotionQueen:
3373 case WhitePromotionRook:
3374 case BlackPromotionRook:
3375 case WhitePromotionBishop:
3376 case BlackPromotionBishop:
3377 case WhitePromotionKnight:
3378 case BlackPromotionKnight:
3379 case WhitePromotionKing:
3380 case BlackPromotionKing:
3382 case WhiteCapturesEnPassant:
3383 case BlackCapturesEnPassant:
3384 case WhiteKingSideCastle:
3385 case WhiteQueenSideCastle:
3386 case BlackKingSideCastle:
3387 case BlackQueenSideCastle:
3388 case WhiteKingSideCastleWild:
3389 case WhiteQueenSideCastleWild:
3390 case BlackKingSideCastleWild:
3391 case BlackQueenSideCastleWild:
3392 /* Code added by Tord: */
3393 case WhiteHSideCastleFR:
3394 case WhiteASideCastleFR:
3395 case BlackHSideCastleFR:
3396 case BlackASideCastleFR:
3397 /* End of code added by Tord */
3398 case IllegalMove: /* bug or odd chess variant */
3399 *fromX = currentMoveString[0] - 'a';
3400 *fromY = currentMoveString[1] - '1';
3401 *toX = currentMoveString[2] - 'a';
3402 *toY = currentMoveString[3] - '1';
3403 *promoChar = currentMoveString[4];
3404 if (*fromX < 0 || *fromX > 7 || *fromY < 0 || *fromY > 7 ||
3405 *toX < 0 || *toX > 7 || *toY < 0 || *toY > 7) {
3406 *fromX = *fromY = *toX = *toY = 0;
3409 if (appData.testLegality) {
3410 return (*moveType != IllegalMove);
3412 return !(fromX == fromY && toX == toY);
3417 *fromX = *moveType == WhiteDrop ?
3418 (int) CharToPiece(ToUpper(currentMoveString[0])) :
3419 (int) CharToPiece(ToLower(currentMoveString[0]));
3421 *toX = currentMoveString[2] - 'a';
3422 *toY = currentMoveString[3] - '1';
3423 *promoChar = NULLCHAR;
3427 case ImpossibleMove:
3428 case (ChessMove) 0: /* end of file */
3438 *fromX = *fromY = *toX = *toY = 0;
3439 *promoChar = NULLCHAR;
3444 /* [AS] FRC game initialization */
3445 static int FindEmptySquare( Board board, int n )
3450 while( board[0][i] != EmptySquare ) i++;
3460 static void ShuffleFRC( Board board )
3466 for( i=0; i<8; i++ ) {
3467 board[0][i] = EmptySquare;
3470 board[0][(rand() % 4)*2 ] = WhiteBishop; /* On dark square */
3471 board[0][(rand() % 4)*2+1] = WhiteBishop; /* On lite square */
3472 board[0][FindEmptySquare(board, rand() % 6)] = WhiteQueen;
3473 board[0][FindEmptySquare(board, rand() % 5)] = WhiteKnight;
3474 board[0][FindEmptySquare(board, rand() % 4)] = WhiteKnight;
3475 board[0][FindEmptySquare(board, 0)] = WhiteRook;
3476 board[0][FindEmptySquare(board, 0)] = WhiteKing;
3477 board[0][FindEmptySquare(board, 0)] = WhiteRook;
3479 for( i=0; i<8; i++ ) {
3480 board[7][i] = board[0][i] + BlackPawn - WhitePawn;
3484 static unsigned char FRC_KnightTable[10] = {
3485 0x00, 0x01, 0x02, 0x03, 0x11, 0x12, 0x13, 0x22, 0x23, 0x33
3488 static void SetupFRC( Board board, int pos_index )
3491 unsigned char knights;
3493 /* Bring the position index into a safe range (just in case...) */
3494 if( pos_index < 0 ) pos_index = 0;
3498 /* Clear the board */
3499 for( i=0; i<8; i++ ) {
3500 board[0][i] = EmptySquare;
3503 /* Place bishops and queen */
3504 board[0][ (pos_index % 4)*2 + 1 ] = WhiteBishop; /* On lite square */
3507 board[0][ (pos_index % 4)*2 ] = WhiteBishop; /* On dark square */
3510 board[0][ FindEmptySquare(board, pos_index % 6) ] = WhiteQueen;
3514 knights = FRC_KnightTable[ pos_index ];
3516 board[0][ FindEmptySquare(board, knights / 16) ] = WhiteKnight;
3517 board[0][ FindEmptySquare(board, knights % 16) ] = WhiteKnight;
3519 /* Place rooks and king */
3520 board[0][ FindEmptySquare(board, 0) ] = WhiteRook;
3521 board[0][ FindEmptySquare(board, 0) ] = WhiteKing;
3522 board[0][ FindEmptySquare(board, 0) ] = WhiteRook;
3524 /* Mirror piece placement for black */
3525 for( i=0; i<8; i++ ) {
3526 board[7][i] = board[0][i] + BlackPawn - WhitePawn;
3531 InitPosition(redraw)
3534 currentMove = forwardMostMove = backwardMostMove = 0;
3536 /* [AS] Initialize pv info list */
3540 for( i=0; i<MAX_MOVES; i++ ) {
3541 pvInfoList[i].depth = 0;
3545 switch (gameInfo.variant) {
3547 CopyBoard(boards[0], initialPosition);
3549 case VariantTwoKings:
3550 CopyBoard(boards[0], twoKingsPosition);
3551 startedFromSetupPosition = TRUE;
3553 case VariantWildCastle:
3554 CopyBoard(boards[0], initialPosition);
3555 /* !!?shuffle with kings guaranteed to be on d or e file */
3557 case VariantNoCastle:
3558 CopyBoard(boards[0], initialPosition);
3559 /* !!?unconstrained back-rank shuffle */
3561 case VariantFischeRandom:
3562 CopyBoard(boards[0], initialPosition);
3563 if( appData.defaultFrcPosition < 0 ) {
3564 ShuffleFRC( boards[0] );
3567 SetupFRC( boards[0], appData.defaultFrcPosition );
3573 DrawPosition(TRUE, boards[currentMove]);
3577 SendBoard(cps, moveNum)
3578 ChessProgramState *cps;
3581 char message[MSG_SIZ];
3583 if (cps->useSetboard) {
3584 char* fen = PositionToFEN(moveNum, cps->useFEN960);
3585 sprintf(message, "setboard %s\n", fen);
3586 SendToProgram(message, cps);
3592 /* Kludge to set black to move, avoiding the troublesome and now
3593 * deprecated "black" command.
3595 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
3597 SendToProgram("edit\n", cps);
3598 SendToProgram("#\n", cps);
3599 for (i = BOARD_SIZE - 1; i >= 0; i--) {
3600 bp = &boards[moveNum][i][0];
3601 for (j = 0; j < BOARD_SIZE; j++, bp++) {
3602 if ((int) *bp < (int) BlackPawn) {
3603 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
3605 SendToProgram(message, cps);
3610 SendToProgram("c\n", cps);
3611 for (i = BOARD_SIZE - 1; i >= 0; i--) {
3612 bp = &boards[moveNum][i][0];
3613 for (j = 0; j < BOARD_SIZE; j++, bp++) {
3614 if (((int) *bp != (int) EmptySquare)
3615 && ((int) *bp >= (int) BlackPawn)) {
3616 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
3618 SendToProgram(message, cps);
3623 SendToProgram(".\n", cps);
3628 IsPromotion(fromX, fromY, toX, toY)
3629 int fromX, fromY, toX, toY;
3631 return gameMode != EditPosition &&
3632 fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0 &&
3633 ((boards[currentMove][fromY][fromX] == WhitePawn && toY == 7) ||
3634 (boards[currentMove][fromY][fromX] == BlackPawn && toY == 0));
3639 PieceForSquare (x, y)
3643 if (x < 0 || x >= BOARD_SIZE || y < 0 || y >= BOARD_SIZE)
3646 return boards[currentMove][y][x];
3650 OKToStartUserMove(x, y)
3653 ChessSquare from_piece;
3656 if (matchMode) return FALSE;
3657 if (gameMode == EditPosition) return TRUE;
3659 if (x >= 0 && y >= 0)
3660 from_piece = boards[currentMove][y][x];
3662 from_piece = EmptySquare;
3664 if (from_piece == EmptySquare) return FALSE;
3666 white_piece = (int)from_piece >= (int)WhitePawn &&
3667 (int)from_piece <= (int)WhiteKing;
3670 case PlayFromGameFile:
3672 case TwoMachinesPlay:
3680 case MachinePlaysWhite:
3681 case IcsPlayingBlack:
3682 if (appData.zippyPlay) return FALSE;
3684 DisplayMoveError("You are playing Black");
3689 case MachinePlaysBlack:
3690 case IcsPlayingWhite:
3691 if (appData.zippyPlay) return FALSE;
3693 DisplayMoveError("You are playing White");
3699 if (!white_piece && WhiteOnMove(currentMove)) {
3700 DisplayMoveError("It is White's turn");
3703 if (white_piece && !WhiteOnMove(currentMove)) {
3704 DisplayMoveError("It is Black's turn");
3707 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
3708 /* Editing correspondence game history */
3709 /* Could disallow this or prompt for confirmation */
3712 if (currentMove < forwardMostMove) {
3713 /* Discarding moves */
3714 /* Could prompt for confirmation here,
3715 but I don't think that's such a good idea */
3716 forwardMostMove = currentMove;
3720 case BeginningOfGame:
3721 if (appData.icsActive) return FALSE;
3722 if (!appData.noChessProgram) {
3724 DisplayMoveError("You are playing White");
3731 if (!white_piece && WhiteOnMove(currentMove)) {
3732 DisplayMoveError("It is White's turn");
3735 if (white_piece && !WhiteOnMove(currentMove)) {
3736 DisplayMoveError("It is Black's turn");
3745 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
3746 && gameMode != AnalyzeFile && gameMode != Training) {
3747 DisplayMoveError("Displayed position is not current");
3753 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
3754 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
3755 int lastLoadGameUseList = FALSE;
3756 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
3757 ChessMove lastLoadGameStart = (ChessMove) 0;
3761 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
3762 int fromX, fromY, toX, toY;
3767 if (fromX < 0 || fromY < 0) return;
3768 if ((fromX == toX) && (fromY == toY)) {
3772 /* Check if the user is playing in turn. This is complicated because we
3773 let the user "pick up" a piece before it is his turn. So the piece he
3774 tried to pick up may have been captured by the time he puts it down!
3775 Therefore we use the color the user is supposed to be playing in this
3776 test, not the color of the piece that is currently on the starting
3777 square---except in EditGame mode, where the user is playing both
3778 sides; fortunately there the capture race can't happen. (It can
3779 now happen in IcsExamining mode, but that's just too bad. The user
3780 will get a somewhat confusing message in that case.)
3784 case PlayFromGameFile:
3786 case TwoMachinesPlay:
3790 /* We switched into a game mode where moves are not accepted,
3791 perhaps while the mouse button was down. */
3794 case MachinePlaysWhite:
3795 /* User is moving for Black */
3796 if (WhiteOnMove(currentMove)) {
3797 DisplayMoveError("It is White's turn");
3802 case MachinePlaysBlack:
3803 /* User is moving for White */
3804 if (!WhiteOnMove(currentMove)) {
3805 DisplayMoveError("It is Black's turn");
3812 case BeginningOfGame:
3815 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
3816 (int) boards[currentMove][fromY][fromX] <= (int) BlackKing) {
3817 /* User is moving for Black */
3818 if (WhiteOnMove(currentMove)) {
3819 DisplayMoveError("It is White's turn");
3823 /* User is moving for White */
3824 if (!WhiteOnMove(currentMove)) {
3825 DisplayMoveError("It is Black's turn");
3831 case IcsPlayingBlack:
3832 /* User is moving for Black */
3833 if (WhiteOnMove(currentMove)) {
3834 if (!appData.premove) {
3835 DisplayMoveError("It is White's turn");
3836 } else if (toX >= 0 && toY >= 0) {
3839 premoveFromX = fromX;
3840 premoveFromY = fromY;
3841 premovePromoChar = promoChar;
3843 if (appData.debugMode)
3844 fprintf(debugFP, "Got premove: fromX %d,"
3845 "fromY %d, toX %d, toY %d\n",
3846 fromX, fromY, toX, toY);
3852 case IcsPlayingWhite:
3853 /* User is moving for White */
3854 if (!WhiteOnMove(currentMove)) {
3855 if (!appData.premove) {
3856 DisplayMoveError("It is Black's turn");
3857 } else if (toX >= 0 && toY >= 0) {
3860 premoveFromX = fromX;
3861 premoveFromY = fromY;
3862 premovePromoChar = promoChar;
3864 if (appData.debugMode)
3865 fprintf(debugFP, "Got premove: fromX %d,"
3866 "fromY %d, toX %d, toY %d\n",
3867 fromX, fromY, toX, toY);
3877 if (toX == -2 || toY == -2) {
3878 boards[0][fromY][fromX] = EmptySquare;
3879 DrawPosition(FALSE, boards[currentMove]);
3880 } else if (toX >= 0 && toY >= 0) {
3881 boards[0][toY][toX] = boards[0][fromY][fromX];
3882 boards[0][fromY][fromX] = EmptySquare;
3883 DrawPosition(FALSE, boards[currentMove]);
3888 if (toX < 0 || toY < 0) return;
3889 userOfferedDraw = FALSE;
3891 if (appData.testLegality) {
3892 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
3893 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar);
3894 if (moveType == IllegalMove || moveType == ImpossibleMove) {
3895 DisplayMoveError("Illegal move");
3899 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
3902 if (gameMode == Training) {
3903 /* compare the move played on the board to the next move in the
3904 * game. If they match, display the move and the opponent's response.
3905 * If they don't match, display an error message.
3909 CopyBoard(testBoard, boards[currentMove]);
3910 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
3912 if (CompareBoards(testBoard, boards[currentMove+1])) {
3913 ForwardInner(currentMove+1);
3915 /* Autoplay the opponent's response.
3916 * if appData.animate was TRUE when Training mode was entered,
3917 * the response will be animated.
3919 saveAnimate = appData.animate;
3920 appData.animate = animateTraining;
3921 ForwardInner(currentMove+1);
3922 appData.animate = saveAnimate;
3924 /* check for the end of the game */
3925 if (currentMove >= forwardMostMove) {
3926 gameMode = PlayFromGameFile;
3928 SetTrainingModeOff();
3929 DisplayInformation("End of game");
3932 DisplayError("Incorrect move", 0);
3937 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
3940 /* Common tail of UserMoveEvent and DropMenuEvent */
3942 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
3944 int fromX, fromY, toX, toY;
3945 /*char*/int promoChar;
3947 /* Ok, now we know that the move is good, so we can kill
3948 the previous line in Analysis Mode */
3949 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
3950 forwardMostMove = currentMove;
3953 /* If we need the chess program but it's dead, restart it */
3954 ResurrectChessProgram();
3956 /* A user move restarts a paused game*/
3960 thinkOutput[0] = NULLCHAR;
3962 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
3964 if (gameMode == BeginningOfGame) {
3965 if (appData.noChessProgram) {
3966 gameMode = EditGame;
3970 gameMode = MachinePlaysBlack;
3972 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
3974 if (first.sendName) {
3975 sprintf(buf, "name %s\n", gameInfo.white);
3976 SendToProgram(buf, &first);
3982 /* Relay move to ICS or chess engine */
3983 if (appData.icsActive) {
3984 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
3985 gameMode == IcsExamining) {
3986 SendMoveToICS(moveType, fromX, fromY, toX, toY);
3990 if (first.sendTime && (gameMode == BeginningOfGame ||
3991 gameMode == MachinePlaysWhite ||
3992 gameMode == MachinePlaysBlack)) {
3993 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
3995 SendMoveToProgram(forwardMostMove-1, &first);
3996 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
3997 first.maybeThinking = TRUE;
3999 if (currentMove == cmailOldMove + 1) {
4000 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
4004 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
4008 switch (MateTest(boards[currentMove], PosFlags(currentMove),
4014 if (WhiteOnMove(currentMove)) {
4015 GameEnds(BlackWins, "Black mates", GE_PLAYER);
4017 GameEnds(WhiteWins, "White mates", GE_PLAYER);
4021 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
4026 case MachinePlaysBlack:
4027 case MachinePlaysWhite:
4028 /* disable certain menu options while machine is thinking */
4029 SetMachineThinkingEnables();
4037 void SendProgramStatsToFrontend( ChessProgramState * cps )
4039 SetProgramStats( cps == &first ? 0 : 1,
4044 programStats.movelist );
4048 HandleMachineMove(message, cps)
4050 ChessProgramState *cps;
4052 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
4053 char realname[MSG_SIZ];
4054 int fromX, fromY, toX, toY;
4061 * Kludge to ignore BEL characters
4063 while (*message == '\007') message++;
4066 * Look for book output
4068 if (cps == &first && bookRequested) {
4069 if (message[0] == '\t' || message[0] == ' ') {
4070 /* Part of the book output is here; append it */
4071 strcat(bookOutput, message);
4072 strcat(bookOutput, " \n");
4074 } else if (bookOutput[0] != NULLCHAR) {
4075 /* All of book output has arrived; display it */
4076 char *p = bookOutput;
4077 while (*p != NULLCHAR) {
4078 if (*p == '\t') *p = ' ';
4081 DisplayInformation(bookOutput);
4082 bookRequested = FALSE;
4083 /* Fall through to parse the current output */
4088 * Look for machine move.
4090 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
4091 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
4093 /* This method is only useful on engines that support ping */
4094 if (cps->lastPing != cps->lastPong) {
4095 if (gameMode == BeginningOfGame) {
4096 /* Extra move from before last new; ignore */
4097 if (appData.debugMode) {
4098 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
4101 if (appData.debugMode) {
4102 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
4103 cps->which, gameMode);
4105 SendToProgram("undo\n", cps);
4111 case BeginningOfGame:
4112 /* Extra move from before last reset; ignore */
4113 if (appData.debugMode) {
4114 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
4121 /* Extra move after we tried to stop. The mode test is
4122 not a reliable way of detecting this problem, but it's
4123 the best we can do on engines that don't support ping.
4125 if (appData.debugMode) {
4126 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
4127 cps->which, gameMode);
4129 SendToProgram("undo\n", cps);
4132 case MachinePlaysWhite:
4133 case IcsPlayingWhite:
4134 machineWhite = TRUE;
4137 case MachinePlaysBlack:
4138 case IcsPlayingBlack:
4139 machineWhite = FALSE;
4142 case TwoMachinesPlay:
4143 machineWhite = (cps->twoMachinesColor[0] == 'w');
4146 if (WhiteOnMove(forwardMostMove) != machineWhite) {
4147 if (appData.debugMode) {
4149 "Ignoring move out of turn by %s, gameMode %d"
4150 ", forwardMost %d\n",
4151 cps->which, gameMode, forwardMostMove);
4156 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
4157 &fromX, &fromY, &toX, &toY, &promoChar)) {
4158 /* Machine move could not be parsed; ignore it. */
4159 sprintf(buf1, "Illegal move \"%s\" from %s machine",
4160 machineMove, cps->which);
4161 DisplayError(buf1, 0);
4162 if (gameMode == TwoMachinesPlay) {
4163 GameEnds(machineWhite ? BlackWins : WhiteWins,
4164 "Forfeit due to illegal move", GE_XBOARD);
4169 hintRequested = FALSE;
4170 lastHint[0] = NULLCHAR;
4171 bookRequested = FALSE;
4172 /* Program may be pondering now */
4173 cps->maybeThinking = TRUE;
4174 if (cps->sendTime == 2) cps->sendTime = 1;
4175 if (cps->offeredDraw) cps->offeredDraw--;
4178 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
4180 SendMoveToICS(moveType, fromX, fromY, toX, toY);
4184 /* currentMoveString is set as a side-effect of ParseOneMove */
4185 strcpy(machineMove, currentMoveString);
4186 strcat(machineMove, "\n");
4187 strcpy(moveList[forwardMostMove], machineMove);
4189 /* [AS] Save move info and clear stats for next move */
4190 pvInfoList[ forwardMostMove ].score = programStats.score;
4191 pvInfoList[ forwardMostMove ].depth = programStats.depth;
4192 pvInfoList[ forwardMostMove ].time = -1;
4193 ClearProgramStats();
4194 thinkOutput[0] = NULLCHAR;
4195 hiddenThinkOutputState = 0;
4197 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
4199 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
4200 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
4203 while( count < adjudicateLossPlies ) {
4204 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
4207 score = -score; /* Flip score for winning side */
4210 if( score > adjudicateLossThreshold ) {
4217 if( count >= adjudicateLossPlies ) {
4218 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
4220 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
4221 "Xboard adjudication",
4228 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
4229 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
4231 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
4236 if (gameMode == TwoMachinesPlay) {
4237 if (cps->other->sendTime) {
4238 SendTimeRemaining(cps->other,
4239 cps->other->twoMachinesColor[0] == 'w');
4241 SendMoveToProgram(forwardMostMove-1, cps->other);
4244 if (cps->other->useColors) {
4245 SendToProgram(cps->other->twoMachinesColor, cps->other);
4247 SendToProgram("go\n", cps->other);
4249 cps->other->maybeThinking = TRUE;
4252 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
4254 if (!pausing && appData.ringBellAfterMoves) {
4259 * Reenable menu items that were disabled while
4260 * machine was thinking
4262 if (gameMode != TwoMachinesPlay)
4263 SetUserThinkingEnables();
4268 /* Set special modes for chess engines. Later something general
4269 * could be added here; for now there is just one kludge feature,
4270 * needed because Crafty 15.10 and earlier don't ignore SIGINT
4271 * when "xboard" is given as an interactive command.
4273 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
4274 cps->useSigint = FALSE;
4275 cps->useSigterm = FALSE;
4279 * Look for communication commands
4281 if (!strncmp(message, "telluser ", 9)) {
4282 DisplayNote(message + 9);
4285 if (!strncmp(message, "tellusererror ", 14)) {
4286 DisplayError(message + 14, 0);
4289 if (!strncmp(message, "tellopponent ", 13)) {
4290 if (appData.icsActive) {
4292 sprintf(buf1, "%ssay %s\n", ics_prefix, message + 13);
4296 DisplayNote(message + 13);
4300 if (!strncmp(message, "tellothers ", 11)) {
4301 if (appData.icsActive) {
4303 sprintf(buf1, "%swhisper %s\n", ics_prefix, message + 11);
4309 if (!strncmp(message, "tellall ", 8)) {
4310 if (appData.icsActive) {
4312 sprintf(buf1, "%skibitz %s\n", ics_prefix, message + 8);
4316 DisplayNote(message + 8);
4320 if (strncmp(message, "warning", 7) == 0) {
4321 /* Undocumented feature, use tellusererror in new code */
4322 DisplayError(message, 0);
4325 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
4326 strcpy(realname, cps->tidy);
4327 strcat(realname, " query");
4328 AskQuestion(realname, buf2, buf1, cps->pr);
4331 /* Commands from the engine directly to ICS. We don't allow these to be
4332 * sent until we are logged on. Crafty kibitzes have been known to
4333 * interfere with the login process.
4336 if (!strncmp(message, "tellics ", 8)) {
4337 SendToICS(message + 8);
4341 if (!strncmp(message, "tellicsnoalias ", 15)) {
4342 SendToICS(ics_prefix);
4343 SendToICS(message + 15);
4347 /* The following are for backward compatibility only */
4348 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
4349 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
4350 SendToICS(ics_prefix);
4356 if (strncmp(message, "feature ", 8) == 0) {
4357 ParseFeatures(message+8, cps);
4359 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
4363 * If the move is illegal, cancel it and redraw the board.
4364 * Also deal with other error cases. Matching is rather loose
4365 * here to accommodate engines written before the spec.
4367 if (strncmp(message + 1, "llegal move", 11) == 0 ||
4368 strncmp(message, "Error", 5) == 0) {
4369 if (StrStr(message, "name") ||
4370 StrStr(message, "rating") || StrStr(message, "?") ||
4371 StrStr(message, "result") || StrStr(message, "board") ||
4372 StrStr(message, "bk") || StrStr(message, "computer") ||
4373 StrStr(message, "variant") || StrStr(message, "hint") ||
4374 StrStr(message, "random") || StrStr(message, "depth") ||
4375 StrStr(message, "accepted")) {
4378 if (StrStr(message, "protover")) {
4379 /* Program is responding to input, so it's apparently done
4380 initializing, and this error message indicates it is
4381 protocol version 1. So we don't need to wait any longer
4382 for it to initialize and send feature commands. */
4383 FeatureDone(cps, 1);
4384 cps->protocolVersion = 1;
4387 cps->maybeThinking = FALSE;
4389 if (StrStr(message, "draw")) {
4390 /* Program doesn't have "draw" command */
4391 cps->sendDrawOffers = 0;
4394 if (cps->sendTime != 1 &&
4395 (StrStr(message, "time") || StrStr(message, "otim"))) {
4396 /* Program apparently doesn't have "time" or "otim" command */
4400 if (StrStr(message, "analyze")) {
4401 cps->analysisSupport = FALSE;
4402 cps->analyzing = FALSE;
4404 sprintf(buf2, "%s does not support analysis", cps->tidy);
4405 DisplayError(buf2, 0);
4408 if (StrStr(message, "(no matching move)st")) {
4409 /* Special kludge for GNU Chess 4 only */
4410 cps->stKludge = TRUE;
4411 SendTimeControl(cps, movesPerSession, timeControl,
4412 timeIncrement, appData.searchDepth,
4416 if (StrStr(message, "(no matching move)sd")) {
4417 /* Special kludge for GNU Chess 4 only */
4418 cps->sdKludge = TRUE;
4419 SendTimeControl(cps, movesPerSession, timeControl,
4420 timeIncrement, appData.searchDepth,
4424 if (!StrStr(message, "llegal")) return;
4425 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
4426 gameMode == IcsIdle) return;
4427 if (forwardMostMove <= backwardMostMove) return;
4429 /* Following removed: it caused a bug where a real illegal move
4430 message in analyze mored would be ignored. */
4431 if (cps == &first && programStats.ok_to_send == 0) {
4432 /* Bogus message from Crafty responding to "." This filtering
4433 can miss some of the bad messages, but fortunately the bug
4434 is fixed in current Crafty versions, so it doesn't matter. */
4438 if (pausing) PauseEvent();
4439 if (gameMode == PlayFromGameFile) {
4440 /* Stop reading this game file */
4441 gameMode = EditGame;
4444 currentMove = --forwardMostMove;
4445 DisplayMove(currentMove-1); /* before DisplayMoveError */
4447 DisplayBothClocks();
4448 sprintf(buf1, "Illegal move \"%s\" (rejected by %s chess program)",
4449 parseList[currentMove], cps->which);
4450 DisplayMoveError(buf1);
4451 DrawPosition(FALSE, boards[currentMove]);
4454 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
4455 /* Program has a broken "time" command that
4456 outputs a string not ending in newline.
4462 * If chess program startup fails, exit with an error message.
4463 * Attempts to recover here are futile.
4465 if ((StrStr(message, "unknown host") != NULL)
4466 || (StrStr(message, "No remote directory") != NULL)
4467 || (StrStr(message, "not found") != NULL)
4468 || (StrStr(message, "No such file") != NULL)
4469 || (StrStr(message, "can't alloc") != NULL)
4470 || (StrStr(message, "Permission denied") != NULL)) {
4472 cps->maybeThinking = FALSE;
4473 sprintf(buf1, "Failed to start %s chess program %s on %s: %s\n",
4474 cps->which, cps->program, cps->host, message);
4475 RemoveInputSource(cps->isr);
4476 DisplayFatalError(buf1, 0, 1);
4481 * Look for hint output
4483 if (sscanf(message, "Hint: %s", buf1) == 1) {
4484 if (cps == &first && hintRequested) {
4485 hintRequested = FALSE;
4486 if (ParseOneMove(buf1, forwardMostMove, &moveType,
4487 &fromX, &fromY, &toX, &toY, &promoChar)) {
4488 (void) CoordsToAlgebraic(boards[forwardMostMove],
4489 PosFlags(forwardMostMove), EP_UNKNOWN,
4490 fromY, fromX, toY, toX, promoChar, buf1);
4491 sprintf(buf2, "Hint: %s", buf1);
4492 DisplayInformation(buf2);
4494 /* Hint move could not be parsed!? */
4496 "Illegal hint move \"%s\"\nfrom %s chess program",
4498 DisplayError(buf2, 0);
4501 strcpy(lastHint, buf1);
4507 * Ignore other messages if game is not in progress
4509 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
4510 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
4513 * look for win, lose, draw, or draw offer
4515 if (strncmp(message, "1-0", 3) == 0) {
4516 char *p, *q, *r = "";
4517 p = strchr(message, '{');
4525 GameEnds(WhiteWins, r, GE_ENGINE);
4527 } else if (strncmp(message, "0-1", 3) == 0) {
4528 char *p, *q, *r = "";
4529 p = strchr(message, '{');
4537 /* Kludge for Arasan 4.1 bug */
4538 if (strcmp(r, "Black resigns") == 0) {
4539 GameEnds(WhiteWins, r, GE_ENGINE);
4542 GameEnds(BlackWins, r, GE_ENGINE);
4544 } else if (strncmp(message, "1/2", 3) == 0) {
4545 char *p, *q, *r = "";
4546 p = strchr(message, '{');
4554 GameEnds(GameIsDrawn, r, GE_ENGINE);
4557 } else if (strncmp(message, "White resign", 12) == 0) {
4558 GameEnds(BlackWins, "White resigns", GE_ENGINE);
4560 } else if (strncmp(message, "Black resign", 12) == 0) {
4561 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
4563 } else if (strncmp(message, "White", 5) == 0 &&
4564 message[5] != '(' &&
4565 StrStr(message, "Black") == NULL) {
4566 GameEnds(WhiteWins, "White mates", GE_ENGINE);
4568 } else if (strncmp(message, "Black", 5) == 0 &&
4569 message[5] != '(') {
4570 GameEnds(BlackWins, "Black mates", GE_ENGINE);
4572 } else if (strcmp(message, "resign") == 0 ||
4573 strcmp(message, "computer resigns") == 0) {
4575 case MachinePlaysBlack:
4576 case IcsPlayingBlack:
4577 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
4579 case MachinePlaysWhite:
4580 case IcsPlayingWhite:
4581 GameEnds(BlackWins, "White resigns", GE_ENGINE);
4583 case TwoMachinesPlay:
4584 if (cps->twoMachinesColor[0] == 'w')
4585 GameEnds(BlackWins, "White resigns", GE_ENGINE);
4587 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
4594 } else if (strncmp(message, "opponent mates", 14) == 0) {
4596 case MachinePlaysBlack:
4597 case IcsPlayingBlack:
4598 GameEnds(WhiteWins, "White mates", GE_ENGINE);
4600 case MachinePlaysWhite:
4601 case IcsPlayingWhite:
4602 GameEnds(BlackWins, "Black mates", GE_ENGINE);
4604 case TwoMachinesPlay:
4605 if (cps->twoMachinesColor[0] == 'w')
4606 GameEnds(BlackWins, "Black mates", GE_ENGINE);
4608 GameEnds(WhiteWins, "White mates", GE_ENGINE);
4615 } else if (strncmp(message, "computer mates", 14) == 0) {
4617 case MachinePlaysBlack:
4618 case IcsPlayingBlack:
4619 GameEnds(BlackWins, "Black mates", GE_ENGINE);
4621 case MachinePlaysWhite:
4622 case IcsPlayingWhite:
4623 GameEnds(WhiteWins, "White mates", GE_ENGINE);
4625 case TwoMachinesPlay:
4626 if (cps->twoMachinesColor[0] == 'w')
4627 GameEnds(WhiteWins, "White mates", GE_ENGINE);
4629 GameEnds(BlackWins, "Black mates", GE_ENGINE);
4636 } else if (strncmp(message, "checkmate", 9) == 0) {
4637 if (WhiteOnMove(forwardMostMove)) {
4638 GameEnds(BlackWins, "Black mates", GE_ENGINE);
4640 GameEnds(WhiteWins, "White mates", GE_ENGINE);
4643 } else if (strstr(message, "Draw") != NULL ||
4644 strstr(message, "game is a draw") != NULL) {
4645 GameEnds(GameIsDrawn, "Draw", GE_ENGINE);
4647 } else if (strstr(message, "offer") != NULL &&
4648 strstr(message, "draw") != NULL) {
4650 if (appData.zippyPlay && first.initDone) {
4651 /* Relay offer to ICS */
4652 SendToICS(ics_prefix);
4653 SendToICS("draw\n");
4656 cps->offeredDraw = 2; /* valid until this engine moves twice */
4657 if (gameMode == TwoMachinesPlay) {
4658 if (cps->other->offeredDraw) {
4659 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
4661 if (cps->other->sendDrawOffers) {
4662 SendToProgram("draw\n", cps->other);
4665 } else if (gameMode == MachinePlaysWhite ||
4666 gameMode == MachinePlaysBlack) {
4667 if (userOfferedDraw) {
4668 DisplayInformation("Machine accepts your draw offer");
4669 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
4671 DisplayInformation("Machine offers a draw\nSelect Action / Draw to agree");
4678 * Look for thinking output
4680 if ( appData.showThinking) {
4681 int plylev, mvleft, mvtot, curscore, time;
4682 char mvname[MOVE_LEN];
4683 unsigned long nodes;
4686 int prefixHint = FALSE;
4687 mvname[0] = NULLCHAR;
4690 case MachinePlaysBlack:
4691 case IcsPlayingBlack:
4692 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
4694 case MachinePlaysWhite:
4695 case IcsPlayingWhite:
4696 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
4701 case TwoMachinesPlay:
4702 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
4713 if (sscanf(message, "%d%c %d %d %lu %[^\n]\n",
4714 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
4716 if (plyext != ' ' && plyext != '\t') {
4720 /* [AS] Negate score if machine is playing black and reporting absolute scores */
4721 if( cps->scoreIsAbsolute &&
4722 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
4724 curscore = -curscore;
4728 programStats.depth = plylev;
4729 programStats.nodes = nodes;
4730 programStats.time = time;
4731 programStats.score = curscore;
4732 programStats.got_only_move = 0;
4734 /* Buffer overflow protection */
4735 if (buf1[0] != NULLCHAR) {
4736 if (strlen(buf1) >= sizeof(programStats.movelist)
4737 && appData.debugMode) {
4739 "PV is too long; using the first %d bytes.\n",
4740 sizeof(programStats.movelist) - 1);
4743 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
4745 sprintf(programStats.movelist, " no PV\n");
4748 if (programStats.seen_stat) {
4749 programStats.ok_to_send = 1;
4752 if (strchr(programStats.movelist, '(') != NULL) {
4753 programStats.line_is_book = 1;
4754 programStats.nr_moves = 0;
4755 programStats.moves_left = 0;
4757 programStats.line_is_book = 0;
4760 SendProgramStatsToFrontend( cps );
4763 [AS] Protect the thinkOutput buffer from overflow... this
4764 is only useful if buf1 hasn't overflowed first!
4766 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
4768 (gameMode == TwoMachinesPlay ?
4769 ToUpper(cps->twoMachinesColor[0]) : ' '),
4770 ((double) curscore) / 100.0,
4771 prefixHint ? lastHint : "",
4772 prefixHint ? " " : "" );
4774 if( buf1[0] != NULLCHAR ) {
4775 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
4777 if( strlen(buf1) > max_len ) {
4778 if( appData.debugMode) {
4779 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
4781 buf1[max_len+1] = '\0';
4784 strcat( thinkOutput, buf1 );
4787 if (currentMove == forwardMostMove || gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
4788 DisplayMove(currentMove - 1);
4793 } else if ((p=StrStr(message, "(only move)")) != NULL) {
4794 /* crafty (9.25+) says "(only move) <move>"
4795 * if there is only 1 legal move
4797 sscanf(p, "(only move) %s", buf1);
4798 sprintf(thinkOutput, "%s (only move)", buf1);
4799 sprintf(programStats.movelist, "%s (only move)", buf1);
4800 programStats.depth = 1;
4801 programStats.nr_moves = 1;
4802 programStats.moves_left = 1;
4803 programStats.nodes = 1;
4804 programStats.time = 1;
4805 programStats.got_only_move = 1;
4807 /* Not really, but we also use this member to
4808 mean "line isn't going to change" (Crafty
4809 isn't searching, so stats won't change) */
4810 programStats.line_is_book = 1;
4812 SendProgramStatsToFrontend( cps );
4814 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || gameMode == AnalyzeFile) {
4815 DisplayMove(currentMove - 1);
4819 } else if (sscanf(message,"stat01: %d %lu %d %d %d %s",
4820 &time, &nodes, &plylev, &mvleft,
4821 &mvtot, mvname) >= 5) {
4822 /* The stat01: line is from Crafty (9.29+) in response
4823 to the "." command */
4824 programStats.seen_stat = 1;
4825 cps->maybeThinking = TRUE;
4827 if (programStats.got_only_move || !appData.periodicUpdates)
4830 programStats.depth = plylev;
4831 programStats.time = time;
4832 programStats.nodes = nodes;
4833 programStats.moves_left = mvleft;
4834 programStats.nr_moves = mvtot;
4835 strcpy(programStats.move_name, mvname);
4836 programStats.ok_to_send = 1;
4838 SendProgramStatsToFrontend( cps );
4843 } else if (strncmp(message,"++",2) == 0) {
4844 /* Crafty 9.29+ outputs this */
4845 programStats.got_fail = 2;
4848 } else if (strncmp(message,"--",2) == 0) {
4849 /* Crafty 9.29+ outputs this */
4850 programStats.got_fail = 1;
4853 } else if (thinkOutput[0] != NULLCHAR &&
4854 strncmp(message, " ", 4) == 0) {
4855 unsigned message_len;
4858 while (*p && *p == ' ') p++;
4860 message_len = strlen( p );
4862 /* [AS] Avoid buffer overflow */
4863 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
4864 strcat(thinkOutput, " ");
4865 strcat(thinkOutput, p);
4868 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
4869 strcat(programStats.movelist, " ");
4870 strcat(programStats.movelist, p);
4873 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || gameMode == AnalyzeFile) {
4874 DisplayMove(currentMove - 1);
4884 /* Parse a game score from the character string "game", and
4885 record it as the history of the current game. The game
4886 score is NOT assumed to start from the standard position.
4887 The display is not updated in any way.
4890 ParseGameHistory(game)
4894 int fromX, fromY, toX, toY, boardIndex;
4899 if (appData.debugMode)
4900 fprintf(debugFP, "Parsing game history: %s\n", game);
4902 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
4903 gameInfo.site = StrSave(appData.icsHost);
4904 gameInfo.date = PGNDate();
4905 gameInfo.round = StrSave("-");
4907 /* Parse out names of players */
4908 while (*game == ' ') game++;
4910 while (*game != ' ') *p++ = *game++;
4912 gameInfo.white = StrSave(buf);
4913 while (*game == ' ') game++;
4915 while (*game != ' ' && *game != '\n') *p++ = *game++;
4917 gameInfo.black = StrSave(buf);
4920 boardIndex = blackPlaysFirst ? 1 : 0;
4923 yyboardindex = boardIndex;
4924 moveType = (ChessMove) yylex();
4926 case WhitePromotionQueen:
4927 case BlackPromotionQueen:
4928 case WhitePromotionRook:
4929 case BlackPromotionRook:
4930 case WhitePromotionBishop:
4931 case BlackPromotionBishop:
4932 case WhitePromotionKnight:
4933 case BlackPromotionKnight:
4934 case WhitePromotionKing:
4935 case BlackPromotionKing:
4937 case WhiteCapturesEnPassant:
4938 case BlackCapturesEnPassant:
4939 case WhiteKingSideCastle:
4940 case WhiteQueenSideCastle:
4941 case BlackKingSideCastle:
4942 case BlackQueenSideCastle:
4943 case WhiteKingSideCastleWild:
4944 case WhiteQueenSideCastleWild:
4945 case BlackKingSideCastleWild:
4946 case BlackQueenSideCastleWild:
4948 case WhiteHSideCastleFR:
4949 case WhiteASideCastleFR:
4950 case BlackHSideCastleFR:
4951 case BlackASideCastleFR:
4953 case IllegalMove: /* maybe suicide chess, etc. */
4954 fromX = currentMoveString[0] - 'a';
4955 fromY = currentMoveString[1] - '1';
4956 toX = currentMoveString[2] - 'a';
4957 toY = currentMoveString[3] - '1';
4958 promoChar = currentMoveString[4];
4962 fromX = moveType == WhiteDrop ?
4963 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4964 (int) CharToPiece(ToLower(currentMoveString[0]));
4966 toX = currentMoveString[2] - 'a';
4967 toY = currentMoveString[3] - '1';
4968 promoChar = NULLCHAR;
4972 sprintf(buf, "Ambiguous move in ICS output: \"%s\"", yy_text);
4973 DisplayError(buf, 0);
4975 case ImpossibleMove:
4977 sprintf(buf, "Illegal move in ICS output: \"%s\"", yy_text);
4978 DisplayError(buf, 0);
4980 case (ChessMove) 0: /* end of file */
4981 if (boardIndex < backwardMostMove) {
4982 /* Oops, gap. How did that happen? */
4983 DisplayError("Gap in move list", 0);
4986 backwardMostMove = blackPlaysFirst ? 1 : 0;
4987 if (boardIndex > forwardMostMove) {
4988 forwardMostMove = boardIndex;
4992 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
4993 strcat(parseList[boardIndex-1], " ");
4994 strcat(parseList[boardIndex-1], yy_text);
5006 case GameUnfinished:
5007 if (gameMode == IcsExamining) {
5008 if (boardIndex < backwardMostMove) {
5009 /* Oops, gap. How did that happen? */
5012 backwardMostMove = blackPlaysFirst ? 1 : 0;
5015 gameInfo.result = moveType;
5016 p = strchr(yy_text, '{');
5017 if (p == NULL) p = strchr(yy_text, '(');
5020 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
5022 q = strchr(p, *p == '{' ? '}' : ')');
5023 if (q != NULL) *q = NULLCHAR;
5026 gameInfo.resultDetails = StrSave(p);
5029 if (boardIndex >= forwardMostMove &&
5030 !(gameMode == IcsObserving && ics_gamenum == -1)) {
5031 backwardMostMove = blackPlaysFirst ? 1 : 0;
5034 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
5035 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
5036 parseList[boardIndex]);
5037 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
5038 /* currentMoveString is set as a side-effect of yylex */
5039 strcpy(moveList[boardIndex], currentMoveString);
5040 strcat(moveList[boardIndex], "\n");
5042 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
5043 switch (MateTest(boards[boardIndex],
5044 PosFlags(boardIndex), EP_UNKNOWN)) {
5050 strcat(parseList[boardIndex - 1], "+");
5053 strcat(parseList[boardIndex - 1], "#");
5060 /* Apply a move to the given board */
5062 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
5063 int fromX, fromY, toX, toY;
5067 ChessSquare captured = board[toY][toX];
5068 if (fromY == DROP_RANK) {
5070 board[toY][toX] = (ChessSquare) fromX;
5071 } else if (fromX == toX && fromY == toY) {
5075 /* Code added by Tord: */
5076 /* FRC castling assumed when king captures friendly rook. */
5077 else if (board[fromY][fromX] == WhiteKing &&
5078 board[toY][toX] == WhiteRook) {
5079 board[fromY][fromX] = EmptySquare;
5080 board[toY][toX] = EmptySquare;
5082 board[0][6] = WhiteKing; board[0][5] = WhiteRook;
5084 board[0][2] = WhiteKing; board[0][3] = WhiteRook;
5086 } else if (board[fromY][fromX] == BlackKing &&
5087 board[toY][toX] == BlackRook) {
5088 board[fromY][fromX] = EmptySquare;
5089 board[toY][toX] = EmptySquare;
5091 board[7][6] = BlackKing; board[7][5] = BlackRook;
5093 board[7][2] = BlackKing; board[7][3] = BlackRook;
5095 /* End of code added by Tord */
5097 } else if (fromY == 0 && fromX == 4
5098 && board[fromY][fromX] == WhiteKing
5099 && toY == 0 && toX == 6) {
5100 board[fromY][fromX] = EmptySquare;
5101 board[toY][toX] = WhiteKing;
5102 board[fromY][7] = EmptySquare;
5103 board[toY][5] = WhiteRook;
5104 } else if (fromY == 0 && fromX == 4
5105 && board[fromY][fromX] == WhiteKing
5106 && toY == 0 && toX == 2) {
5107 board[fromY][fromX] = EmptySquare;
5108 board[toY][toX] = WhiteKing;
5109 board[fromY][0] = EmptySquare;
5110 board[toY][3] = WhiteRook;
5111 } else if (fromY == 0 && fromX == 3
5112 && board[fromY][fromX] == WhiteKing
5113 && toY == 0 && toX == 5) {
5114 board[fromY][fromX] = EmptySquare;
5115 board[toY][toX] = WhiteKing;
5116 board[fromY][7] = EmptySquare;
5117 board[toY][4] = WhiteRook;
5118 } else if (fromY == 0 && fromX == 3
5119 && board[fromY][fromX] == WhiteKing
5120 && toY == 0 && toX == 1) {
5121 board[fromY][fromX] = EmptySquare;
5122 board[toY][toX] = WhiteKing;
5123 board[fromY][0] = EmptySquare;
5124 board[toY][2] = WhiteRook;
5125 } else if (board[fromY][fromX] == WhitePawn
5127 /* white pawn promotion */
5128 board[7][toX] = CharToPiece(ToUpper(promoChar));
5129 if (board[7][toX] == EmptySquare) {
5130 board[7][toX] = WhiteQueen;
5132 board[fromY][fromX] = EmptySquare;
5133 } else if ((fromY == 4)
5135 && (board[fromY][fromX] == WhitePawn)
5136 && (board[toY][toX] == EmptySquare)) {
5137 board[fromY][fromX] = EmptySquare;
5138 board[toY][toX] = WhitePawn;
5139 captured = board[toY - 1][toX];
5140 board[toY - 1][toX] = EmptySquare;
5141 } else if (fromY == 7 && fromX == 4
5142 && board[fromY][fromX] == BlackKing
5143 && toY == 7 && toX == 6) {
5144 board[fromY][fromX] = EmptySquare;
5145 board[toY][toX] = BlackKing;
5146 board[fromY][7] = EmptySquare;
5147 board[toY][5] = BlackRook;
5148 } else if (fromY == 7 && fromX == 4
5149 && board[fromY][fromX] == BlackKing
5150 && toY == 7 && toX == 2) {
5151 board[fromY][fromX] = EmptySquare;
5152 board[toY][toX] = BlackKing;
5153 board[fromY][0] = EmptySquare;
5154 board[toY][3] = BlackRook;
5155 } else if (fromY == 7 && fromX == 3
5156 && board[fromY][fromX] == BlackKing
5157 && toY == 7 && toX == 5) {
5158 board[fromY][fromX] = EmptySquare;
5159 board[toY][toX] = BlackKing;
5160 board[fromY][7] = EmptySquare;
5161 board[toY][4] = BlackRook;
5162 } else if (fromY == 7 && fromX == 3
5163 && board[fromY][fromX] == BlackKing
5164 && toY == 7 && toX == 1) {
5165 board[fromY][fromX] = EmptySquare;
5166 board[toY][toX] = BlackKing;
5167 board[fromY][0] = EmptySquare;
5168 board[toY][2] = BlackRook;
5169 } else if (board[fromY][fromX] == BlackPawn
5171 /* black pawn promotion */
5172 board[0][toX] = CharToPiece(ToLower(promoChar));
5173 if (board[0][toX] == EmptySquare) {
5174 board[0][toX] = BlackQueen;
5176 board[fromY][fromX] = EmptySquare;
5177 } else if ((fromY == 3)
5179 && (board[fromY][fromX] == BlackPawn)
5180 && (board[toY][toX] == EmptySquare)) {
5181 board[fromY][fromX] = EmptySquare;
5182 board[toY][toX] = BlackPawn;
5183 captured = board[toY + 1][toX];
5184 board[toY + 1][toX] = EmptySquare;
5186 board[toY][toX] = board[fromY][fromX];
5187 board[fromY][fromX] = EmptySquare;
5189 if (gameInfo.variant == VariantCrazyhouse) {
5191 /* !!A lot more code needs to be written to support holdings */
5192 if (fromY == DROP_RANK) {
5193 /* Delete from holdings */
5194 if (holdings[(int) fromX] > 0) holdings[(int) fromX]--;
5196 if (captured != EmptySquare) {
5197 /* Add to holdings */
5198 if (captured < BlackPawn) {
5199 holdings[(int)captured - (int)BlackPawn + (int)WhitePawn]++;
5201 holdings[(int)captured - (int)WhitePawn + (int)BlackPawn]++;
5205 } else if (gameInfo.variant == VariantAtomic) {
5206 if (captured != EmptySquare) {
5208 for (y = toY-1; y <= toY+1; y++) {
5209 for (x = toX-1; x <= toX+1; x++) {
5210 if (y >= 0 && y <= 7 && x >= 0 && x <= 7 &&
5211 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
5212 board[y][x] = EmptySquare;
5216 board[toY][toX] = EmptySquare;
5221 /* Updates forwardMostMove */
5223 MakeMove(fromX, fromY, toX, toY, promoChar)
5224 int fromX, fromY, toX, toY;
5228 if (forwardMostMove >= MAX_MOVES) {
5229 DisplayFatalError("Game too long; increase MAX_MOVES and recompile",
5234 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
5235 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
5236 if (commentList[forwardMostMove] != NULL) {
5237 free(commentList[forwardMostMove]);
5238 commentList[forwardMostMove] = NULL;
5240 CopyBoard(boards[forwardMostMove], boards[forwardMostMove - 1]);
5241 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove]);
5242 gameInfo.result = GameUnfinished;
5243 if (gameInfo.resultDetails != NULL) {
5244 free(gameInfo.resultDetails);
5245 gameInfo.resultDetails = NULL;
5247 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
5248 moveList[forwardMostMove - 1]);
5249 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
5250 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
5251 fromY, fromX, toY, toX, promoChar,
5252 parseList[forwardMostMove - 1]);
5253 switch (MateTest(boards[forwardMostMove],
5254 PosFlags(forwardMostMove), EP_UNKNOWN)){
5260 strcat(parseList[forwardMostMove - 1], "+");
5263 strcat(parseList[forwardMostMove - 1], "#");
5268 /* Updates currentMove if not pausing */
5270 ShowMove(fromX, fromY, toX, toY)
5272 int instant = (gameMode == PlayFromGameFile) ?
5273 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
5274 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
5276 if (forwardMostMove == currentMove + 1) {
5277 AnimateMove(boards[forwardMostMove - 1],
5278 fromX, fromY, toX, toY);
5280 if (appData.highlightLastMove) {
5281 SetHighlights(fromX, fromY, toX, toY);
5284 currentMove = forwardMostMove;
5287 if (instant) return;
5289 DisplayMove(currentMove - 1);
5290 DrawPosition(FALSE, boards[currentMove]);
5291 DisplayBothClocks();
5292 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
5297 InitChessProgram(cps)
5298 ChessProgramState *cps;
5301 if (appData.noChessProgram) return;
5302 hintRequested = FALSE;
5303 bookRequested = FALSE;
5304 SendToProgram(cps->initString, cps);
5305 if (gameInfo.variant != VariantNormal &&
5306 gameInfo.variant != VariantLoadable) {
5307 char *v = VariantName(gameInfo.variant);
5308 if (StrStr(cps->variants, v) == NULL) {
5309 sprintf(buf, "Variant %s not supported by %s", v, cps->tidy);
5310 DisplayFatalError(buf, 0, 1);
5313 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
5314 SendToProgram(buf, cps);
5317 sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-");
5318 SendToProgram(buf, cps);
5320 cps->maybeThinking = FALSE;
5321 cps->offeredDraw = 0;
5322 if (!appData.icsActive) {
5323 SendTimeControl(cps, movesPerSession, timeControl,
5324 timeIncrement, appData.searchDepth,
5327 if (appData.showThinking) {
5328 SendToProgram("post\n", cps);
5330 SendToProgram("hard\n", cps);
5331 if (!appData.ponderNextMove) {
5332 /* Warning: "easy" is a toggle in GNU Chess, so don't send
5333 it without being sure what state we are in first. "hard"
5334 is not a toggle, so that one is OK.
5336 SendToProgram("easy\n", cps);
5339 sprintf(buf, "ping %d\n", ++cps->lastPing);
5340 SendToProgram(buf, cps);
5342 cps->initDone = TRUE;
5347 StartChessProgram(cps)
5348 ChessProgramState *cps;
5353 if (appData.noChessProgram) return;
5354 cps->initDone = FALSE;
5356 if (strcmp(cps->host, "localhost") == 0) {
5357 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
5358 } else if (*appData.remoteShell == NULLCHAR) {
5359 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
5361 if (*appData.remoteUser == NULLCHAR) {
5362 sprintf(buf, "%s %s %s", appData.remoteShell, cps->host,
5365 sprintf(buf, "%s %s -l %s %s", appData.remoteShell,
5366 cps->host, appData.remoteUser, cps->program);
5368 err = StartChildProcess(buf, "", &cps->pr);
5372 sprintf(buf, "Startup failure on '%s'", cps->program);
5373 DisplayFatalError(buf, err, 1);
5379 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
5380 if (cps->protocolVersion > 1) {
5381 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
5382 SendToProgram(buf, cps);
5384 SendToProgram("xboard\n", cps);
5390 TwoMachinesEventIfReady P((void))
5392 if (first.lastPing != first.lastPong) {
5393 DisplayMessage("", "Waiting for first chess program");
5394 ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000);
5397 if (second.lastPing != second.lastPong) {
5398 DisplayMessage("", "Waiting for second chess program");
5399 ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000);
5407 NextMatchGame P((void))
5410 if (*appData.loadGameFile != NULLCHAR) {
5411 LoadGameFromFile(appData.loadGameFile,
5412 appData.loadGameIndex,
5413 appData.loadGameFile, FALSE);
5414 } else if (*appData.loadPositionFile != NULLCHAR) {
5415 LoadPositionFromFile(appData.loadPositionFile,
5416 appData.loadPositionIndex,
5417 appData.loadPositionFile);
5419 TwoMachinesEventIfReady();
5422 void UserAdjudicationEvent( int result )
5424 ChessMove gameResult = GameIsDrawn;
5427 gameResult = WhiteWins;
5429 else if( result < 0 ) {
5430 gameResult = BlackWins;
5433 if( gameMode == TwoMachinesPlay ) {
5434 GameEnds( gameResult, "User adjudication", GE_XBOARD );
5440 GameEnds(result, resultDetails, whosays)
5442 char *resultDetails;
5445 GameMode nextGameMode;
5448 if (appData.debugMode) {
5449 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
5450 result, resultDetails ? resultDetails : "(null)", whosays);
5453 if (appData.icsActive && whosays == GE_ENGINE) {
5454 /* If we are playing on ICS, the server decides when the
5455 game is over, but the engine can offer to draw, claim
5459 if (appData.zippyPlay && first.initDone) {
5460 if (result == GameIsDrawn) {
5461 /* In case draw still needs to be claimed */
5462 SendToICS(ics_prefix);
5463 SendToICS("draw\n");
5464 } else if (StrCaseStr(resultDetails, "resign")) {
5465 SendToICS(ics_prefix);
5466 SendToICS("resign\n");
5473 /* If we're loading the game from a file, stop */
5474 if (whosays == GE_FILE) {
5475 (void) StopLoadGameTimer();
5479 /* Cancel draw offers */
5480 first.offeredDraw = second.offeredDraw = 0;
5482 /* If this is an ICS game, only ICS can really say it's done;
5483 if not, anyone can. */
5484 isIcsGame = (gameMode == IcsPlayingWhite ||
5485 gameMode == IcsPlayingBlack ||
5486 gameMode == IcsObserving ||
5487 gameMode == IcsExamining);
5489 if (!isIcsGame || whosays == GE_ICS) {
5490 /* OK -- not an ICS game, or ICS said it was done */
5492 if (!isIcsGame && !appData.noChessProgram)
5493 SetUserThinkingEnables();
5495 if (resultDetails != NULL) {
5496 gameInfo.result = result;
5497 gameInfo.resultDetails = StrSave(resultDetails);
5499 /* Tell program how game ended in case it is learning */
5500 if (gameMode == MachinePlaysWhite ||
5501 gameMode == MachinePlaysBlack ||
5502 gameMode == TwoMachinesPlay ||
5503 gameMode == IcsPlayingWhite ||
5504 gameMode == IcsPlayingBlack ||
5505 gameMode == BeginningOfGame) {
5507 sprintf(buf, "result %s {%s}\n", PGNResult(result),
5509 if (first.pr != NoProc) {
5510 SendToProgram(buf, &first);
5512 if (second.pr != NoProc &&
5513 gameMode == TwoMachinesPlay) {
5514 SendToProgram(buf, &second);
5518 /* display last move only if game was not loaded from file */
5519 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
5520 DisplayMove(currentMove - 1);
5522 if (forwardMostMove != 0) {
5523 if (gameMode != PlayFromGameFile && gameMode != EditGame) {
5524 if (*appData.saveGameFile != NULLCHAR) {
5525 SaveGameToFile(appData.saveGameFile, TRUE);
5526 } else if (appData.autoSaveGames) {
5529 if (*appData.savePositionFile != NULLCHAR) {
5530 SavePositionToFile(appData.savePositionFile);
5536 if (appData.icsActive) {
5537 if (appData.quietPlay &&
5538 (gameMode == IcsPlayingWhite ||
5539 gameMode == IcsPlayingBlack)) {
5540 SendToICS(ics_prefix);
5541 SendToICS("set shout 1\n");
5543 nextGameMode = IcsIdle;
5544 ics_user_moved = FALSE;
5545 /* clean up premove. It's ugly when the game has ended and the
5546 * premove highlights are still on the board.
5550 ClearPremoveHighlights();
5551 DrawPosition(FALSE, boards[currentMove]);
5553 if (whosays == GE_ICS) {
5556 if (gameMode == IcsPlayingWhite)
5558 else if(gameMode == IcsPlayingBlack)
5562 if (gameMode == IcsPlayingBlack)
5564 else if(gameMode == IcsPlayingWhite)
5571 PlayIcsUnfinishedSound();
5574 } else if (gameMode == EditGame ||
5575 gameMode == PlayFromGameFile ||
5576 gameMode == AnalyzeMode ||
5577 gameMode == AnalyzeFile) {
5578 nextGameMode = gameMode;
5580 nextGameMode = EndOfGame;
5585 nextGameMode = gameMode;
5588 if (appData.noChessProgram) {
5589 gameMode = nextGameMode;
5595 /* Put first chess program into idle state */
5596 if (first.pr != NoProc &&
5597 (gameMode == MachinePlaysWhite ||
5598 gameMode == MachinePlaysBlack ||
5599 gameMode == TwoMachinesPlay ||
5600 gameMode == IcsPlayingWhite ||
5601 gameMode == IcsPlayingBlack ||
5602 gameMode == BeginningOfGame)) {
5603 SendToProgram("force\n", &first);
5604 if (first.usePing) {
5606 sprintf(buf, "ping %d\n", ++first.lastPing);
5607 SendToProgram(buf, &first);
5610 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
5611 /* Kill off first chess program */
5612 if (first.isr != NULL)
5613 RemoveInputSource(first.isr);
5616 if (first.pr != NoProc) {
5618 DoSleep( appData.delayBeforeQuit );
5619 SendToProgram("quit\n", &first);
5620 DoSleep( appData.delayAfterQuit );
5621 DestroyChildProcess(first.pr, first.useSigterm);
5626 /* Put second chess program into idle state */
5627 if (second.pr != NoProc &&
5628 gameMode == TwoMachinesPlay) {
5629 SendToProgram("force\n", &second);
5630 if (second.usePing) {
5632 sprintf(buf, "ping %d\n", ++second.lastPing);
5633 SendToProgram(buf, &second);
5636 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
5637 /* Kill off second chess program */
5638 if (second.isr != NULL)
5639 RemoveInputSource(second.isr);
5642 if (second.pr != NoProc) {
5643 DoSleep( appData.delayBeforeQuit );
5644 SendToProgram("quit\n", &second);
5645 DoSleep( appData.delayAfterQuit );
5646 DestroyChildProcess(second.pr, second.useSigterm);
5651 if (matchMode && gameMode == TwoMachinesPlay) {
5654 if (first.twoMachinesColor[0] == 'w') {
5661 if (first.twoMachinesColor[0] == 'b') {
5670 if (matchGame < appData.matchGames) {
5672 tmp = first.twoMachinesColor;
5673 first.twoMachinesColor = second.twoMachinesColor;
5674 second.twoMachinesColor = tmp;
5675 gameMode = nextGameMode;
5677 ScheduleDelayedEvent(NextMatchGame, 10000);
5681 gameMode = nextGameMode;
5682 sprintf(buf, "Match %s vs. %s: final score %d-%d-%d",
5683 first.tidy, second.tidy,
5684 first.matchWins, second.matchWins,
5685 appData.matchGames - (first.matchWins + second.matchWins));
5686 DisplayFatalError(buf, 0, 0);
5689 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
5690 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
5692 gameMode = nextGameMode;
5696 /* Assumes program was just initialized (initString sent).
5697 Leaves program in force mode. */
5699 FeedMovesToProgram(cps, upto)
5700 ChessProgramState *cps;
5705 if (appData.debugMode)
5706 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
5707 startedFromSetupPosition ? "position and " : "",
5708 backwardMostMove, upto, cps->which);
5709 SendToProgram("force\n", cps);
5710 if (startedFromSetupPosition) {
5711 SendBoard(cps, backwardMostMove);
5713 for (i = backwardMostMove; i < upto; i++) {
5714 SendMoveToProgram(i, cps);
5720 ResurrectChessProgram()
5722 /* The chess program may have exited.
5723 If so, restart it and feed it all the moves made so far. */
5725 if (appData.noChessProgram || first.pr != NoProc) return;
5727 StartChessProgram(&first);
5728 InitChessProgram(&first);
5729 FeedMovesToProgram(&first, currentMove);
5731 if (!first.sendTime) {
5732 /* can't tell gnuchess what its clock should read,
5733 so we bow to its notion. */
5735 timeRemaining[0][currentMove] = whiteTimeRemaining;
5736 timeRemaining[1][currentMove] = blackTimeRemaining;
5739 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
5740 first.analysisSupport) {
5741 SendToProgram("analyze\n", &first);
5742 first.analyzing = TRUE;
5755 if (appData.debugMode) {
5756 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
5757 redraw, init, gameMode);
5760 pausing = pauseExamInvalid = FALSE;
5761 startedFromSetupPosition = blackPlaysFirst = FALSE;
5763 whiteFlag = blackFlag = FALSE;
5764 userOfferedDraw = FALSE;
5765 hintRequested = bookRequested = FALSE;
5766 first.maybeThinking = FALSE;
5767 second.maybeThinking = FALSE;
5768 thinkOutput[0] = NULLCHAR;
5769 lastHint[0] = NULLCHAR;
5770 ClearGameInfo(&gameInfo);
5771 gameInfo.variant = StringToVariant(appData.variant);
5772 ics_user_moved = ics_clock_paused = FALSE;
5773 ics_getting_history = H_FALSE;
5775 white_holding[0] = black_holding[0] = NULLCHAR;
5776 ClearProgramStats();
5780 flipView = appData.flipView;
5781 ClearPremoveHighlights();
5783 alarmSounded = FALSE;
5785 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
5787 gameMode = BeginningOfGame;
5789 InitPosition(redraw);
5790 for (i = 0; i < MAX_MOVES; i++) {
5791 if (commentList[i] != NULL) {
5792 free(commentList[i]);
5793 commentList[i] = NULL;
5797 timeRemaining[0][0] = whiteTimeRemaining;
5798 timeRemaining[1][0] = blackTimeRemaining;
5799 if (first.pr == NULL) {
5800 StartChessProgram(&first);
5802 if (init) InitChessProgram(&first);
5804 DisplayMessage("", "");
5805 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5812 if (!AutoPlayOneMove())
5814 if (matchMode || appData.timeDelay == 0)
5816 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
5818 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
5827 int fromX, fromY, toX, toY;
5829 if (appData.debugMode) {
5830 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
5833 if (gameMode != PlayFromGameFile)
5836 if (currentMove >= forwardMostMove) {
5837 gameMode = EditGame;
5840 /* [AS] Clear current move marker at the end of a game */
5841 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
5846 toX = moveList[currentMove][2] - 'a';
5847 toY = moveList[currentMove][3] - '1';
5849 if (moveList[currentMove][1] == '@') {
5850 if (appData.highlightLastMove) {
5851 SetHighlights(-1, -1, toX, toY);
5854 fromX = moveList[currentMove][0] - 'a';
5855 fromY = moveList[currentMove][1] - '1';
5857 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
5859 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
5861 if (appData.highlightLastMove) {
5862 SetHighlights(fromX, fromY, toX, toY);
5865 DisplayMove(currentMove);
5866 SendMoveToProgram(currentMove++, &first);
5867 DisplayBothClocks();
5868 DrawPosition(FALSE, boards[currentMove]);
5869 if (commentList[currentMove] != NULL) {
5870 DisplayComment(currentMove - 1, commentList[currentMove]);
5877 LoadGameOneMove(readAhead)
5878 ChessMove readAhead;
5880 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
5881 char promoChar = NULLCHAR;
5886 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
5887 gameMode != AnalyzeMode && gameMode != Training) {
5892 yyboardindex = forwardMostMove;
5893 if (readAhead != (ChessMove)0) {
5894 moveType = readAhead;
5896 if (gameFileFP == NULL)
5898 moveType = (ChessMove) yylex();
5904 if (appData.debugMode)
5905 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
5907 if (*p == '{' || *p == '[' || *p == '(') {
5908 p[strlen(p) - 1] = NULLCHAR;
5912 /* append the comment but don't display it */
5913 while (*p == '\n') p++;
5914 AppendComment(currentMove, p);
5917 case WhiteCapturesEnPassant:
5918 case BlackCapturesEnPassant:
5919 case WhitePromotionQueen:
5920 case BlackPromotionQueen:
5921 case WhitePromotionRook:
5922 case BlackPromotionRook:
5923 case WhitePromotionBishop:
5924 case BlackPromotionBishop:
5925 case WhitePromotionKnight:
5926 case BlackPromotionKnight:
5927 case WhitePromotionKing:
5928 case BlackPromotionKing:
5930 case WhiteKingSideCastle:
5931 case WhiteQueenSideCastle:
5932 case BlackKingSideCastle:
5933 case BlackQueenSideCastle:
5934 case WhiteKingSideCastleWild:
5935 case WhiteQueenSideCastleWild:
5936 case BlackKingSideCastleWild:
5937 case BlackQueenSideCastleWild:
5939 case WhiteHSideCastleFR:
5940 case WhiteASideCastleFR:
5941 case BlackHSideCastleFR:
5942 case BlackASideCastleFR:
5944 if (appData.debugMode)
5945 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
5946 fromX = currentMoveString[0] - 'a';
5947 fromY = currentMoveString[1] - '1';
5948 toX = currentMoveString[2] - 'a';
5949 toY = currentMoveString[3] - '1';
5950 promoChar = currentMoveString[4];
5955 if (appData.debugMode)
5956 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
5957 fromX = moveType == WhiteDrop ?
5958 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5959 (int) CharToPiece(ToLower(currentMoveString[0]));
5961 toX = currentMoveString[2] - 'a';
5962 toY = currentMoveString[3] - '1';
5968 case GameUnfinished:
5969 if (appData.debugMode)
5970 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
5971 p = strchr(yy_text, '{');
5972 if (p == NULL) p = strchr(yy_text, '(');
5975 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
5977 q = strchr(p, *p == '{' ? '}' : ')');
5978 if (q != NULL) *q = NULLCHAR;
5981 GameEnds(moveType, p, GE_FILE);
5983 if (cmailMsgLoaded) {
5985 flipView = WhiteOnMove(currentMove);
5986 if (moveType == GameUnfinished) flipView = !flipView;
5987 if (appData.debugMode)
5988 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
5992 case (ChessMove) 0: /* end of file */
5993 if (appData.debugMode)
5994 fprintf(debugFP, "Parser hit end of file\n");
5995 switch (MateTest(boards[currentMove], PosFlags(currentMove),
6001 if (WhiteOnMove(currentMove)) {
6002 GameEnds(BlackWins, "Black mates", GE_FILE);
6004 GameEnds(WhiteWins, "White mates", GE_FILE);
6008 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
6015 if (lastLoadGameStart == GNUChessGame) {
6016 /* GNUChessGames have numbers, but they aren't move numbers */
6017 if (appData.debugMode)
6018 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
6019 yy_text, (int) moveType);
6020 return LoadGameOneMove((ChessMove)0); /* tail recursion */
6022 /* else fall thru */
6027 /* Reached start of next game in file */
6028 if (appData.debugMode)
6029 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
6030 switch (MateTest(boards[currentMove], PosFlags(currentMove),
6036 if (WhiteOnMove(currentMove)) {
6037 GameEnds(BlackWins, "Black mates", GE_FILE);
6039 GameEnds(WhiteWins, "White mates", GE_FILE);
6043 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
6049 case PositionDiagram: /* should not happen; ignore */
6050 case ElapsedTime: /* ignore */
6051 case NAG: /* ignore */
6052 if (appData.debugMode)
6053 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
6054 yy_text, (int) moveType);
6055 return LoadGameOneMove((ChessMove)0); /* tail recursion */
6058 if (appData.testLegality) {
6059 if (appData.debugMode)
6060 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
6061 sprintf(move, "Illegal move: %d.%s%s",
6062 (forwardMostMove / 2) + 1,
6063 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
6064 DisplayError(move, 0);
6067 if (appData.debugMode)
6068 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
6069 yy_text, currentMoveString);
6070 fromX = currentMoveString[0] - 'a';
6071 fromY = currentMoveString[1] - '1';
6072 toX = currentMoveString[2] - 'a';
6073 toY = currentMoveString[3] - '1';
6074 promoChar = currentMoveString[4];
6079 if (appData.debugMode)
6080 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
6081 sprintf(move, "Ambiguous move: %d.%s%s",
6082 (forwardMostMove / 2) + 1,
6083 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
6084 DisplayError(move, 0);
6089 case ImpossibleMove:
6090 if (appData.debugMode)
6091 fprintf(debugFP, "Parsed ImpossibleMove: %s\n", yy_text);
6092 sprintf(move, "Illegal move: %d.%s%s",
6093 (forwardMostMove / 2) + 1,
6094 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
6095 DisplayError(move, 0);
6101 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
6102 DrawPosition(FALSE, boards[currentMove]);
6103 DisplayBothClocks();
6104 if (!appData.matchMode && commentList[currentMove] != NULL)
6105 DisplayComment(currentMove - 1, commentList[currentMove]);
6107 (void) StopLoadGameTimer();
6109 cmailOldMove = forwardMostMove;
6112 /* currentMoveString is set as a side-effect of yylex */
6113 strcat(currentMoveString, "\n");
6114 strcpy(moveList[forwardMostMove], currentMoveString);
6116 thinkOutput[0] = NULLCHAR;
6117 MakeMove(fromX, fromY, toX, toY, promoChar);
6118 currentMove = forwardMostMove;
6123 /* Load the nth game from the given file */
6125 LoadGameFromFile(filename, n, title, useList)
6129 /*Boolean*/ int useList;
6134 if (strcmp(filename, "-") == 0) {
6138 f = fopen(filename, "rb");
6140 sprintf(buf, "Can't open \"%s\"", filename);
6141 DisplayError(buf, errno);
6145 if (fseek(f, 0, 0) == -1) {
6146 /* f is not seekable; probably a pipe */
6149 if (useList && n == 0) {
6150 int error = GameListBuild(f);
6152 DisplayError("Cannot build game list", error);
6153 } else if (!ListEmpty(&gameList) &&
6154 ((ListGame *) gameList.tailPred)->number > 1) {
6155 GameListPopUp(f, title);
6162 return LoadGame(f, n, title, FALSE);
6167 MakeRegisteredMove()
6169 int fromX, fromY, toX, toY;
6171 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
6172 switch (cmailMoveType[lastLoadGameNumber - 1]) {
6175 if (appData.debugMode)
6176 fprintf(debugFP, "Restoring %s for game %d\n",
6177 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
6179 thinkOutput[0] = NULLCHAR;
6180 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
6181 fromX = cmailMove[lastLoadGameNumber - 1][0] - 'a';
6182 fromY = cmailMove[lastLoadGameNumber - 1][1] - '1';
6183 toX = cmailMove[lastLoadGameNumber - 1][2] - 'a';
6184 toY = cmailMove[lastLoadGameNumber - 1][3] - '1';
6185 promoChar = cmailMove[lastLoadGameNumber - 1][4];
6186 MakeMove(fromX, fromY, toX, toY, promoChar);
6187 ShowMove(fromX, fromY, toX, toY);
6189 switch (MateTest(boards[currentMove], PosFlags(currentMove),
6196 if (WhiteOnMove(currentMove)) {
6197 GameEnds(BlackWins, "Black mates", GE_PLAYER);
6199 GameEnds(WhiteWins, "White mates", GE_PLAYER);
6204 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6211 if (WhiteOnMove(currentMove)) {
6212 GameEnds(BlackWins, "White resigns", GE_PLAYER);
6214 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
6219 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
6230 /* Wrapper around LoadGame for use when a Cmail message is loaded */
6232 CmailLoadGame(f, gameNumber, title, useList)
6240 if (gameNumber > nCmailGames) {
6241 DisplayError("No more games in this message", 0);
6244 if (f == lastLoadGameFP) {
6245 int offset = gameNumber - lastLoadGameNumber;
6247 cmailMsg[0] = NULLCHAR;
6248 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
6249 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
6250 nCmailMovesRegistered--;
6252 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6253 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
6254 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
6257 if (! RegisterMove()) return FALSE;
6261 retVal = LoadGame(f, gameNumber, title, useList);
6263 /* Make move registered during previous look at this game, if any */
6264 MakeRegisteredMove();
6266 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
6267 commentList[currentMove]
6268 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
6269 DisplayComment(currentMove - 1, commentList[currentMove]);
6275 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
6280 int gameNumber = lastLoadGameNumber + offset;
6281 if (lastLoadGameFP == NULL) {
6282 DisplayError("No game has been loaded yet", 0);
6285 if (gameNumber <= 0) {
6286 DisplayError("Can't back up any further", 0);
6289 if (cmailMsgLoaded) {
6290 return CmailLoadGame(lastLoadGameFP, gameNumber,
6291 lastLoadGameTitle, lastLoadGameUseList);
6293 return LoadGame(lastLoadGameFP, gameNumber,
6294 lastLoadGameTitle, lastLoadGameUseList);
6300 /* Load the nth game from open file f */
6302 LoadGame(f, gameNumber, title, useList)
6310 int gn = gameNumber;
6311 ListGame *lg = NULL;
6314 GameMode oldGameMode;
6316 if (appData.debugMode)
6317 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
6319 if (gameMode == Training )
6320 SetTrainingModeOff();
6322 oldGameMode = gameMode;
6323 if (gameMode != BeginningOfGame) {
6328 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
6329 fclose(lastLoadGameFP);
6333 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
6336 fseek(f, lg->offset, 0);
6337 GameListHighlight(gameNumber);
6341 DisplayError("Game number out of range", 0);
6346 if (fseek(f, 0, 0) == -1) {
6347 if (f == lastLoadGameFP ?
6348 gameNumber == lastLoadGameNumber + 1 :
6352 DisplayError("Can't seek on game file", 0);
6358 lastLoadGameNumber = gameNumber;
6359 strcpy(lastLoadGameTitle, title);
6360 lastLoadGameUseList = useList;
6365 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
6366 sprintf(buf, "%s vs. %s", lg->gameInfo.white,
6367 lg->gameInfo.black);
6369 } else if (*title != NULLCHAR) {
6370 if (gameNumber > 1) {
6371 sprintf(buf, "%s %d", title, gameNumber);
6374 DisplayTitle(title);
6378 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
6379 gameMode = PlayFromGameFile;
6383 currentMove = forwardMostMove = backwardMostMove = 0;
6384 CopyBoard(boards[0], initialPosition);
6388 * Skip the first gn-1 games in the file.
6389 * Also skip over anything that precedes an identifiable
6390 * start of game marker, to avoid being confused by
6391 * garbage at the start of the file. Currently
6392 * recognized start of game markers are the move number "1",
6393 * the pattern "gnuchess .* game", the pattern
6394 * "^[#;%] [^ ]* game file", and a PGN tag block.
6395 * A game that starts with one of the latter two patterns
6396 * will also have a move number 1, possibly
6397 * following a position diagram.
6398 * 5-4-02: Let's try being more lenient and allowing a game to
6399 * start with an unnumbered move. Does that break anything?
6401 cm = lastLoadGameStart = (ChessMove) 0;
6403 yyboardindex = forwardMostMove;
6404 cm = (ChessMove) yylex();
6407 if (cmailMsgLoaded) {
6408 nCmailGames = CMAIL_MAX_GAMES - gn;
6411 DisplayError("Game not found in file", 0);
6418 lastLoadGameStart = cm;
6422 switch (lastLoadGameStart) {
6429 gn--; /* count this game */
6430 lastLoadGameStart = cm;
6439 switch (lastLoadGameStart) {
6444 gn--; /* count this game */
6445 lastLoadGameStart = cm;
6448 lastLoadGameStart = cm; /* game counted already */
6456 yyboardindex = forwardMostMove;
6457 cm = (ChessMove) yylex();
6458 } while (cm == PGNTag || cm == Comment);
6465 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
6466 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
6467 != CMAIL_OLD_RESULT) {
6469 cmailResult[ CMAIL_MAX_GAMES
6470 - gn - 1] = CMAIL_OLD_RESULT;
6476 /* Only a NormalMove can be at the start of a game
6477 * without a position diagram. */
6478 if (lastLoadGameStart == (ChessMove) 0) {
6480 lastLoadGameStart = MoveNumberOne;
6489 if (appData.debugMode)
6490 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
6492 if (cm == XBoardGame) {
6493 /* Skip any header junk before position diagram and/or move 1 */
6495 yyboardindex = forwardMostMove;
6496 cm = (ChessMove) yylex();
6498 if (cm == (ChessMove) 0 ||
6499 cm == GNUChessGame || cm == XBoardGame) {
6500 /* Empty game; pretend end-of-file and handle later */
6505 if (cm == MoveNumberOne || cm == PositionDiagram ||
6506 cm == PGNTag || cm == Comment)
6509 } else if (cm == GNUChessGame) {
6510 if (gameInfo.event != NULL) {
6511 free(gameInfo.event);
6513 gameInfo.event = StrSave(yy_text);
6516 startedFromSetupPosition = FALSE;
6517 while (cm == PGNTag) {
6518 if (appData.debugMode)
6519 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
6520 err = ParsePGNTag(yy_text, &gameInfo);
6521 if (!err) numPGNTags++;
6523 if (gameInfo.fen != NULL) {
6524 Board initial_position;
6525 startedFromSetupPosition = TRUE;
6526 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
6528 DisplayError("Bad FEN position in file", 0);
6531 CopyBoard(boards[0], initial_position);
6532 if (blackPlaysFirst) {
6533 currentMove = forwardMostMove = backwardMostMove = 1;
6534 CopyBoard(boards[1], initial_position);
6535 strcpy(moveList[0], "");
6536 strcpy(parseList[0], "");
6537 timeRemaining[0][1] = whiteTimeRemaining;
6538 timeRemaining[1][1] = blackTimeRemaining;
6539 if (commentList[0] != NULL) {
6540 commentList[1] = commentList[0];
6541 commentList[0] = NULL;
6544 currentMove = forwardMostMove = backwardMostMove = 0;
6546 yyboardindex = forwardMostMove;
6548 gameInfo.fen = NULL;
6551 yyboardindex = forwardMostMove;
6552 cm = (ChessMove) yylex();
6554 /* Handle comments interspersed among the tags */
6555 while (cm == Comment) {
6557 if (appData.debugMode)
6558 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
6560 if (*p == '{' || *p == '[' || *p == '(') {
6561 p[strlen(p) - 1] = NULLCHAR;
6564 while (*p == '\n') p++;
6565 AppendComment(currentMove, p);
6566 yyboardindex = forwardMostMove;
6567 cm = (ChessMove) yylex();
6571 /* don't rely on existence of Event tag since if game was
6572 * pasted from clipboard the Event tag may not exist
6574 if (numPGNTags > 0){
6576 if (gameInfo.variant == VariantNormal) {
6577 gameInfo.variant = StringToVariant(gameInfo.event);
6580 if( appData.autoDisplayTags ) {
6581 tags = PGNTags(&gameInfo);
6582 TagsPopUp(tags, CmailMsg());
6587 /* Make something up, but don't display it now */
6592 if (cm == PositionDiagram) {
6595 Board initial_position;
6597 if (appData.debugMode)
6598 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
6600 if (!startedFromSetupPosition) {
6602 for (i = BOARD_SIZE - 1; i >= 0; i--)
6603 for (j = 0; j < BOARD_SIZE; p++)
6613 initial_position[i][j++] = CharToPiece(*p);
6616 while (*p == ' ' || *p == '\t' ||
6617 *p == '\n' || *p == '\r') p++;
6619 if (strncmp(p, "black", strlen("black"))==0)
6620 blackPlaysFirst = TRUE;
6622 blackPlaysFirst = FALSE;
6623 startedFromSetupPosition = TRUE;
6625 CopyBoard(boards[0], initial_position);
6626 if (blackPlaysFirst) {
6627 currentMove = forwardMostMove = backwardMostMove = 1;
6628 CopyBoard(boards[1], initial_position);
6629 strcpy(moveList[0], "");
6630 strcpy(parseList[0], "");
6631 timeRemaining[0][1] = whiteTimeRemaining;
6632 timeRemaining[1][1] = blackTimeRemaining;
6633 if (commentList[0] != NULL) {
6634 commentList[1] = commentList[0];
6635 commentList[0] = NULL;
6638 currentMove = forwardMostMove = backwardMostMove = 0;
6641 yyboardindex = forwardMostMove;
6642 cm = (ChessMove) yylex();
6645 if (first.pr == NoProc) {
6646 StartChessProgram(&first);
6648 InitChessProgram(&first);
6649 SendToProgram("force\n", &first);
6650 if (startedFromSetupPosition) {
6651 SendBoard(&first, forwardMostMove);
6652 DisplayBothClocks();
6655 while (cm == Comment) {
6657 if (appData.debugMode)
6658 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
6660 if (*p == '{' || *p == '[' || *p == '(') {
6661 p[strlen(p) - 1] = NULLCHAR;
6664 while (*p == '\n') p++;
6665 AppendComment(currentMove, p);
6666 yyboardindex = forwardMostMove;
6667 cm = (ChessMove) yylex();
6670 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
6671 cm == WhiteWins || cm == BlackWins ||
6672 cm == GameIsDrawn || cm == GameUnfinished) {
6673 DisplayMessage("", "No moves in game");
6674 if (cmailMsgLoaded) {
6675 if (appData.debugMode)
6676 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
6680 DrawPosition(FALSE, boards[currentMove]);
6681 DisplayBothClocks();
6682 gameMode = EditGame;
6689 if (commentList[currentMove] != NULL) {
6690 if (!matchMode && (pausing || appData.timeDelay != 0)) {
6691 DisplayComment(currentMove - 1, commentList[currentMove]);
6694 if (!matchMode && appData.timeDelay != 0)
6695 DrawPosition(FALSE, boards[currentMove]);
6697 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
6698 programStats.ok_to_send = 1;
6701 /* if the first token after the PGN tags is a move
6702 * and not move number 1, retrieve it from the parser
6704 if (cm != MoveNumberOne)
6705 LoadGameOneMove(cm);
6707 /* load the remaining moves from the file */
6708 while (LoadGameOneMove((ChessMove)0)) {
6709 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
6710 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
6713 /* rewind to the start of the game */
6714 currentMove = backwardMostMove;
6716 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
6718 if (oldGameMode == AnalyzeFile ||
6719 oldGameMode == AnalyzeMode) {
6723 if (matchMode || appData.timeDelay == 0) {
6725 gameMode = EditGame;
6727 } else if (appData.timeDelay > 0) {
6731 if (appData.debugMode)
6732 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
6736 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
6738 ReloadPosition(offset)
6741 int positionNumber = lastLoadPositionNumber + offset;
6742 if (lastLoadPositionFP == NULL) {
6743 DisplayError("No position has been loaded yet", 0);
6746 if (positionNumber <= 0) {
6747 DisplayError("Can't back up any further", 0);
6750 return LoadPosition(lastLoadPositionFP, positionNumber,
6751 lastLoadPositionTitle);
6754 /* Load the nth position from the given file */
6756 LoadPositionFromFile(filename, n, title)
6764 if (strcmp(filename, "-") == 0) {
6765 return LoadPosition(stdin, n, "stdin");
6767 f = fopen(filename, "rb");
6769 sprintf(buf, "Can't open \"%s\"", filename);
6770 DisplayError(buf, errno);
6773 return LoadPosition(f, n, title);
6778 /* Load the nth position from the given open file, and close it */
6780 LoadPosition(f, positionNumber, title)
6785 char *p, line[MSG_SIZ];
6786 Board initial_position;
6787 int i, j, fenMode, pn;
6789 if (gameMode == Training )
6790 SetTrainingModeOff();
6792 if (gameMode != BeginningOfGame) {
6795 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
6796 fclose(lastLoadPositionFP);
6798 if (positionNumber == 0) positionNumber = 1;
6799 lastLoadPositionFP = f;
6800 lastLoadPositionNumber = positionNumber;
6801 strcpy(lastLoadPositionTitle, title);
6802 if (first.pr == NoProc) {
6803 StartChessProgram(&first);
6804 InitChessProgram(&first);
6806 pn = positionNumber;
6807 if (positionNumber < 0) {
6808 /* Negative position number means to seek to that byte offset */
6809 if (fseek(f, -positionNumber, 0) == -1) {
6810 DisplayError("Can't seek on position file", 0);
6815 if (fseek(f, 0, 0) == -1) {
6816 if (f == lastLoadPositionFP ?
6817 positionNumber == lastLoadPositionNumber + 1 :
6818 positionNumber == 1) {
6821 DisplayError("Can't seek on position file", 0);
6826 /* See if this file is FEN or old-style xboard */
6827 if (fgets(line, MSG_SIZ, f) == NULL) {
6828 DisplayError("Position not found in file", 0);
6836 case 'p': case 'n': case 'b': case 'r': case 'q': case 'k':
6837 case 'P': case 'N': case 'B': case 'R': case 'Q': case 'K':
6838 case '1': case '2': case '3': case '4': case '5': case '6':
6845 if (fenMode || line[0] == '#') pn--;
6847 /* skip postions before number pn */
6848 if (fgets(line, MSG_SIZ, f) == NULL) {
6850 DisplayError("Position not found in file", 0);
6853 if (fenMode || line[0] == '#') pn--;
6858 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
6859 DisplayError("Bad FEN position in file", 0);
6863 (void) fgets(line, MSG_SIZ, f);
6864 (void) fgets(line, MSG_SIZ, f);
6866 for (i = BOARD_SIZE - 1; i >= 0; i--) {
6867 (void) fgets(line, MSG_SIZ, f);
6868 for (p = line, j = 0; j < BOARD_SIZE; p++) {
6871 initial_position[i][j++] = CharToPiece(*p);
6875 blackPlaysFirst = FALSE;
6877 (void) fgets(line, MSG_SIZ, f);
6878 if (strncmp(line, "black", strlen("black"))==0)
6879 blackPlaysFirst = TRUE;
6882 startedFromSetupPosition = TRUE;
6884 SendToProgram("force\n", &first);
6885 CopyBoard(boards[0], initial_position);
6886 if (blackPlaysFirst) {
6887 currentMove = forwardMostMove = backwardMostMove = 1;
6888 strcpy(moveList[0], "");
6889 strcpy(parseList[0], "");
6890 CopyBoard(boards[1], initial_position);
6891 DisplayMessage("", "Black to play");
6893 currentMove = forwardMostMove = backwardMostMove = 0;
6894 DisplayMessage("", "White to play");
6896 SendBoard(&first, forwardMostMove);
6898 if (positionNumber > 1) {
6899 sprintf(line, "%s %d", title, positionNumber);
6902 DisplayTitle(title);
6904 gameMode = EditGame;
6907 timeRemaining[0][1] = whiteTimeRemaining;
6908 timeRemaining[1][1] = blackTimeRemaining;
6909 DrawPosition(FALSE, boards[currentMove]);
6916 CopyPlayerNameIntoFileName(dest, src)
6919 while (*src != NULLCHAR && *src != ',') {
6924 *(*dest)++ = *src++;
6929 char *DefaultFileName(ext)
6932 static char def[MSG_SIZ];
6935 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
6937 CopyPlayerNameIntoFileName(&p, gameInfo.white);
6939 CopyPlayerNameIntoFileName(&p, gameInfo.black);
6948 /* Save the current game to the given file */
6950 SaveGameToFile(filename, append)
6957 if (strcmp(filename, "-") == 0) {
6958 return SaveGame(stdout, 0, NULL);
6960 f = fopen(filename, append ? "a" : "w");
6962 sprintf(buf, "Can't open \"%s\"", filename);
6963 DisplayError(buf, errno);
6966 return SaveGame(f, 0, NULL);
6975 static char buf[MSG_SIZ];
6978 p = strchr(str, ' ');
6979 if (p == NULL) return str;
6980 strncpy(buf, str, p - str);
6981 buf[p - str] = NULLCHAR;
6985 #define PGN_MAX_LINE 75
6987 #define PGN_SIDE_WHITE 0
6988 #define PGN_SIDE_BLACK 1
6990 static int FindFirstMoveOutOfBook( int side )
6994 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
6995 int index = backwardMostMove;
6996 int has_book_hit = 0;
6998 if( (index % 2) != side ) {
7002 while( index < forwardMostMove ) {
7003 /* Check to see if engine is in book */
7004 int depth = pvInfoList[index].depth;
7005 int score = pvInfoList[index].score;
7009 in_book = 1; /* Yace */
7012 if( depth <= 1 || depth == 63 /* Zappa */ ) {
7017 has_book_hit += in_book;
7032 void GetOutOfBookInfo( char * buf )
7036 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
7038 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
7039 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
7043 if( oob[0] >= 0 || oob[1] >= 0 ) {
7044 for( i=0; i<2; i++ ) {
7048 if( i > 0 && oob[0] >= 0 ) {
7052 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
7053 sprintf( buf+strlen(buf), "%s%.2f",
7054 pvInfoList[idx].score >= 0 ? "+" : "",
7055 pvInfoList[idx].score / 100.0 );
7061 /* Save game in PGN style and close the file */
7066 int i, offset, linelen, newblock;
7070 int movelen, numlen, blank;
7071 char move_buffer[100]; /* [AS] Buffer for move+PV info */
7073 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
7075 tm = time((time_t *) NULL);
7077 PrintPGNTags(f, &gameInfo);
7079 if (backwardMostMove > 0 || startedFromSetupPosition) {
7080 char *fen = PositionToFEN(backwardMostMove, 1);
7081 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
7082 fprintf(f, "\n{--------------\n");
7083 PrintPosition(f, backwardMostMove);
7084 fprintf(f, "--------------}\n");
7088 /* [AS] Out of book annotation */
7089 if( appData.saveOutOfBookInfo ) {
7092 GetOutOfBookInfo( buf );
7094 if( buf[0] != '\0' ) {
7095 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
7102 i = backwardMostMove;
7106 while (i < forwardMostMove) {
7107 /* Print comments preceding this move */
7108 if (commentList[i] != NULL) {
7109 if (linelen > 0) fprintf(f, "\n");
7110 fprintf(f, "{\n%s}\n", commentList[i]);
7115 /* Format move number */
7117 sprintf(numtext, "%d.", (i - offset)/2 + 1);
7120 sprintf(numtext, "%d...", (i - offset)/2 + 1);
7122 numtext[0] = NULLCHAR;
7125 numlen = strlen(numtext);
7128 /* Print move number */
7129 blank = linelen > 0 && numlen > 0;
7130 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
7139 fprintf(f, numtext);
7143 movetext = SavePart(parseList[i]);
7145 /* [AS] Add PV info if present */
7146 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
7147 sprintf( move_buffer, "%s {%s%.2f/%d}",
7149 pvInfoList[i].score >= 0 ? "+" : "",
7150 pvInfoList[i].score / 100.0,
7151 pvInfoList[i].depth );
7152 movetext = move_buffer;
7155 movelen = strlen(movetext);
7158 blank = linelen > 0 && movelen > 0;
7159 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
7168 fprintf(f, movetext);
7174 /* Start a new line */
7175 if (linelen > 0) fprintf(f, "\n");
7177 /* Print comments after last move */
7178 if (commentList[i] != NULL) {
7179 fprintf(f, "{\n%s}\n", commentList[i]);
7183 if (gameInfo.resultDetails != NULL &&
7184 gameInfo.resultDetails[0] != NULLCHAR) {
7185 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
7186 PGNResult(gameInfo.result));
7188 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
7195 /* Save game in old style and close the file */
7203 tm = time((time_t *) NULL);
7205 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
7208 if (backwardMostMove > 0 || startedFromSetupPosition) {
7209 fprintf(f, "\n[--------------\n");
7210 PrintPosition(f, backwardMostMove);
7211 fprintf(f, "--------------]\n");
7216 i = backwardMostMove;
7217 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
7219 while (i < forwardMostMove) {
7220 if (commentList[i] != NULL) {
7221 fprintf(f, "[%s]\n", commentList[i]);
7225 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
7228 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
7230 if (commentList[i] != NULL) {
7234 if (i >= forwardMostMove) {
7238 fprintf(f, "%s\n", parseList[i]);
7243 if (commentList[i] != NULL) {
7244 fprintf(f, "[%s]\n", commentList[i]);
7247 /* This isn't really the old style, but it's close enough */
7248 if (gameInfo.resultDetails != NULL &&
7249 gameInfo.resultDetails[0] != NULLCHAR) {
7250 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
7251 gameInfo.resultDetails);
7253 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
7260 /* Save the current game to open file f and close the file */
7262 SaveGame(f, dummy, dummy2)
7267 if (gameMode == EditPosition) EditPositionDone();
7268 if (appData.oldSaveStyle)
7269 return SaveGameOldStyle(f);
7271 return SaveGamePGN(f);
7274 /* Save the current position to the given file */
7276 SavePositionToFile(filename)
7282 if (strcmp(filename, "-") == 0) {
7283 return SavePosition(stdout, 0, NULL);
7285 f = fopen(filename, "a");
7287 sprintf(buf, "Can't open \"%s\"", filename);
7288 DisplayError(buf, errno);
7291 SavePosition(f, 0, NULL);
7297 /* Save the current position to the given open file and close the file */
7299 SavePosition(f, dummy, dummy2)
7307 if (appData.oldSaveStyle) {
7308 tm = time((time_t *) NULL);
7310 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
7312 fprintf(f, "[--------------\n");
7313 PrintPosition(f, currentMove);
7314 fprintf(f, "--------------]\n");
7316 fen = PositionToFEN(currentMove, 1);
7317 fprintf(f, "%s\n", fen);
7325 ReloadCmailMsgEvent(unregister)
7329 static char *inFilename = NULL;
7330 static char *outFilename;
7332 struct stat inbuf, outbuf;
7335 /* Any registered moves are unregistered if unregister is set, */
7336 /* i.e. invoked by the signal handler */
7338 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
7339 cmailMoveRegistered[i] = FALSE;
7340 if (cmailCommentList[i] != NULL) {
7341 free(cmailCommentList[i]);
7342 cmailCommentList[i] = NULL;
7345 nCmailMovesRegistered = 0;
7348 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
7349 cmailResult[i] = CMAIL_NOT_RESULT;
7353 if (inFilename == NULL) {
7354 /* Because the filenames are static they only get malloced once */
7355 /* and they never get freed */
7356 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
7357 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
7359 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
7360 sprintf(outFilename, "%s.out", appData.cmailGameName);
7363 status = stat(outFilename, &outbuf);
7365 cmailMailedMove = FALSE;
7367 status = stat(inFilename, &inbuf);
7368 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
7371 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
7372 counts the games, notes how each one terminated, etc.
7374 It would be nice to remove this kludge and instead gather all
7375 the information while building the game list. (And to keep it
7376 in the game list nodes instead of having a bunch of fixed-size
7377 parallel arrays.) Note this will require getting each game's
7378 termination from the PGN tags, as the game list builder does
7379 not process the game moves. --mann
7381 cmailMsgLoaded = TRUE;
7382 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
7384 /* Load first game in the file or popup game menu */
7385 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
7395 char string[MSG_SIZ];
7397 if ( cmailMailedMove
7398 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
7399 return TRUE; /* Allow free viewing */
7402 /* Unregister move to ensure that we don't leave RegisterMove */
7403 /* with the move registered when the conditions for registering no */
7405 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
7406 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
7407 nCmailMovesRegistered --;
7409 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
7411 free(cmailCommentList[lastLoadGameNumber - 1]);
7412 cmailCommentList[lastLoadGameNumber - 1] = NULL;
7416 if (cmailOldMove == -1) {
7417 DisplayError("You have edited the game history.\nUse Reload Same Game and make your move again.", 0);
7421 if (currentMove > cmailOldMove + 1) {
7422 DisplayError("You have entered too many moves.\nBack up to the correct position and try again.", 0);
7426 if (currentMove < cmailOldMove) {
7427 DisplayError("Displayed position is not current.\nStep forward to the correct position and try again.", 0);
7431 if (forwardMostMove > currentMove) {
7432 /* Silently truncate extra moves */
7436 if ( (currentMove == cmailOldMove + 1)
7437 || ( (currentMove == cmailOldMove)
7438 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
7439 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
7440 if (gameInfo.result != GameUnfinished) {
7441 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
7444 if (commentList[currentMove] != NULL) {
7445 cmailCommentList[lastLoadGameNumber - 1]
7446 = StrSave(commentList[currentMove]);
7448 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
7450 if (appData.debugMode)
7451 fprintf(debugFP, "Saving %s for game %d\n",
7452 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
7455 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
7457 f = fopen(string, "w");
7458 if (appData.oldSaveStyle) {
7459 SaveGameOldStyle(f); /* also closes the file */
7461 sprintf(string, "%s.pos.out", appData.cmailGameName);
7462 f = fopen(string, "w");
7463 SavePosition(f, 0, NULL); /* also closes the file */
7465 fprintf(f, "{--------------\n");
7466 PrintPosition(f, currentMove);
7467 fprintf(f, "--------------}\n\n");
7469 SaveGame(f, 0, NULL); /* also closes the file*/
7472 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
7473 nCmailMovesRegistered ++;
7474 } else if (nCmailGames == 1) {
7475 DisplayError("You have not made a move yet", 0);
7486 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
7487 FILE *commandOutput;
7488 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
7489 int nBytes = 0; /* Suppress warnings on uninitialized variables */
7495 if (! cmailMsgLoaded) {
7496 DisplayError("The cmail message is not loaded.\nUse Reload CMail Message and make your move again.", 0);
7500 if (nCmailGames == nCmailResults) {
7501 DisplayError("No unfinished games", 0);
7505 #if CMAIL_PROHIBIT_REMAIL
7506 if (cmailMailedMove) {
7507 sprintf(msg, "You have already mailed a move.\nWait until a move arrives from your opponent.\nTo resend the same move, type\n\"cmail -remail -game %s\"\non the command line.", appData.cmailGameName);
7508 DisplayError(msg, 0);
7513 if (! (cmailMailedMove || RegisterMove())) return;
7515 if ( cmailMailedMove
7516 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
7517 sprintf(string, partCommandString,
7518 appData.debugMode ? " -v" : "", appData.cmailGameName);
7519 commandOutput = popen(string, "rb");
7521 if (commandOutput == NULL) {
7522 DisplayError("Failed to invoke cmail", 0);
7524 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
7525 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
7528 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
7529 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
7530 nBytes = MSG_SIZ - 1;
7532 (void) memcpy(msg, buffer, nBytes);
7534 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
7536 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
7537 cmailMailedMove = TRUE; /* Prevent >1 moves */
7540 for (i = 0; i < nCmailGames; i ++) {
7541 if (cmailResult[i] == CMAIL_NOT_RESULT) {
7546 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
7548 sprintf(buffer, "%s/%s.%s.archive",
7550 appData.cmailGameName,
7552 LoadGameFromFile(buffer, 1, buffer, FALSE);
7553 cmailMsgLoaded = FALSE;
7557 DisplayInformation(msg);
7558 pclose(commandOutput);
7561 if ((*cmailMsg) != '\0') {
7562 DisplayInformation(cmailMsg);
7576 int prependComma = 0;
7578 char string[MSG_SIZ]; /* Space for game-list */
7581 if (!cmailMsgLoaded) return "";
7583 if (cmailMailedMove) {
7584 sprintf(cmailMsg, "Waiting for reply from opponent\n");
7586 /* Create a list of games left */
7587 sprintf(string, "[");
7588 for (i = 0; i < nCmailGames; i ++) {
7589 if (! ( cmailMoveRegistered[i]
7590 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
7592 sprintf(number, ",%d", i + 1);
7594 sprintf(number, "%d", i + 1);
7598 strcat(string, number);
7601 strcat(string, "]");
7603 if (nCmailMovesRegistered + nCmailResults == 0) {
7604 switch (nCmailGames) {
7607 "Still need to make move for game\n");
7612 "Still need to make moves for both games\n");
7617 "Still need to make moves for all %d games\n",
7622 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
7625 "Still need to make a move for game %s\n",
7630 if (nCmailResults == nCmailGames) {
7631 sprintf(cmailMsg, "No unfinished games\n");
7633 sprintf(cmailMsg, "Ready to send mail\n");
7639 "Still need to make moves for games %s\n",
7651 if (gameMode == Training)
7652 SetTrainingModeOff();
7655 cmailMsgLoaded = FALSE;
7656 if (appData.icsActive) {
7657 SendToICS(ics_prefix);
7658 SendToICS("refresh\n");
7662 static int exiting = 0;
7670 /* Give up on clean exit */
7674 /* Keep trying for clean exit */
7678 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
7680 if (telnetISR != NULL) {
7681 RemoveInputSource(telnetISR);
7683 if (icsPR != NoProc) {
7684 DestroyChildProcess(icsPR, TRUE);
7686 /* Save game if resource set and not already saved by GameEnds() */
7687 if (gameInfo.resultDetails == NULL && forwardMostMove > 0) {
7688 if (*appData.saveGameFile != NULLCHAR) {
7689 SaveGameToFile(appData.saveGameFile, TRUE);
7690 } else if (appData.autoSaveGames) {
7693 if (*appData.savePositionFile != NULLCHAR) {
7694 SavePositionToFile(appData.savePositionFile);
7697 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
7699 /* Kill off chess programs */
7700 if (first.pr != NoProc) {
7703 DoSleep( appData.delayBeforeQuit );
7704 SendToProgram("quit\n", &first);
7705 DoSleep( appData.delayAfterQuit );
7706 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
7708 if (second.pr != NoProc) {
7709 DoSleep( appData.delayBeforeQuit );
7710 SendToProgram("quit\n", &second);
7711 DoSleep( appData.delayAfterQuit );
7712 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
7714 if (first.isr != NULL) {
7715 RemoveInputSource(first.isr);
7717 if (second.isr != NULL) {
7718 RemoveInputSource(second.isr);
7728 if (appData.debugMode)
7729 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
7733 if (gameMode == MachinePlaysWhite ||
7734 gameMode == MachinePlaysBlack) {
7737 DisplayBothClocks();
7739 if (gameMode == PlayFromGameFile) {
7740 if (appData.timeDelay >= 0)
7742 } else if (gameMode == IcsExamining && pauseExamInvalid) {
7744 SendToICS(ics_prefix);
7745 SendToICS("refresh\n");
7746 } else if (currentMove < forwardMostMove) {
7747 ForwardInner(forwardMostMove);
7749 pauseExamInvalid = FALSE;
7755 pauseExamForwardMostMove = forwardMostMove;
7756 pauseExamInvalid = FALSE;
7759 case IcsPlayingWhite:
7760 case IcsPlayingBlack:
7764 case PlayFromGameFile:
7765 (void) StopLoadGameTimer();
7769 case BeginningOfGame:
7770 if (appData.icsActive) return;
7771 /* else fall through */
7772 case MachinePlaysWhite:
7773 case MachinePlaysBlack:
7774 case TwoMachinesPlay:
7775 if (forwardMostMove == 0)
7776 return; /* don't pause if no one has moved */
7777 if ((gameMode == MachinePlaysWhite &&
7778 !WhiteOnMove(forwardMostMove)) ||
7779 (gameMode == MachinePlaysBlack &&
7780 WhiteOnMove(forwardMostMove))) {
7793 char title[MSG_SIZ];
7795 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
7796 strcpy(title, "Edit comment");
7798 sprintf(title, "Edit comment on %d.%s%s", (currentMove - 1) / 2 + 1,
7799 WhiteOnMove(currentMove - 1) ? " " : ".. ",
7800 parseList[currentMove - 1]);
7803 EditCommentPopUp(currentMove, title, commentList[currentMove]);
7810 char *tags = PGNTags(&gameInfo);
7811 EditTagsPopUp(tags);
7818 if (appData.noChessProgram || gameMode == AnalyzeMode)
7821 if (gameMode != AnalyzeFile) {
7823 if (gameMode != EditGame) return;
7824 ResurrectChessProgram();
7825 SendToProgram("analyze\n", &first);
7826 first.analyzing = TRUE;
7827 /*first.maybeThinking = TRUE;*/
7828 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
7829 AnalysisPopUp("Analysis",
7830 "Starting analysis mode...\nIf this message stays up, your chess program does not support analysis.");
7832 gameMode = AnalyzeMode;
7837 StartAnalysisClock();
7838 GetTimeMark(&lastNodeCountTime);
7845 if (appData.noChessProgram || gameMode == AnalyzeFile)
7848 if (gameMode != AnalyzeMode) {
7850 if (gameMode != EditGame) return;
7851 ResurrectChessProgram();
7852 SendToProgram("analyze\n", &first);
7853 first.analyzing = TRUE;
7854 /*first.maybeThinking = TRUE;*/
7855 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
7856 AnalysisPopUp("Analysis",
7857 "Starting analysis mode...\nIf this message stays up, your chess program does not support analysis.");
7859 gameMode = AnalyzeFile;
7864 StartAnalysisClock();
7865 GetTimeMark(&lastNodeCountTime);
7874 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
7878 if (gameMode == PlayFromGameFile ||
7879 gameMode == TwoMachinesPlay ||
7880 gameMode == Training ||
7881 gameMode == AnalyzeMode ||
7882 gameMode == EndOfGame)
7885 if (gameMode == EditPosition)
7888 if (!WhiteOnMove(currentMove)) {
7889 DisplayError("It is not White's turn", 0);
7893 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
7896 if (gameMode == EditGame || gameMode == AnalyzeMode ||
7897 gameMode == AnalyzeFile)
7900 ResurrectChessProgram(); /* in case it isn't running */
7901 gameMode = MachinePlaysWhite;
7905 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
7907 if (first.sendName) {
7908 sprintf(buf, "name %s\n", gameInfo.black);
7909 SendToProgram(buf, &first);
7911 if (first.sendTime) {
7912 if (first.useColors) {
7913 SendToProgram("black\n", &first); /*gnu kludge*/
7915 SendTimeRemaining(&first, TRUE);
7917 if (first.useColors) {
7918 SendToProgram("white\ngo\n", &first);
7920 SendToProgram("go\n", &first);
7922 SetMachineThinkingEnables();
7923 first.maybeThinking = TRUE;
7926 if (appData.autoFlipView && !flipView) {
7927 flipView = !flipView;
7928 DrawPosition(FALSE, NULL);
7937 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
7941 if (gameMode == PlayFromGameFile ||
7942 gameMode == TwoMachinesPlay ||
7943 gameMode == Training ||
7944 gameMode == AnalyzeMode ||
7945 gameMode == EndOfGame)
7948 if (gameMode == EditPosition)
7951 if (WhiteOnMove(currentMove)) {
7952 DisplayError("It is not Black's turn", 0);
7956 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
7959 if (gameMode == EditGame || gameMode == AnalyzeMode ||
7960 gameMode == AnalyzeFile)
7963 ResurrectChessProgram(); /* in case it isn't running */
7964 gameMode = MachinePlaysBlack;
7968 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
7970 if (first.sendName) {
7971 sprintf(buf, "name %s\n", gameInfo.white);
7972 SendToProgram(buf, &first);
7974 if (first.sendTime) {
7975 if (first.useColors) {
7976 SendToProgram("white\n", &first); /*gnu kludge*/
7978 SendTimeRemaining(&first, FALSE);
7980 if (first.useColors) {
7981 SendToProgram("black\ngo\n", &first);
7983 SendToProgram("go\n", &first);
7985 SetMachineThinkingEnables();
7986 first.maybeThinking = TRUE;
7989 if (appData.autoFlipView && flipView) {
7990 flipView = !flipView;
7991 DrawPosition(FALSE, NULL);
7997 DisplayTwoMachinesTitle()
8000 if (appData.matchGames > 0) {
8001 if (first.twoMachinesColor[0] == 'w') {
8002 sprintf(buf, "%s vs. %s (%d-%d-%d)",
8003 gameInfo.white, gameInfo.black,
8004 first.matchWins, second.matchWins,
8005 matchGame - 1 - (first.matchWins + second.matchWins));
8007 sprintf(buf, "%s vs. %s (%d-%d-%d)",
8008 gameInfo.white, gameInfo.black,
8009 second.matchWins, first.matchWins,
8010 matchGame - 1 - (first.matchWins + second.matchWins));
8013 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
8019 TwoMachinesEvent P((void))
8023 ChessProgramState *onmove;
8025 if (appData.noChessProgram) return;
8028 case TwoMachinesPlay:
8030 case MachinePlaysWhite:
8031 case MachinePlaysBlack:
8032 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
8033 DisplayError("Wait until your turn,\nor select Move Now", 0);
8037 case BeginningOfGame:
8038 case PlayFromGameFile:
8041 if (gameMode != EditGame) return;
8055 forwardMostMove = currentMove;
8056 ResurrectChessProgram(); /* in case first program isn't running */
8058 if (second.pr == NULL) {
8059 StartChessProgram(&second);
8060 if (second.protocolVersion == 1) {
8061 TwoMachinesEventIfReady();
8063 /* kludge: allow timeout for initial "feature" command */
8065 DisplayMessage("", "Starting second chess program");
8066 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
8070 DisplayMessage("", "");
8071 InitChessProgram(&second);
8072 SendToProgram("force\n", &second);
8073 if (startedFromSetupPosition) {
8074 SendBoard(&second, backwardMostMove);
8076 for (i = backwardMostMove; i < forwardMostMove; i++) {
8077 SendMoveToProgram(i, &second);
8080 gameMode = TwoMachinesPlay;
8084 DisplayTwoMachinesTitle();
8086 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
8092 SendToProgram(first.computerString, &first);
8093 if (first.sendName) {
8094 sprintf(buf, "name %s\n", second.tidy);
8095 SendToProgram(buf, &first);
8097 SendToProgram(second.computerString, &second);
8098 if (second.sendName) {
8099 sprintf(buf, "name %s\n", first.tidy);
8100 SendToProgram(buf, &second);
8103 if (!first.sendTime || !second.sendTime) {
8105 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8106 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8108 if (onmove->sendTime) {
8109 if (onmove->useColors) {
8110 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
8112 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
8114 if (onmove->useColors) {
8115 SendToProgram(onmove->twoMachinesColor, onmove);
8117 SendToProgram("go\n", onmove);
8118 onmove->maybeThinking = TRUE;
8119 SetMachineThinkingEnables();
8127 if (gameMode == Training) {
8128 SetTrainingModeOff();
8129 gameMode = PlayFromGameFile;
8130 DisplayMessage("", "Training mode off");
8132 gameMode = Training;
8133 animateTraining = appData.animate;
8135 /* make sure we are not already at the end of the game */
8136 if (currentMove < forwardMostMove) {
8137 SetTrainingModeOn();
8138 DisplayMessage("", "Training mode on");
8140 gameMode = PlayFromGameFile;
8141 DisplayError("Already at end of game", 0);
8150 if (!appData.icsActive) return;
8152 case IcsPlayingWhite:
8153 case IcsPlayingBlack:
8156 case BeginningOfGame:
8190 SetTrainingModeOff();
8192 case MachinePlaysWhite:
8193 case MachinePlaysBlack:
8194 case BeginningOfGame:
8195 SendToProgram("force\n", &first);
8196 SetUserThinkingEnables();
8198 case PlayFromGameFile:
8199 (void) StopLoadGameTimer();
8200 if (gameFileFP != NULL) {
8210 SendToProgram("force\n", &first);
8212 case TwoMachinesPlay:
8213 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8214 ResurrectChessProgram();
8215 SetUserThinkingEnables();
8218 ResurrectChessProgram();
8220 case IcsPlayingBlack:
8221 case IcsPlayingWhite:
8222 DisplayError("Warning: You are still playing a game", 0);
8225 DisplayError("Warning: You are still observing a game", 0);
8228 DisplayError("Warning: You are still examining a game", 0);
8239 first.offeredDraw = second.offeredDraw = 0;
8241 if (gameMode == PlayFromGameFile) {
8242 whiteTimeRemaining = timeRemaining[0][currentMove];
8243 blackTimeRemaining = timeRemaining[1][currentMove];
8247 if (gameMode == MachinePlaysWhite ||
8248 gameMode == MachinePlaysBlack ||
8249 gameMode == TwoMachinesPlay ||
8250 gameMode == EndOfGame) {
8251 i = forwardMostMove;
8252 while (i > currentMove) {
8253 SendToProgram("undo\n", &first);
8256 whiteTimeRemaining = timeRemaining[0][currentMove];
8257 blackTimeRemaining = timeRemaining[1][currentMove];
8258 DisplayBothClocks();
8259 if (whiteFlag || blackFlag) {
8260 whiteFlag = blackFlag = 0;
8265 gameMode = EditGame;
8274 if (gameMode == EditPosition) {
8280 if (gameMode != EditGame) return;
8282 gameMode = EditPosition;
8285 if (currentMove > 0)
8286 CopyBoard(boards[0], boards[currentMove]);
8288 blackPlaysFirst = !WhiteOnMove(currentMove);
8290 currentMove = forwardMostMove = backwardMostMove = 0;
8291 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8298 if (first.analysisSupport && first.analyzing) {
8299 SendToProgram("exit\n", &first);
8300 first.analyzing = FALSE;
8303 thinkOutput[0] = NULLCHAR;
8309 startedFromSetupPosition = TRUE;
8310 InitChessProgram(&first);
8311 SendToProgram("force\n", &first);
8312 if (blackPlaysFirst) {
8313 strcpy(moveList[0], "");
8314 strcpy(parseList[0], "");
8315 currentMove = forwardMostMove = backwardMostMove = 1;
8316 CopyBoard(boards[1], boards[0]);
8318 currentMove = forwardMostMove = backwardMostMove = 0;
8320 SendBoard(&first, forwardMostMove);
8322 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8323 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8324 gameMode = EditGame;
8326 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8327 ClearHighlights(); /* [AS] */
8330 /* Pause for `ms' milliseconds */
8331 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
8341 } while (SubtractTimeMarks(&m2, &m1) < ms);
8344 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
8346 SendMultiLineToICS(buf)
8349 char temp[MSG_SIZ+1], *p;
8356 strncpy(temp, buf, len);
8361 if (*p == '\n' || *p == '\r')
8368 SendToPlayer(temp, strlen(temp));
8372 SetWhiteToPlayEvent()
8374 if (gameMode == EditPosition) {
8375 blackPlaysFirst = FALSE;
8376 DisplayBothClocks(); /* works because currentMove is 0 */
8377 } else if (gameMode == IcsExamining) {
8378 SendToICS(ics_prefix);
8379 SendToICS("tomove white\n");
8384 SetBlackToPlayEvent()
8386 if (gameMode == EditPosition) {
8387 blackPlaysFirst = TRUE;
8388 currentMove = 1; /* kludge */
8389 DisplayBothClocks();
8391 } else if (gameMode == IcsExamining) {
8392 SendToICS(ics_prefix);
8393 SendToICS("tomove black\n");
8398 EditPositionMenuEvent(selection, x, y)
8399 ChessSquare selection;
8404 if (gameMode != EditPosition && gameMode != IcsExamining) return;
8406 switch (selection) {
8408 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
8409 SendToICS(ics_prefix);
8410 SendToICS("bsetup clear\n");
8411 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
8412 SendToICS(ics_prefix);
8413 SendToICS("clearboard\n");
8415 for (x = 0; x < BOARD_SIZE; x++) {
8416 for (y = 0; y < BOARD_SIZE; y++) {
8417 if (gameMode == IcsExamining) {
8418 if (boards[currentMove][y][x] != EmptySquare) {
8419 sprintf(buf, "%sx@%c%c\n", ics_prefix,
8424 boards[0][y][x] = EmptySquare;
8429 if (gameMode == EditPosition) {
8430 DrawPosition(FALSE, boards[0]);
8435 SetWhiteToPlayEvent();
8439 SetBlackToPlayEvent();
8443 if (gameMode == IcsExamining) {
8444 sprintf(buf, "%sx@%c%c\n", ics_prefix, 'a' + x, '1' + y);
8447 boards[0][y][x] = EmptySquare;
8448 DrawPosition(FALSE, boards[0]);
8453 if (gameMode == IcsExamining) {
8454 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
8455 PieceToChar(selection), 'a' + x, '1' + y);
8458 boards[0][y][x] = selection;
8459 DrawPosition(FALSE, boards[0]);
8467 DropMenuEvent(selection, x, y)
8468 ChessSquare selection;
8474 case IcsPlayingWhite:
8475 case MachinePlaysBlack:
8476 if (!WhiteOnMove(currentMove)) {
8477 DisplayMoveError("It is Black's turn");
8480 moveType = WhiteDrop;
8482 case IcsPlayingBlack:
8483 case MachinePlaysWhite:
8484 if (WhiteOnMove(currentMove)) {
8485 DisplayMoveError("It is White's turn");
8488 moveType = BlackDrop;
8491 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
8497 if (moveType == BlackDrop && selection < BlackPawn) {
8498 selection = (ChessSquare) ((int) selection
8499 + (int) BlackPawn - (int) WhitePawn);
8501 if (boards[currentMove][y][x] != EmptySquare) {
8502 DisplayMoveError("That square is occupied");
8506 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
8512 /* Accept a pending offer of any kind from opponent */
8514 if (appData.icsActive) {
8515 SendToICS(ics_prefix);
8516 SendToICS("accept\n");
8517 } else if (cmailMsgLoaded) {
8518 if (currentMove == cmailOldMove &&
8519 commentList[cmailOldMove] != NULL &&
8520 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
8521 "Black offers a draw" : "White offers a draw")) {
8523 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8524 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
8526 DisplayError("There is no pending offer on this move", 0);
8527 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8530 /* Not used for offers from chess program */
8537 /* Decline a pending offer of any kind from opponent */
8539 if (appData.icsActive) {
8540 SendToICS(ics_prefix);
8541 SendToICS("decline\n");
8542 } else if (cmailMsgLoaded) {
8543 if (currentMove == cmailOldMove &&
8544 commentList[cmailOldMove] != NULL &&
8545 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
8546 "Black offers a draw" : "White offers a draw")) {
8548 AppendComment(cmailOldMove, "Draw declined");
8549 DisplayComment(cmailOldMove - 1, "Draw declined");
8552 DisplayError("There is no pending offer on this move", 0);
8555 /* Not used for offers from chess program */
8562 /* Issue ICS rematch command */
8563 if (appData.icsActive) {
8564 SendToICS(ics_prefix);
8565 SendToICS("rematch\n");
8572 /* Call your opponent's flag (claim a win on time) */
8573 if (appData.icsActive) {
8574 SendToICS(ics_prefix);
8575 SendToICS("flag\n");
8580 case MachinePlaysWhite:
8583 GameEnds(GameIsDrawn, "Both players ran out of time",
8586 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
8588 DisplayError("Your opponent is not out of time", 0);
8591 case MachinePlaysBlack:
8594 GameEnds(GameIsDrawn, "Both players ran out of time",
8597 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
8599 DisplayError("Your opponent is not out of time", 0);
8609 /* Offer draw or accept pending draw offer from opponent */
8611 if (appData.icsActive) {
8612 /* Note: tournament rules require draw offers to be
8613 made after you make your move but before you punch
8614 your clock. Currently ICS doesn't let you do that;
8615 instead, you immediately punch your clock after making
8616 a move, but you can offer a draw at any time. */
8618 SendToICS(ics_prefix);
8619 SendToICS("draw\n");
8620 } else if (cmailMsgLoaded) {
8621 if (currentMove == cmailOldMove &&
8622 commentList[cmailOldMove] != NULL &&
8623 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
8624 "Black offers a draw" : "White offers a draw")) {
8625 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8626 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
8627 } else if (currentMove == cmailOldMove + 1) {
8628 char *offer = WhiteOnMove(cmailOldMove) ?
8629 "White offers a draw" : "Black offers a draw";
8630 AppendComment(currentMove, offer);
8631 DisplayComment(currentMove - 1, offer);
8632 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
8634 DisplayError("You must make your move before offering a draw", 0);
8635 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8637 } else if (first.offeredDraw) {
8638 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8640 if (first.sendDrawOffers) {
8641 SendToProgram("draw\n", &first);
8642 userOfferedDraw = TRUE;
8650 /* Offer Adjourn or accept pending Adjourn offer from opponent */
8652 if (appData.icsActive) {
8653 SendToICS(ics_prefix);
8654 SendToICS("adjourn\n");
8656 /* Currently GNU Chess doesn't offer or accept Adjourns */
8664 /* Offer Abort or accept pending Abort offer from opponent */
8666 if (appData.icsActive) {
8667 SendToICS(ics_prefix);
8668 SendToICS("abort\n");
8670 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
8677 /* Resign. You can do this even if it's not your turn. */
8679 if (appData.icsActive) {
8680 SendToICS(ics_prefix);
8681 SendToICS("resign\n");
8684 case MachinePlaysWhite:
8685 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8687 case MachinePlaysBlack:
8688 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8691 if (cmailMsgLoaded) {
8693 if (WhiteOnMove(cmailOldMove)) {
8694 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8696 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8698 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
8709 StopObservingEvent()
8711 /* Stop observing current games */
8712 SendToICS(ics_prefix);
8713 SendToICS("unobserve\n");
8717 StopExaminingEvent()
8719 /* Stop observing current game */
8720 SendToICS(ics_prefix);
8721 SendToICS("unexamine\n");
8725 ForwardInner(target)
8730 if (appData.debugMode)
8731 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
8732 target, currentMove, forwardMostMove);
8734 if (gameMode == EditPosition)
8737 if (gameMode == PlayFromGameFile && !pausing)
8740 if (gameMode == IcsExamining && pausing)
8741 limit = pauseExamForwardMostMove;
8743 limit = forwardMostMove;
8745 if (target > limit) target = limit;
8747 if (target > 0 && moveList[target - 1][0]) {
8748 int fromX, fromY, toX, toY;
8749 toX = moveList[target - 1][2] - 'a';
8750 toY = moveList[target - 1][3] - '1';
8751 if (moveList[target - 1][1] == '@') {
8752 if (appData.highlightLastMove) {
8753 SetHighlights(-1, -1, toX, toY);
8756 fromX = moveList[target - 1][0] - 'a';
8757 fromY = moveList[target - 1][1] - '1';
8758 if (target == currentMove + 1) {
8759 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8761 if (appData.highlightLastMove) {
8762 SetHighlights(fromX, fromY, toX, toY);
8766 if (gameMode == EditGame || gameMode == AnalyzeMode ||
8767 gameMode == Training || gameMode == PlayFromGameFile ||
8768 gameMode == AnalyzeFile) {
8769 while (currentMove < target) {
8770 SendMoveToProgram(currentMove++, &first);
8773 currentMove = target;
8776 if (gameMode == EditGame || gameMode == EndOfGame) {
8777 whiteTimeRemaining = timeRemaining[0][currentMove];
8778 blackTimeRemaining = timeRemaining[1][currentMove];
8780 DisplayBothClocks();
8781 DisplayMove(currentMove - 1);
8782 DrawPosition(FALSE, boards[currentMove]);
8783 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8784 if (commentList[currentMove] && !matchMode && gameMode != Training) {
8785 DisplayComment(currentMove - 1, commentList[currentMove]);
8793 if (gameMode == IcsExamining && !pausing) {
8794 SendToICS(ics_prefix);
8795 SendToICS("forward\n");
8797 ForwardInner(currentMove + 1);
8804 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
8805 /* to optimze, we temporarily turn off analysis mode while we feed
8806 * the remaining moves to the engine. Otherwise we get analysis output
8809 if (first.analysisSupport) {
8810 SendToProgram("exit\nforce\n", &first);
8811 first.analyzing = FALSE;
8815 if (gameMode == IcsExamining && !pausing) {
8816 SendToICS(ics_prefix);
8817 SendToICS("forward 999999\n");
8819 ForwardInner(forwardMostMove);
8822 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
8823 /* we have fed all the moves, so reactivate analysis mode */
8824 SendToProgram("analyze\n", &first);
8825 first.analyzing = TRUE;
8826 /*first.maybeThinking = TRUE;*/
8827 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
8832 BackwardInner(target)
8835 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
8837 if (appData.debugMode)
8838 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
8839 target, currentMove, forwardMostMove);
8841 if (gameMode == EditPosition) return;
8842 if (currentMove <= backwardMostMove) {
8844 DrawPosition(full_redraw, boards[currentMove]);
8847 if (gameMode == PlayFromGameFile && !pausing)
8850 if (moveList[target][0]) {
8851 int fromX, fromY, toX, toY;
8852 toX = moveList[target][2] - 'a';
8853 toY = moveList[target][3] - '1';
8854 if (moveList[target][1] == '@') {
8855 if (appData.highlightLastMove) {
8856 SetHighlights(-1, -1, toX, toY);
8859 fromX = moveList[target][0] - 'a';
8860 fromY = moveList[target][1] - '1';
8861 if (target == currentMove - 1) {
8862 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
8864 if (appData.highlightLastMove) {
8865 SetHighlights(fromX, fromY, toX, toY);
8869 if (gameMode == EditGame || gameMode==AnalyzeMode ||
8870 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8871 while (currentMove > target) {
8872 SendToProgram("undo\n", &first);
8876 currentMove = target;
8879 if (gameMode == EditGame || gameMode == EndOfGame) {
8880 whiteTimeRemaining = timeRemaining[0][currentMove];
8881 blackTimeRemaining = timeRemaining[1][currentMove];
8883 DisplayBothClocks();
8884 DisplayMove(currentMove - 1);
8885 DrawPosition(full_redraw, boards[currentMove]);
8886 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8887 if (commentList[currentMove] != NULL) {
8888 DisplayComment(currentMove - 1, commentList[currentMove]);
8895 if (gameMode == IcsExamining && !pausing) {
8896 SendToICS(ics_prefix);
8897 SendToICS("backward\n");
8899 BackwardInner(currentMove - 1);
8906 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
8907 /* to optimze, we temporarily turn off analysis mode while we undo
8908 * all the moves. Otherwise we get analysis output after each undo.
8910 if (first.analysisSupport) {
8911 SendToProgram("exit\nforce\n", &first);
8912 first.analyzing = FALSE;
8916 if (gameMode == IcsExamining && !pausing) {
8917 SendToICS(ics_prefix);
8918 SendToICS("backward 999999\n");
8920 BackwardInner(backwardMostMove);
8923 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
8924 /* we have fed all the moves, so reactivate analysis mode */
8925 SendToProgram("analyze\n", &first);
8926 first.analyzing = TRUE;
8927 /*first.maybeThinking = TRUE;*/
8928 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
8935 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
8936 if (to >= forwardMostMove) to = forwardMostMove;
8937 if (to <= backwardMostMove) to = backwardMostMove;
8938 if (to < currentMove) {
8948 if (gameMode != IcsExamining) {
8949 DisplayError("You are not examining a game", 0);
8953 DisplayError("You can't revert while pausing", 0);
8956 SendToICS(ics_prefix);
8957 SendToICS("revert\n");
8964 case MachinePlaysWhite:
8965 case MachinePlaysBlack:
8966 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
8967 DisplayError("Wait until your turn,\nor select Move Now", 0);
8970 if (forwardMostMove < 2) return;
8971 currentMove = forwardMostMove = forwardMostMove - 2;
8972 whiteTimeRemaining = timeRemaining[0][currentMove];
8973 blackTimeRemaining = timeRemaining[1][currentMove];
8974 DisplayBothClocks();
8975 DisplayMove(currentMove - 1);
8976 ClearHighlights();/*!! could figure this out*/
8977 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
8978 SendToProgram("remove\n", &first);
8979 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
8982 case BeginningOfGame:
8986 case IcsPlayingWhite:
8987 case IcsPlayingBlack:
8988 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
8989 SendToICS(ics_prefix);
8990 SendToICS("takeback 2\n");
8992 SendToICS(ics_prefix);
8993 SendToICS("takeback 1\n");
9002 ChessProgramState *cps;
9005 case MachinePlaysWhite:
9006 if (!WhiteOnMove(forwardMostMove)) {
9007 DisplayError("It is your turn", 0);
9012 case MachinePlaysBlack:
9013 if (WhiteOnMove(forwardMostMove)) {
9014 DisplayError("It is your turn", 0);
9019 case TwoMachinesPlay:
9020 if (WhiteOnMove(forwardMostMove) ==
9021 (first.twoMachinesColor[0] == 'w')) {
9027 case BeginningOfGame:
9031 SendToProgram("?\n", cps);
9038 if (gameMode != EditGame) return;
9045 if (forwardMostMove > currentMove) {
9046 if (gameInfo.resultDetails != NULL) {
9047 free(gameInfo.resultDetails);
9048 gameInfo.resultDetails = NULL;
9049 gameInfo.result = GameUnfinished;
9051 forwardMostMove = currentMove;
9052 HistorySet(parseList, backwardMostMove, forwardMostMove,
9060 if (appData.noChessProgram) return;
9062 case MachinePlaysWhite:
9063 if (WhiteOnMove(forwardMostMove)) {
9064 DisplayError("Wait until your turn", 0);
9068 case BeginningOfGame:
9069 case MachinePlaysBlack:
9070 if (!WhiteOnMove(forwardMostMove)) {
9071 DisplayError("Wait until your turn", 0);
9076 DisplayError("No hint available", 0);
9079 SendToProgram("hint\n", &first);
9080 hintRequested = TRUE;
9086 if (appData.noChessProgram) return;
9088 case MachinePlaysWhite:
9089 if (WhiteOnMove(forwardMostMove)) {
9090 DisplayError("Wait until your turn", 0);
9094 case BeginningOfGame:
9095 case MachinePlaysBlack:
9096 if (!WhiteOnMove(forwardMostMove)) {
9097 DisplayError("Wait until your turn", 0);
9104 case TwoMachinesPlay:
9109 SendToProgram("bk\n", &first);
9110 bookOutput[0] = NULLCHAR;
9111 bookRequested = TRUE;
9117 char *tags = PGNTags(&gameInfo);
9118 TagsPopUp(tags, CmailMsg());
9122 /* end button procedures */
9125 PrintPosition(fp, move)
9131 for (i = BOARD_SIZE - 1; i >= 0; i--) {
9132 for (j = 0; j < BOARD_SIZE; j++) {
9133 char c = PieceToChar(boards[move][i][j]);
9134 fputc(c == 'x' ? '.' : c, fp);
9135 fputc(j == BOARD_SIZE - 1 ? '\n' : ' ', fp);
9138 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
9139 fprintf(fp, "white to play\n");
9141 fprintf(fp, "black to play\n");
9148 if (gameInfo.white != NULL) {
9149 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
9155 /* Find last component of program's own name, using some heuristics */
9157 TidyProgramName(prog, host, buf)
9158 char *prog, *host, buf[MSG_SIZ];
9161 int local = (strcmp(host, "localhost") == 0);
9162 while (!local && (p = strchr(prog, ';')) != NULL) {
9164 while (*p == ' ') p++;
9167 if (*prog == '"' || *prog == '\'') {
9168 q = strchr(prog + 1, *prog);
9170 q = strchr(prog, ' ');
9172 if (q == NULL) q = prog + strlen(prog);
9174 while (p >= prog && *p != '/' && *p != '\\') p--;
9176 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
9177 memcpy(buf, p, q - p);
9178 buf[q - p] = NULLCHAR;
9186 TimeControlTagValue()
9189 if (!appData.clockMode) {
9191 } else if (movesPerSession > 0) {
9192 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
9193 } else if (timeIncrement == 0) {
9194 sprintf(buf, "%ld", timeControl/1000);
9196 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
9198 return StrSave(buf);
9204 /* This routine is used only for certain modes */
9205 VariantClass v = gameInfo.variant;
9206 ClearGameInfo(&gameInfo);
9207 gameInfo.variant = v;
9210 case MachinePlaysWhite:
9211 gameInfo.event = StrSave( appData.pgnEventHeader );
9212 gameInfo.site = StrSave(HostName());
9213 gameInfo.date = PGNDate();
9214 gameInfo.round = StrSave("-");
9215 gameInfo.white = StrSave(first.tidy);
9216 gameInfo.black = StrSave(UserName());
9217 gameInfo.timeControl = TimeControlTagValue();
9220 case MachinePlaysBlack:
9221 gameInfo.event = StrSave( appData.pgnEventHeader );
9222 gameInfo.site = StrSave(HostName());
9223 gameInfo.date = PGNDate();
9224 gameInfo.round = StrSave("-");
9225 gameInfo.white = StrSave(UserName());
9226 gameInfo.black = StrSave(first.tidy);
9227 gameInfo.timeControl = TimeControlTagValue();
9230 case TwoMachinesPlay:
9231 gameInfo.event = StrSave( appData.pgnEventHeader );
9232 gameInfo.site = StrSave(HostName());
9233 gameInfo.date = PGNDate();
9234 if (matchGame > 0) {
9236 sprintf(buf, "%d", matchGame);
9237 gameInfo.round = StrSave(buf);
9239 gameInfo.round = StrSave("-");
9241 if (first.twoMachinesColor[0] == 'w') {
9242 gameInfo.white = StrSave(first.tidy);
9243 gameInfo.black = StrSave(second.tidy);
9245 gameInfo.white = StrSave(second.tidy);
9246 gameInfo.black = StrSave(first.tidy);
9248 gameInfo.timeControl = TimeControlTagValue();
9252 gameInfo.event = StrSave("Edited game");
9253 gameInfo.site = StrSave(HostName());
9254 gameInfo.date = PGNDate();
9255 gameInfo.round = StrSave("-");
9256 gameInfo.white = StrSave("-");
9257 gameInfo.black = StrSave("-");
9261 gameInfo.event = StrSave("Edited position");
9262 gameInfo.site = StrSave(HostName());
9263 gameInfo.date = PGNDate();
9264 gameInfo.round = StrSave("-");
9265 gameInfo.white = StrSave("-");
9266 gameInfo.black = StrSave("-");
9269 case IcsPlayingWhite:
9270 case IcsPlayingBlack:
9275 case PlayFromGameFile:
9276 gameInfo.event = StrSave("Game from non-PGN file");
9277 gameInfo.site = StrSave(HostName());
9278 gameInfo.date = PGNDate();
9279 gameInfo.round = StrSave("-");
9280 gameInfo.white = StrSave("?");
9281 gameInfo.black = StrSave("?");
9290 ReplaceComment(index, text)
9296 while (*text == '\n') text++;
9298 while (len > 0 && text[len - 1] == '\n') len--;
9300 if (commentList[index] != NULL)
9301 free(commentList[index]);
9304 commentList[index] = NULL;
9307 commentList[index] = (char *) malloc(len + 2);
9308 strncpy(commentList[index], text, len);
9309 commentList[index][len] = '\n';
9310 commentList[index][len + 1] = NULLCHAR;
9323 if (ch == '\r') continue;
9325 } while (ch != '\0');
9329 AppendComment(index, text)
9336 GetInfoFromComment( index, text );
9339 while (*text == '\n') text++;
9341 while (len > 0 && text[len - 1] == '\n') len--;
9343 if (len == 0) return;
9345 if (commentList[index] != NULL) {
9346 old = commentList[index];
9347 oldlen = strlen(old);
9348 commentList[index] = (char *) malloc(oldlen + len + 2);
9349 strcpy(commentList[index], old);
9351 strncpy(&commentList[index][oldlen], text, len);
9352 commentList[index][oldlen + len] = '\n';
9353 commentList[index][oldlen + len + 1] = NULLCHAR;
9355 commentList[index] = (char *) malloc(len + 2);
9356 strncpy(commentList[index], text, len);
9357 commentList[index][len] = '\n';
9358 commentList[index][len + 1] = NULLCHAR;
9362 static char * FindStr( char * text, char * sub_text )
9364 char * result = strstr( text, sub_text );
9366 if( result != NULL ) {
9367 result += strlen( sub_text );
9373 /* [AS] Try to extract PV info from PGN comment */
9374 void GetInfoFromComment( int index, char * text )
9376 if( text != NULL && index > 0 ) {
9380 char * s_eval = FindStr( text, "[%eval " );
9381 char * s_emt = FindStr( text, "[%emt " );
9383 if( s_eval != NULL || s_emt != NULL ) {
9387 if( s_eval != NULL ) {
9388 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
9392 if( delim != ']' ) {
9397 if( s_emt != NULL ) {
9401 /* We expect something like: [+|-]nnn.nn/dd */
9402 char * sep = strchr( text, '/' );
9405 if( sep == NULL || sep < (text+4) ) {
9409 if( sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
9413 if( score_lo < 0 || score_lo >= 100 ) {
9417 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
9428 pvInfoList[index-1].depth = depth;
9429 pvInfoList[index-1].score = score;
9430 pvInfoList[index-1].time = time;
9435 SendToProgram(message, cps)
9437 ChessProgramState *cps;
9439 int count, outCount, error;
9442 if (cps->pr == NULL) return;
9445 if (appData.debugMode) {
9448 fprintf(debugFP, "%ld >%-6s: %s",
9449 SubtractTimeMarks(&now, &programStartTime),
9450 cps->which, message);
9453 count = strlen(message);
9454 outCount = OutputToProcess(cps->pr, message, count, &error);
9455 if (outCount < count && !exiting) {
9456 sprintf(buf, "Error writing to %s chess program", cps->which);
9457 DisplayFatalError(buf, error, 1);
9462 ReceiveFromProgram(isr, closure, message, count, error)
9471 ChessProgramState *cps = (ChessProgramState *)closure;
9473 if (isr != cps->isr) return; /* Killed intentionally */
9477 "Error: %s chess program (%s) exited unexpectedly",
9478 cps->which, cps->program);
9479 RemoveInputSource(cps->isr);
9480 DisplayFatalError(buf, 0, 1);
9483 "Error reading from %s chess program (%s)",
9484 cps->which, cps->program);
9485 RemoveInputSource(cps->isr);
9487 /* [AS] Program is misbehaving badly... kill it */
9489 DestroyChildProcess( cps->pr, 9 );
9493 DisplayFatalError(buf, error, 1);
9495 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9499 if ((end_str = strchr(message, '\r')) != NULL)
9500 *end_str = NULLCHAR;
9501 if ((end_str = strchr(message, '\n')) != NULL)
9502 *end_str = NULLCHAR;
9504 if (appData.debugMode) {
9507 fprintf(debugFP, "%ld <%-6s: %s\n",
9508 SubtractTimeMarks(&now, &programStartTime),
9509 cps->which, message);
9511 HandleMachineMove(message, cps);
9516 SendTimeControl(cps, mps, tc, inc, sd, st)
9517 ChessProgramState *cps;
9518 int mps, inc, sd, st;
9522 int seconds = (tc / 1000) % 60;
9524 if( timeControl_2 > 0 ) {
9525 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
9531 /* Set exact time per move, normally using st command */
9532 if (cps->stKludge) {
9533 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
9536 sprintf(buf, "level 1 %d\n", st/60);
9538 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
9541 sprintf(buf, "st %d\n", st);
9544 /* Set conventional or incremental time control, using level command */
9546 /* Note old gnuchess bug -- minutes:seconds used to not work.
9547 Fixed in later versions, but still avoid :seconds
9548 when seconds is 0. */
9549 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
9551 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
9555 SendToProgram(buf, cps);
9557 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
9558 /* Orthogonally, limit search to given depth */
9560 if (cps->sdKludge) {
9561 sprintf(buf, "depth\n%d\n", sd);
9563 sprintf(buf, "sd %d\n", sd);
9565 SendToProgram(buf, cps);
9570 SendTimeRemaining(cps, machineWhite)
9571 ChessProgramState *cps;
9572 int /*boolean*/ machineWhite;
9574 char message[MSG_SIZ];
9577 /* Note: this routine must be called when the clocks are stopped
9578 or when they have *just* been set or switched; otherwise
9579 it will be off by the time since the current tick started.
9582 time = whiteTimeRemaining / 10;
9583 otime = blackTimeRemaining / 10;
9585 time = blackTimeRemaining / 10;
9586 otime = whiteTimeRemaining / 10;
9588 if (time <= 0) time = 1;
9589 if (otime <= 0) otime = 1;
9591 sprintf(message, "time %ld\notim %ld\n", time, otime);
9592 SendToProgram(message, cps);
9596 BoolFeature(p, name, loc, cps)
9600 ChessProgramState *cps;
9603 int len = strlen(name);
9605 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
9607 sscanf(*p, "%d", &val);
9609 while (**p && **p != ' ') (*p)++;
9610 sprintf(buf, "accepted %s\n", name);
9611 SendToProgram(buf, cps);
9618 IntFeature(p, name, loc, cps)
9622 ChessProgramState *cps;
9625 int len = strlen(name);
9626 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
9628 sscanf(*p, "%d", loc);
9629 while (**p && **p != ' ') (*p)++;
9630 sprintf(buf, "accepted %s\n", name);
9631 SendToProgram(buf, cps);
9638 StringFeature(p, name, loc, cps)
9642 ChessProgramState *cps;
9645 int len = strlen(name);
9646 if (strncmp((*p), name, len) == 0
9647 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
9649 sscanf(*p, "%[^\"]", loc);
9650 while (**p && **p != '\"') (*p)++;
9651 if (**p == '\"') (*p)++;
9652 sprintf(buf, "accepted %s\n", name);
9653 SendToProgram(buf, cps);
9660 FeatureDone(cps, val)
9661 ChessProgramState* cps;
9664 DelayedEventCallback cb = GetDelayedEvent();
9665 if ((cb == InitBackEnd3 && cps == &first) ||
9666 (cb == TwoMachinesEventIfReady && cps == &second)) {
9667 CancelDelayedEvent();
9668 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
9670 cps->initDone = val;
9673 /* Parse feature command from engine */
9675 ParseFeatures(args, cps)
9677 ChessProgramState *cps;
9685 while (*p == ' ') p++;
9686 if (*p == NULLCHAR) return;
9688 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
9689 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
9690 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
9691 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
9692 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
9693 if (BoolFeature(&p, "reuse", &val, cps)) {
9694 /* Engine can disable reuse, but can't enable it if user said no */
9695 if (!val) cps->reuse = FALSE;
9698 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
9699 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
9700 if (gameMode == TwoMachinesPlay) {
9701 DisplayTwoMachinesTitle();
9707 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
9708 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
9709 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
9710 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
9711 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
9712 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
9713 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
9714 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
9715 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
9716 if (IntFeature(&p, "done", &val, cps)) {
9717 FeatureDone(cps, val);
9720 /* Added by Tord: */
9721 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
9722 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
9723 /* End of additions by Tord */
9725 /* unknown feature: complain and skip */
9727 while (*q && *q != '=') q++;
9728 sprintf(buf, "rejected %.*s\n", q-p, p);
9729 SendToProgram(buf, cps);
9735 while (*p && *p != '\"') p++;
9736 if (*p == '\"') p++;
9738 while (*p && *p != ' ') p++;
9746 PeriodicUpdatesEvent(newState)
9749 if (newState == appData.periodicUpdates)
9752 appData.periodicUpdates=newState;
9754 /* Display type changes, so update it now */
9757 /* Get the ball rolling again... */
9759 AnalysisPeriodicEvent(1);
9760 StartAnalysisClock();
9765 PonderNextMoveEvent(newState)
9768 if (newState == appData.ponderNextMove) return;
9769 if (gameMode == EditPosition) EditPositionDone();
9771 SendToProgram("hard\n", &first);
9772 if (gameMode == TwoMachinesPlay) {
9773 SendToProgram("hard\n", &second);
9776 SendToProgram("easy\n", &first);
9777 thinkOutput[0] = NULLCHAR;
9778 if (gameMode == TwoMachinesPlay) {
9779 SendToProgram("easy\n", &second);
9782 appData.ponderNextMove = newState;
9786 ShowThinkingEvent(newState)
9789 if (newState == appData.showThinking) return;
9790 if (gameMode == EditPosition) EditPositionDone();
9792 SendToProgram("post\n", &first);
9793 if (gameMode == TwoMachinesPlay) {
9794 SendToProgram("post\n", &second);
9797 SendToProgram("nopost\n", &first);
9798 thinkOutput[0] = NULLCHAR;
9799 if (gameMode == TwoMachinesPlay) {
9800 SendToProgram("nopost\n", &second);
9803 appData.showThinking = newState;
9807 AskQuestionEvent(title, question, replyPrefix, which)
9808 char *title; char *question; char *replyPrefix; char *which;
9810 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
9811 if (pr == NoProc) return;
9812 AskQuestion(title, question, replyPrefix, pr);
9816 DisplayMove(moveNumber)
9819 char message[MSG_SIZ];
9821 char cpThinkOutput[MSG_SIZ];
9823 if (moveNumber == forwardMostMove - 1 ||
9824 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
9826 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
9828 if (strchr(cpThinkOutput, '\n')) {
9829 *strchr(cpThinkOutput, '\n') = NULLCHAR;
9832 *cpThinkOutput = NULLCHAR;
9835 /* [AS] Hide thinking from human user */
9836 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
9837 *cpThinkOutput = NULLCHAR;
9838 if( thinkOutput[0] != NULLCHAR ) {
9841 for( i=0; i<=hiddenThinkOutputState; i++ ) {
9842 cpThinkOutput[i] = '.';
9844 cpThinkOutput[i] = NULLCHAR;
9845 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
9849 if (moveNumber == forwardMostMove - 1 &&
9850 gameInfo.resultDetails != NULL) {
9851 if (gameInfo.resultDetails[0] == NULLCHAR) {
9852 sprintf(res, " %s", PGNResult(gameInfo.result));
9854 sprintf(res, " {%s} %s",
9855 gameInfo.resultDetails, PGNResult(gameInfo.result));
9861 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
9862 DisplayMessage(res, cpThinkOutput);
9864 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
9865 WhiteOnMove(moveNumber) ? " " : ".. ",
9866 parseList[moveNumber], res);
9867 DisplayMessage(message, cpThinkOutput);
9872 DisplayAnalysisText(text)
9877 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
9878 sprintf(buf, "Analysis (%s)", first.tidy);
9879 AnalysisPopUp(buf, text);
9887 while (*str && isspace(*str)) ++str;
9888 while (*str && !isspace(*str)) ++str;
9889 if (!*str) return 1;
9890 while (*str && isspace(*str)) ++str;
9891 if (!*str) return 1;
9899 char lst[MSG_SIZ / 2];
9901 static char *xtra[] = { "", " (--)", " (++)" };
9904 if (programStats.time == 0) {
9905 programStats.time = 1;
9908 if (programStats.got_only_move) {
9909 safeStrCpy(buf, programStats.movelist, sizeof(buf));
9911 safeStrCpy( lst, programStats.movelist, sizeof(lst));
9913 nps = (((double)programStats.nodes) /
9914 (((double)programStats.time)/100.0));
9916 cs = programStats.time % 100;
9917 s = programStats.time / 100;
9923 if (programStats.moves_left > 0 && appData.periodicUpdates) {
9924 if (programStats.move_name[0] != NULLCHAR) {
9925 sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",
9927 programStats.nr_moves-programStats.moves_left,
9928 programStats.nr_moves, programStats.move_name,
9929 ((float)programStats.score)/100.0, lst,
9931 xtra[programStats.got_fail] : "",
9932 programStats.nodes, (int)nps, h, m, s, cs);
9934 sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",
9936 programStats.nr_moves-programStats.moves_left,
9937 programStats.nr_moves, ((float)programStats.score)/100.0,
9940 xtra[programStats.got_fail] : "",
9941 programStats.nodes, (int)nps, h, m, s, cs);
9944 sprintf(buf, "depth=%d %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",
9946 ((float)programStats.score)/100.0,
9949 xtra[programStats.got_fail] : "",
9950 programStats.nodes, (int)nps, h, m, s, cs);
9953 DisplayAnalysisText(buf);
9957 DisplayComment(moveNumber, text)
9961 char title[MSG_SIZ];
9963 if( appData.autoDisplayComment ) {
9964 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
9965 strcpy(title, "Comment");
9967 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
9968 WhiteOnMove(moveNumber) ? " " : ".. ",
9969 parseList[moveNumber]);
9972 CommentPopUp(title, text);
9976 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
9977 * might be busy thinking or pondering. It can be omitted if your
9978 * gnuchess is configured to stop thinking immediately on any user
9979 * input. However, that gnuchess feature depends on the FIONREAD
9980 * ioctl, which does not work properly on some flavors of Unix.
9984 ChessProgramState *cps;
9987 if (!cps->useSigint) return;
9988 if (appData.noChessProgram || (cps->pr == NoProc)) return;
9990 case MachinePlaysWhite:
9991 case MachinePlaysBlack:
9992 case TwoMachinesPlay:
9993 case IcsPlayingWhite:
9994 case IcsPlayingBlack:
9997 /* Skip if we know it isn't thinking */
9998 if (!cps->maybeThinking) return;
9999 if (appData.debugMode)
10000 fprintf(debugFP, "Interrupting %s\n", cps->which);
10001 InterruptChildProcess(cps->pr);
10002 cps->maybeThinking = FALSE;
10007 #endif /*ATTENTION*/
10013 if (whiteTimeRemaining <= 0) {
10016 if (appData.icsActive) {
10017 if (appData.autoCallFlag &&
10018 gameMode == IcsPlayingBlack && !blackFlag) {
10019 SendToICS(ics_prefix);
10020 SendToICS("flag\n");
10024 DisplayTitle("Both flags fell");
10026 DisplayTitle("White's flag fell");
10027 if (appData.autoCallFlag) {
10028 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
10035 if (blackTimeRemaining <= 0) {
10038 if (appData.icsActive) {
10039 if (appData.autoCallFlag &&
10040 gameMode == IcsPlayingWhite && !whiteFlag) {
10041 SendToICS(ics_prefix);
10042 SendToICS("flag\n");
10046 DisplayTitle("Both flags fell");
10048 DisplayTitle("Black's flag fell");
10049 if (appData.autoCallFlag) {
10050 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
10063 if (!appData.clockMode || appData.icsActive ||
10064 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
10066 if (timeIncrement >= 0) {
10067 if (WhiteOnMove(forwardMostMove)) {
10068 blackTimeRemaining += timeIncrement;
10070 whiteTimeRemaining += timeIncrement;
10074 * add time to clocks when time control is achieved
10076 if (movesPerSession) {
10077 switch ((forwardMostMove + 1) % (movesPerSession * 2)) {
10079 /* White made time control */
10080 whiteTimeRemaining += GetTimeControlForWhite();
10083 /* Black made time control */
10084 blackTimeRemaining += GetTimeControlForBlack();
10093 DisplayBothClocks()
10095 int wom = gameMode == EditPosition ?
10096 !blackPlaysFirst : WhiteOnMove(currentMove);
10097 DisplayWhiteClock(whiteTimeRemaining, wom);
10098 DisplayBlackClock(blackTimeRemaining, !wom);
10102 /* Timekeeping seems to be a portability nightmare. I think everyone
10103 has ftime(), but I'm really not sure, so I'm including some ifdefs
10104 to use other calls if you don't. Clocks will be less accurate if
10105 you have neither ftime nor gettimeofday.
10108 /* Get the current time as a TimeMark */
10113 #if HAVE_GETTIMEOFDAY
10115 struct timeval timeVal;
10116 struct timezone timeZone;
10118 gettimeofday(&timeVal, &timeZone);
10119 tm->sec = (long) timeVal.tv_sec;
10120 tm->ms = (int) (timeVal.tv_usec / 1000L);
10122 #else /*!HAVE_GETTIMEOFDAY*/
10125 #include <sys/timeb.h>
10126 struct timeb timeB;
10129 tm->sec = (long) timeB.time;
10130 tm->ms = (int) timeB.millitm;
10132 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
10133 tm->sec = (long) time(NULL);
10139 /* Return the difference in milliseconds between two
10140 time marks. We assume the difference will fit in a long!
10143 SubtractTimeMarks(tm2, tm1)
10144 TimeMark *tm2, *tm1;
10146 return 1000L*(tm2->sec - tm1->sec) +
10147 (long) (tm2->ms - tm1->ms);
10152 * Code to manage the game clocks.
10154 * In tournament play, black starts the clock and then white makes a move.
10155 * We give the human user a slight advantage if he is playing white---the
10156 * clocks don't run until he makes his first move, so it takes zero time.
10157 * Also, we don't account for network lag, so we could get out of sync
10158 * with GNU Chess's clock -- but then, referees are always right.
10161 static TimeMark tickStartTM;
10162 static long intendedTickLength;
10165 NextTickLength(timeRemaining)
10166 long timeRemaining;
10168 long nominalTickLength, nextTickLength;
10170 if (timeRemaining > 0L && timeRemaining <= 10000L)
10171 nominalTickLength = 100L;
10173 nominalTickLength = 1000L;
10174 nextTickLength = timeRemaining % nominalTickLength;
10175 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
10177 return nextTickLength;
10180 /* Stop clocks and reset to a fresh time control */
10184 (void) StopClockTimer();
10185 if (appData.icsActive) {
10186 whiteTimeRemaining = blackTimeRemaining = 0;
10188 whiteTimeRemaining = GetTimeControlForWhite();
10189 blackTimeRemaining = GetTimeControlForBlack();
10191 if (whiteFlag || blackFlag) {
10193 whiteFlag = blackFlag = FALSE;
10195 DisplayBothClocks();
10198 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
10200 /* Decrement running clock by amount of time that has passed */
10204 long timeRemaining;
10205 long lastTickLength, fudge;
10208 if (!appData.clockMode) return;
10209 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
10213 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
10215 /* Fudge if we woke up a little too soon */
10216 fudge = intendedTickLength - lastTickLength;
10217 if (fudge < 0 || fudge > FUDGE) fudge = 0;
10219 if (WhiteOnMove(forwardMostMove)) {
10220 timeRemaining = whiteTimeRemaining -= lastTickLength;
10221 DisplayWhiteClock(whiteTimeRemaining - fudge,
10222 WhiteOnMove(currentMove));
10224 timeRemaining = blackTimeRemaining -= lastTickLength;
10225 DisplayBlackClock(blackTimeRemaining - fudge,
10226 !WhiteOnMove(currentMove));
10229 if (CheckFlags()) return;
10232 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
10233 StartClockTimer(intendedTickLength);
10235 /* if the time remaining has fallen below the alarm threshold, sound the
10236 * alarm. if the alarm has sounded and (due to a takeback or time control
10237 * with increment) the time remaining has increased to a level above the
10238 * threshold, reset the alarm so it can sound again.
10241 if (appData.icsActive && appData.icsAlarm) {
10243 /* make sure we are dealing with the user's clock */
10244 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
10245 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
10248 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
10249 alarmSounded = FALSE;
10250 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
10252 alarmSounded = TRUE;
10258 /* A player has just moved, so stop the previously running
10259 clock and (if in clock mode) start the other one.
10260 We redisplay both clocks in case we're in ICS mode, because
10261 ICS gives us an update to both clocks after every move.
10262 Note that this routine is called *after* forwardMostMove
10263 is updated, so the last fractional tick must be subtracted
10264 from the color that is *not* on move now.
10269 long lastTickLength;
10271 int flagged = FALSE;
10275 if (StopClockTimer() && appData.clockMode) {
10276 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
10277 if (WhiteOnMove(forwardMostMove)) {
10278 blackTimeRemaining -= lastTickLength;
10280 whiteTimeRemaining -= lastTickLength;
10282 flagged = CheckFlags();
10284 CheckTimeControl();
10286 if (flagged || !appData.clockMode) return;
10288 switch (gameMode) {
10289 case MachinePlaysBlack:
10290 case MachinePlaysWhite:
10291 case BeginningOfGame:
10292 if (pausing) return;
10296 case PlayFromGameFile:
10305 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
10306 whiteTimeRemaining : blackTimeRemaining);
10307 StartClockTimer(intendedTickLength);
10311 /* Stop both clocks */
10315 long lastTickLength;
10318 if (!StopClockTimer()) return;
10319 if (!appData.clockMode) return;
10323 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
10324 if (WhiteOnMove(forwardMostMove)) {
10325 whiteTimeRemaining -= lastTickLength;
10326 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
10328 blackTimeRemaining -= lastTickLength;
10329 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
10334 /* Start clock of player on move. Time may have been reset, so
10335 if clock is already running, stop and restart it. */
10339 (void) StopClockTimer(); /* in case it was running already */
10340 DisplayBothClocks();
10341 if (CheckFlags()) return;
10343 if (!appData.clockMode) return;
10344 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
10346 GetTimeMark(&tickStartTM);
10347 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
10348 whiteTimeRemaining : blackTimeRemaining);
10349 StartClockTimer(intendedTickLength);
10356 long second, minute, hour, day;
10358 static char buf[32];
10360 if (ms > 0 && ms <= 9900) {
10361 /* convert milliseconds to tenths, rounding up */
10362 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
10364 sprintf(buf, " %03.1f ", tenths/10.0);
10368 /* convert milliseconds to seconds, rounding up */
10369 /* use floating point to avoid strangeness of integer division
10370 with negative dividends on many machines */
10371 second = (long) floor(((double) (ms + 999L)) / 1000.0);
10378 day = second / (60 * 60 * 24);
10379 second = second % (60 * 60 * 24);
10380 hour = second / (60 * 60);
10381 second = second % (60 * 60);
10382 minute = second / 60;
10383 second = second % 60;
10386 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
10387 sign, day, hour, minute, second);
10389 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
10391 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
10398 * This is necessary because some C libraries aren't ANSI C compliant yet.
10401 StrStr(string, match)
10402 char *string, *match;
10406 length = strlen(match);
10408 for (i = strlen(string) - length; i >= 0; i--, string++)
10409 if (!strncmp(match, string, length))
10416 StrCaseStr(string, match)
10417 char *string, *match;
10421 length = strlen(match);
10423 for (i = strlen(string) - length; i >= 0; i--, string++) {
10424 for (j = 0; j < length; j++) {
10425 if (ToLower(match[j]) != ToLower(string[j]))
10428 if (j == length) return string;
10442 c1 = ToLower(*s1++);
10443 c2 = ToLower(*s2++);
10444 if (c1 > c2) return 1;
10445 if (c1 < c2) return -1;
10446 if (c1 == NULLCHAR) return 0;
10455 return isupper(c) ? tolower(c) : c;
10463 return islower(c) ? toupper(c) : c;
10465 #endif /* !_amigados */
10473 if ((ret = (char *) malloc(strlen(s) + 1))) {
10480 StrSavePtr(s, savePtr)
10481 char *s, **savePtr;
10486 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
10487 strcpy(*savePtr, s);
10499 clock = time((time_t *)NULL);
10500 tm = localtime(&clock);
10501 sprintf(buf, "%04d.%02d.%02d",
10502 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
10503 return StrSave(buf);
10508 PositionToFEN(move, useFEN960)
10512 int i, j, fromX, fromY, toX, toY;
10518 whiteToPlay = (gameMode == EditPosition) ?
10519 !blackPlaysFirst : (move % 2 == 0);
10522 /* Piece placement data */
10523 for (i = BOARD_SIZE - 1; i >= 0; i--) {
10525 for (j = 0; j < BOARD_SIZE; j++) {
10526 if (boards[move][i][j] == EmptySquare) {
10529 if (emptycount > 0) {
10530 *p++ = '0' + emptycount;
10533 *p++ = PieceToChar(boards[move][i][j]);
10536 if (emptycount > 0) {
10537 *p++ = '0' + emptycount;
10545 *p++ = whiteToPlay ? 'w' : 'b';
10548 /* HACK: we don't keep track of castling availability, so fake it! */
10550 /* PUSH Fabien & Tord */
10552 /* Declare all potential FRC castling rights (conservative) */
10553 /* outermost rook on each side of the king */
10555 if( gameInfo.variant == VariantFischeRandom ) {
10560 /* White castling rights */
10562 for (fk = 1; fk < 7; fk++) {
10564 if (boards[move][0][fk] == WhiteKing) {
10566 for (fr = 7; fr > fk; fr--) { /* H side */
10567 if (boards[move][0][fr] == WhiteRook) {
10568 *p++ = useFEN960 ? 'A' + fr : 'K';
10573 for (fr = 0; fr < fk; fr++) { /* A side */
10574 if (boards[move][0][fr] == WhiteRook) {
10575 *p++ = useFEN960 ? 'A' + fr : 'Q';
10582 /* Black castling rights */
10584 for (fk = 1; fk < 7; fk++) {
10586 if (boards[move][7][fk] == BlackKing) {
10588 for (fr = 7; fr > fk; fr--) { /* H side */
10589 if (boards[move][7][fr] == BlackRook) {
10590 *p++ = useFEN960 ? 'a' + fr : 'k';
10595 for (fr = 0; fr < fk; fr++) { /* A side */
10596 if (boards[move][7][fr] == BlackRook) {
10597 *p++ = useFEN960 ? 'a' + fr : 'q';
10604 if (q == p) *p++ = '-'; /* No castling rights */
10609 if (boards[move][0][4] == WhiteKing) {
10610 if (boards[move][0][7] == WhiteRook) *p++ = 'K';
10611 if (boards[move][0][0] == WhiteRook) *p++ = 'Q';
10613 if (boards[move][7][4] == BlackKing) {
10614 if (boards[move][7][7] == BlackRook) *p++ = 'k';
10615 if (boards[move][7][0] == BlackRook) *p++ = 'q';
10617 if (q == p) *p++ = '-';
10621 /* POP Fabien & Tord */
10623 /* En passant target square */
10624 if (move > backwardMostMove) {
10625 fromX = moveList[move - 1][0] - 'a';
10626 fromY = moveList[move - 1][1] - '1';
10627 toX = moveList[move - 1][2] - 'a';
10628 toY = moveList[move - 1][3] - '1';
10629 if (fromY == (whiteToPlay ? 6 : 1) &&
10630 toY == (whiteToPlay ? 4 : 3) &&
10631 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
10633 /* 2-square pawn move just happened */
10635 *p++ = whiteToPlay ? '6' : '3';
10643 /* We don't keep track of halfmove clock for 50-move rule */
10647 /* Fullmove number */
10648 sprintf(p, "%d", (move / 2) + 1);
10650 return StrSave(buf);
10654 ParseFEN(board, blackPlaysFirst, fen)
10656 int *blackPlaysFirst;
10665 /* Piece placement data */
10666 for (i = BOARD_SIZE - 1; i >= 0; i--) {
10669 if (*p == '/' || *p == ' ') {
10670 if (*p == '/') p++;
10671 emptycount = BOARD_SIZE - j;
10672 while (emptycount--) board[i][j++] = EmptySquare;
10674 } else if (isdigit(*p)) {
10675 emptycount = *p++ - '0';
10676 if (j + emptycount > BOARD_SIZE) return FALSE;
10677 while (emptycount--) board[i][j++] = EmptySquare;
10678 } else if (isalpha(*p)) {
10679 if (j >= BOARD_SIZE) return FALSE;
10680 board[i][j++] = CharToPiece(*p++);
10686 while (*p == '/' || *p == ' ') p++;
10691 *blackPlaysFirst = FALSE;
10694 *blackPlaysFirst = TRUE;
10700 /* !!We ignore the rest of the FEN notation */
10705 EditPositionPasteFEN(char *fen)
10708 Board initial_position;
10710 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
10711 DisplayError("Bad FEN position in clipboard", 0);
10714 int savedBlackPlaysFirst = blackPlaysFirst;
10715 EditPositionEvent();
10716 blackPlaysFirst = savedBlackPlaysFirst;
10717 CopyBoard(boards[0], initial_position);
10718 EditPositionDone();
10719 DisplayBothClocks();
10720 DrawPosition(FALSE, boards[currentMove]);