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] For debugging purposes */
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 /* [AS] Search stats from chessprogram, for the played move */
151 } ChessProgramStats_Move;
153 int establish P((void));
154 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
155 char *buf, int count, int error));
156 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
157 char *buf, int count, int error));
158 void SendToICS P((char *s));
159 void SendToICSDelayed P((char *s, long msdelay));
160 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
162 void InitPosition P((int redraw));
163 void HandleMachineMove P((char *message, ChessProgramState *cps));
164 int AutoPlayOneMove P((void));
165 int LoadGameOneMove P((ChessMove readAhead));
166 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
167 int LoadPositionFromFile P((char *filename, int n, char *title));
168 int SavePositionToFile P((char *filename));
169 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
171 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
172 void ShowMove P((int fromX, int fromY, int toX, int toY));
173 void FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
174 /*char*/int promoChar));
175 void BackwardInner P((int target));
176 void ForwardInner P((int target));
177 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
178 void EditPositionDone P((void));
179 void PrintOpponents P((FILE *fp));
180 void PrintPosition P((FILE *fp, int move));
181 void StartChessProgram P((ChessProgramState *cps));
182 void SendToProgram P((char *message, ChessProgramState *cps));
183 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
184 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
185 char *buf, int count, int error));
186 void SendTimeControl P((ChessProgramState *cps,
187 int mps, long tc, int inc, int sd, int st));
188 char *TimeControlTagValue P((void));
189 void Attention P((ChessProgramState *cps));
190 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
191 void ResurrectChessProgram P((void));
192 void DisplayComment P((int moveNumber, char *text));
193 void DisplayMove P((int moveNumber));
194 void DisplayAnalysis P((void));
196 void ParseGameHistory P((char *game));
197 void ParseBoard12 P((char *string));
198 void StartClocks P((void));
199 void SwitchClocks P((void));
200 void StopClocks P((void));
201 void ResetClocks P((void));
202 char *PGNDate P((void));
203 void SetGameInfo P((void));
204 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
205 int RegisterMove P((void));
206 void MakeRegisteredMove P((void));
207 void TruncateGame P((void));
208 int looking_at P((char *, int *, char *));
209 void CopyPlayerNameIntoFileName P((char **, char *));
210 char *SavePart P((char *));
211 int SaveGameOldStyle P((FILE *));
212 int SaveGamePGN P((FILE *));
213 void GetTimeMark P((TimeMark *));
214 long SubtractTimeMarks P((TimeMark *, TimeMark *));
215 int CheckFlags P((void));
216 long NextTickLength P((long));
217 void CheckTimeControl P((void));
218 void show_bytes P((FILE *, char *, int));
219 int string_to_rating P((char *str));
220 void ParseFeatures P((char* args, ChessProgramState *cps));
221 void InitBackEnd3 P((void));
222 void FeatureDone P((ChessProgramState* cps, int val));
223 void InitChessProgram P((ChessProgramState *cps));
225 extern int tinyLayout, smallLayout;
226 static ChessProgramStats programStats;
228 /* States for ics_getting_history */
230 #define H_REQUESTED 1
231 #define H_GOT_REQ_HEADER 2
232 #define H_GOT_UNREQ_HEADER 3
233 #define H_GETTING_MOVES 4
234 #define H_GOT_UNWANTED_HEADER 5
236 /* whosays values for GameEnds */
243 /* Maximum number of games in a cmail message */
244 #define CMAIL_MAX_GAMES 20
246 /* Different types of move when calling RegisterMove */
248 #define CMAIL_RESIGN 1
250 #define CMAIL_ACCEPT 3
252 /* Different types of result to remember for each game */
253 #define CMAIL_NOT_RESULT 0
254 #define CMAIL_OLD_RESULT 1
255 #define CMAIL_NEW_RESULT 2
257 /* Telnet protocol constants */
268 static char * safeStrCpy( char * dst, const char * src, size_t count )
270 assert( dst != NULL );
271 assert( src != NULL );
274 strncpy( dst, src, count );
275 dst[ count-1 ] = '\0';
279 static char * safeStrCat( char * dst, const char * src, size_t count )
283 assert( dst != NULL );
284 assert( src != NULL );
287 dst_len = strlen(dst);
289 assert( count > dst_len ); /* Buffer size must be greater than current length */
291 safeStrCpy( dst + dst_len, src, count - dst_len );
296 /* Fake up flags for now, as we aren't keeping track of castling
301 int flags = F_ALL_CASTLE_OK;
302 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
303 switch (gameInfo.variant) {
305 case VariantGiveaway:
306 flags |= F_IGNORE_CHECK;
307 flags &= ~F_ALL_CASTLE_OK;
310 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
312 case VariantKriegspiel:
313 flags |= F_KRIEGSPIEL_CAPTURE;
315 case VariantNoCastle:
316 flags &= ~F_ALL_CASTLE_OK;
324 FILE *gameFileFP, *debugFP;
327 [AS] Note: sometimes, the sscanf() function is used to parse the input
328 into a fixed-size buffer. Because of this, we must be prepared to
329 receive strings as long as the size of the input buffer, which is currently
330 set to 4K for Windows and 8K for the rest.
331 So, we must either allocate sufficiently large buffers here, or
332 reduce the size of the input buffer in the input reading part.
335 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
336 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
337 char thinkOutput1[MSG_SIZ*10];
339 ChessProgramState first, second;
341 /* premove variables */
344 int premoveFromX = 0;
345 int premoveFromY = 0;
346 int premovePromoChar = 0;
348 Boolean alarmSounded;
349 /* end premove variables */
351 #define ICS_GENERIC 0
354 #define ICS_CHESSNET 3 /* not really supported */
355 int ics_type = ICS_GENERIC;
356 char *ics_prefix = "$";
358 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
359 int pauseExamForwardMostMove = 0;
360 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
361 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
362 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
363 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
364 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
365 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
366 int whiteFlag = FALSE, blackFlag = FALSE;
367 int userOfferedDraw = FALSE;
368 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
369 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
370 int cmailMoveType[CMAIL_MAX_GAMES];
371 long ics_clock_paused = 0;
372 ProcRef icsPR = NoProc, cmailPR = NoProc;
373 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
374 GameMode gameMode = BeginningOfGame;
375 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
376 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
377 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
378 int hiddenThinkOutputState = 0; /* [AS] */
379 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
380 int adjudicateLossPlies = 6;
381 char white_holding[64], black_holding[64];
382 TimeMark lastNodeCountTime;
383 long lastNodeCount=0;
384 int have_sent_ICS_logon = 0;
386 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
387 long timeControl_2; /* [AS] Allow separate time controls */
388 long timeRemaining[2][MAX_MOVES];
390 TimeMark programStartTime;
391 char ics_handle[MSG_SIZ];
392 int have_set_title = 0;
394 /* animateTraining preserves the state of appData.animate
395 * when Training mode is activated. This allows the
396 * response to be animated when appData.animate == TRUE and
397 * appData.animateDragging == TRUE.
399 Boolean animateTraining;
405 Board boards[MAX_MOVES];
406 Board initialPosition = {
407 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
408 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
409 { WhitePawn, WhitePawn, WhitePawn, WhitePawn,
410 WhitePawn, WhitePawn, WhitePawn, WhitePawn },
411 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
412 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
413 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
414 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
415 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
416 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
417 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
418 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
419 { BlackPawn, BlackPawn, BlackPawn, BlackPawn,
420 BlackPawn, BlackPawn, BlackPawn, BlackPawn },
421 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
422 BlackKing, BlackBishop, BlackKnight, BlackRook }
424 Board twoKingsPosition = {
425 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
426 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
427 { WhitePawn, WhitePawn, WhitePawn, WhitePawn,
428 WhitePawn, WhitePawn, WhitePawn, WhitePawn },
429 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
430 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
431 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
432 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
433 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
434 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
435 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
436 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
437 { BlackPawn, BlackPawn, BlackPawn, BlackPawn,
438 BlackPawn, BlackPawn, BlackPawn, BlackPawn },
439 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
440 BlackKing, BlackKing, BlackKnight, BlackRook }
444 /* Convert str to a rating. Checks for special cases of "----",
445 "++++", etc. Also strips ()'s */
447 string_to_rating(str)
450 while(*str && !isdigit(*str)) ++str;
452 return 0; /* One of the special "no rating" cases */
460 /* Init programStats */
461 programStats.movelist[0] = 0;
462 programStats.depth = 0;
463 programStats.nr_moves = 0;
464 programStats.moves_left = 0;
465 programStats.nodes = 0;
466 programStats.time = 100;
467 programStats.score = 0;
468 programStats.got_only_move = 0;
469 programStats.got_fail = 0;
470 programStats.line_is_book = 0;
476 int matched, min, sec;
478 GetTimeMark(&programStartTime);
481 programStats.ok_to_send = 1;
482 programStats.seen_stat = 0;
485 * Initialize game list
491 * Internet chess server status
493 if (appData.icsActive) {
494 appData.matchMode = FALSE;
495 appData.matchGames = 0;
497 appData.noChessProgram = !appData.zippyPlay;
499 appData.zippyPlay = FALSE;
500 appData.zippyTalk = FALSE;
501 appData.noChessProgram = TRUE;
503 if (*appData.icsHelper != NULLCHAR) {
504 appData.useTelnet = TRUE;
505 appData.telnetProgram = appData.icsHelper;
508 appData.zippyTalk = appData.zippyPlay = FALSE;
511 /* [AS] Initialize pv info list */
515 for( i=0; i<MAX_MOVES; i++ ) {
516 pvInfoList[i].depth = 0;
521 * Parse timeControl resource
523 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
524 appData.movesPerSession)) {
526 sprintf(buf, "bad timeControl option %s", appData.timeControl);
527 DisplayFatalError(buf, 0, 2);
531 * Parse searchTime resource
533 if (*appData.searchTime != NULLCHAR) {
534 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
536 searchTime = min * 60;
537 } else if (matched == 2) {
538 searchTime = min * 60 + sec;
541 sprintf(buf, "bad searchTime option %s", appData.searchTime);
542 DisplayFatalError(buf, 0, 2);
546 /* [AS] Adjudication threshold */
547 adjudicateLossThreshold = appData.adjudicateLossThreshold;
549 first.which = "first";
550 second.which = "second";
551 first.maybeThinking = second.maybeThinking = FALSE;
552 first.pr = second.pr = NoProc;
553 first.isr = second.isr = NULL;
554 first.sendTime = second.sendTime = 2;
555 first.sendDrawOffers = 1;
556 if (appData.firstPlaysBlack) {
557 first.twoMachinesColor = "black\n";
558 second.twoMachinesColor = "white\n";
560 first.twoMachinesColor = "white\n";
561 second.twoMachinesColor = "black\n";
563 first.program = appData.firstChessProgram;
564 second.program = appData.secondChessProgram;
565 first.host = appData.firstHost;
566 second.host = appData.secondHost;
567 first.dir = appData.firstDirectory;
568 second.dir = appData.secondDirectory;
569 first.other = &second;
570 second.other = &first;
571 first.initString = appData.initString;
572 second.initString = appData.secondInitString;
573 first.computerString = appData.firstComputerString;
574 second.computerString = appData.secondComputerString;
575 first.useSigint = second.useSigint = TRUE;
576 first.useSigterm = second.useSigterm = TRUE;
577 first.reuse = appData.reuseFirst;
578 second.reuse = appData.reuseSecond;
579 first.useSetboard = second.useSetboard = FALSE;
580 first.useSAN = second.useSAN = FALSE;
581 first.usePing = second.usePing = FALSE;
582 first.lastPing = second.lastPing = 0;
583 first.lastPong = second.lastPong = 0;
584 first.usePlayother = second.usePlayother = FALSE;
585 first.useColors = second.useColors = TRUE;
586 first.useUsermove = second.useUsermove = FALSE;
587 first.sendICS = second.sendICS = FALSE;
588 first.sendName = second.sendName = appData.icsActive;
589 first.sdKludge = second.sdKludge = FALSE;
590 first.stKludge = second.stKludge = FALSE;
591 TidyProgramName(first.program, first.host, first.tidy);
592 TidyProgramName(second.program, second.host, second.tidy);
593 first.matchWins = second.matchWins = 0;
594 strcpy(first.variants, appData.variant);
595 strcpy(second.variants, appData.variant);
596 first.analysisSupport = second.analysisSupport = 2; /* detect */
597 first.analyzing = second.analyzing = FALSE;
598 first.initDone = second.initDone = FALSE;
600 /* New features added by Tord: */
601 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
602 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
603 /* End of new features added by Tord. */
604 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
605 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
607 if (appData.firstProtocolVersion > PROTOVER ||
608 appData.firstProtocolVersion < 1) {
610 sprintf(buf, "protocol version %d not supported",
611 appData.firstProtocolVersion);
612 DisplayFatalError(buf, 0, 2);
614 first.protocolVersion = appData.firstProtocolVersion;
617 if (appData.secondProtocolVersion > PROTOVER ||
618 appData.secondProtocolVersion < 1) {
620 sprintf(buf, "protocol version %d not supported",
621 appData.secondProtocolVersion);
622 DisplayFatalError(buf, 0, 2);
624 second.protocolVersion = appData.secondProtocolVersion;
627 if (appData.icsActive) {
628 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
629 } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
630 appData.clockMode = FALSE;
631 first.sendTime = second.sendTime = 0;
635 /* Override some settings from environment variables, for backward
636 compatibility. Unfortunately it's not feasible to have the env
637 vars just set defaults, at least in xboard. Ugh.
639 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
644 if (appData.noChessProgram) {
645 programVersion = (char*) malloc(5 + strlen(PRODUCT) + strlen(VERSION)
646 + strlen(PATCHLEVEL));
647 sprintf(programVersion, "%s %s.%s", PRODUCT, VERSION, PATCHLEVEL);
651 while (*q != ' ' && *q != NULLCHAR) q++;
653 while (p > first.program && *(p-1) != '/') p--;
654 programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)
655 + strlen(PATCHLEVEL) + (q - p));
656 sprintf(programVersion, "%s %s.%s + ", PRODUCT, VERSION, PATCHLEVEL);
657 strncat(programVersion, p, q - p);
660 if (!appData.icsActive) {
662 /* Check for variants that are supported only in ICS mode,
663 or not at all. Some that are accepted here nevertheless
664 have bugs; see comments below.
666 VariantClass variant = StringToVariant(appData.variant);
668 case VariantBughouse: /* need four players and two boards */
669 case VariantKriegspiel: /* need to hide pieces and move details */
670 /* case VariantFischeRandom: (Fabien: moved below) */
671 sprintf(buf, "Variant %s supported only in ICS mode", appData.variant);
672 DisplayFatalError(buf, 0, 2);
676 case VariantLoadable:
686 sprintf(buf, "Unknown variant name %s", appData.variant);
687 DisplayFatalError(buf, 0, 2);
690 case VariantNormal: /* definitely works! */
691 case VariantWildCastle: /* pieces not automatically shuffled */
692 case VariantNoCastle: /* pieces not automatically shuffled */
693 case VariantFischeRandom: /* Fabien: pieces not automatically shuffled */
694 case VariantCrazyhouse: /* holdings not shown,
695 offboard interposition not understood */
696 case VariantLosers: /* should work except for win condition,
697 and doesn't know captures are mandatory */
698 case VariantSuicide: /* should work except for win condition,
699 and doesn't know captures are mandatory */
700 case VariantGiveaway: /* should work except for win condition,
701 and doesn't know captures are mandatory */
702 case VariantTwoKings: /* should work */
703 case VariantAtomic: /* should work except for win condition */
704 case Variant3Check: /* should work except for win condition */
705 case VariantShatranj: /* might work if TestLegality is off */
711 int NextIntegerFromString( char ** str, long * value )
716 while( *s == ' ' || *s == '\t' ) {
722 if( *s >= '0' && *s <= '9' ) {
723 while( *s >= '0' && *s <= '9' ) {
724 *value = *value * 10 + (*s - '0');
736 int NextTimeControlFromString( char ** str, long * value )
739 int result = NextIntegerFromString( str, &temp );
742 *value = temp * 60; /* Minutes */
745 result = NextIntegerFromString( str, &temp );
746 *value += temp; /* Seconds */
753 int GetTimeControlForWhite()
755 int result = timeControl;
760 int GetTimeControlForBlack()
762 int result = timeControl;
764 if( timeControl_2 > 0 ) {
765 result = timeControl_2;
772 ParseTimeControl(tc, ti, mps)
778 int matched, min, sec;
780 matched = sscanf(tc, "%d:%d", &min, &sec);
782 timeControl = min * 60 * 1000;
783 } else if (matched == 2) {
784 timeControl = (min * 60 + sec) * 1000;
792 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
797 /* Parse second time control */
800 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
808 timeControl_2 = tc2 * 1000;
818 timeControl = tc1 * 1000;
822 timeIncrement = ti * 1000; /* convert to ms */
826 movesPerSession = mps;
834 if (appData.debugMode) {
835 fprintf(debugFP, "%s\n", programVersion);
838 if (appData.matchGames > 0) {
839 appData.matchMode = TRUE;
840 } else if (appData.matchMode) {
841 appData.matchGames = 1;
844 if (appData.noChessProgram || first.protocolVersion == 1) {
847 /* kludge: allow timeout for initial "feature" commands */
849 DisplayMessage("", "Starting chess program");
850 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
855 InitBackEnd3 P((void))
857 GameMode initialMode;
861 InitChessProgram(&first);
863 if (appData.icsActive) {
866 if (*appData.icsCommPort != NULLCHAR) {
867 sprintf(buf, "Could not open comm port %s",
868 appData.icsCommPort);
870 sprintf(buf, "Could not connect to host %s, port %s",
871 appData.icsHost, appData.icsPort);
873 DisplayFatalError(buf, err, 1);
878 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
880 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
881 } else if (appData.noChessProgram) {
887 if (*appData.cmailGameName != NULLCHAR) {
889 OpenLoopback(&cmailPR);
891 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
895 DisplayMessage("", "");
896 if (StrCaseCmp(appData.initialMode, "") == 0) {
897 initialMode = BeginningOfGame;
898 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
899 initialMode = TwoMachinesPlay;
900 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
901 initialMode = AnalyzeFile;
902 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
903 initialMode = AnalyzeMode;
904 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
905 initialMode = MachinePlaysWhite;
906 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
907 initialMode = MachinePlaysBlack;
908 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
909 initialMode = EditGame;
910 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
911 initialMode = EditPosition;
912 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
913 initialMode = Training;
915 sprintf(buf, "Unknown initialMode %s", appData.initialMode);
916 DisplayFatalError(buf, 0, 2);
920 if (appData.matchMode) {
921 /* Set up machine vs. machine match */
922 if (appData.noChessProgram) {
923 DisplayFatalError("Can't have a match with no chess programs",
929 if (*appData.loadGameFile != NULLCHAR) {
930 if (!LoadGameFromFile(appData.loadGameFile,
931 appData.loadGameIndex,
932 appData.loadGameFile, FALSE)) {
933 DisplayFatalError("Bad game file", 0, 1);
936 } else if (*appData.loadPositionFile != NULLCHAR) {
937 if (!LoadPositionFromFile(appData.loadPositionFile,
938 appData.loadPositionIndex,
939 appData.loadPositionFile)) {
940 DisplayFatalError("Bad position file", 0, 1);
945 } else if (*appData.cmailGameName != NULLCHAR) {
946 /* Set up cmail mode */
947 ReloadCmailMsgEvent(TRUE);
949 /* Set up other modes */
950 if (initialMode == AnalyzeFile) {
951 if (*appData.loadGameFile == NULLCHAR) {
952 DisplayFatalError("AnalyzeFile mode requires a game file", 0, 1);
956 if (*appData.loadGameFile != NULLCHAR) {
957 (void) LoadGameFromFile(appData.loadGameFile,
958 appData.loadGameIndex,
959 appData.loadGameFile, TRUE);
960 } else if (*appData.loadPositionFile != NULLCHAR) {
961 (void) LoadPositionFromFile(appData.loadPositionFile,
962 appData.loadPositionIndex,
963 appData.loadPositionFile);
965 if (initialMode == AnalyzeMode) {
966 if (appData.noChessProgram) {
967 DisplayFatalError("Analysis mode requires a chess engine", 0, 2);
970 if (appData.icsActive) {
971 DisplayFatalError("Analysis mode does not work with ICS mode",0,2);
975 } else if (initialMode == AnalyzeFile) {
976 ShowThinkingEvent(TRUE);
978 AnalysisPeriodicEvent(1);
979 } else if (initialMode == MachinePlaysWhite) {
980 if (appData.noChessProgram) {
981 DisplayFatalError("MachineWhite mode requires a chess engine",
985 if (appData.icsActive) {
986 DisplayFatalError("MachineWhite mode does not work with ICS mode",
991 } else if (initialMode == MachinePlaysBlack) {
992 if (appData.noChessProgram) {
993 DisplayFatalError("MachineBlack mode requires a chess engine",
997 if (appData.icsActive) {
998 DisplayFatalError("MachineBlack mode does not work with ICS mode",
1002 MachineBlackEvent();
1003 } else if (initialMode == TwoMachinesPlay) {
1004 if (appData.noChessProgram) {
1005 DisplayFatalError("TwoMachines mode requires a chess engine",
1009 if (appData.icsActive) {
1010 DisplayFatalError("TwoMachines mode does not work with ICS mode",
1015 } else if (initialMode == EditGame) {
1017 } else if (initialMode == EditPosition) {
1018 EditPositionEvent();
1019 } else if (initialMode == Training) {
1020 if (*appData.loadGameFile == NULLCHAR) {
1021 DisplayFatalError("Training mode requires a game file", 0, 2);
1030 * Establish will establish a contact to a remote host.port.
1031 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1032 * used to talk to the host.
1033 * Returns 0 if okay, error code if not.
1040 if (*appData.icsCommPort != NULLCHAR) {
1041 /* Talk to the host through a serial comm port */
1042 return OpenCommPort(appData.icsCommPort, &icsPR);
1044 } else if (*appData.gateway != NULLCHAR) {
1045 if (*appData.remoteShell == NULLCHAR) {
1046 /* Use the rcmd protocol to run telnet program on a gateway host */
1047 sprintf(buf, "%s %s %s",
1048 appData.telnetProgram, appData.icsHost, appData.icsPort);
1049 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1052 /* Use the rsh program to run telnet program on a gateway host */
1053 if (*appData.remoteUser == NULLCHAR) {
1054 sprintf(buf, "%s %s %s %s %s", appData.remoteShell,
1055 appData.gateway, appData.telnetProgram,
1056 appData.icsHost, appData.icsPort);
1058 sprintf(buf, "%s %s -l %s %s %s %s",
1059 appData.remoteShell, appData.gateway,
1060 appData.remoteUser, appData.telnetProgram,
1061 appData.icsHost, appData.icsPort);
1063 return StartChildProcess(buf, "", &icsPR);
1066 } else if (appData.useTelnet) {
1067 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1070 /* TCP socket interface differs somewhat between
1071 Unix and NT; handle details in the front end.
1073 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1078 show_bytes(fp, buf, count)
1084 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1085 fprintf(fp, "\\%03o", *buf & 0xff);
1094 /* Returns an errno value */
1096 OutputMaybeTelnet(pr, message, count, outError)
1102 char buf[8192], *p, *q, *buflim;
1103 int left, newcount, outcount;
1105 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1106 *appData.gateway != NULLCHAR) {
1107 if (appData.debugMode) {
1108 fprintf(debugFP, ">ICS: ");
1109 show_bytes(debugFP, message, count);
1110 fprintf(debugFP, "\n");
1112 return OutputToProcess(pr, message, count, outError);
1115 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1122 if (appData.debugMode) {
1123 fprintf(debugFP, ">ICS: ");
1124 show_bytes(debugFP, buf, newcount);
1125 fprintf(debugFP, "\n");
1127 outcount = OutputToProcess(pr, buf, newcount, outError);
1128 if (outcount < newcount) return -1; /* to be sure */
1135 } else if (((unsigned char) *p) == TN_IAC) {
1136 *q++ = (char) TN_IAC;
1143 if (appData.debugMode) {
1144 fprintf(debugFP, ">ICS: ");
1145 show_bytes(debugFP, buf, newcount);
1146 fprintf(debugFP, "\n");
1148 outcount = OutputToProcess(pr, buf, newcount, outError);
1149 if (outcount < newcount) return -1; /* to be sure */
1154 read_from_player(isr, closure, message, count, error)
1161 int outError, outCount;
1162 static int gotEof = 0;
1164 /* Pass data read from player on to ICS */
1167 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1168 if (outCount < count) {
1169 DisplayFatalError("Error writing to ICS", outError, 1);
1171 } else if (count < 0) {
1172 RemoveInputSource(isr);
1173 DisplayFatalError("Error reading from keyboard", error, 1);
1174 } else if (gotEof++ > 0) {
1175 RemoveInputSource(isr);
1176 DisplayFatalError("Got end of file from keyboard", 0, 0);
1184 int count, outCount, outError;
1186 if (icsPR == NULL) return;
1189 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1190 if (outCount < count) {
1191 DisplayFatalError("Error writing to ICS", outError, 1);
1195 /* This is used for sending logon scripts to the ICS. Sending
1196 without a delay causes problems when using timestamp on ICC
1197 (at least on my machine). */
1199 SendToICSDelayed(s,msdelay)
1203 int count, outCount, outError;
1205 if (icsPR == NULL) return;
1208 if (appData.debugMode) {
1209 fprintf(debugFP, ">ICS: ");
1210 show_bytes(debugFP, s, count);
1211 fprintf(debugFP, "\n");
1213 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1215 if (outCount < count) {
1216 DisplayFatalError("Error writing to ICS", outError, 1);
1221 /* Remove all highlighting escape sequences in s
1222 Also deletes any suffix starting with '('
1225 StripHighlightAndTitle(s)
1228 static char retbuf[MSG_SIZ];
1231 while (*s != NULLCHAR) {
1232 while (*s == '\033') {
1233 while (*s != NULLCHAR && !isalpha(*s)) s++;
1234 if (*s != NULLCHAR) s++;
1236 while (*s != NULLCHAR && *s != '\033') {
1237 if (*s == '(' || *s == '[') {
1248 /* Remove all highlighting escape sequences in s */
1253 static char retbuf[MSG_SIZ];
1256 while (*s != NULLCHAR) {
1257 while (*s == '\033') {
1258 while (*s != NULLCHAR && !isalpha(*s)) s++;
1259 if (*s != NULLCHAR) s++;
1261 while (*s != NULLCHAR && *s != '\033') {
1269 char *variantNames[] = VARIANT_NAMES;
1274 return variantNames[v];
1278 /* Identify a variant from the strings the chess servers use or the
1279 PGN Variant tag names we use. */
1286 VariantClass v = VariantNormal;
1287 int i, found = FALSE;
1292 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1293 if (StrCaseStr(e, variantNames[i])) {
1294 v = (VariantClass) i;
1301 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1302 || StrCaseStr(e, "wild/fr")) {
1303 v = VariantFischeRandom;
1304 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1305 (i = 1, p = StrCaseStr(e, "w"))) {
1307 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1314 case 0: /* FICS only, actually */
1316 /* Castling legal even if K starts on d-file */
1317 v = VariantWildCastle;
1322 /* Castling illegal even if K & R happen to start in
1323 normal positions. */
1324 v = VariantNoCastle;
1337 /* Castling legal iff K & R start in normal positions */
1343 /* Special wilds for position setup; unclear what to do here */
1344 v = VariantLoadable;
1347 /* Bizarre ICC game */
1348 v = VariantTwoKings;
1351 v = VariantKriegspiel;
1357 v = VariantFischeRandom;
1360 v = VariantCrazyhouse;
1363 v = VariantBughouse;
1369 /* Not quite the same as FICS suicide! */
1370 v = VariantGiveaway;
1376 v = VariantShatranj;
1379 /* Temporary names for future ICC types. The name *will* change in
1380 the next xboard/WinBoard release after ICC defines it. */
1407 /* Found "wild" or "w" in the string but no number;
1408 must assume it's normal chess. */
1412 sprintf(buf, "Unknown wild type %d", wnum);
1413 DisplayError(buf, 0);
1419 if (appData.debugMode) {
1420 fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
1421 e, wnum, VariantName(v));
1426 static int leftover_start = 0, leftover_len = 0;
1427 char star_match[STAR_MATCH_N][MSG_SIZ];
1429 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1430 advance *index beyond it, and set leftover_start to the new value of
1431 *index; else return FALSE. If pattern contains the character '*', it
1432 matches any sequence of characters not containing '\r', '\n', or the
1433 character following the '*' (if any), and the matched sequence(s) are
1434 copied into star_match.
1437 looking_at(buf, index, pattern)
1442 char *bufp = &buf[*index], *patternp = pattern;
1444 char *matchp = star_match[0];
1447 if (*patternp == NULLCHAR) {
1448 *index = leftover_start = bufp - buf;
1452 if (*bufp == NULLCHAR) return FALSE;
1453 if (*patternp == '*') {
1454 if (*bufp == *(patternp + 1)) {
1456 matchp = star_match[++star_count];
1460 } else if (*bufp == '\n' || *bufp == '\r') {
1462 if (*patternp == NULLCHAR)
1467 *matchp++ = *bufp++;
1471 if (*patternp != *bufp) return FALSE;
1478 SendToPlayer(data, length)
1482 int error, outCount;
1483 outCount = OutputToProcess(NoProc, data, length, &error);
1484 if (outCount < length) {
1485 DisplayFatalError("Error writing to display", error, 1);
1490 PackHolding(packed, holding)
1502 switch (runlength) {
1513 sprintf(q, "%d", runlength);
1525 /* Telnet protocol requests from the front end */
1527 TelnetRequest(ddww, option)
1528 unsigned char ddww, option;
1530 unsigned char msg[3];
1531 int outCount, outError;
1533 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1535 if (appData.debugMode) {
1536 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1552 sprintf(buf1, "%d", ddww);
1561 sprintf(buf2, "%d", option);
1564 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1569 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1571 DisplayFatalError("Error writing to ICS", outError, 1);
1578 if (!appData.icsActive) return;
1579 TelnetRequest(TN_DO, TN_ECHO);
1585 if (!appData.icsActive) return;
1586 TelnetRequest(TN_DONT, TN_ECHO);
1589 static int loggedOn = FALSE;
1591 /*-- Game start info cache: --*/
1593 char gs_kind[MSG_SIZ];
1594 static char player1Name[128] = "";
1595 static char player2Name[128] = "";
1596 static int player1Rating = -1;
1597 static int player2Rating = -1;
1598 /*----------------------------*/
1601 read_from_ics(isr, closure, data, count, error)
1608 #define BUF_SIZE 8192
1609 #define STARTED_NONE 0
1610 #define STARTED_MOVES 1
1611 #define STARTED_BOARD 2
1612 #define STARTED_OBSERVE 3
1613 #define STARTED_HOLDINGS 4
1614 #define STARTED_CHATTER 5
1615 #define STARTED_COMMENT 6
1616 #define STARTED_MOVES_NOHIDE 7
1618 static int started = STARTED_NONE;
1619 static char parse[20000];
1620 static int parse_pos = 0;
1621 static char buf[BUF_SIZE + 1];
1622 static int firstTime = TRUE, intfSet = FALSE;
1623 static ColorClass curColor = ColorNormal;
1624 static ColorClass prevColor = ColorNormal;
1625 static int savingComment = FALSE;
1634 if (appData.debugMode) {
1636 fprintf(debugFP, "<ICS: ");
1637 show_bytes(debugFP, data, count);
1638 fprintf(debugFP, "\n");
1644 /* If last read ended with a partial line that we couldn't parse,
1645 prepend it to the new read and try again. */
1646 if (leftover_len > 0) {
1647 for (i=0; i<leftover_len; i++)
1648 buf[i] = buf[leftover_start + i];
1651 /* Copy in new characters, removing nulls and \r's */
1652 buf_len = leftover_len;
1653 for (i = 0; i < count; i++) {
1654 if (data[i] != NULLCHAR && data[i] != '\r')
1655 buf[buf_len++] = data[i];
1658 buf[buf_len] = NULLCHAR;
1659 next_out = leftover_len;
1663 while (i < buf_len) {
1664 /* Deal with part of the TELNET option negotiation
1665 protocol. We refuse to do anything beyond the
1666 defaults, except that we allow the WILL ECHO option,
1667 which ICS uses to turn off password echoing when we are
1668 directly connected to it. We reject this option
1669 if localLineEditing mode is on (always on in xboard)
1670 and we are talking to port 23, which might be a real
1671 telnet server that will try to keep WILL ECHO on permanently.
1673 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
1674 static int remoteEchoOption = FALSE; /* telnet ECHO option */
1675 unsigned char option;
1677 switch ((unsigned char) buf[++i]) {
1679 if (appData.debugMode)
1680 fprintf(debugFP, "\n<WILL ");
1681 switch (option = (unsigned char) buf[++i]) {
1683 if (appData.debugMode)
1684 fprintf(debugFP, "ECHO ");
1685 /* Reply only if this is a change, according
1686 to the protocol rules. */
1687 if (remoteEchoOption) break;
1688 if (appData.localLineEditing &&
1689 atoi(appData.icsPort) == TN_PORT) {
1690 TelnetRequest(TN_DONT, TN_ECHO);
1693 TelnetRequest(TN_DO, TN_ECHO);
1694 remoteEchoOption = TRUE;
1698 if (appData.debugMode)
1699 fprintf(debugFP, "%d ", option);
1700 /* Whatever this is, we don't want it. */
1701 TelnetRequest(TN_DONT, option);
1706 if (appData.debugMode)
1707 fprintf(debugFP, "\n<WONT ");
1708 switch (option = (unsigned char) buf[++i]) {
1710 if (appData.debugMode)
1711 fprintf(debugFP, "ECHO ");
1712 /* Reply only if this is a change, according
1713 to the protocol rules. */
1714 if (!remoteEchoOption) break;
1716 TelnetRequest(TN_DONT, TN_ECHO);
1717 remoteEchoOption = FALSE;
1720 if (appData.debugMode)
1721 fprintf(debugFP, "%d ", (unsigned char) option);
1722 /* Whatever this is, it must already be turned
1723 off, because we never agree to turn on
1724 anything non-default, so according to the
1725 protocol rules, we don't reply. */
1730 if (appData.debugMode)
1731 fprintf(debugFP, "\n<DO ");
1732 switch (option = (unsigned char) buf[++i]) {
1734 /* Whatever this is, we refuse to do it. */
1735 if (appData.debugMode)
1736 fprintf(debugFP, "%d ", option);
1737 TelnetRequest(TN_WONT, option);
1742 if (appData.debugMode)
1743 fprintf(debugFP, "\n<DONT ");
1744 switch (option = (unsigned char) buf[++i]) {
1746 if (appData.debugMode)
1747 fprintf(debugFP, "%d ", option);
1748 /* Whatever this is, we are already not doing
1749 it, because we never agree to do anything
1750 non-default, so according to the protocol
1751 rules, we don't reply. */
1756 if (appData.debugMode)
1757 fprintf(debugFP, "\n<IAC ");
1758 /* Doubled IAC; pass it through */
1762 if (appData.debugMode)
1763 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
1764 /* Drop all other telnet commands on the floor */
1767 if (oldi > next_out)
1768 SendToPlayer(&buf[next_out], oldi - next_out);
1774 /* OK, this at least will *usually* work */
1775 if (!loggedOn && looking_at(buf, &i, "ics%")) {
1779 if (loggedOn && !intfSet) {
1780 if (ics_type == ICS_ICC) {
1782 "/set-quietly interface %s\n/set-quietly style 12\n",
1785 } else if (ics_type == ICS_CHESSNET) {
1786 sprintf(str, "/style 12\n");
1788 strcpy(str, "alias $ @\n$set interface ");
1789 strcat(str, programVersion);
1790 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
1792 strcat(str, "$iset nohighlight 1\n");
1794 strcat(str, "$iset lock 1\n$style 12\n");
1800 if (started == STARTED_COMMENT) {
1801 /* Accumulate characters in comment */
1802 parse[parse_pos++] = buf[i];
1803 if (buf[i] == '\n') {
1804 parse[parse_pos] = NULLCHAR;
1805 AppendComment(forwardMostMove, StripHighlight(parse));
1806 started = STARTED_NONE;
1808 /* Don't match patterns against characters in chatter */
1813 if (started == STARTED_CHATTER) {
1814 if (buf[i] != '\n') {
1815 /* Don't match patterns against characters in chatter */
1819 started = STARTED_NONE;
1822 /* Kludge to deal with rcmd protocol */
1823 if (firstTime && looking_at(buf, &i, "\001*")) {
1824 DisplayFatalError(&buf[1], 0, 1);
1830 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
1833 if (appData.debugMode)
1834 fprintf(debugFP, "ics_type %d\n", ics_type);
1837 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
1838 ics_type = ICS_FICS;
1840 if (appData.debugMode)
1841 fprintf(debugFP, "ics_type %d\n", ics_type);
1844 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
1845 ics_type = ICS_CHESSNET;
1847 if (appData.debugMode)
1848 fprintf(debugFP, "ics_type %d\n", ics_type);
1853 (looking_at(buf, &i, "\"*\" is *a registered name") ||
1854 looking_at(buf, &i, "Logging you in as \"*\"") ||
1855 looking_at(buf, &i, "will be \"*\""))) {
1856 strcpy(ics_handle, star_match[0]);
1860 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
1862 sprintf(buf, "%s@%s", ics_handle, appData.icsHost);
1863 DisplayIcsInteractionTitle(buf);
1864 have_set_title = TRUE;
1867 /* skip finger notes */
1868 if (started == STARTED_NONE &&
1869 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
1870 (buf[i] == '1' && buf[i+1] == '0')) &&
1871 buf[i+2] == ':' && buf[i+3] == ' ') {
1872 started = STARTED_CHATTER;
1877 /* skip formula vars */
1878 if (started == STARTED_NONE &&
1879 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
1880 started = STARTED_CHATTER;
1886 if (appData.zippyTalk || appData.zippyPlay) {
1888 if (ZippyControl(buf, &i) ||
1889 ZippyConverse(buf, &i) ||
1890 (appData.zippyPlay && ZippyMatch(buf, &i))) {
1896 if (/* Don't color "message" or "messages" output */
1897 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
1898 looking_at(buf, &i, "*. * at *:*: ") ||
1899 looking_at(buf, &i, "--* (*:*): ") ||
1900 /* Regular tells and says */
1901 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
1902 looking_at(buf, &i, "* (your partner) tells you: ") ||
1903 looking_at(buf, &i, "* says: ") ||
1904 /* Message notifications (same color as tells) */
1905 looking_at(buf, &i, "* has left a message ") ||
1906 looking_at(buf, &i, "* just sent you a message:\n") ||
1907 /* Whispers and kibitzes */
1908 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
1909 looking_at(buf, &i, "* kibitzes: ") ||
1911 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
1913 if (tkind == 1 && strchr(star_match[0], ':')) {
1914 /* Avoid "tells you:" spoofs in channels */
1917 if (star_match[0][0] == NULLCHAR ||
1918 strchr(star_match[0], ' ') ||
1919 (tkind == 3 && strchr(star_match[1], ' '))) {
1920 /* Reject bogus matches */
1923 if (appData.colorize) {
1924 if (oldi > next_out) {
1925 SendToPlayer(&buf[next_out], oldi - next_out);
1930 Colorize(ColorTell, FALSE);
1931 curColor = ColorTell;
1934 Colorize(ColorKibitz, FALSE);
1935 curColor = ColorKibitz;
1938 p = strrchr(star_match[1], '(');
1945 Colorize(ColorChannel1, FALSE);
1946 curColor = ColorChannel1;
1948 Colorize(ColorChannel, FALSE);
1949 curColor = ColorChannel;
1953 curColor = ColorNormal;
1957 if (started == STARTED_NONE && appData.autoComment &&
1958 (gameMode == IcsObserving ||
1959 gameMode == IcsPlayingWhite ||
1960 gameMode == IcsPlayingBlack)) {
1961 parse_pos = i - oldi;
1962 memcpy(parse, &buf[oldi], parse_pos);
1963 parse[parse_pos] = NULLCHAR;
1964 started = STARTED_COMMENT;
1965 savingComment = TRUE;
1967 started = STARTED_CHATTER;
1968 savingComment = FALSE;
1975 if (looking_at(buf, &i, "* s-shouts: ") ||
1976 looking_at(buf, &i, "* c-shouts: ")) {
1977 if (appData.colorize) {
1978 if (oldi > next_out) {
1979 SendToPlayer(&buf[next_out], oldi - next_out);
1982 Colorize(ColorSShout, FALSE);
1983 curColor = ColorSShout;
1986 started = STARTED_CHATTER;
1990 if (looking_at(buf, &i, "--->")) {
1995 if (looking_at(buf, &i, "* shouts: ") ||
1996 looking_at(buf, &i, "--> ")) {
1997 if (appData.colorize) {
1998 if (oldi > next_out) {
1999 SendToPlayer(&buf[next_out], oldi - next_out);
2002 Colorize(ColorShout, FALSE);
2003 curColor = ColorShout;
2006 started = STARTED_CHATTER;
2010 if (looking_at( buf, &i, "Challenge:")) {
2011 if (appData.colorize) {
2012 if (oldi > next_out) {
2013 SendToPlayer(&buf[next_out], oldi - next_out);
2016 Colorize(ColorChallenge, FALSE);
2017 curColor = ColorChallenge;
2023 if (looking_at(buf, &i, "* offers you") ||
2024 looking_at(buf, &i, "* offers to be") ||
2025 looking_at(buf, &i, "* would like to") ||
2026 looking_at(buf, &i, "* requests to") ||
2027 looking_at(buf, &i, "Your opponent offers") ||
2028 looking_at(buf, &i, "Your opponent requests")) {
2030 if (appData.colorize) {
2031 if (oldi > next_out) {
2032 SendToPlayer(&buf[next_out], oldi - next_out);
2035 Colorize(ColorRequest, FALSE);
2036 curColor = ColorRequest;
2041 if (looking_at(buf, &i, "* (*) seeking")) {
2042 if (appData.colorize) {
2043 if (oldi > next_out) {
2044 SendToPlayer(&buf[next_out], oldi - next_out);
2047 Colorize(ColorSeek, FALSE);
2048 curColor = ColorSeek;
2054 if (looking_at(buf, &i, "\\ ")) {
2055 if (prevColor != ColorNormal) {
2056 if (oldi > next_out) {
2057 SendToPlayer(&buf[next_out], oldi - next_out);
2060 Colorize(prevColor, TRUE);
2061 curColor = prevColor;
2063 if (savingComment) {
2064 parse_pos = i - oldi;
2065 memcpy(parse, &buf[oldi], parse_pos);
2066 parse[parse_pos] = NULLCHAR;
2067 started = STARTED_COMMENT;
2069 started = STARTED_CHATTER;
2074 if (looking_at(buf, &i, "Black Strength :") ||
2075 looking_at(buf, &i, "<<< style 10 board >>>") ||
2076 looking_at(buf, &i, "<10>") ||
2077 looking_at(buf, &i, "#@#")) {
2078 /* Wrong board style */
2080 SendToICS(ics_prefix);
2081 SendToICS("set style 12\n");
2082 SendToICS(ics_prefix);
2083 SendToICS("refresh\n");
2087 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2089 have_sent_ICS_logon = 1;
2093 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2094 (looking_at(buf, &i, "\n<12> ") ||
2095 looking_at(buf, &i, "<12> "))) {
2097 if (oldi > next_out) {
2098 SendToPlayer(&buf[next_out], oldi - next_out);
2101 started = STARTED_BOARD;
2106 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2107 looking_at(buf, &i, "<b1> ")) {
2108 if (oldi > next_out) {
2109 SendToPlayer(&buf[next_out], oldi - next_out);
2112 started = STARTED_HOLDINGS;
2117 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2119 /* Header for a move list -- first line */
2121 switch (ics_getting_history) {
2125 case BeginningOfGame:
2126 /* User typed "moves" or "oldmoves" while we
2127 were idle. Pretend we asked for these
2128 moves and soak them up so user can step
2129 through them and/or save them.
2132 gameMode = IcsObserving;
2135 ics_getting_history = H_GOT_UNREQ_HEADER;
2137 case EditGame: /*?*/
2138 case EditPosition: /*?*/
2139 /* Should above feature work in these modes too? */
2140 /* For now it doesn't */
2141 ics_getting_history = H_GOT_UNWANTED_HEADER;
2144 ics_getting_history = H_GOT_UNWANTED_HEADER;
2149 /* Is this the right one? */
2150 if (gameInfo.white && gameInfo.black &&
2151 strcmp(gameInfo.white, star_match[0]) == 0 &&
2152 strcmp(gameInfo.black, star_match[2]) == 0) {
2154 ics_getting_history = H_GOT_REQ_HEADER;
2157 case H_GOT_REQ_HEADER:
2158 case H_GOT_UNREQ_HEADER:
2159 case H_GOT_UNWANTED_HEADER:
2160 case H_GETTING_MOVES:
2161 /* Should not happen */
2162 DisplayError("Error gathering move list: two headers", 0);
2163 ics_getting_history = H_FALSE;
2167 /* Save player ratings into gameInfo if needed */
2168 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2169 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2170 (gameInfo.whiteRating == -1 ||
2171 gameInfo.blackRating == -1)) {
2173 gameInfo.whiteRating = string_to_rating(star_match[1]);
2174 gameInfo.blackRating = string_to_rating(star_match[3]);
2175 if (appData.debugMode)
2176 fprintf(debugFP, "Ratings from header: W %d, B %d\n",
2177 gameInfo.whiteRating, gameInfo.blackRating);
2182 if (looking_at(buf, &i,
2183 "* * match, initial time: * minute*, increment: * second")) {
2184 /* Header for a move list -- second line */
2185 /* Initial board will follow if this is a wild game */
2187 if (gameInfo.event != NULL) free(gameInfo.event);
2188 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2189 gameInfo.event = StrSave(str);
2190 gameInfo.variant = StringToVariant(gameInfo.event);
2194 if (looking_at(buf, &i, "Move ")) {
2195 /* Beginning of a move list */
2196 switch (ics_getting_history) {
2198 /* Normally should not happen */
2199 /* Maybe user hit reset while we were parsing */
2202 /* Happens if we are ignoring a move list that is not
2203 * the one we just requested. Common if the user
2204 * tries to observe two games without turning off
2207 case H_GETTING_MOVES:
2208 /* Should not happen */
2209 DisplayError("Error gathering move list: nested", 0);
2210 ics_getting_history = H_FALSE;
2212 case H_GOT_REQ_HEADER:
2213 ics_getting_history = H_GETTING_MOVES;
2214 started = STARTED_MOVES;
2216 if (oldi > next_out) {
2217 SendToPlayer(&buf[next_out], oldi - next_out);
2220 case H_GOT_UNREQ_HEADER:
2221 ics_getting_history = H_GETTING_MOVES;
2222 started = STARTED_MOVES_NOHIDE;
2225 case H_GOT_UNWANTED_HEADER:
2226 ics_getting_history = H_FALSE;
2232 if (looking_at(buf, &i, "% ") ||
2233 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2234 && looking_at(buf, &i, "}*"))) {
2235 savingComment = FALSE;
2238 case STARTED_MOVES_NOHIDE:
2239 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2240 parse[parse_pos + i - oldi] = NULLCHAR;
2241 ParseGameHistory(parse);
2243 if (appData.zippyPlay && first.initDone) {
2244 FeedMovesToProgram(&first, forwardMostMove);
2245 if (gameMode == IcsPlayingWhite) {
2246 if (WhiteOnMove(forwardMostMove)) {
2247 if (first.sendTime) {
2248 if (first.useColors) {
2249 SendToProgram("black\n", &first);
2251 SendTimeRemaining(&first, TRUE);
2253 if (first.useColors) {
2254 SendToProgram("white\ngo\n", &first);
2256 SendToProgram("go\n", &first);
2258 first.maybeThinking = TRUE;
2260 if (first.usePlayother) {
2261 if (first.sendTime) {
2262 SendTimeRemaining(&first, TRUE);
2264 SendToProgram("playother\n", &first);
2270 } else if (gameMode == IcsPlayingBlack) {
2271 if (!WhiteOnMove(forwardMostMove)) {
2272 if (first.sendTime) {
2273 if (first.useColors) {
2274 SendToProgram("white\n", &first);
2276 SendTimeRemaining(&first, FALSE);
2278 if (first.useColors) {
2279 SendToProgram("black\ngo\n", &first);
2281 SendToProgram("go\n", &first);
2283 first.maybeThinking = TRUE;
2285 if (first.usePlayother) {
2286 if (first.sendTime) {
2287 SendTimeRemaining(&first, FALSE);
2289 SendToProgram("playother\n", &first);
2298 if (gameMode == IcsObserving && ics_gamenum == -1) {
2299 /* Moves came from oldmoves or moves command
2300 while we weren't doing anything else.
2302 currentMove = forwardMostMove;
2303 ClearHighlights();/*!!could figure this out*/
2304 flipView = appData.flipView;
2305 DrawPosition(FALSE, boards[currentMove]);
2306 DisplayBothClocks();
2307 sprintf(str, "%s vs. %s",
2308 gameInfo.white, gameInfo.black);
2312 /* Moves were history of an active game */
2313 if (gameInfo.resultDetails != NULL) {
2314 free(gameInfo.resultDetails);
2315 gameInfo.resultDetails = NULL;
2318 HistorySet(parseList, backwardMostMove,
2319 forwardMostMove, currentMove-1);
2320 DisplayMove(currentMove - 1);
2321 if (started == STARTED_MOVES) next_out = i;
2322 started = STARTED_NONE;
2323 ics_getting_history = H_FALSE;
2326 case STARTED_OBSERVE:
2327 started = STARTED_NONE;
2328 SendToICS(ics_prefix);
2329 SendToICS("refresh\n");
2338 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2339 started == STARTED_HOLDINGS ||
2340 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2341 /* Accumulate characters in move list or board */
2342 parse[parse_pos++] = buf[i];
2345 /* Start of game messages. Mostly we detect start of game
2346 when the first board image arrives. On some versions
2347 of the ICS, though, we need to do a "refresh" after starting
2348 to observe in order to get the current board right away. */
2349 if (looking_at(buf, &i, "Adding game * to observation list")) {
2350 started = STARTED_OBSERVE;
2354 /* Handle auto-observe */
2355 if (appData.autoObserve &&
2356 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2357 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2359 /* Choose the player that was highlighted, if any. */
2360 if (star_match[0][0] == '\033' ||
2361 star_match[1][0] != '\033') {
2362 player = star_match[0];
2364 player = star_match[2];
2366 sprintf(str, "%sobserve %s\n",
2367 ics_prefix, StripHighlightAndTitle(player));
2370 /* Save ratings from notify string */
2371 strcpy(player1Name, star_match[0]);
2372 player1Rating = string_to_rating(star_match[1]);
2373 strcpy(player2Name, star_match[2]);
2374 player2Rating = string_to_rating(star_match[3]);
2376 if (appData.debugMode)
2378 "Ratings from 'Game notification:' %s %d, %s %d\n",
2379 player1Name, player1Rating,
2380 player2Name, player2Rating);
2385 /* Deal with automatic examine mode after a game,
2386 and with IcsObserving -> IcsExamining transition */
2387 if (looking_at(buf, &i, "Entering examine mode for game *") ||
2388 looking_at(buf, &i, "has made you an examiner of game *")) {
2390 int gamenum = atoi(star_match[0]);
2391 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2392 gamenum == ics_gamenum) {
2393 /* We were already playing or observing this game;
2394 no need to refetch history */
2395 gameMode = IcsExamining;
2397 pauseExamForwardMostMove = forwardMostMove;
2398 } else if (currentMove < forwardMostMove) {
2399 ForwardInner(forwardMostMove);
2402 /* I don't think this case really can happen */
2403 SendToICS(ics_prefix);
2404 SendToICS("refresh\n");
2409 /* Error messages */
2410 if (ics_user_moved) {
2411 if (looking_at(buf, &i, "Illegal move") ||
2412 looking_at(buf, &i, "Not a legal move") ||
2413 looking_at(buf, &i, "Your king is in check") ||
2414 looking_at(buf, &i, "It isn't your turn") ||
2415 looking_at(buf, &i, "It is not your move")) {
2418 if (forwardMostMove > backwardMostMove) {
2419 currentMove = --forwardMostMove;
2420 DisplayMove(currentMove - 1); /* before DMError */
2421 DisplayMoveError("Illegal move (rejected by ICS)");
2422 DrawPosition(FALSE, boards[currentMove]);
2424 DisplayBothClocks();
2430 if (looking_at(buf, &i, "still have time") ||
2431 looking_at(buf, &i, "not out of time") ||
2432 looking_at(buf, &i, "either player is out of time") ||
2433 looking_at(buf, &i, "has timeseal; checking")) {
2434 /* We must have called his flag a little too soon */
2435 whiteFlag = blackFlag = FALSE;
2439 if (looking_at(buf, &i, "added * seconds to") ||
2440 looking_at(buf, &i, "seconds were added to")) {
2441 /* Update the clocks */
2442 SendToICS(ics_prefix);
2443 SendToICS("refresh\n");
2447 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
2448 ics_clock_paused = TRUE;
2453 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
2454 ics_clock_paused = FALSE;
2459 /* Grab player ratings from the Creating: message.
2460 Note we have to check for the special case when
2461 the ICS inserts things like [white] or [black]. */
2462 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
2463 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
2465 0 player 1 name (not necessarily white)
2467 2 empty, white, or black (IGNORED)
2468 3 player 2 name (not necessarily black)
2471 The names/ratings are sorted out when the game
2472 actually starts (below).
2474 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
2475 player1Rating = string_to_rating(star_match[1]);
2476 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
2477 player2Rating = string_to_rating(star_match[4]);
2479 if (appData.debugMode)
2481 "Ratings from 'Creating:' %s %d, %s %d\n",
2482 player1Name, player1Rating,
2483 player2Name, player2Rating);
2488 /* Improved generic start/end-of-game messages */
2489 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
2490 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
2491 /* If tkind == 0: */
2492 /* star_match[0] is the game number */
2493 /* [1] is the white player's name */
2494 /* [2] is the black player's name */
2495 /* For end-of-game: */
2496 /* [3] is the reason for the game end */
2497 /* [4] is a PGN end game-token, preceded by " " */
2498 /* For start-of-game: */
2499 /* [3] begins with "Creating" or "Continuing" */
2500 /* [4] is " *" or empty (don't care). */
2501 int gamenum = atoi(star_match[0]);
2502 char *whitename, *blackname, *why, *endtoken;
2503 ChessMove endtype = (ChessMove) 0;
2506 whitename = star_match[1];
2507 blackname = star_match[2];
2508 why = star_match[3];
2509 endtoken = star_match[4];
2511 whitename = star_match[1];
2512 blackname = star_match[3];
2513 why = star_match[5];
2514 endtoken = star_match[6];
2517 /* Game start messages */
2518 if (strncmp(why, "Creating ", 9) == 0 ||
2519 strncmp(why, "Continuing ", 11) == 0) {
2520 gs_gamenum = gamenum;
2521 strcpy(gs_kind, strchr(why, ' ') + 1);
2523 if (appData.zippyPlay) {
2524 ZippyGameStart(whitename, blackname);
2530 /* Game end messages */
2531 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
2532 ics_gamenum != gamenum) {
2535 while (endtoken[0] == ' ') endtoken++;
2536 switch (endtoken[0]) {
2539 endtype = GameUnfinished;
2542 endtype = BlackWins;
2545 if (endtoken[1] == '/')
2546 endtype = GameIsDrawn;
2548 endtype = WhiteWins;
2551 GameEnds(endtype, why, GE_ICS);
2553 if (appData.zippyPlay && first.initDone) {
2554 ZippyGameEnd(endtype, why);
2555 if (first.pr == NULL) {
2556 /* Start the next process early so that we'll
2557 be ready for the next challenge */
2558 StartChessProgram(&first);
2560 /* Send "new" early, in case this command takes
2561 a long time to finish, so that we'll be ready
2562 for the next challenge. */
2569 if (looking_at(buf, &i, "Removing game * from observation") ||
2570 looking_at(buf, &i, "no longer observing game *") ||
2571 looking_at(buf, &i, "Game * (*) has no examiners")) {
2572 if (gameMode == IcsObserving &&
2573 atoi(star_match[0]) == ics_gamenum)
2578 ics_user_moved = FALSE;
2583 if (looking_at(buf, &i, "no longer examining game *")) {
2584 if (gameMode == IcsExamining &&
2585 atoi(star_match[0]) == ics_gamenum)
2589 ics_user_moved = FALSE;
2594 /* Advance leftover_start past any newlines we find,
2595 so only partial lines can get reparsed */
2596 if (looking_at(buf, &i, "\n")) {
2597 prevColor = curColor;
2598 if (curColor != ColorNormal) {
2599 if (oldi > next_out) {
2600 SendToPlayer(&buf[next_out], oldi - next_out);
2603 Colorize(ColorNormal, FALSE);
2604 curColor = ColorNormal;
2606 if (started == STARTED_BOARD) {
2607 started = STARTED_NONE;
2608 parse[parse_pos] = NULLCHAR;
2609 ParseBoard12(parse);
2612 /* Send premove here */
2613 if (appData.premove) {
2615 if (currentMove == 0 &&
2616 gameMode == IcsPlayingWhite &&
2617 appData.premoveWhite) {
2618 sprintf(str, "%s%s\n", ics_prefix,
2619 appData.premoveWhiteText);
2620 if (appData.debugMode)
2621 fprintf(debugFP, "Sending premove:\n");
2623 } else if (currentMove == 1 &&
2624 gameMode == IcsPlayingBlack &&
2625 appData.premoveBlack) {
2626 sprintf(str, "%s%s\n", ics_prefix,
2627 appData.premoveBlackText);
2628 if (appData.debugMode)
2629 fprintf(debugFP, "Sending premove:\n");
2631 } else if (gotPremove) {
2633 ClearPremoveHighlights();
2634 if (appData.debugMode)
2635 fprintf(debugFP, "Sending premove:\n");
2636 UserMoveEvent(premoveFromX, premoveFromY,
2637 premoveToX, premoveToY,
2642 /* Usually suppress following prompt */
2643 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
2644 if (looking_at(buf, &i, "*% ")) {
2645 savingComment = FALSE;
2649 } else if (started == STARTED_HOLDINGS) {
2651 char new_piece[MSG_SIZ];
2652 started = STARTED_NONE;
2653 parse[parse_pos] = NULLCHAR;
2654 if (appData.debugMode)
2655 fprintf(debugFP, "Parsing holdings: %s\n", parse);
2656 if (sscanf(parse, " game %d", &gamenum) == 1 &&
2657 gamenum == ics_gamenum) {
2658 if (gameInfo.variant == VariantNormal) {
2659 gameInfo.variant = VariantCrazyhouse; /*temp guess*/
2660 /* Get a move list just to see the header, which
2661 will tell us whether this is really bug or zh */
2662 if (ics_getting_history == H_FALSE) {
2663 ics_getting_history = H_REQUESTED;
2664 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
2668 new_piece[0] = NULLCHAR;
2669 sscanf(parse, "game %d white [%s black [%s <- %s",
2670 &gamenum, white_holding, black_holding,
2672 white_holding[strlen(white_holding)-1] = NULLCHAR;
2673 black_holding[strlen(black_holding)-1] = NULLCHAR;
2675 if (appData.zippyPlay && first.initDone) {
2676 ZippyHoldings(white_holding, black_holding,
2680 if (tinyLayout || smallLayout) {
2681 char wh[16], bh[16];
2682 PackHolding(wh, white_holding);
2683 PackHolding(bh, black_holding);
2684 sprintf(str, "[%s-%s] %s-%s", wh, bh,
2685 gameInfo.white, gameInfo.black);
2687 sprintf(str, "%s [%s] vs. %s [%s]",
2688 gameInfo.white, white_holding,
2689 gameInfo.black, black_holding);
2691 DrawPosition(FALSE, NULL);
2694 /* Suppress following prompt */
2695 if (looking_at(buf, &i, "*% ")) {
2696 savingComment = FALSE;
2703 i++; /* skip unparsed character and loop back */
2706 if (started != STARTED_MOVES && started != STARTED_BOARD &&
2707 started != STARTED_HOLDINGS && i > next_out) {
2708 SendToPlayer(&buf[next_out], i - next_out);
2712 leftover_len = buf_len - leftover_start;
2713 /* if buffer ends with something we couldn't parse,
2714 reparse it after appending the next read */
2716 } else if (count == 0) {
2717 RemoveInputSource(isr);
2718 DisplayFatalError("Connection closed by ICS", 0, 0);
2720 DisplayFatalError("Error reading from ICS", error, 1);
2725 /* Board style 12 looks like this:
2727 <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
2729 * The "<12> " is stripped before it gets to this routine. The two
2730 * trailing 0's (flip state and clock ticking) are later addition, and
2731 * some chess servers may not have them, or may have only the first.
2732 * Additional trailing fields may be added in the future.
2735 #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"
2737 #define RELATION_OBSERVING_PLAYED 0
2738 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
2739 #define RELATION_PLAYING_MYMOVE 1
2740 #define RELATION_PLAYING_NOTMYMOVE -1
2741 #define RELATION_EXAMINING 2
2742 #define RELATION_ISOLATED_BOARD -3
2743 #define RELATION_STARTING_POSITION -4 /* FICS only */
2746 ParseBoard12(string)
2749 GameMode newGameMode;
2750 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
2751 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
2752 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
2753 char to_play, board_chars[72];
2754 char move_str[500], str[500], elapsed_time[500];
2755 char black[32], white[32];
2757 int prevMove = currentMove;
2760 int fromX, fromY, toX, toY;
2763 fromX = fromY = toX = toY = -1;
2767 if (appData.debugMode)
2768 fprintf(debugFP, "Parsing board: %s\n", string);
2770 move_str[0] = NULLCHAR;
2771 elapsed_time[0] = NULLCHAR;
2772 n = sscanf(string, PATTERN, board_chars, &to_play, &double_push,
2773 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
2774 &gamenum, white, black, &relation, &basetime, &increment,
2775 &white_stren, &black_stren, &white_time, &black_time,
2776 &moveNum, str, elapsed_time, move_str, &ics_flip,
2780 sprintf(str, "Failed to parse board string:\n\"%s\"", string);
2781 DisplayError(str, 0);
2785 /* Convert the move number to internal form */
2786 moveNum = (moveNum - 1) * 2;
2787 if (to_play == 'B') moveNum++;
2788 if (moveNum >= MAX_MOVES) {
2789 DisplayFatalError("Game too long; increase MAX_MOVES and recompile",
2795 case RELATION_OBSERVING_PLAYED:
2796 case RELATION_OBSERVING_STATIC:
2797 if (gamenum == -1) {
2798 /* Old ICC buglet */
2799 relation = RELATION_OBSERVING_STATIC;
2801 newGameMode = IcsObserving;
2803 case RELATION_PLAYING_MYMOVE:
2804 case RELATION_PLAYING_NOTMYMOVE:
2806 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
2807 IcsPlayingWhite : IcsPlayingBlack;
2809 case RELATION_EXAMINING:
2810 newGameMode = IcsExamining;
2812 case RELATION_ISOLATED_BOARD:
2814 /* Just display this board. If user was doing something else,
2815 we will forget about it until the next board comes. */
2816 newGameMode = IcsIdle;
2818 case RELATION_STARTING_POSITION:
2819 newGameMode = gameMode;
2823 /* Modify behavior for initial board display on move listing
2826 switch (ics_getting_history) {
2830 case H_GOT_REQ_HEADER:
2831 case H_GOT_UNREQ_HEADER:
2832 /* This is the initial position of the current game */
2833 gamenum = ics_gamenum;
2834 moveNum = 0; /* old ICS bug workaround */
2835 if (to_play == 'B') {
2836 startedFromSetupPosition = TRUE;
2837 blackPlaysFirst = TRUE;
2839 if (forwardMostMove == 0) forwardMostMove = 1;
2840 if (backwardMostMove == 0) backwardMostMove = 1;
2841 if (currentMove == 0) currentMove = 1;
2843 newGameMode = gameMode;
2844 relation = RELATION_STARTING_POSITION; /* ICC needs this */
2846 case H_GOT_UNWANTED_HEADER:
2847 /* This is an initial board that we don't want */
2849 case H_GETTING_MOVES:
2850 /* Should not happen */
2851 DisplayError("Error gathering move list: extra board", 0);
2852 ics_getting_history = H_FALSE;
2856 /* Take action if this is the first board of a new game, or of a
2857 different game than is currently being displayed. */
2858 if (gamenum != ics_gamenum || newGameMode != gameMode ||
2859 relation == RELATION_ISOLATED_BOARD) {
2861 /* Forget the old game and get the history (if any) of the new one */
2862 if (gameMode != BeginningOfGame) {
2866 if (appData.autoRaiseBoard) BoardToTop();
2868 if (gamenum == -1) {
2869 newGameMode = IcsIdle;
2870 } else if (moveNum > 0 && newGameMode != IcsIdle &&
2871 appData.getMoveList) {
2872 /* Need to get game history */
2873 ics_getting_history = H_REQUESTED;
2874 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
2878 /* Initially flip the board to have black on the bottom if playing
2879 black or if the ICS flip flag is set, but let the user change
2880 it with the Flip View button. */
2881 flipView = appData.autoFlipView ?
2882 (newGameMode == IcsPlayingBlack) || ics_flip :
2885 /* Done with values from previous mode; copy in new ones */
2886 gameMode = newGameMode;
2888 ics_gamenum = gamenum;
2889 if (gamenum == gs_gamenum) {
2890 int klen = strlen(gs_kind);
2891 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
2892 sprintf(str, "ICS %s", gs_kind);
2893 gameInfo.event = StrSave(str);
2895 gameInfo.event = StrSave("ICS game");
2897 gameInfo.site = StrSave(appData.icsHost);
2898 gameInfo.date = PGNDate();
2899 gameInfo.round = StrSave("-");
2900 gameInfo.white = StrSave(white);
2901 gameInfo.black = StrSave(black);
2902 timeControl = basetime * 60 * 1000;
2904 timeIncrement = increment * 1000;
2905 movesPerSession = 0;
2906 gameInfo.timeControl = TimeControlTagValue();
2907 gameInfo.variant = StringToVariant(gameInfo.event);
2909 /* Do we have the ratings? */
2910 if (strcmp(player1Name, white) == 0 &&
2911 strcmp(player2Name, black) == 0) {
2912 if (appData.debugMode)
2913 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
2914 player1Rating, player2Rating);
2915 gameInfo.whiteRating = player1Rating;
2916 gameInfo.blackRating = player2Rating;
2917 } else if (strcmp(player2Name, white) == 0 &&
2918 strcmp(player1Name, black) == 0) {
2919 if (appData.debugMode)
2920 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
2921 player2Rating, player1Rating);
2922 gameInfo.whiteRating = player2Rating;
2923 gameInfo.blackRating = player1Rating;
2925 player1Name[0] = player2Name[0] = NULLCHAR;
2927 /* Silence shouts if requested */
2928 if (appData.quietPlay &&
2929 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
2930 SendToICS(ics_prefix);
2931 SendToICS("set shout 0\n");
2935 /* Deal with midgame name changes */
2937 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
2938 if (gameInfo.white) free(gameInfo.white);
2939 gameInfo.white = StrSave(white);
2941 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
2942 if (gameInfo.black) free(gameInfo.black);
2943 gameInfo.black = StrSave(black);
2947 /* Throw away game result if anything actually changes in examine mode */
2948 if (gameMode == IcsExamining && !newGame) {
2949 gameInfo.result = GameUnfinished;
2950 if (gameInfo.resultDetails != NULL) {
2951 free(gameInfo.resultDetails);
2952 gameInfo.resultDetails = NULL;
2956 /* In pausing && IcsExamining mode, we ignore boards coming
2957 in if they are in a different variation than we are. */
2958 if (pauseExamInvalid) return;
2959 if (pausing && gameMode == IcsExamining) {
2960 if (moveNum <= pauseExamForwardMostMove) {
2961 pauseExamInvalid = TRUE;
2962 forwardMostMove = pauseExamForwardMostMove;
2967 /* Parse the board */
2968 for (k = 0; k < 8; k++)
2969 for (j = 0; j < 8; j++)
2970 board[k][j] = CharToPiece(board_chars[(7-k)*9 + j]);
2971 CopyBoard(boards[moveNum], board);
2973 startedFromSetupPosition =
2974 !CompareBoards(board, initialPosition);
2977 if (ics_getting_history == H_GOT_REQ_HEADER ||
2978 ics_getting_history == H_GOT_UNREQ_HEADER) {
2979 /* This was an initial position from a move list, not
2980 the current position */
2984 /* Update currentMove and known move number limits */
2985 newMove = newGame || moveNum > forwardMostMove;
2987 forwardMostMove = backwardMostMove = currentMove = moveNum;
2988 if (gameMode == IcsExamining && moveNum == 0) {
2989 /* Workaround for ICS limitation: we are not told the wild
2990 type when starting to examine a game. But if we ask for
2991 the move list, the move list header will tell us */
2992 ics_getting_history = H_REQUESTED;
2993 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
2996 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
2997 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
2998 forwardMostMove = moveNum;
2999 if (!pausing || currentMove > forwardMostMove)
3000 currentMove = forwardMostMove;
3002 /* New part of history that is not contiguous with old part */
3003 if (pausing && gameMode == IcsExamining) {
3004 pauseExamInvalid = TRUE;
3005 forwardMostMove = pauseExamForwardMostMove;
3008 forwardMostMove = backwardMostMove = currentMove = moveNum;
3009 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3010 ics_getting_history = H_REQUESTED;
3011 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3016 /* Update the clocks */
3017 if (strchr(elapsed_time, '.')) {
3019 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3020 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3022 /* Time is in seconds */
3023 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3024 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3029 if (appData.zippyPlay && newGame &&
3030 gameMode != IcsObserving && gameMode != IcsIdle &&
3031 gameMode != IcsExamining)
3032 ZippyFirstBoard(moveNum, basetime, increment);
3035 /* Put the move on the move list, first converting
3036 to canonical algebraic form. */
3038 if (moveNum <= backwardMostMove) {
3039 /* We don't know what the board looked like before
3041 strcpy(parseList[moveNum - 1], move_str);
3042 strcat(parseList[moveNum - 1], " ");
3043 strcat(parseList[moveNum - 1], elapsed_time);
3044 moveList[moveNum - 1][0] = NULLCHAR;
3045 } else if (ParseOneMove(move_str, moveNum - 1, &moveType,
3046 &fromX, &fromY, &toX, &toY, &promoChar)) {
3047 (void) CoordsToAlgebraic(boards[moveNum - 1],
3048 PosFlags(moveNum - 1), EP_UNKNOWN,
3049 fromY, fromX, toY, toX, promoChar,
3050 parseList[moveNum-1]);
3051 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN)){
3057 strcat(parseList[moveNum - 1], "+");
3060 strcat(parseList[moveNum - 1], "#");
3063 strcat(parseList[moveNum - 1], " ");
3064 strcat(parseList[moveNum - 1], elapsed_time);
3065 /* currentMoveString is set as a side-effect of ParseOneMove */
3066 strcpy(moveList[moveNum - 1], currentMoveString);
3067 strcat(moveList[moveNum - 1], "\n");
3068 } else if (strcmp(move_str, "none") == 0) {
3069 /* Again, we don't know what the board looked like;
3070 this is really the start of the game. */
3071 parseList[moveNum - 1][0] = NULLCHAR;
3072 moveList[moveNum - 1][0] = NULLCHAR;
3073 backwardMostMove = moveNum;
3074 startedFromSetupPosition = TRUE;
3075 fromX = fromY = toX = toY = -1;
3077 /* Move from ICS was illegal!? Punt. */
3079 if (appData.testLegality && appData.debugMode) {
3080 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
3081 DisplayError(str, 0);
3084 strcpy(parseList[moveNum - 1], move_str);
3085 strcat(parseList[moveNum - 1], " ");
3086 strcat(parseList[moveNum - 1], elapsed_time);
3087 moveList[moveNum - 1][0] = NULLCHAR;
3088 fromX = fromY = toX = toY = -1;
3092 /* Send move to chess program (BEFORE animating it). */
3093 if (appData.zippyPlay && !newGame && newMove &&
3094 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3096 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3097 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3098 if (moveList[moveNum - 1][0] == NULLCHAR) {
3099 sprintf(str, "Couldn't parse move \"%s\" from ICS",
3101 DisplayError(str, 0);
3103 if (first.sendTime) {
3104 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3106 SendMoveToProgram(moveNum - 1, &first);
3109 if (first.useColors) {
3110 SendToProgram(gameMode == IcsPlayingWhite ?
3112 "black\ngo\n", &first);
3114 SendToProgram("go\n", &first);
3116 first.maybeThinking = TRUE;
3119 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3120 if (moveList[moveNum - 1][0] == NULLCHAR) {
3121 sprintf(str, "Couldn't parse move \"%s\" from ICS", move_str);
3122 DisplayError(str, 0);
3124 SendMoveToProgram(moveNum - 1, &first);
3131 if (moveNum > 0 && !gotPremove) {
3132 /* If move comes from a remote source, animate it. If it
3133 isn't remote, it will have already been animated. */
3134 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3135 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3137 if (!pausing && appData.highlightLastMove) {
3138 SetHighlights(fromX, fromY, toX, toY);
3142 /* Start the clocks */
3143 whiteFlag = blackFlag = FALSE;
3144 appData.clockMode = !(basetime == 0 && increment == 0);
3146 ics_clock_paused = TRUE;
3148 } else if (ticking == 1) {
3149 ics_clock_paused = FALSE;
3151 if (gameMode == IcsIdle ||
3152 relation == RELATION_OBSERVING_STATIC ||
3153 relation == RELATION_EXAMINING ||
3155 DisplayBothClocks();
3159 /* Display opponents and material strengths */
3160 if (gameInfo.variant != VariantBughouse &&
3161 gameInfo.variant != VariantCrazyhouse) {
3162 if (tinyLayout || smallLayout) {
3163 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3164 gameInfo.white, white_stren, gameInfo.black, black_stren,
3165 basetime, increment);
3167 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3168 gameInfo.white, white_stren, gameInfo.black, black_stren,
3169 basetime, increment);
3175 /* Display the board */
3178 if (appData.premove)
3180 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3181 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3182 ClearPremoveHighlights();
3184 DrawPosition(FALSE, boards[currentMove]);
3185 DisplayMove(moveNum - 1);
3186 if (appData.ringBellAfterMoves && !ics_user_moved)
3190 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3197 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3198 ics_getting_history = H_REQUESTED;
3199 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3205 AnalysisPeriodicEvent(force)
3208 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3209 && !force) || !appData.periodicUpdates)
3212 /* Send . command to Crafty to collect stats */
3213 SendToProgram(".\n", &first);
3215 /* Don't send another until we get a response (this makes
3216 us stop sending to old Crafty's which don't understand
3217 the "." command (sending illegal cmds resets node count & time,
3218 which looks bad)) */
3219 programStats.ok_to_send = 0;
3223 SendMoveToProgram(moveNum, cps)
3225 ChessProgramState *cps;
3228 if (cps->useUsermove) {
3229 SendToProgram("usermove ", cps);
3233 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
3234 int len = space - parseList[moveNum];
3235 memcpy(buf, parseList[moveNum], len);
3237 buf[len] = NULLCHAR;
3239 sprintf(buf, "%s\n", parseList[moveNum]);
3241 SendToProgram(buf, cps);
3243 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
3244 * the engine. It would be nice to have a better way to identify castle
3246 if(gameInfo.variant == VariantFischeRandom && cps->useOOCastle) {
3247 int fromX = moveList[moveNum][0] - 'a';
3248 int fromY = moveList[moveNum][1] - '1';
3249 int toX = moveList[moveNum][2] - 'a';
3250 int toY = moveList[moveNum][3] - '1';
3251 if((boards[currentMove][fromY][fromX] == WhiteKing
3252 && boards[currentMove][toY][toX] == WhiteRook)
3253 || (boards[currentMove][fromY][fromX] == BlackKing
3254 && boards[currentMove][toY][toX] == BlackRook)) {
3255 if(toX > fromX) SendToProgram("O-O\n", cps);
3256 else SendToProgram("O-O-O\n", cps);
3258 else SendToProgram(moveList[moveNum], cps);
3260 else SendToProgram(moveList[moveNum], cps);
3261 /* End of additions by Tord */
3266 SendMoveToICS(moveType, fromX, fromY, toX, toY)
3268 int fromX, fromY, toX, toY;
3270 char user_move[MSG_SIZ];
3274 sprintf(user_move, "say Internal error; bad moveType %d (%d,%d-%d,%d)",
3275 (int)moveType, fromX, fromY, toX, toY);
3276 DisplayError(user_move + strlen("say "), 0);
3278 case WhiteKingSideCastle:
3279 case BlackKingSideCastle:
3280 case WhiteQueenSideCastleWild:
3281 case BlackQueenSideCastleWild:
3283 case WhiteHSideCastleFR:
3284 case BlackHSideCastleFR:
3286 sprintf(user_move, "o-o\n");
3288 case WhiteQueenSideCastle:
3289 case BlackQueenSideCastle:
3290 case WhiteKingSideCastleWild:
3291 case BlackKingSideCastleWild:
3293 case WhiteASideCastleFR:
3294 case BlackASideCastleFR:
3296 sprintf(user_move, "o-o-o\n");
3298 case WhitePromotionQueen:
3299 case BlackPromotionQueen:
3300 case WhitePromotionRook:
3301 case BlackPromotionRook:
3302 case WhitePromotionBishop:
3303 case BlackPromotionBishop:
3304 case WhitePromotionKnight:
3305 case BlackPromotionKnight:
3306 case WhitePromotionKing:
3307 case BlackPromotionKing:
3308 sprintf(user_move, "%c%c%c%c=%c\n",
3309 'a' + fromX, '1' + fromY, 'a' + toX, '1' + toY,
3310 PieceToChar(PromoPiece(moveType)));
3314 sprintf(user_move, "%c@%c%c\n",
3315 ToUpper(PieceToChar((ChessSquare) fromX)),
3316 'a' + toX, '1' + toY);
3319 case WhiteCapturesEnPassant:
3320 case BlackCapturesEnPassant:
3321 case IllegalMove: /* could be a variant we don't quite understand */
3322 sprintf(user_move, "%c%c%c%c\n",
3323 'a' + fromX, '1' + fromY, 'a' + toX, '1' + toY);
3326 SendToICS(user_move);
3330 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
3335 if (rf == DROP_RANK) {
3336 sprintf(move, "%c@%c%c\n",
3337 ToUpper(PieceToChar((ChessSquare) ff)), 'a' + ft, '1' + rt);
3339 if (promoChar == 'x' || promoChar == NULLCHAR) {
3340 sprintf(move, "%c%c%c%c\n",
3341 'a' + ff, '1' + rf, 'a' + ft, '1' + rt);
3343 sprintf(move, "%c%c%c%c%c\n",
3344 'a' + ff, '1' + rf, 'a' + ft, '1' + rt, promoChar);
3350 ProcessICSInitScript(f)
3355 while (fgets(buf, MSG_SIZ, f)) {
3356 SendToICSDelayed(buf,(long)appData.msLoginDelay);
3363 /* Parser for moves from gnuchess, ICS, or user typein box */
3365 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
3368 ChessMove *moveType;
3369 int *fromX, *fromY, *toX, *toY;
3372 *moveType = yylexstr(moveNum, move);
3373 switch (*moveType) {
3374 case WhitePromotionQueen:
3375 case BlackPromotionQueen:
3376 case WhitePromotionRook:
3377 case BlackPromotionRook:
3378 case WhitePromotionBishop:
3379 case BlackPromotionBishop:
3380 case WhitePromotionKnight:
3381 case BlackPromotionKnight:
3382 case WhitePromotionKing:
3383 case BlackPromotionKing:
3385 case WhiteCapturesEnPassant:
3386 case BlackCapturesEnPassant:
3387 case WhiteKingSideCastle:
3388 case WhiteQueenSideCastle:
3389 case BlackKingSideCastle:
3390 case BlackQueenSideCastle:
3391 case WhiteKingSideCastleWild:
3392 case WhiteQueenSideCastleWild:
3393 case BlackKingSideCastleWild:
3394 case BlackQueenSideCastleWild:
3395 /* Code added by Tord: */
3396 case WhiteHSideCastleFR:
3397 case WhiteASideCastleFR:
3398 case BlackHSideCastleFR:
3399 case BlackASideCastleFR:
3400 /* End of code added by Tord */
3401 case IllegalMove: /* bug or odd chess variant */
3402 *fromX = currentMoveString[0] - 'a';
3403 *fromY = currentMoveString[1] - '1';
3404 *toX = currentMoveString[2] - 'a';
3405 *toY = currentMoveString[3] - '1';
3406 *promoChar = currentMoveString[4];
3407 if (*fromX < 0 || *fromX > 7 || *fromY < 0 || *fromY > 7 ||
3408 *toX < 0 || *toX > 7 || *toY < 0 || *toY > 7) {
3409 *fromX = *fromY = *toX = *toY = 0;
3412 if (appData.testLegality) {
3413 return (*moveType != IllegalMove);
3415 return !(fromX == fromY && toX == toY);
3420 *fromX = *moveType == WhiteDrop ?
3421 (int) CharToPiece(ToUpper(currentMoveString[0])) :
3422 (int) CharToPiece(ToLower(currentMoveString[0]));
3424 *toX = currentMoveString[2] - 'a';
3425 *toY = currentMoveString[3] - '1';
3426 *promoChar = NULLCHAR;
3430 case ImpossibleMove:
3431 case (ChessMove) 0: /* end of file */
3441 *fromX = *fromY = *toX = *toY = 0;
3442 *promoChar = NULLCHAR;
3447 /* [AS] FRC game initialization */
3448 static int FindEmptySquare( Board board, int n )
3453 while( board[0][i] != EmptySquare ) i++;
3463 static void ShuffleFRC( Board board )
3469 for( i=0; i<8; i++ ) {
3470 board[0][i] = EmptySquare;
3473 board[0][(rand() % 4)*2 ] = WhiteBishop; /* On dark square */
3474 board[0][(rand() % 4)*2+1] = WhiteBishop; /* On lite square */
3475 board[0][FindEmptySquare(board, rand() % 6)] = WhiteQueen;
3476 board[0][FindEmptySquare(board, rand() % 5)] = WhiteKnight;
3477 board[0][FindEmptySquare(board, rand() % 4)] = WhiteKnight;
3478 board[0][FindEmptySquare(board, 0)] = WhiteRook;
3479 board[0][FindEmptySquare(board, 0)] = WhiteKing;
3480 board[0][FindEmptySquare(board, 0)] = WhiteRook;
3482 for( i=0; i<8; i++ ) {
3483 board[7][i] = board[0][i] + BlackPawn - WhitePawn;
3487 static unsigned char FRC_KnightTable[10] = {
3488 0x00, 0x01, 0x02, 0x03, 0x11, 0x12, 0x13, 0x22, 0x23, 0x33
3491 static void SetupFRC( Board board, int pos_index )
3494 unsigned char knights;
3496 /* Bring the position index into a safe range (just in case...) */
3497 if( pos_index < 0 ) pos_index = 0;
3501 /* Clear the board */
3502 for( i=0; i<8; i++ ) {
3503 board[0][i] = EmptySquare;
3506 /* Place bishops and queen */
3507 board[0][ (pos_index % 4)*2 + 1 ] = WhiteBishop; /* On lite square */
3510 board[0][ (pos_index % 4)*2 ] = WhiteBishop; /* On dark square */
3513 board[0][ FindEmptySquare(board, pos_index % 6) ] = WhiteQueen;
3517 knights = FRC_KnightTable[ pos_index ];
3519 board[0][ FindEmptySquare(board, knights / 16) ] = WhiteKnight;
3520 board[0][ FindEmptySquare(board, knights % 16) ] = WhiteKnight;
3522 /* Place rooks and king */
3523 board[0][ FindEmptySquare(board, 0) ] = WhiteRook;
3524 board[0][ FindEmptySquare(board, 0) ] = WhiteKing;
3525 board[0][ FindEmptySquare(board, 0) ] = WhiteRook;
3527 /* Mirror piece placement for black */
3528 for( i=0; i<8; i++ ) {
3529 board[7][i] = board[0][i] + BlackPawn - WhitePawn;
3534 InitPosition(redraw)
3537 currentMove = forwardMostMove = backwardMostMove = 0;
3539 /* [AS] Initialize pv info list */
3543 for( i=0; i<MAX_MOVES; i++ ) {
3544 pvInfoList[i].depth = 0;
3548 switch (gameInfo.variant) {
3550 CopyBoard(boards[0], initialPosition);
3552 case VariantTwoKings:
3553 CopyBoard(boards[0], twoKingsPosition);
3554 startedFromSetupPosition = TRUE;
3556 case VariantWildCastle:
3557 CopyBoard(boards[0], initialPosition);
3558 /* !!?shuffle with kings guaranteed to be on d or e file */
3560 case VariantNoCastle:
3561 CopyBoard(boards[0], initialPosition);
3562 /* !!?unconstrained back-rank shuffle */
3564 case VariantFischeRandom:
3565 CopyBoard(boards[0], initialPosition);
3566 if( appData.defaultFrcPosition < 0 ) {
3567 ShuffleFRC( boards[0] );
3570 SetupFRC( boards[0], appData.defaultFrcPosition );
3576 DrawPosition(FALSE, boards[currentMove]);
3580 SendBoard(cps, moveNum)
3581 ChessProgramState *cps;
3584 char message[MSG_SIZ];
3586 if (cps->useSetboard) {
3587 char* fen = PositionToFEN(moveNum, cps->useFEN960);
3588 sprintf(message, "setboard %s\n", fen);
3589 SendToProgram(message, cps);
3595 /* Kludge to set black to move, avoiding the troublesome and now
3596 * deprecated "black" command.
3598 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
3600 SendToProgram("edit\n", cps);
3601 SendToProgram("#\n", cps);
3602 for (i = BOARD_SIZE - 1; i >= 0; i--) {
3603 bp = &boards[moveNum][i][0];
3604 for (j = 0; j < BOARD_SIZE; j++, bp++) {
3605 if ((int) *bp < (int) BlackPawn) {
3606 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
3608 SendToProgram(message, cps);
3613 SendToProgram("c\n", cps);
3614 for (i = BOARD_SIZE - 1; i >= 0; i--) {
3615 bp = &boards[moveNum][i][0];
3616 for (j = 0; j < BOARD_SIZE; j++, bp++) {
3617 if (((int) *bp != (int) EmptySquare)
3618 && ((int) *bp >= (int) BlackPawn)) {
3619 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
3621 SendToProgram(message, cps);
3626 SendToProgram(".\n", cps);
3631 IsPromotion(fromX, fromY, toX, toY)
3632 int fromX, fromY, toX, toY;
3634 return gameMode != EditPosition &&
3635 fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0 &&
3636 ((boards[currentMove][fromY][fromX] == WhitePawn && toY == 7) ||
3637 (boards[currentMove][fromY][fromX] == BlackPawn && toY == 0));
3642 PieceForSquare (x, y)
3646 if (x < 0 || x >= BOARD_SIZE || y < 0 || y >= BOARD_SIZE)
3649 return boards[currentMove][y][x];
3653 OKToStartUserMove(x, y)
3656 ChessSquare from_piece;
3659 if (matchMode) return FALSE;
3660 if (gameMode == EditPosition) return TRUE;
3662 if (x >= 0 && y >= 0)
3663 from_piece = boards[currentMove][y][x];
3665 from_piece = EmptySquare;
3667 if (from_piece == EmptySquare) return FALSE;
3669 white_piece = (int)from_piece >= (int)WhitePawn &&
3670 (int)from_piece <= (int)WhiteKing;
3673 case PlayFromGameFile:
3675 case TwoMachinesPlay:
3683 case MachinePlaysWhite:
3684 case IcsPlayingBlack:
3685 if (appData.zippyPlay) return FALSE;
3687 DisplayMoveError("You are playing Black");
3692 case MachinePlaysBlack:
3693 case IcsPlayingWhite:
3694 if (appData.zippyPlay) return FALSE;
3696 DisplayMoveError("You are playing White");
3702 if (!white_piece && WhiteOnMove(currentMove)) {
3703 DisplayMoveError("It is White's turn");
3706 if (white_piece && !WhiteOnMove(currentMove)) {
3707 DisplayMoveError("It is Black's turn");
3710 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
3711 /* Editing correspondence game history */
3712 /* Could disallow this or prompt for confirmation */
3715 if (currentMove < forwardMostMove) {
3716 /* Discarding moves */
3717 /* Could prompt for confirmation here,
3718 but I don't think that's such a good idea */
3719 forwardMostMove = currentMove;
3723 case BeginningOfGame:
3724 if (appData.icsActive) return FALSE;
3725 if (!appData.noChessProgram) {
3727 DisplayMoveError("You are playing White");
3734 if (!white_piece && WhiteOnMove(currentMove)) {
3735 DisplayMoveError("It is White's turn");
3738 if (white_piece && !WhiteOnMove(currentMove)) {
3739 DisplayMoveError("It is Black's turn");
3748 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
3749 && gameMode != AnalyzeFile && gameMode != Training) {
3750 DisplayMoveError("Displayed position is not current");
3756 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
3757 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
3758 int lastLoadGameUseList = FALSE;
3759 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
3760 ChessMove lastLoadGameStart = (ChessMove) 0;
3764 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
3765 int fromX, fromY, toX, toY;
3770 if (fromX < 0 || fromY < 0) return;
3771 if ((fromX == toX) && (fromY == toY)) {
3775 /* Check if the user is playing in turn. This is complicated because we
3776 let the user "pick up" a piece before it is his turn. So the piece he
3777 tried to pick up may have been captured by the time he puts it down!
3778 Therefore we use the color the user is supposed to be playing in this
3779 test, not the color of the piece that is currently on the starting
3780 square---except in EditGame mode, where the user is playing both
3781 sides; fortunately there the capture race can't happen. (It can
3782 now happen in IcsExamining mode, but that's just too bad. The user
3783 will get a somewhat confusing message in that case.)
3787 case PlayFromGameFile:
3789 case TwoMachinesPlay:
3793 /* We switched into a game mode where moves are not accepted,
3794 perhaps while the mouse button was down. */
3797 case MachinePlaysWhite:
3798 /* User is moving for Black */
3799 if (WhiteOnMove(currentMove)) {
3800 DisplayMoveError("It is White's turn");
3805 case MachinePlaysBlack:
3806 /* User is moving for White */
3807 if (!WhiteOnMove(currentMove)) {
3808 DisplayMoveError("It is Black's turn");
3815 case BeginningOfGame:
3818 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
3819 (int) boards[currentMove][fromY][fromX] <= (int) BlackKing) {
3820 /* User is moving for Black */
3821 if (WhiteOnMove(currentMove)) {
3822 DisplayMoveError("It is White's turn");
3826 /* User is moving for White */
3827 if (!WhiteOnMove(currentMove)) {
3828 DisplayMoveError("It is Black's turn");
3834 case IcsPlayingBlack:
3835 /* User is moving for Black */
3836 if (WhiteOnMove(currentMove)) {
3837 if (!appData.premove) {
3838 DisplayMoveError("It is White's turn");
3839 } else if (toX >= 0 && toY >= 0) {
3842 premoveFromX = fromX;
3843 premoveFromY = fromY;
3844 premovePromoChar = promoChar;
3846 if (appData.debugMode)
3847 fprintf(debugFP, "Got premove: fromX %d,"
3848 "fromY %d, toX %d, toY %d\n",
3849 fromX, fromY, toX, toY);
3855 case IcsPlayingWhite:
3856 /* User is moving for White */
3857 if (!WhiteOnMove(currentMove)) {
3858 if (!appData.premove) {
3859 DisplayMoveError("It is Black's turn");
3860 } else if (toX >= 0 && toY >= 0) {
3863 premoveFromX = fromX;
3864 premoveFromY = fromY;
3865 premovePromoChar = promoChar;
3867 if (appData.debugMode)
3868 fprintf(debugFP, "Got premove: fromX %d,"
3869 "fromY %d, toX %d, toY %d\n",
3870 fromX, fromY, toX, toY);
3880 if (toX == -2 || toY == -2) {
3881 boards[0][fromY][fromX] = EmptySquare;
3882 DrawPosition(FALSE, boards[currentMove]);
3883 } else if (toX >= 0 && toY >= 0) {
3884 boards[0][toY][toX] = boards[0][fromY][fromX];
3885 boards[0][fromY][fromX] = EmptySquare;
3886 DrawPosition(FALSE, boards[currentMove]);
3891 if (toX < 0 || toY < 0) return;
3892 userOfferedDraw = FALSE;
3894 if (appData.testLegality) {
3895 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
3896 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar);
3897 if (moveType == IllegalMove || moveType == ImpossibleMove) {
3898 DisplayMoveError("Illegal move");
3902 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
3905 if (gameMode == Training) {
3906 /* compare the move played on the board to the next move in the
3907 * game. If they match, display the move and the opponent's response.
3908 * If they don't match, display an error message.
3912 CopyBoard(testBoard, boards[currentMove]);
3913 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
3915 if (CompareBoards(testBoard, boards[currentMove+1])) {
3916 ForwardInner(currentMove+1);
3918 /* Autoplay the opponent's response.
3919 * if appData.animate was TRUE when Training mode was entered,
3920 * the response will be animated.
3922 saveAnimate = appData.animate;
3923 appData.animate = animateTraining;
3924 ForwardInner(currentMove+1);
3925 appData.animate = saveAnimate;
3927 /* check for the end of the game */
3928 if (currentMove >= forwardMostMove) {
3929 gameMode = PlayFromGameFile;
3931 SetTrainingModeOff();
3932 DisplayInformation("End of game");
3935 DisplayError("Incorrect move", 0);
3940 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
3943 /* Common tail of UserMoveEvent and DropMenuEvent */
3945 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
3947 int fromX, fromY, toX, toY;
3948 /*char*/int promoChar;
3950 /* Ok, now we know that the move is good, so we can kill
3951 the previous line in Analysis Mode */
3952 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
3953 forwardMostMove = currentMove;
3956 /* If we need the chess program but it's dead, restart it */
3957 ResurrectChessProgram();
3959 /* A user move restarts a paused game*/
3963 thinkOutput[0] = NULLCHAR;
3965 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
3967 if (gameMode == BeginningOfGame) {
3968 if (appData.noChessProgram) {
3969 gameMode = EditGame;
3973 gameMode = MachinePlaysBlack;
3975 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
3977 if (first.sendName) {
3978 sprintf(buf, "name %s\n", gameInfo.white);
3979 SendToProgram(buf, &first);
3985 /* Relay move to ICS or chess engine */
3986 if (appData.icsActive) {
3987 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
3988 gameMode == IcsExamining) {
3989 SendMoveToICS(moveType, fromX, fromY, toX, toY);
3993 if (first.sendTime && (gameMode == BeginningOfGame ||
3994 gameMode == MachinePlaysWhite ||
3995 gameMode == MachinePlaysBlack)) {
3996 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
3998 SendMoveToProgram(forwardMostMove-1, &first);
3999 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
4000 first.maybeThinking = TRUE;
4002 if (currentMove == cmailOldMove + 1) {
4003 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
4007 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
4011 switch (MateTest(boards[currentMove], PosFlags(currentMove),
4017 if (WhiteOnMove(currentMove)) {
4018 GameEnds(BlackWins, "Black mates", GE_PLAYER);
4020 GameEnds(WhiteWins, "White mates", GE_PLAYER);
4024 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
4029 case MachinePlaysBlack:
4030 case MachinePlaysWhite:
4031 /* disable certain menu options while machine is thinking */
4032 SetMachineThinkingEnables();
4041 HandleMachineMove(message, cps)
4043 ChessProgramState *cps;
4045 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
4046 char realname[MSG_SIZ];
4047 int fromX, fromY, toX, toY;
4054 * Kludge to ignore BEL characters
4056 while (*message == '\007') message++;
4059 * Look for book output
4061 if (cps == &first && bookRequested) {
4062 if (message[0] == '\t' || message[0] == ' ') {
4063 /* Part of the book output is here; append it */
4064 strcat(bookOutput, message);
4065 strcat(bookOutput, " \n");
4067 } else if (bookOutput[0] != NULLCHAR) {
4068 /* All of book output has arrived; display it */
4069 char *p = bookOutput;
4070 while (*p != NULLCHAR) {
4071 if (*p == '\t') *p = ' ';
4074 DisplayInformation(bookOutput);
4075 bookRequested = FALSE;
4076 /* Fall through to parse the current output */
4081 * Look for machine move.
4083 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 &&
4084 strcmp(buf2, "...") == 0) ||
4085 (sscanf(message, "%s %s", buf1, machineMove) == 2 &&
4086 strcmp(buf1, "move") == 0)) {
4088 /* This method is only useful on engines that support ping */
4089 if (cps->lastPing != cps->lastPong) {
4090 if (gameMode == BeginningOfGame) {
4091 /* Extra move from before last new; ignore */
4092 if (appData.debugMode) {
4093 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
4096 if (appData.debugMode) {
4097 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
4098 cps->which, gameMode);
4100 SendToProgram("undo\n", cps);
4106 case BeginningOfGame:
4107 /* Extra move from before last reset; ignore */
4108 if (appData.debugMode) {
4109 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
4116 /* Extra move after we tried to stop. The mode test is
4117 not a reliable way of detecting this problem, but it's
4118 the best we can do on engines that don't support ping.
4120 if (appData.debugMode) {
4121 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
4122 cps->which, gameMode);
4124 SendToProgram("undo\n", cps);
4127 case MachinePlaysWhite:
4128 case IcsPlayingWhite:
4129 machineWhite = TRUE;
4132 case MachinePlaysBlack:
4133 case IcsPlayingBlack:
4134 machineWhite = FALSE;
4137 case TwoMachinesPlay:
4138 machineWhite = (cps->twoMachinesColor[0] == 'w');
4141 if (WhiteOnMove(forwardMostMove) != machineWhite) {
4142 if (appData.debugMode) {
4144 "Ignoring move out of turn by %s, gameMode %d"
4145 ", forwardMost %d\n",
4146 cps->which, gameMode, forwardMostMove);
4151 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
4152 &fromX, &fromY, &toX, &toY, &promoChar)) {
4153 /* Machine move could not be parsed; ignore it. */
4154 sprintf(buf1, "Illegal move \"%s\" from %s machine",
4155 machineMove, cps->which);
4156 DisplayError(buf1, 0);
4157 if (gameMode == TwoMachinesPlay) {
4158 GameEnds(machineWhite ? BlackWins : WhiteWins,
4159 "Forfeit due to illegal move", GE_XBOARD);
4164 hintRequested = FALSE;
4165 lastHint[0] = NULLCHAR;
4166 bookRequested = FALSE;
4167 /* Program may be pondering now */
4168 cps->maybeThinking = TRUE;
4169 if (cps->sendTime == 2) cps->sendTime = 1;
4170 if (cps->offeredDraw) cps->offeredDraw--;
4173 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
4175 SendMoveToICS(moveType, fromX, fromY, toX, toY);
4179 /* currentMoveString is set as a side-effect of ParseOneMove */
4180 strcpy(machineMove, currentMoveString);
4181 strcat(machineMove, "\n");
4182 strcpy(moveList[forwardMostMove], machineMove);
4184 /* [AS] Save move info and clear stats for next move */
4185 pvInfoList[ forwardMostMove ].score = programStats.score;
4186 pvInfoList[ forwardMostMove ].depth = programStats.depth;
4187 ClearProgramStats();
4188 thinkOutput[0] = NULLCHAR;
4189 hiddenThinkOutputState = 0;
4191 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
4193 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
4194 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
4197 while( count < adjudicateLossPlies ) {
4198 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
4201 score = -score; /* Flip score for winning side */
4204 if( score > adjudicateLossThreshold ) {
4211 if( count >= adjudicateLossPlies ) {
4212 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
4214 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
4215 "Xboard adjudication",
4222 if (gameMode == TwoMachinesPlay) {
4223 if (cps->other->sendTime) {
4224 SendTimeRemaining(cps->other,
4225 cps->other->twoMachinesColor[0] == 'w');
4227 SendMoveToProgram(forwardMostMove-1, cps->other);
4230 if (cps->other->useColors) {
4231 SendToProgram(cps->other->twoMachinesColor, cps->other);
4233 SendToProgram("go\n", cps->other);
4235 cps->other->maybeThinking = TRUE;
4238 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
4240 if (!pausing && appData.ringBellAfterMoves) {
4245 * Reenable menu items that were disabled while
4246 * machine was thinking
4248 if (gameMode != TwoMachinesPlay)
4249 SetUserThinkingEnables();
4254 /* Set special modes for chess engines. Later something general
4255 * could be added here; for now there is just one kludge feature,
4256 * needed because Crafty 15.10 and earlier don't ignore SIGINT
4257 * when "xboard" is given as an interactive command.
4259 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
4260 cps->useSigint = FALSE;
4261 cps->useSigterm = FALSE;
4265 * Look for communication commands
4267 if (!strncmp(message, "telluser ", 9)) {
4268 DisplayNote(message + 9);
4271 if (!strncmp(message, "tellusererror ", 14)) {
4272 DisplayError(message + 14, 0);
4275 if (!strncmp(message, "tellopponent ", 13)) {
4276 if (appData.icsActive) {
4278 sprintf(buf1, "%ssay %s\n", ics_prefix, message + 13);
4282 DisplayNote(message + 13);
4286 if (!strncmp(message, "tellothers ", 11)) {
4287 if (appData.icsActive) {
4289 sprintf(buf1, "%swhisper %s\n", ics_prefix, message + 11);
4295 if (!strncmp(message, "tellall ", 8)) {
4296 if (appData.icsActive) {
4298 sprintf(buf1, "%skibitz %s\n", ics_prefix, message + 8);
4302 DisplayNote(message + 8);
4306 if (strncmp(message, "warning", 7) == 0) {
4307 /* Undocumented feature, use tellusererror in new code */
4308 DisplayError(message, 0);
4311 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
4312 strcpy(realname, cps->tidy);
4313 strcat(realname, " query");
4314 AskQuestion(realname, buf2, buf1, cps->pr);
4317 /* Commands from the engine directly to ICS. We don't allow these to be
4318 * sent until we are logged on. Crafty kibitzes have been known to
4319 * interfere with the login process.
4322 if (!strncmp(message, "tellics ", 8)) {
4323 SendToICS(message + 8);
4327 if (!strncmp(message, "tellicsnoalias ", 15)) {
4328 SendToICS(ics_prefix);
4329 SendToICS(message + 15);
4333 /* The following are for backward compatibility only */
4334 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
4335 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
4336 SendToICS(ics_prefix);
4342 if (strncmp(message, "feature ", 8) == 0) {
4343 ParseFeatures(message+8, cps);
4345 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
4349 * If the move is illegal, cancel it and redraw the board.
4350 * Also deal with other error cases. Matching is rather loose
4351 * here to accommodate engines written before the spec.
4353 if (strncmp(message + 1, "llegal move", 11) == 0 ||
4354 strncmp(message, "Error", 5) == 0) {
4355 if (StrStr(message, "name") ||
4356 StrStr(message, "rating") || StrStr(message, "?") ||
4357 StrStr(message, "result") || StrStr(message, "board") ||
4358 StrStr(message, "bk") || StrStr(message, "computer") ||
4359 StrStr(message, "variant") || StrStr(message, "hint") ||
4360 StrStr(message, "random") || StrStr(message, "depth") ||
4361 StrStr(message, "accepted")) {
4364 if (StrStr(message, "protover")) {
4365 /* Program is responding to input, so it's apparently done
4366 initializing, and this error message indicates it is
4367 protocol version 1. So we don't need to wait any longer
4368 for it to initialize and send feature commands. */
4369 FeatureDone(cps, 1);
4370 cps->protocolVersion = 1;
4373 cps->maybeThinking = FALSE;
4375 if (StrStr(message, "draw")) {
4376 /* Program doesn't have "draw" command */
4377 cps->sendDrawOffers = 0;
4380 if (cps->sendTime != 1 &&
4381 (StrStr(message, "time") || StrStr(message, "otim"))) {
4382 /* Program apparently doesn't have "time" or "otim" command */
4386 if (StrStr(message, "analyze")) {
4387 cps->analysisSupport = FALSE;
4388 cps->analyzing = FALSE;
4390 sprintf(buf2, "%s does not support analysis", cps->tidy);
4391 DisplayError(buf2, 0);
4394 if (StrStr(message, "(no matching move)st")) {
4395 /* Special kludge for GNU Chess 4 only */
4396 cps->stKludge = TRUE;
4397 SendTimeControl(cps, movesPerSession, timeControl,
4398 timeIncrement, appData.searchDepth,
4402 if (StrStr(message, "(no matching move)sd")) {
4403 /* Special kludge for GNU Chess 4 only */
4404 cps->sdKludge = TRUE;
4405 SendTimeControl(cps, movesPerSession, timeControl,
4406 timeIncrement, appData.searchDepth,
4410 if (!StrStr(message, "llegal")) return;
4411 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
4412 gameMode == IcsIdle) return;
4413 if (forwardMostMove <= backwardMostMove) return;
4415 /* Following removed: it caused a bug where a real illegal move
4416 message in analyze mored would be ignored. */
4417 if (cps == &first && programStats.ok_to_send == 0) {
4418 /* Bogus message from Crafty responding to "." This filtering
4419 can miss some of the bad messages, but fortunately the bug
4420 is fixed in current Crafty versions, so it doesn't matter. */
4424 if (pausing) PauseEvent();
4425 if (gameMode == PlayFromGameFile) {
4426 /* Stop reading this game file */
4427 gameMode = EditGame;
4430 currentMove = --forwardMostMove;
4431 DisplayMove(currentMove-1); /* before DisplayMoveError */
4433 DisplayBothClocks();
4434 sprintf(buf1, "Illegal move \"%s\" (rejected by %s chess program)",
4435 parseList[currentMove], cps->which);
4436 DisplayMoveError(buf1);
4437 DrawPosition(FALSE, boards[currentMove]);
4440 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
4441 /* Program has a broken "time" command that
4442 outputs a string not ending in newline.
4448 * If chess program startup fails, exit with an error message.
4449 * Attempts to recover here are futile.
4451 if ((StrStr(message, "unknown host") != NULL)
4452 || (StrStr(message, "No remote directory") != NULL)
4453 || (StrStr(message, "not found") != NULL)
4454 || (StrStr(message, "No such file") != NULL)
4455 || (StrStr(message, "can't alloc") != NULL)
4456 || (StrStr(message, "Permission denied") != NULL)) {
4458 cps->maybeThinking = FALSE;
4459 sprintf(buf1, "Failed to start %s chess program %s on %s: %s\n",
4460 cps->which, cps->program, cps->host, message);
4461 RemoveInputSource(cps->isr);
4462 DisplayFatalError(buf1, 0, 1);
4467 * Look for hint output
4469 if (sscanf(message, "Hint: %s", buf1) == 1) {
4470 if (cps == &first && hintRequested) {
4471 hintRequested = FALSE;
4472 if (ParseOneMove(buf1, forwardMostMove, &moveType,
4473 &fromX, &fromY, &toX, &toY, &promoChar)) {
4474 (void) CoordsToAlgebraic(boards[forwardMostMove],
4475 PosFlags(forwardMostMove), EP_UNKNOWN,
4476 fromY, fromX, toY, toX, promoChar, buf1);
4477 sprintf(buf2, "Hint: %s", buf1);
4478 DisplayInformation(buf2);
4480 /* Hint move could not be parsed!? */
4482 "Illegal hint move \"%s\"\nfrom %s chess program",
4484 DisplayError(buf2, 0);
4487 strcpy(lastHint, buf1);
4493 * Ignore other messages if game is not in progress
4495 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
4496 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
4499 * look for win, lose, draw, or draw offer
4501 if (strncmp(message, "1-0", 3) == 0) {
4502 char *p, *q, *r = "";
4503 p = strchr(message, '{');
4511 GameEnds(WhiteWins, r, GE_ENGINE);
4513 } else if (strncmp(message, "0-1", 3) == 0) {
4514 char *p, *q, *r = "";
4515 p = strchr(message, '{');
4523 /* Kludge for Arasan 4.1 bug */
4524 if (strcmp(r, "Black resigns") == 0) {
4525 GameEnds(WhiteWins, r, GE_ENGINE);
4528 GameEnds(BlackWins, r, GE_ENGINE);
4530 } else if (strncmp(message, "1/2", 3) == 0) {
4531 char *p, *q, *r = "";
4532 p = strchr(message, '{');
4540 GameEnds(GameIsDrawn, r, GE_ENGINE);
4543 } else if (strncmp(message, "White resign", 12) == 0) {
4544 GameEnds(BlackWins, "White resigns", GE_ENGINE);
4546 } else if (strncmp(message, "Black resign", 12) == 0) {
4547 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
4549 } else if (strncmp(message, "White", 5) == 0 &&
4550 message[5] != '(' &&
4551 StrStr(message, "Black") == NULL) {
4552 GameEnds(WhiteWins, "White mates", GE_ENGINE);
4554 } else if (strncmp(message, "Black", 5) == 0 &&
4555 message[5] != '(') {
4556 GameEnds(BlackWins, "Black mates", GE_ENGINE);
4558 } else if (strcmp(message, "resign") == 0 ||
4559 strcmp(message, "computer resigns") == 0) {
4561 case MachinePlaysBlack:
4562 case IcsPlayingBlack:
4563 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
4565 case MachinePlaysWhite:
4566 case IcsPlayingWhite:
4567 GameEnds(BlackWins, "White resigns", GE_ENGINE);
4569 case TwoMachinesPlay:
4570 if (cps->twoMachinesColor[0] == 'w')
4571 GameEnds(BlackWins, "White resigns", GE_ENGINE);
4573 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
4580 } else if (strncmp(message, "opponent mates", 14) == 0) {
4582 case MachinePlaysBlack:
4583 case IcsPlayingBlack:
4584 GameEnds(WhiteWins, "White mates", GE_ENGINE);
4586 case MachinePlaysWhite:
4587 case IcsPlayingWhite:
4588 GameEnds(BlackWins, "Black mates", GE_ENGINE);
4590 case TwoMachinesPlay:
4591 if (cps->twoMachinesColor[0] == 'w')
4592 GameEnds(BlackWins, "Black mates", GE_ENGINE);
4594 GameEnds(WhiteWins, "White mates", GE_ENGINE);
4601 } else if (strncmp(message, "computer mates", 14) == 0) {
4603 case MachinePlaysBlack:
4604 case IcsPlayingBlack:
4605 GameEnds(BlackWins, "Black mates", GE_ENGINE);
4607 case MachinePlaysWhite:
4608 case IcsPlayingWhite:
4609 GameEnds(WhiteWins, "White mates", GE_ENGINE);
4611 case TwoMachinesPlay:
4612 if (cps->twoMachinesColor[0] == 'w')
4613 GameEnds(WhiteWins, "White mates", GE_ENGINE);
4615 GameEnds(BlackWins, "Black mates", GE_ENGINE);
4622 } else if (strncmp(message, "checkmate", 9) == 0) {
4623 if (WhiteOnMove(forwardMostMove)) {
4624 GameEnds(BlackWins, "Black mates", GE_ENGINE);
4626 GameEnds(WhiteWins, "White mates", GE_ENGINE);
4629 } else if (strstr(message, "Draw") != NULL ||
4630 strstr(message, "game is a draw") != NULL) {
4631 GameEnds(GameIsDrawn, "Draw", GE_ENGINE);
4633 } else if (strstr(message, "offer") != NULL &&
4634 strstr(message, "draw") != NULL) {
4636 if (appData.zippyPlay && first.initDone) {
4637 /* Relay offer to ICS */
4638 SendToICS(ics_prefix);
4639 SendToICS("draw\n");
4642 cps->offeredDraw = 2; /* valid until this engine moves twice */
4643 if (gameMode == TwoMachinesPlay) {
4644 if (cps->other->offeredDraw) {
4645 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
4647 if (cps->other->sendDrawOffers) {
4648 SendToProgram("draw\n", cps->other);
4651 } else if (gameMode == MachinePlaysWhite ||
4652 gameMode == MachinePlaysBlack) {
4653 if (userOfferedDraw) {
4654 DisplayInformation("Machine accepts your draw offer");
4655 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
4657 DisplayInformation("Machine offers a draw\nSelect Action / Draw to agree");
4664 * Look for thinking output
4666 if ( appData.showThinking) {
4667 int plylev, mvleft, mvtot, curscore, time;
4668 char mvname[MOVE_LEN];
4669 unsigned long nodes;
4672 int prefixHint = FALSE;
4673 mvname[0] = NULLCHAR;
4676 case MachinePlaysBlack:
4677 case IcsPlayingBlack:
4678 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
4680 case MachinePlaysWhite:
4681 case IcsPlayingWhite:
4682 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
4687 case TwoMachinesPlay:
4688 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
4699 if (sscanf(message, "%d%c %d %d %lu %[^\n]\n",
4700 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
4702 if (plyext != ' ' && plyext != '\t') {
4705 programStats.depth = plylev;
4706 programStats.nodes = nodes;
4707 programStats.time = time;
4708 programStats.score = curscore;
4709 programStats.got_only_move = 0;
4711 /* [AS] Negate score if machine is playing black and it's reporting absolute scores */
4712 if( cps->scoreIsAbsolute &&
4713 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
4715 programStats.score = -curscore;
4718 /* Buffer overflow protection */
4719 if (buf1[0] != NULLCHAR) {
4720 if (strlen(buf1) >= sizeof(programStats.movelist)
4721 && appData.debugMode) {
4723 "PV is too long; using the first %d bytes.\n",
4724 sizeof(programStats.movelist) - 1);
4727 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
4729 sprintf(programStats.movelist, " no PV\n");
4732 if (programStats.seen_stat) {
4733 programStats.ok_to_send = 1;
4736 if (strchr(programStats.movelist, '(') != NULL) {
4737 programStats.line_is_book = 1;
4738 programStats.nr_moves = 0;
4739 programStats.moves_left = 0;
4741 programStats.line_is_book = 0;
4745 [AS] Protect the thinkOutput buffer from overflow... this
4746 is only useful if buf1 hasn't overflowed first!
4748 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
4750 (gameMode == TwoMachinesPlay ?
4751 ToUpper(cps->twoMachinesColor[0]) : ' '),
4752 ((double) curscore) / 100.0,
4753 prefixHint ? lastHint : "",
4754 prefixHint ? " " : "" );
4756 if( buf1[0] != NULLCHAR ) {
4757 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
4759 if( strlen(buf1) > max_len ) {
4760 if( appData.debugMode) {
4761 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
4763 buf1[max_len+1] = '\0';
4766 strcat( thinkOutput, buf1 );
4769 if (currentMove == forwardMostMove || gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
4770 DisplayMove(currentMove - 1);
4775 } else if ((p=StrStr(message, "(only move)")) != NULL) {
4776 /* crafty (9.25+) says "(only move) <move>"
4777 * if there is only 1 legal move
4779 sscanf(p, "(only move) %s", buf1);
4780 sprintf(thinkOutput, "%s (only move)", buf1);
4781 sprintf(programStats.movelist, "%s (only move)", buf1);
4782 programStats.depth = 1;
4783 programStats.nr_moves = 1;
4784 programStats.moves_left = 1;
4785 programStats.nodes = 1;
4786 programStats.time = 1;
4787 programStats.got_only_move = 1;
4789 /* Not really, but we also use this member to
4790 mean "line isn't going to change" (Crafty
4791 isn't searching, so stats won't change) */
4792 programStats.line_is_book = 1;
4794 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || gameMode == AnalyzeFile) {
4795 DisplayMove(currentMove - 1);
4799 } else if (sscanf(message,"stat01: %d %lu %d %d %d %s",
4800 &time, &nodes, &plylev, &mvleft,
4801 &mvtot, mvname) >= 5) {
4802 /* The stat01: line is from Crafty (9.29+) in response
4803 to the "." command */
4804 programStats.seen_stat = 1;
4805 cps->maybeThinking = TRUE;
4807 if (programStats.got_only_move || !appData.periodicUpdates)
4810 programStats.depth = plylev;
4811 programStats.time = time;
4812 programStats.nodes = nodes;
4813 programStats.moves_left = mvleft;
4814 programStats.nr_moves = mvtot;
4815 strcpy(programStats.move_name, mvname);
4816 programStats.ok_to_send = 1;
4820 } else if (strncmp(message,"++",2) == 0) {
4821 /* Crafty 9.29+ outputs this */
4822 programStats.got_fail = 2;
4825 } else if (strncmp(message,"--",2) == 0) {
4826 /* Crafty 9.29+ outputs this */
4827 programStats.got_fail = 1;
4830 } else if (thinkOutput[0] != NULLCHAR &&
4831 strncmp(message, " ", 4) == 0) {
4832 unsigned message_len;
4835 while (*p && *p == ' ') p++;
4837 message_len = strlen( p );
4839 /* [AS] Avoid buffer overflow */
4840 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
4841 strcat(thinkOutput, " ");
4842 strcat(thinkOutput, p);
4845 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
4846 strcat(programStats.movelist, " ");
4847 strcat(programStats.movelist, p);
4850 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || gameMode == AnalyzeFile) {
4851 DisplayMove(currentMove - 1);
4861 /* Parse a game score from the character string "game", and
4862 record it as the history of the current game. The game
4863 score is NOT assumed to start from the standard position.
4864 The display is not updated in any way.
4867 ParseGameHistory(game)
4871 int fromX, fromY, toX, toY, boardIndex;
4876 if (appData.debugMode)
4877 fprintf(debugFP, "Parsing game history: %s\n", game);
4879 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
4880 gameInfo.site = StrSave(appData.icsHost);
4881 gameInfo.date = PGNDate();
4882 gameInfo.round = StrSave("-");
4884 /* Parse out names of players */
4885 while (*game == ' ') game++;
4887 while (*game != ' ') *p++ = *game++;
4889 gameInfo.white = StrSave(buf);
4890 while (*game == ' ') game++;
4892 while (*game != ' ' && *game != '\n') *p++ = *game++;
4894 gameInfo.black = StrSave(buf);
4897 boardIndex = blackPlaysFirst ? 1 : 0;
4900 yyboardindex = boardIndex;
4901 moveType = (ChessMove) yylex();
4903 case WhitePromotionQueen:
4904 case BlackPromotionQueen:
4905 case WhitePromotionRook:
4906 case BlackPromotionRook:
4907 case WhitePromotionBishop:
4908 case BlackPromotionBishop:
4909 case WhitePromotionKnight:
4910 case BlackPromotionKnight:
4911 case WhitePromotionKing:
4912 case BlackPromotionKing:
4914 case WhiteCapturesEnPassant:
4915 case BlackCapturesEnPassant:
4916 case WhiteKingSideCastle:
4917 case WhiteQueenSideCastle:
4918 case BlackKingSideCastle:
4919 case BlackQueenSideCastle:
4920 case WhiteKingSideCastleWild:
4921 case WhiteQueenSideCastleWild:
4922 case BlackKingSideCastleWild:
4923 case BlackQueenSideCastleWild:
4925 case WhiteHSideCastleFR:
4926 case WhiteASideCastleFR:
4927 case BlackHSideCastleFR:
4928 case BlackASideCastleFR:
4930 case IllegalMove: /* maybe suicide chess, etc. */
4931 fromX = currentMoveString[0] - 'a';
4932 fromY = currentMoveString[1] - '1';
4933 toX = currentMoveString[2] - 'a';
4934 toY = currentMoveString[3] - '1';
4935 promoChar = currentMoveString[4];
4939 fromX = moveType == WhiteDrop ?
4940 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4941 (int) CharToPiece(ToLower(currentMoveString[0]));
4943 toX = currentMoveString[2] - 'a';
4944 toY = currentMoveString[3] - '1';
4945 promoChar = NULLCHAR;
4949 sprintf(buf, "Ambiguous move in ICS output: \"%s\"", yy_text);
4950 DisplayError(buf, 0);
4952 case ImpossibleMove:
4954 sprintf(buf, "Illegal move in ICS output: \"%s\"", yy_text);
4955 DisplayError(buf, 0);
4957 case (ChessMove) 0: /* end of file */
4958 if (boardIndex < backwardMostMove) {
4959 /* Oops, gap. How did that happen? */
4960 DisplayError("Gap in move list", 0);
4963 backwardMostMove = blackPlaysFirst ? 1 : 0;
4964 if (boardIndex > forwardMostMove) {
4965 forwardMostMove = boardIndex;
4969 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
4970 strcat(parseList[boardIndex-1], " ");
4971 strcat(parseList[boardIndex-1], yy_text);
4983 case GameUnfinished:
4984 if (gameMode == IcsExamining) {
4985 if (boardIndex < backwardMostMove) {
4986 /* Oops, gap. How did that happen? */
4989 backwardMostMove = blackPlaysFirst ? 1 : 0;
4992 gameInfo.result = moveType;
4993 p = strchr(yy_text, '{');
4994 if (p == NULL) p = strchr(yy_text, '(');
4997 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
4999 q = strchr(p, *p == '{' ? '}' : ')');
5000 if (q != NULL) *q = NULLCHAR;
5003 gameInfo.resultDetails = StrSave(p);
5006 if (boardIndex >= forwardMostMove &&
5007 !(gameMode == IcsObserving && ics_gamenum == -1)) {
5008 backwardMostMove = blackPlaysFirst ? 1 : 0;
5011 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
5012 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
5013 parseList[boardIndex]);
5014 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
5015 /* currentMoveString is set as a side-effect of yylex */
5016 strcpy(moveList[boardIndex], currentMoveString);
5017 strcat(moveList[boardIndex], "\n");
5019 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
5020 switch (MateTest(boards[boardIndex],
5021 PosFlags(boardIndex), EP_UNKNOWN)) {
5027 strcat(parseList[boardIndex - 1], "+");
5030 strcat(parseList[boardIndex - 1], "#");
5037 /* Apply a move to the given board */
5039 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
5040 int fromX, fromY, toX, toY;
5044 ChessSquare captured = board[toY][toX];
5045 if (fromY == DROP_RANK) {
5047 board[toY][toX] = (ChessSquare) fromX;
5048 } else if (fromX == toX && fromY == toY) {
5052 /* Code added by Tord: */
5053 /* FRC castling assumed when king captures friendly rook. */
5054 else if (board[fromY][fromX] == WhiteKing &&
5055 board[toY][toX] == WhiteRook) {
5056 board[fromY][fromX] = EmptySquare;
5057 board[toY][toX] = EmptySquare;
5059 board[0][6] = WhiteKing; board[0][5] = WhiteRook;
5061 board[0][2] = WhiteKing; board[0][3] = WhiteRook;
5063 } else if (board[fromY][fromX] == BlackKing &&
5064 board[toY][toX] == BlackRook) {
5065 board[fromY][fromX] = EmptySquare;
5066 board[toY][toX] = EmptySquare;
5068 board[7][6] = BlackKing; board[7][5] = BlackRook;
5070 board[7][2] = BlackKing; board[7][3] = BlackRook;
5072 /* End of code added by Tord */
5074 } else if (fromY == 0 && fromX == 4
5075 && board[fromY][fromX] == WhiteKing
5076 && toY == 0 && toX == 6) {
5077 board[fromY][fromX] = EmptySquare;
5078 board[toY][toX] = WhiteKing;
5079 board[fromY][7] = EmptySquare;
5080 board[toY][5] = WhiteRook;
5081 } else if (fromY == 0 && fromX == 4
5082 && board[fromY][fromX] == WhiteKing
5083 && toY == 0 && toX == 2) {
5084 board[fromY][fromX] = EmptySquare;
5085 board[toY][toX] = WhiteKing;
5086 board[fromY][0] = EmptySquare;
5087 board[toY][3] = WhiteRook;
5088 } else if (fromY == 0 && fromX == 3
5089 && board[fromY][fromX] == WhiteKing
5090 && toY == 0 && toX == 5) {
5091 board[fromY][fromX] = EmptySquare;
5092 board[toY][toX] = WhiteKing;
5093 board[fromY][7] = EmptySquare;
5094 board[toY][4] = WhiteRook;
5095 } else if (fromY == 0 && fromX == 3
5096 && board[fromY][fromX] == WhiteKing
5097 && toY == 0 && toX == 1) {
5098 board[fromY][fromX] = EmptySquare;
5099 board[toY][toX] = WhiteKing;
5100 board[fromY][0] = EmptySquare;
5101 board[toY][2] = WhiteRook;
5102 } else if (board[fromY][fromX] == WhitePawn
5104 /* white pawn promotion */
5105 board[7][toX] = CharToPiece(ToUpper(promoChar));
5106 if (board[7][toX] == EmptySquare) {
5107 board[7][toX] = WhiteQueen;
5109 board[fromY][fromX] = EmptySquare;
5110 } else if ((fromY == 4)
5112 && (board[fromY][fromX] == WhitePawn)
5113 && (board[toY][toX] == EmptySquare)) {
5114 board[fromY][fromX] = EmptySquare;
5115 board[toY][toX] = WhitePawn;
5116 captured = board[toY - 1][toX];
5117 board[toY - 1][toX] = EmptySquare;
5118 } else if (fromY == 7 && fromX == 4
5119 && board[fromY][fromX] == BlackKing
5120 && toY == 7 && toX == 6) {
5121 board[fromY][fromX] = EmptySquare;
5122 board[toY][toX] = BlackKing;
5123 board[fromY][7] = EmptySquare;
5124 board[toY][5] = BlackRook;
5125 } else if (fromY == 7 && fromX == 4
5126 && board[fromY][fromX] == BlackKing
5127 && toY == 7 && toX == 2) {
5128 board[fromY][fromX] = EmptySquare;
5129 board[toY][toX] = BlackKing;
5130 board[fromY][0] = EmptySquare;
5131 board[toY][3] = BlackRook;
5132 } else if (fromY == 7 && fromX == 3
5133 && board[fromY][fromX] == BlackKing
5134 && toY == 7 && toX == 5) {
5135 board[fromY][fromX] = EmptySquare;
5136 board[toY][toX] = BlackKing;
5137 board[fromY][7] = EmptySquare;
5138 board[toY][4] = BlackRook;
5139 } else if (fromY == 7 && fromX == 3
5140 && board[fromY][fromX] == BlackKing
5141 && toY == 7 && toX == 1) {
5142 board[fromY][fromX] = EmptySquare;
5143 board[toY][toX] = BlackKing;
5144 board[fromY][0] = EmptySquare;
5145 board[toY][2] = BlackRook;
5146 } else if (board[fromY][fromX] == BlackPawn
5148 /* black pawn promotion */
5149 board[0][toX] = CharToPiece(ToLower(promoChar));
5150 if (board[0][toX] == EmptySquare) {
5151 board[0][toX] = BlackQueen;
5153 board[fromY][fromX] = EmptySquare;
5154 } else if ((fromY == 3)
5156 && (board[fromY][fromX] == BlackPawn)
5157 && (board[toY][toX] == EmptySquare)) {
5158 board[fromY][fromX] = EmptySquare;
5159 board[toY][toX] = BlackPawn;
5160 captured = board[toY + 1][toX];
5161 board[toY + 1][toX] = EmptySquare;
5163 board[toY][toX] = board[fromY][fromX];
5164 board[fromY][fromX] = EmptySquare;
5166 if (gameInfo.variant == VariantCrazyhouse) {
5168 /* !!A lot more code needs to be written to support holdings */
5169 if (fromY == DROP_RANK) {
5170 /* Delete from holdings */
5171 if (holdings[(int) fromX] > 0) holdings[(int) fromX]--;
5173 if (captured != EmptySquare) {
5174 /* Add to holdings */
5175 if (captured < BlackPawn) {
5176 holdings[(int)captured - (int)BlackPawn + (int)WhitePawn]++;
5178 holdings[(int)captured - (int)WhitePawn + (int)BlackPawn]++;
5182 } else if (gameInfo.variant == VariantAtomic) {
5183 if (captured != EmptySquare) {
5185 for (y = toY-1; y <= toY+1; y++) {
5186 for (x = toX-1; x <= toX+1; x++) {
5187 if (y >= 0 && y <= 7 && x >= 0 && x <= 7 &&
5188 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
5189 board[y][x] = EmptySquare;
5193 board[toY][toX] = EmptySquare;
5198 /* Updates forwardMostMove */
5200 MakeMove(fromX, fromY, toX, toY, promoChar)
5201 int fromX, fromY, toX, toY;
5205 if (forwardMostMove >= MAX_MOVES) {
5206 DisplayFatalError("Game too long; increase MAX_MOVES and recompile",
5211 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
5212 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
5213 if (commentList[forwardMostMove] != NULL) {
5214 free(commentList[forwardMostMove]);
5215 commentList[forwardMostMove] = NULL;
5217 CopyBoard(boards[forwardMostMove], boards[forwardMostMove - 1]);
5218 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove]);
5219 gameInfo.result = GameUnfinished;
5220 if (gameInfo.resultDetails != NULL) {
5221 free(gameInfo.resultDetails);
5222 gameInfo.resultDetails = NULL;
5224 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
5225 moveList[forwardMostMove - 1]);
5226 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
5227 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
5228 fromY, fromX, toY, toX, promoChar,
5229 parseList[forwardMostMove - 1]);
5230 switch (MateTest(boards[forwardMostMove],
5231 PosFlags(forwardMostMove), EP_UNKNOWN)){
5237 strcat(parseList[forwardMostMove - 1], "+");
5240 strcat(parseList[forwardMostMove - 1], "#");
5245 /* Updates currentMove if not pausing */
5247 ShowMove(fromX, fromY, toX, toY)
5249 int instant = (gameMode == PlayFromGameFile) ?
5250 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
5251 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
5253 if (forwardMostMove == currentMove + 1) {
5254 AnimateMove(boards[forwardMostMove - 1],
5255 fromX, fromY, toX, toY);
5257 if (appData.highlightLastMove) {
5258 SetHighlights(fromX, fromY, toX, toY);
5261 currentMove = forwardMostMove;
5264 if (instant) return;
5265 DisplayMove(currentMove - 1);
5266 DrawPosition(FALSE, boards[currentMove]);
5267 DisplayBothClocks();
5268 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
5273 InitChessProgram(cps)
5274 ChessProgramState *cps;
5277 if (appData.noChessProgram) return;
5278 hintRequested = FALSE;
5279 bookRequested = FALSE;
5280 SendToProgram(cps->initString, cps);
5281 if (gameInfo.variant != VariantNormal &&
5282 gameInfo.variant != VariantLoadable) {
5283 char *v = VariantName(gameInfo.variant);
5284 if (StrStr(cps->variants, v) == NULL) {
5285 sprintf(buf, "Variant %s not supported by %s", v, cps->tidy);
5286 DisplayFatalError(buf, 0, 1);
5289 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
5290 SendToProgram(buf, cps);
5293 sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-");
5294 SendToProgram(buf, cps);
5296 cps->maybeThinking = FALSE;
5297 cps->offeredDraw = 0;
5298 if (!appData.icsActive) {
5299 SendTimeControl(cps, movesPerSession, timeControl,
5300 timeIncrement, appData.searchDepth,
5303 if (appData.showThinking) {
5304 SendToProgram("post\n", cps);
5306 SendToProgram("hard\n", cps);
5307 if (!appData.ponderNextMove) {
5308 /* Warning: "easy" is a toggle in GNU Chess, so don't send
5309 it without being sure what state we are in first. "hard"
5310 is not a toggle, so that one is OK.
5312 SendToProgram("easy\n", cps);
5315 sprintf(buf, "ping %d\n", ++cps->lastPing);
5316 SendToProgram(buf, cps);
5318 cps->initDone = TRUE;
5323 StartChessProgram(cps)
5324 ChessProgramState *cps;
5329 if (appData.noChessProgram) return;
5330 cps->initDone = FALSE;
5332 if (strcmp(cps->host, "localhost") == 0) {
5333 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
5334 } else if (*appData.remoteShell == NULLCHAR) {
5335 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
5337 if (*appData.remoteUser == NULLCHAR) {
5338 sprintf(buf, "%s %s %s", appData.remoteShell, cps->host,
5341 sprintf(buf, "%s %s -l %s %s", appData.remoteShell,
5342 cps->host, appData.remoteUser, cps->program);
5344 err = StartChildProcess(buf, "", &cps->pr);
5348 sprintf(buf, "Startup failure on '%s'", cps->program);
5349 DisplayFatalError(buf, err, 1);
5355 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
5356 if (cps->protocolVersion > 1) {
5357 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
5358 SendToProgram(buf, cps);
5360 SendToProgram("xboard\n", cps);
5366 TwoMachinesEventIfReady P((void))
5368 if (first.lastPing != first.lastPong) {
5369 DisplayMessage("", "Waiting for first chess program");
5370 ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000);
5373 if (second.lastPing != second.lastPong) {
5374 DisplayMessage("", "Waiting for second chess program");
5375 ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000);
5383 NextMatchGame P((void))
5386 if (*appData.loadGameFile != NULLCHAR) {
5387 LoadGameFromFile(appData.loadGameFile,
5388 appData.loadGameIndex,
5389 appData.loadGameFile, FALSE);
5390 } else if (*appData.loadPositionFile != NULLCHAR) {
5391 LoadPositionFromFile(appData.loadPositionFile,
5392 appData.loadPositionIndex,
5393 appData.loadPositionFile);
5395 TwoMachinesEventIfReady();
5398 void UserAdjudicationEvent( int result )
5400 ChessMove gameResult = GameIsDrawn;
5403 gameResult = WhiteWins;
5405 else if( result < 0 ) {
5406 gameResult = BlackWins;
5409 if( gameMode == TwoMachinesPlay ) {
5410 GameEnds( gameResult, "User adjudication", GE_XBOARD );
5416 GameEnds(result, resultDetails, whosays)
5418 char *resultDetails;
5421 GameMode nextGameMode;
5424 if (appData.debugMode) {
5425 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
5426 result, resultDetails ? resultDetails : "(null)", whosays);
5429 if (appData.icsActive && whosays == GE_ENGINE) {
5430 /* If we are playing on ICS, the server decides when the
5431 game is over, but the engine can offer to draw, claim
5435 if (appData.zippyPlay && first.initDone) {
5436 if (result == GameIsDrawn) {
5437 /* In case draw still needs to be claimed */
5438 SendToICS(ics_prefix);
5439 SendToICS("draw\n");
5440 } else if (StrCaseStr(resultDetails, "resign")) {
5441 SendToICS(ics_prefix);
5442 SendToICS("resign\n");
5449 /* If we're loading the game from a file, stop */
5450 if (whosays == GE_FILE) {
5451 (void) StopLoadGameTimer();
5455 /* Cancel draw offers */
5456 first.offeredDraw = second.offeredDraw = 0;
5458 /* If this is an ICS game, only ICS can really say it's done;
5459 if not, anyone can. */
5460 isIcsGame = (gameMode == IcsPlayingWhite ||
5461 gameMode == IcsPlayingBlack ||
5462 gameMode == IcsObserving ||
5463 gameMode == IcsExamining);
5465 if (!isIcsGame || whosays == GE_ICS) {
5466 /* OK -- not an ICS game, or ICS said it was done */
5468 if (!isIcsGame && !appData.noChessProgram)
5469 SetUserThinkingEnables();
5471 if (resultDetails != NULL) {
5472 gameInfo.result = result;
5473 gameInfo.resultDetails = StrSave(resultDetails);
5475 /* Tell program how game ended in case it is learning */
5476 if (gameMode == MachinePlaysWhite ||
5477 gameMode == MachinePlaysBlack ||
5478 gameMode == TwoMachinesPlay ||
5479 gameMode == IcsPlayingWhite ||
5480 gameMode == IcsPlayingBlack ||
5481 gameMode == BeginningOfGame) {
5483 sprintf(buf, "result %s {%s}\n", PGNResult(result),
5485 if (first.pr != NoProc) {
5486 SendToProgram(buf, &first);
5488 if (second.pr != NoProc &&
5489 gameMode == TwoMachinesPlay) {
5490 SendToProgram(buf, &second);
5494 /* display last move only if game was not loaded from file */
5495 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
5496 DisplayMove(currentMove - 1);
5498 if (forwardMostMove != 0) {
5499 if (gameMode != PlayFromGameFile && gameMode != EditGame) {
5500 if (*appData.saveGameFile != NULLCHAR) {
5501 SaveGameToFile(appData.saveGameFile, TRUE);
5502 } else if (appData.autoSaveGames) {
5505 if (*appData.savePositionFile != NULLCHAR) {
5506 SavePositionToFile(appData.savePositionFile);
5512 if (appData.icsActive) {
5513 if (appData.quietPlay &&
5514 (gameMode == IcsPlayingWhite ||
5515 gameMode == IcsPlayingBlack)) {
5516 SendToICS(ics_prefix);
5517 SendToICS("set shout 1\n");
5519 nextGameMode = IcsIdle;
5520 ics_user_moved = FALSE;
5521 /* clean up premove. It's ugly when the game has ended and the
5522 * premove highlights are still on the board.
5526 ClearPremoveHighlights();
5527 DrawPosition(FALSE, boards[currentMove]);
5529 if (whosays == GE_ICS) {
5532 if (gameMode == IcsPlayingWhite)
5534 else if(gameMode == IcsPlayingBlack)
5538 if (gameMode == IcsPlayingBlack)
5540 else if(gameMode == IcsPlayingWhite)
5547 PlayIcsUnfinishedSound();
5550 } else if (gameMode == EditGame ||
5551 gameMode == PlayFromGameFile ||
5552 gameMode == AnalyzeMode ||
5553 gameMode == AnalyzeFile) {
5554 nextGameMode = gameMode;
5556 nextGameMode = EndOfGame;
5561 nextGameMode = gameMode;
5564 if (appData.noChessProgram) {
5565 gameMode = nextGameMode;
5571 /* Put first chess program into idle state */
5572 if (first.pr != NoProc &&
5573 (gameMode == MachinePlaysWhite ||
5574 gameMode == MachinePlaysBlack ||
5575 gameMode == TwoMachinesPlay ||
5576 gameMode == IcsPlayingWhite ||
5577 gameMode == IcsPlayingBlack ||
5578 gameMode == BeginningOfGame)) {
5579 SendToProgram("force\n", &first);
5580 if (first.usePing) {
5582 sprintf(buf, "ping %d\n", ++first.lastPing);
5583 SendToProgram(buf, &first);
5586 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
5587 /* Kill off first chess program */
5588 if (first.isr != NULL)
5589 RemoveInputSource(first.isr);
5592 if (first.pr != NoProc) {
5594 DoSleep( appData.delayBeforeQuit );
5595 SendToProgram("quit\n", &first);
5596 DoSleep( appData.delayAfterQuit );
5597 DestroyChildProcess(first.pr, first.useSigterm);
5602 /* Put second chess program into idle state */
5603 if (second.pr != NoProc &&
5604 gameMode == TwoMachinesPlay) {
5605 SendToProgram("force\n", &second);
5606 if (second.usePing) {
5608 sprintf(buf, "ping %d\n", ++second.lastPing);
5609 SendToProgram(buf, &second);
5612 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
5613 /* Kill off second chess program */
5614 if (second.isr != NULL)
5615 RemoveInputSource(second.isr);
5618 if (second.pr != NoProc) {
5619 DoSleep( appData.delayBeforeQuit );
5620 SendToProgram("quit\n", &second);
5621 DoSleep( appData.delayAfterQuit );
5622 DestroyChildProcess(second.pr, second.useSigterm);
5627 if (matchMode && gameMode == TwoMachinesPlay) {
5630 if (first.twoMachinesColor[0] == 'w') {
5637 if (first.twoMachinesColor[0] == 'b') {
5646 if (matchGame < appData.matchGames) {
5648 tmp = first.twoMachinesColor;
5649 first.twoMachinesColor = second.twoMachinesColor;
5650 second.twoMachinesColor = tmp;
5651 gameMode = nextGameMode;
5653 ScheduleDelayedEvent(NextMatchGame, 10000);
5657 gameMode = nextGameMode;
5658 sprintf(buf, "Match %s vs. %s: final score %d-%d-%d",
5659 first.tidy, second.tidy,
5660 first.matchWins, second.matchWins,
5661 appData.matchGames - (first.matchWins + second.matchWins));
5662 DisplayFatalError(buf, 0, 0);
5665 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
5666 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
5668 gameMode = nextGameMode;
5672 /* Assumes program was just initialized (initString sent).
5673 Leaves program in force mode. */
5675 FeedMovesToProgram(cps, upto)
5676 ChessProgramState *cps;
5681 if (appData.debugMode)
5682 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
5683 startedFromSetupPosition ? "position and " : "",
5684 backwardMostMove, upto, cps->which);
5685 SendToProgram("force\n", cps);
5686 if (startedFromSetupPosition) {
5687 SendBoard(cps, backwardMostMove);
5689 for (i = backwardMostMove; i < upto; i++) {
5690 SendMoveToProgram(i, cps);
5696 ResurrectChessProgram()
5698 /* The chess program may have exited.
5699 If so, restart it and feed it all the moves made so far. */
5701 if (appData.noChessProgram || first.pr != NoProc) return;
5703 StartChessProgram(&first);
5704 InitChessProgram(&first);
5705 FeedMovesToProgram(&first, currentMove);
5707 if (!first.sendTime) {
5708 /* can't tell gnuchess what its clock should read,
5709 so we bow to its notion. */
5711 timeRemaining[0][currentMove] = whiteTimeRemaining;
5712 timeRemaining[1][currentMove] = blackTimeRemaining;
5715 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
5716 first.analysisSupport) {
5717 SendToProgram("analyze\n", &first);
5718 first.analyzing = TRUE;
5731 if (appData.debugMode) {
5732 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
5733 redraw, init, gameMode);
5736 pausing = pauseExamInvalid = FALSE;
5737 startedFromSetupPosition = blackPlaysFirst = FALSE;
5739 whiteFlag = blackFlag = FALSE;
5740 userOfferedDraw = FALSE;
5741 hintRequested = bookRequested = FALSE;
5742 first.maybeThinking = FALSE;
5743 second.maybeThinking = FALSE;
5744 thinkOutput[0] = NULLCHAR;
5745 lastHint[0] = NULLCHAR;
5746 ClearGameInfo(&gameInfo);
5747 gameInfo.variant = StringToVariant(appData.variant);
5748 ics_user_moved = ics_clock_paused = FALSE;
5749 ics_getting_history = H_FALSE;
5751 white_holding[0] = black_holding[0] = NULLCHAR;
5752 ClearProgramStats();
5756 flipView = appData.flipView;
5757 ClearPremoveHighlights();
5759 alarmSounded = FALSE;
5761 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
5763 gameMode = BeginningOfGame;
5765 InitPosition(redraw);
5766 for (i = 0; i < MAX_MOVES; i++) {
5767 if (commentList[i] != NULL) {
5768 free(commentList[i]);
5769 commentList[i] = NULL;
5773 timeRemaining[0][0] = whiteTimeRemaining;
5774 timeRemaining[1][0] = blackTimeRemaining;
5775 if (first.pr == NULL) {
5776 StartChessProgram(&first);
5778 if (init) InitChessProgram(&first);
5780 DisplayMessage("", "");
5781 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5788 if (!AutoPlayOneMove())
5790 if (matchMode || appData.timeDelay == 0)
5792 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
5794 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
5803 int fromX, fromY, toX, toY;
5805 if (appData.debugMode) {
5806 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
5809 if (gameMode != PlayFromGameFile)
5812 if (currentMove >= forwardMostMove) {
5813 gameMode = EditGame;
5818 toX = moveList[currentMove][2] - 'a';
5819 toY = moveList[currentMove][3] - '1';
5821 if (moveList[currentMove][1] == '@') {
5822 if (appData.highlightLastMove) {
5823 SetHighlights(-1, -1, toX, toY);
5826 fromX = moveList[currentMove][0] - 'a';
5827 fromY = moveList[currentMove][1] - '1';
5828 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
5830 if (appData.highlightLastMove) {
5831 SetHighlights(fromX, fromY, toX, toY);
5834 DisplayMove(currentMove);
5835 SendMoveToProgram(currentMove++, &first);
5836 DisplayBothClocks();
5837 DrawPosition(FALSE, boards[currentMove]);
5838 if (commentList[currentMove] != NULL) {
5839 DisplayComment(currentMove - 1, commentList[currentMove]);
5846 LoadGameOneMove(readAhead)
5847 ChessMove readAhead;
5849 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
5850 char promoChar = NULLCHAR;
5855 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
5856 gameMode != AnalyzeMode && gameMode != Training) {
5861 yyboardindex = forwardMostMove;
5862 if (readAhead != (ChessMove)0) {
5863 moveType = readAhead;
5865 if (gameFileFP == NULL)
5867 moveType = (ChessMove) yylex();
5873 if (appData.debugMode)
5874 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
5876 if (*p == '{' || *p == '[' || *p == '(') {
5877 p[strlen(p) - 1] = NULLCHAR;
5881 /* append the comment but don't display it */
5882 while (*p == '\n') p++;
5883 AppendComment(currentMove, p);
5886 case WhiteCapturesEnPassant:
5887 case BlackCapturesEnPassant:
5888 case WhitePromotionQueen:
5889 case BlackPromotionQueen:
5890 case WhitePromotionRook:
5891 case BlackPromotionRook:
5892 case WhitePromotionBishop:
5893 case BlackPromotionBishop:
5894 case WhitePromotionKnight:
5895 case BlackPromotionKnight:
5896 case WhitePromotionKing:
5897 case BlackPromotionKing:
5899 case WhiteKingSideCastle:
5900 case WhiteQueenSideCastle:
5901 case BlackKingSideCastle:
5902 case BlackQueenSideCastle:
5903 case WhiteKingSideCastleWild:
5904 case WhiteQueenSideCastleWild:
5905 case BlackKingSideCastleWild:
5906 case BlackQueenSideCastleWild:
5908 case WhiteHSideCastleFR:
5909 case WhiteASideCastleFR:
5910 case BlackHSideCastleFR:
5911 case BlackASideCastleFR:
5913 if (appData.debugMode)
5914 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
5915 fromX = currentMoveString[0] - 'a';
5916 fromY = currentMoveString[1] - '1';
5917 toX = currentMoveString[2] - 'a';
5918 toY = currentMoveString[3] - '1';
5919 promoChar = currentMoveString[4];
5924 if (appData.debugMode)
5925 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
5926 fromX = moveType == WhiteDrop ?
5927 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5928 (int) CharToPiece(ToLower(currentMoveString[0]));
5930 toX = currentMoveString[2] - 'a';
5931 toY = currentMoveString[3] - '1';
5937 case GameUnfinished:
5938 if (appData.debugMode)
5939 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
5940 p = strchr(yy_text, '{');
5941 if (p == NULL) p = strchr(yy_text, '(');
5944 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
5946 q = strchr(p, *p == '{' ? '}' : ')');
5947 if (q != NULL) *q = NULLCHAR;
5950 GameEnds(moveType, p, GE_FILE);
5952 if (cmailMsgLoaded) {
5954 flipView = WhiteOnMove(currentMove);
5955 if (moveType == GameUnfinished) flipView = !flipView;
5956 if (appData.debugMode)
5957 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
5961 case (ChessMove) 0: /* end of file */
5962 if (appData.debugMode)
5963 fprintf(debugFP, "Parser hit end of file\n");
5964 switch (MateTest(boards[currentMove], PosFlags(currentMove),
5970 if (WhiteOnMove(currentMove)) {
5971 GameEnds(BlackWins, "Black mates", GE_FILE);
5973 GameEnds(WhiteWins, "White mates", GE_FILE);
5977 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
5984 if (lastLoadGameStart == GNUChessGame) {
5985 /* GNUChessGames have numbers, but they aren't move numbers */
5986 if (appData.debugMode)
5987 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
5988 yy_text, (int) moveType);
5989 return LoadGameOneMove((ChessMove)0); /* tail recursion */
5991 /* else fall thru */
5996 /* Reached start of next game in file */
5997 if (appData.debugMode)
5998 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
5999 switch (MateTest(boards[currentMove], PosFlags(currentMove),
6005 if (WhiteOnMove(currentMove)) {
6006 GameEnds(BlackWins, "Black mates", GE_FILE);
6008 GameEnds(WhiteWins, "White mates", GE_FILE);
6012 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
6018 case PositionDiagram: /* should not happen; ignore */
6019 case ElapsedTime: /* ignore */
6020 case NAG: /* ignore */
6021 if (appData.debugMode)
6022 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
6023 yy_text, (int) moveType);
6024 return LoadGameOneMove((ChessMove)0); /* tail recursion */
6027 if (appData.testLegality) {
6028 if (appData.debugMode)
6029 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
6030 sprintf(move, "Illegal move: %d.%s%s",
6031 (forwardMostMove / 2) + 1,
6032 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
6033 DisplayError(move, 0);
6036 if (appData.debugMode)
6037 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
6038 yy_text, currentMoveString);
6039 fromX = currentMoveString[0] - 'a';
6040 fromY = currentMoveString[1] - '1';
6041 toX = currentMoveString[2] - 'a';
6042 toY = currentMoveString[3] - '1';
6043 promoChar = currentMoveString[4];
6048 if (appData.debugMode)
6049 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
6050 sprintf(move, "Ambiguous move: %d.%s%s",
6051 (forwardMostMove / 2) + 1,
6052 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
6053 DisplayError(move, 0);
6058 case ImpossibleMove:
6059 if (appData.debugMode)
6060 fprintf(debugFP, "Parsed ImpossibleMove: %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);
6070 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
6071 DrawPosition(FALSE, boards[currentMove]);
6072 DisplayBothClocks();
6073 if (!appData.matchMode && commentList[currentMove] != NULL)
6074 DisplayComment(currentMove - 1, commentList[currentMove]);
6076 (void) StopLoadGameTimer();
6078 cmailOldMove = forwardMostMove;
6081 /* currentMoveString is set as a side-effect of yylex */
6082 strcat(currentMoveString, "\n");
6083 strcpy(moveList[forwardMostMove], currentMoveString);
6085 thinkOutput[0] = NULLCHAR;
6086 MakeMove(fromX, fromY, toX, toY, promoChar);
6087 currentMove = forwardMostMove;
6092 /* Load the nth game from the given file */
6094 LoadGameFromFile(filename, n, title, useList)
6098 /*Boolean*/ int useList;
6103 if (strcmp(filename, "-") == 0) {
6107 f = fopen(filename, "rb");
6109 sprintf(buf, "Can't open \"%s\"", filename);
6110 DisplayError(buf, errno);
6114 if (fseek(f, 0, 0) == -1) {
6115 /* f is not seekable; probably a pipe */
6118 if (useList && n == 0) {
6119 int error = GameListBuild(f);
6121 DisplayError("Cannot build game list", error);
6122 } else if (!ListEmpty(&gameList) &&
6123 ((ListGame *) gameList.tailPred)->number > 1) {
6124 GameListPopUp(f, title);
6131 return LoadGame(f, n, title, FALSE);
6136 MakeRegisteredMove()
6138 int fromX, fromY, toX, toY;
6140 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
6141 switch (cmailMoveType[lastLoadGameNumber - 1]) {
6144 if (appData.debugMode)
6145 fprintf(debugFP, "Restoring %s for game %d\n",
6146 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
6148 thinkOutput[0] = NULLCHAR;
6149 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
6150 fromX = cmailMove[lastLoadGameNumber - 1][0] - 'a';
6151 fromY = cmailMove[lastLoadGameNumber - 1][1] - '1';
6152 toX = cmailMove[lastLoadGameNumber - 1][2] - 'a';
6153 toY = cmailMove[lastLoadGameNumber - 1][3] - '1';
6154 promoChar = cmailMove[lastLoadGameNumber - 1][4];
6155 MakeMove(fromX, fromY, toX, toY, promoChar);
6156 ShowMove(fromX, fromY, toX, toY);
6158 switch (MateTest(boards[currentMove], PosFlags(currentMove),
6165 if (WhiteOnMove(currentMove)) {
6166 GameEnds(BlackWins, "Black mates", GE_PLAYER);
6168 GameEnds(WhiteWins, "White mates", GE_PLAYER);
6173 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6180 if (WhiteOnMove(currentMove)) {
6181 GameEnds(BlackWins, "White resigns", GE_PLAYER);
6183 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
6188 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
6199 /* Wrapper around LoadGame for use when a Cmail message is loaded */
6201 CmailLoadGame(f, gameNumber, title, useList)
6209 if (gameNumber > nCmailGames) {
6210 DisplayError("No more games in this message", 0);
6213 if (f == lastLoadGameFP) {
6214 int offset = gameNumber - lastLoadGameNumber;
6216 cmailMsg[0] = NULLCHAR;
6217 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
6218 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
6219 nCmailMovesRegistered--;
6221 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6222 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
6223 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
6226 if (! RegisterMove()) return FALSE;
6230 retVal = LoadGame(f, gameNumber, title, useList);
6232 /* Make move registered during previous look at this game, if any */
6233 MakeRegisteredMove();
6235 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
6236 commentList[currentMove]
6237 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
6238 DisplayComment(currentMove - 1, commentList[currentMove]);
6244 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
6249 int gameNumber = lastLoadGameNumber + offset;
6250 if (lastLoadGameFP == NULL) {
6251 DisplayError("No game has been loaded yet", 0);
6254 if (gameNumber <= 0) {
6255 DisplayError("Can't back up any further", 0);
6258 if (cmailMsgLoaded) {
6259 return CmailLoadGame(lastLoadGameFP, gameNumber,
6260 lastLoadGameTitle, lastLoadGameUseList);
6262 return LoadGame(lastLoadGameFP, gameNumber,
6263 lastLoadGameTitle, lastLoadGameUseList);
6269 /* Load the nth game from open file f */
6271 LoadGame(f, gameNumber, title, useList)
6279 int gn = gameNumber;
6280 ListGame *lg = NULL;
6283 GameMode oldGameMode;
6285 if (appData.debugMode)
6286 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
6288 if (gameMode == Training )
6289 SetTrainingModeOff();
6291 oldGameMode = gameMode;
6292 if (gameMode != BeginningOfGame) {
6297 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
6298 fclose(lastLoadGameFP);
6302 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
6305 fseek(f, lg->offset, 0);
6306 GameListHighlight(gameNumber);
6310 DisplayError("Game number out of range", 0);
6315 if (fseek(f, 0, 0) == -1) {
6316 if (f == lastLoadGameFP ?
6317 gameNumber == lastLoadGameNumber + 1 :
6321 DisplayError("Can't seek on game file", 0);
6327 lastLoadGameNumber = gameNumber;
6328 strcpy(lastLoadGameTitle, title);
6329 lastLoadGameUseList = useList;
6334 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
6335 sprintf(buf, "%s vs. %s", lg->gameInfo.white,
6336 lg->gameInfo.black);
6338 } else if (*title != NULLCHAR) {
6339 if (gameNumber > 1) {
6340 sprintf(buf, "%s %d", title, gameNumber);
6343 DisplayTitle(title);
6347 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
6348 gameMode = PlayFromGameFile;
6352 currentMove = forwardMostMove = backwardMostMove = 0;
6353 CopyBoard(boards[0], initialPosition);
6357 * Skip the first gn-1 games in the file.
6358 * Also skip over anything that precedes an identifiable
6359 * start of game marker, to avoid being confused by
6360 * garbage at the start of the file. Currently
6361 * recognized start of game markers are the move number "1",
6362 * the pattern "gnuchess .* game", the pattern
6363 * "^[#;%] [^ ]* game file", and a PGN tag block.
6364 * A game that starts with one of the latter two patterns
6365 * will also have a move number 1, possibly
6366 * following a position diagram.
6367 * 5-4-02: Let's try being more lenient and allowing a game to
6368 * start with an unnumbered move. Does that break anything?
6370 cm = lastLoadGameStart = (ChessMove) 0;
6372 yyboardindex = forwardMostMove;
6373 cm = (ChessMove) yylex();
6376 if (cmailMsgLoaded) {
6377 nCmailGames = CMAIL_MAX_GAMES - gn;
6380 DisplayError("Game not found in file", 0);
6387 lastLoadGameStart = cm;
6391 switch (lastLoadGameStart) {
6398 gn--; /* count this game */
6399 lastLoadGameStart = cm;
6408 switch (lastLoadGameStart) {
6413 gn--; /* count this game */
6414 lastLoadGameStart = cm;
6417 lastLoadGameStart = cm; /* game counted already */
6425 yyboardindex = forwardMostMove;
6426 cm = (ChessMove) yylex();
6427 } while (cm == PGNTag || cm == Comment);
6434 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
6435 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
6436 != CMAIL_OLD_RESULT) {
6438 cmailResult[ CMAIL_MAX_GAMES
6439 - gn - 1] = CMAIL_OLD_RESULT;
6445 /* Only a NormalMove can be at the start of a game
6446 * without a position diagram. */
6447 if (lastLoadGameStart == (ChessMove) 0) {
6449 lastLoadGameStart = MoveNumberOne;
6458 if (appData.debugMode)
6459 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
6461 if (cm == XBoardGame) {
6462 /* Skip any header junk before position diagram and/or move 1 */
6464 yyboardindex = forwardMostMove;
6465 cm = (ChessMove) yylex();
6467 if (cm == (ChessMove) 0 ||
6468 cm == GNUChessGame || cm == XBoardGame) {
6469 /* Empty game; pretend end-of-file and handle later */
6474 if (cm == MoveNumberOne || cm == PositionDiagram ||
6475 cm == PGNTag || cm == Comment)
6478 } else if (cm == GNUChessGame) {
6479 if (gameInfo.event != NULL) {
6480 free(gameInfo.event);
6482 gameInfo.event = StrSave(yy_text);
6485 startedFromSetupPosition = FALSE;
6486 while (cm == PGNTag) {
6487 if (appData.debugMode)
6488 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
6489 err = ParsePGNTag(yy_text, &gameInfo);
6490 if (!err) numPGNTags++;
6492 if (gameInfo.fen != NULL) {
6493 Board initial_position;
6494 startedFromSetupPosition = TRUE;
6495 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
6497 DisplayError("Bad FEN position in file", 0);
6500 CopyBoard(boards[0], initial_position);
6501 if (blackPlaysFirst) {
6502 currentMove = forwardMostMove = backwardMostMove = 1;
6503 CopyBoard(boards[1], initial_position);
6504 strcpy(moveList[0], "");
6505 strcpy(parseList[0], "");
6506 timeRemaining[0][1] = whiteTimeRemaining;
6507 timeRemaining[1][1] = blackTimeRemaining;
6508 if (commentList[0] != NULL) {
6509 commentList[1] = commentList[0];
6510 commentList[0] = NULL;
6513 currentMove = forwardMostMove = backwardMostMove = 0;
6515 yyboardindex = forwardMostMove;
6517 gameInfo.fen = NULL;
6520 yyboardindex = forwardMostMove;
6521 cm = (ChessMove) yylex();
6523 /* Handle comments interspersed among the tags */
6524 while (cm == Comment) {
6526 if (appData.debugMode)
6527 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
6529 if (*p == '{' || *p == '[' || *p == '(') {
6530 p[strlen(p) - 1] = NULLCHAR;
6533 while (*p == '\n') p++;
6534 AppendComment(currentMove, p);
6535 yyboardindex = forwardMostMove;
6536 cm = (ChessMove) yylex();
6540 /* don't rely on existence of Event tag since if game was
6541 * pasted from clipboard the Event tag may not exist
6543 if (numPGNTags > 0){
6545 if (gameInfo.variant == VariantNormal) {
6546 gameInfo.variant = StringToVariant(gameInfo.event);
6549 tags = PGNTags(&gameInfo);
6550 TagsPopUp(tags, CmailMsg());
6554 /* Make something up, but don't display it now */
6559 if (cm == PositionDiagram) {
6562 Board initial_position;
6564 if (appData.debugMode)
6565 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
6567 if (!startedFromSetupPosition) {
6569 for (i = BOARD_SIZE - 1; i >= 0; i--)
6570 for (j = 0; j < BOARD_SIZE; p++)
6580 initial_position[i][j++] = CharToPiece(*p);
6583 while (*p == ' ' || *p == '\t' ||
6584 *p == '\n' || *p == '\r') p++;
6586 if (strncmp(p, "black", strlen("black"))==0)
6587 blackPlaysFirst = TRUE;
6589 blackPlaysFirst = FALSE;
6590 startedFromSetupPosition = TRUE;
6592 CopyBoard(boards[0], initial_position);
6593 if (blackPlaysFirst) {
6594 currentMove = forwardMostMove = backwardMostMove = 1;
6595 CopyBoard(boards[1], initial_position);
6596 strcpy(moveList[0], "");
6597 strcpy(parseList[0], "");
6598 timeRemaining[0][1] = whiteTimeRemaining;
6599 timeRemaining[1][1] = blackTimeRemaining;
6600 if (commentList[0] != NULL) {
6601 commentList[1] = commentList[0];
6602 commentList[0] = NULL;
6605 currentMove = forwardMostMove = backwardMostMove = 0;
6608 yyboardindex = forwardMostMove;
6609 cm = (ChessMove) yylex();
6612 if (first.pr == NoProc) {
6613 StartChessProgram(&first);
6615 InitChessProgram(&first);
6616 SendToProgram("force\n", &first);
6617 if (startedFromSetupPosition) {
6618 SendBoard(&first, forwardMostMove);
6619 DisplayBothClocks();
6622 while (cm == Comment) {
6624 if (appData.debugMode)
6625 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
6627 if (*p == '{' || *p == '[' || *p == '(') {
6628 p[strlen(p) - 1] = NULLCHAR;
6631 while (*p == '\n') p++;
6632 AppendComment(currentMove, p);
6633 yyboardindex = forwardMostMove;
6634 cm = (ChessMove) yylex();
6637 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
6638 cm == WhiteWins || cm == BlackWins ||
6639 cm == GameIsDrawn || cm == GameUnfinished) {
6640 DisplayMessage("", "No moves in game");
6641 if (cmailMsgLoaded) {
6642 if (appData.debugMode)
6643 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
6647 DrawPosition(FALSE, boards[currentMove]);
6648 DisplayBothClocks();
6649 gameMode = EditGame;
6656 if (commentList[currentMove] != NULL) {
6657 if (!matchMode && (pausing || appData.timeDelay != 0)) {
6658 DisplayComment(currentMove - 1, commentList[currentMove]);
6661 if (!matchMode && appData.timeDelay != 0)
6662 DrawPosition(FALSE, boards[currentMove]);
6664 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
6665 programStats.ok_to_send = 1;
6668 /* if the first token after the PGN tags is a move
6669 * and not move number 1, retrieve it from the parser
6671 if (cm != MoveNumberOne)
6672 LoadGameOneMove(cm);
6674 /* load the remaining moves from the file */
6675 while (LoadGameOneMove((ChessMove)0)) {
6676 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
6677 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
6680 /* rewind to the start of the game */
6681 currentMove = backwardMostMove;
6683 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
6685 if (oldGameMode == AnalyzeFile ||
6686 oldGameMode == AnalyzeMode) {
6690 if (matchMode || appData.timeDelay == 0) {
6692 gameMode = EditGame;
6694 } else if (appData.timeDelay > 0) {
6698 if (appData.debugMode)
6699 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
6703 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
6705 ReloadPosition(offset)
6708 int positionNumber = lastLoadPositionNumber + offset;
6709 if (lastLoadPositionFP == NULL) {
6710 DisplayError("No position has been loaded yet", 0);
6713 if (positionNumber <= 0) {
6714 DisplayError("Can't back up any further", 0);
6717 return LoadPosition(lastLoadPositionFP, positionNumber,
6718 lastLoadPositionTitle);
6721 /* Load the nth position from the given file */
6723 LoadPositionFromFile(filename, n, title)
6731 if (strcmp(filename, "-") == 0) {
6732 return LoadPosition(stdin, n, "stdin");
6734 f = fopen(filename, "rb");
6736 sprintf(buf, "Can't open \"%s\"", filename);
6737 DisplayError(buf, errno);
6740 return LoadPosition(f, n, title);
6745 /* Load the nth position from the given open file, and close it */
6747 LoadPosition(f, positionNumber, title)
6752 char *p, line[MSG_SIZ];
6753 Board initial_position;
6754 int i, j, fenMode, pn;
6756 if (gameMode == Training )
6757 SetTrainingModeOff();
6759 if (gameMode != BeginningOfGame) {
6762 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
6763 fclose(lastLoadPositionFP);
6765 if (positionNumber == 0) positionNumber = 1;
6766 lastLoadPositionFP = f;
6767 lastLoadPositionNumber = positionNumber;
6768 strcpy(lastLoadPositionTitle, title);
6769 if (first.pr == NoProc) {
6770 StartChessProgram(&first);
6771 InitChessProgram(&first);
6773 pn = positionNumber;
6774 if (positionNumber < 0) {
6775 /* Negative position number means to seek to that byte offset */
6776 if (fseek(f, -positionNumber, 0) == -1) {
6777 DisplayError("Can't seek on position file", 0);
6782 if (fseek(f, 0, 0) == -1) {
6783 if (f == lastLoadPositionFP ?
6784 positionNumber == lastLoadPositionNumber + 1 :
6785 positionNumber == 1) {
6788 DisplayError("Can't seek on position file", 0);
6793 /* See if this file is FEN or old-style xboard */
6794 if (fgets(line, MSG_SIZ, f) == NULL) {
6795 DisplayError("Position not found in file", 0);
6803 case 'p': case 'n': case 'b': case 'r': case 'q': case 'k':
6804 case 'P': case 'N': case 'B': case 'R': case 'Q': case 'K':
6805 case '1': case '2': case '3': case '4': case '5': case '6':
6812 if (fenMode || line[0] == '#') pn--;
6814 /* skip postions before number pn */
6815 if (fgets(line, MSG_SIZ, f) == NULL) {
6817 DisplayError("Position not found in file", 0);
6820 if (fenMode || line[0] == '#') pn--;
6825 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
6826 DisplayError("Bad FEN position in file", 0);
6830 (void) fgets(line, MSG_SIZ, f);
6831 (void) fgets(line, MSG_SIZ, f);
6833 for (i = BOARD_SIZE - 1; i >= 0; i--) {
6834 (void) fgets(line, MSG_SIZ, f);
6835 for (p = line, j = 0; j < BOARD_SIZE; p++) {
6838 initial_position[i][j++] = CharToPiece(*p);
6842 blackPlaysFirst = FALSE;
6844 (void) fgets(line, MSG_SIZ, f);
6845 if (strncmp(line, "black", strlen("black"))==0)
6846 blackPlaysFirst = TRUE;
6849 startedFromSetupPosition = TRUE;
6851 SendToProgram("force\n", &first);
6852 CopyBoard(boards[0], initial_position);
6853 if (blackPlaysFirst) {
6854 currentMove = forwardMostMove = backwardMostMove = 1;
6855 strcpy(moveList[0], "");
6856 strcpy(parseList[0], "");
6857 CopyBoard(boards[1], initial_position);
6858 DisplayMessage("", "Black to play");
6860 currentMove = forwardMostMove = backwardMostMove = 0;
6861 DisplayMessage("", "White to play");
6863 SendBoard(&first, forwardMostMove);
6865 if (positionNumber > 1) {
6866 sprintf(line, "%s %d", title, positionNumber);
6869 DisplayTitle(title);
6871 gameMode = EditGame;
6874 timeRemaining[0][1] = whiteTimeRemaining;
6875 timeRemaining[1][1] = blackTimeRemaining;
6876 DrawPosition(FALSE, boards[currentMove]);
6883 CopyPlayerNameIntoFileName(dest, src)
6886 while (*src != NULLCHAR && *src != ',') {
6891 *(*dest)++ = *src++;
6896 char *DefaultFileName(ext)
6899 static char def[MSG_SIZ];
6902 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
6904 CopyPlayerNameIntoFileName(&p, gameInfo.white);
6906 CopyPlayerNameIntoFileName(&p, gameInfo.black);
6915 /* Save the current game to the given file */
6917 SaveGameToFile(filename, append)
6924 if (strcmp(filename, "-") == 0) {
6925 return SaveGame(stdout, 0, NULL);
6927 f = fopen(filename, append ? "a" : "w");
6929 sprintf(buf, "Can't open \"%s\"", filename);
6930 DisplayError(buf, errno);
6933 return SaveGame(f, 0, NULL);
6942 static char buf[MSG_SIZ];
6945 p = strchr(str, ' ');
6946 if (p == NULL) return str;
6947 strncpy(buf, str, p - str);
6948 buf[p - str] = NULLCHAR;
6952 #define PGN_MAX_LINE 75
6954 /* Save game in PGN style and close the file */
6959 int i, offset, linelen, newblock;
6963 int movelen, numlen, blank;
6964 char move_buffer[100]; /* [AS] Buffer for move+PV info */
6966 tm = time((time_t *) NULL);
6968 PrintPGNTags(f, &gameInfo);
6970 if (backwardMostMove > 0 || startedFromSetupPosition) {
6971 char *fen = PositionToFEN(backwardMostMove, 1);
6972 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
6973 fprintf(f, "\n{--------------\n");
6974 PrintPosition(f, backwardMostMove);
6975 fprintf(f, "--------------}\n");
6981 i = backwardMostMove;
6982 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
6986 while (i < forwardMostMove) {
6987 /* Print comments preceding this move */
6988 if (commentList[i] != NULL) {
6989 if (linelen > 0) fprintf(f, "\n");
6990 fprintf(f, "{\n%s}\n", commentList[i]);
6995 /* Format move number */
6997 sprintf(numtext, "%d.", (i - offset)/2 + 1);
7000 sprintf(numtext, "%d...", (i - offset)/2 + 1);
7002 numtext[0] = NULLCHAR;
7005 numlen = strlen(numtext);
7008 /* Print move number */
7009 blank = linelen > 0 && numlen > 0;
7010 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
7019 fprintf(f, numtext);
7023 movetext = SavePart(parseList[i]);
7025 /* [AS] Add PV info if present */
7026 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
7027 sprintf( move_buffer, "%s {%s%.2f/%d}",
7029 pvInfoList[i].score >= 0 ? "+" : "",
7030 pvInfoList[i].score / 100.0,
7031 pvInfoList[i].depth );
7032 movetext = move_buffer;
7035 movelen = strlen(movetext);
7038 blank = linelen > 0 && movelen > 0;
7039 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
7048 fprintf(f, movetext);
7054 /* Start a new line */
7055 if (linelen > 0) fprintf(f, "\n");
7057 /* Print comments after last move */
7058 if (commentList[i] != NULL) {
7059 fprintf(f, "{\n%s}\n", commentList[i]);
7063 if (gameInfo.resultDetails != NULL &&
7064 gameInfo.resultDetails[0] != NULLCHAR) {
7065 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
7066 PGNResult(gameInfo.result));
7068 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
7075 /* Save game in old style and close the file */
7083 tm = time((time_t *) NULL);
7085 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
7088 if (backwardMostMove > 0 || startedFromSetupPosition) {
7089 fprintf(f, "\n[--------------\n");
7090 PrintPosition(f, backwardMostMove);
7091 fprintf(f, "--------------]\n");
7096 i = backwardMostMove;
7097 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
7099 while (i < forwardMostMove) {
7100 if (commentList[i] != NULL) {
7101 fprintf(f, "[%s]\n", commentList[i]);
7105 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
7108 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
7110 if (commentList[i] != NULL) {
7114 if (i >= forwardMostMove) {
7118 fprintf(f, "%s\n", parseList[i]);
7123 if (commentList[i] != NULL) {
7124 fprintf(f, "[%s]\n", commentList[i]);
7127 /* This isn't really the old style, but it's close enough */
7128 if (gameInfo.resultDetails != NULL &&
7129 gameInfo.resultDetails[0] != NULLCHAR) {
7130 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
7131 gameInfo.resultDetails);
7133 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
7140 /* Save the current game to open file f and close the file */
7142 SaveGame(f, dummy, dummy2)
7147 if (gameMode == EditPosition) EditPositionDone();
7148 if (appData.oldSaveStyle)
7149 return SaveGameOldStyle(f);
7151 return SaveGamePGN(f);
7154 /* Save the current position to the given file */
7156 SavePositionToFile(filename)
7162 if (strcmp(filename, "-") == 0) {
7163 return SavePosition(stdout, 0, NULL);
7165 f = fopen(filename, "a");
7167 sprintf(buf, "Can't open \"%s\"", filename);
7168 DisplayError(buf, errno);
7171 SavePosition(f, 0, NULL);
7177 /* Save the current position to the given open file and close the file */
7179 SavePosition(f, dummy, dummy2)
7187 if (appData.oldSaveStyle) {
7188 tm = time((time_t *) NULL);
7190 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
7192 fprintf(f, "[--------------\n");
7193 PrintPosition(f, currentMove);
7194 fprintf(f, "--------------]\n");
7196 fen = PositionToFEN(currentMove, 1);
7197 fprintf(f, "%s\n", fen);
7205 ReloadCmailMsgEvent(unregister)
7209 static char *inFilename = NULL;
7210 static char *outFilename;
7212 struct stat inbuf, outbuf;
7215 /* Any registered moves are unregistered if unregister is set, */
7216 /* i.e. invoked by the signal handler */
7218 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
7219 cmailMoveRegistered[i] = FALSE;
7220 if (cmailCommentList[i] != NULL) {
7221 free(cmailCommentList[i]);
7222 cmailCommentList[i] = NULL;
7225 nCmailMovesRegistered = 0;
7228 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
7229 cmailResult[i] = CMAIL_NOT_RESULT;
7233 if (inFilename == NULL) {
7234 /* Because the filenames are static they only get malloced once */
7235 /* and they never get freed */
7236 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
7237 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
7239 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
7240 sprintf(outFilename, "%s.out", appData.cmailGameName);
7243 status = stat(outFilename, &outbuf);
7245 cmailMailedMove = FALSE;
7247 status = stat(inFilename, &inbuf);
7248 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
7251 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
7252 counts the games, notes how each one terminated, etc.
7254 It would be nice to remove this kludge and instead gather all
7255 the information while building the game list. (And to keep it
7256 in the game list nodes instead of having a bunch of fixed-size
7257 parallel arrays.) Note this will require getting each game's
7258 termination from the PGN tags, as the game list builder does
7259 not process the game moves. --mann
7261 cmailMsgLoaded = TRUE;
7262 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
7264 /* Load first game in the file or popup game menu */
7265 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
7275 char string[MSG_SIZ];
7277 if ( cmailMailedMove
7278 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
7279 return TRUE; /* Allow free viewing */
7282 /* Unregister move to ensure that we don't leave RegisterMove */
7283 /* with the move registered when the conditions for registering no */
7285 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
7286 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
7287 nCmailMovesRegistered --;
7289 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
7291 free(cmailCommentList[lastLoadGameNumber - 1]);
7292 cmailCommentList[lastLoadGameNumber - 1] = NULL;
7296 if (cmailOldMove == -1) {
7297 DisplayError("You have edited the game history.\nUse Reload Same Game and make your move again.", 0);
7301 if (currentMove > cmailOldMove + 1) {
7302 DisplayError("You have entered too many moves.\nBack up to the correct position and try again.", 0);
7306 if (currentMove < cmailOldMove) {
7307 DisplayError("Displayed position is not current.\nStep forward to the correct position and try again.", 0);
7311 if (forwardMostMove > currentMove) {
7312 /* Silently truncate extra moves */
7316 if ( (currentMove == cmailOldMove + 1)
7317 || ( (currentMove == cmailOldMove)
7318 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
7319 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
7320 if (gameInfo.result != GameUnfinished) {
7321 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
7324 if (commentList[currentMove] != NULL) {
7325 cmailCommentList[lastLoadGameNumber - 1]
7326 = StrSave(commentList[currentMove]);
7328 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
7330 if (appData.debugMode)
7331 fprintf(debugFP, "Saving %s for game %d\n",
7332 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
7335 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
7337 f = fopen(string, "w");
7338 if (appData.oldSaveStyle) {
7339 SaveGameOldStyle(f); /* also closes the file */
7341 sprintf(string, "%s.pos.out", appData.cmailGameName);
7342 f = fopen(string, "w");
7343 SavePosition(f, 0, NULL); /* also closes the file */
7345 fprintf(f, "{--------------\n");
7346 PrintPosition(f, currentMove);
7347 fprintf(f, "--------------}\n\n");
7349 SaveGame(f, 0, NULL); /* also closes the file*/
7352 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
7353 nCmailMovesRegistered ++;
7354 } else if (nCmailGames == 1) {
7355 DisplayError("You have not made a move yet", 0);
7366 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
7367 FILE *commandOutput;
7368 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
7369 int nBytes = 0; /* Suppress warnings on uninitialized variables */
7375 if (! cmailMsgLoaded) {
7376 DisplayError("The cmail message is not loaded.\nUse Reload CMail Message and make your move again.", 0);
7380 if (nCmailGames == nCmailResults) {
7381 DisplayError("No unfinished games", 0);
7385 #if CMAIL_PROHIBIT_REMAIL
7386 if (cmailMailedMove) {
7387 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);
7388 DisplayError(msg, 0);
7393 if (! (cmailMailedMove || RegisterMove())) return;
7395 if ( cmailMailedMove
7396 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
7397 sprintf(string, partCommandString,
7398 appData.debugMode ? " -v" : "", appData.cmailGameName);
7399 commandOutput = popen(string, "rb");
7401 if (commandOutput == NULL) {
7402 DisplayError("Failed to invoke cmail", 0);
7404 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
7405 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
7408 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
7409 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
7410 nBytes = MSG_SIZ - 1;
7412 (void) memcpy(msg, buffer, nBytes);
7414 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
7416 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
7417 cmailMailedMove = TRUE; /* Prevent >1 moves */
7420 for (i = 0; i < nCmailGames; i ++) {
7421 if (cmailResult[i] == CMAIL_NOT_RESULT) {
7426 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
7428 sprintf(buffer, "%s/%s.%s.archive",
7430 appData.cmailGameName,
7432 LoadGameFromFile(buffer, 1, buffer, FALSE);
7433 cmailMsgLoaded = FALSE;
7437 DisplayInformation(msg);
7438 pclose(commandOutput);
7441 if ((*cmailMsg) != '\0') {
7442 DisplayInformation(cmailMsg);
7456 int prependComma = 0;
7458 char string[MSG_SIZ]; /* Space for game-list */
7461 if (!cmailMsgLoaded) return "";
7463 if (cmailMailedMove) {
7464 sprintf(cmailMsg, "Waiting for reply from opponent\n");
7466 /* Create a list of games left */
7467 sprintf(string, "[");
7468 for (i = 0; i < nCmailGames; i ++) {
7469 if (! ( cmailMoveRegistered[i]
7470 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
7472 sprintf(number, ",%d", i + 1);
7474 sprintf(number, "%d", i + 1);
7478 strcat(string, number);
7481 strcat(string, "]");
7483 if (nCmailMovesRegistered + nCmailResults == 0) {
7484 switch (nCmailGames) {
7487 "Still need to make move for game\n");
7492 "Still need to make moves for both games\n");
7497 "Still need to make moves for all %d games\n",
7502 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
7505 "Still need to make a move for game %s\n",
7510 if (nCmailResults == nCmailGames) {
7511 sprintf(cmailMsg, "No unfinished games\n");
7513 sprintf(cmailMsg, "Ready to send mail\n");
7519 "Still need to make moves for games %s\n",
7531 if (gameMode == Training)
7532 SetTrainingModeOff();
7535 cmailMsgLoaded = FALSE;
7536 if (appData.icsActive) {
7537 SendToICS(ics_prefix);
7538 SendToICS("refresh\n");
7542 static int exiting = 0;
7550 /* Give up on clean exit */
7554 /* Keep trying for clean exit */
7558 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
7560 if (telnetISR != NULL) {
7561 RemoveInputSource(telnetISR);
7563 if (icsPR != NoProc) {
7564 DestroyChildProcess(icsPR, TRUE);
7566 /* Save game if resource set and not already saved by GameEnds() */
7567 if (gameInfo.resultDetails == NULL && forwardMostMove > 0) {
7568 if (*appData.saveGameFile != NULLCHAR) {
7569 SaveGameToFile(appData.saveGameFile, TRUE);
7570 } else if (appData.autoSaveGames) {
7573 if (*appData.savePositionFile != NULLCHAR) {
7574 SavePositionToFile(appData.savePositionFile);
7577 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
7579 /* Kill off chess programs */
7580 if (first.pr != NoProc) {
7583 DoSleep( appData.delayBeforeQuit );
7584 SendToProgram("quit\n", &first);
7585 DoSleep( appData.delayAfterQuit );
7586 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
7588 if (second.pr != NoProc) {
7589 DoSleep( appData.delayBeforeQuit );
7590 SendToProgram("quit\n", &second);
7591 DoSleep( appData.delayAfterQuit );
7592 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
7594 if (first.isr != NULL) {
7595 RemoveInputSource(first.isr);
7597 if (second.isr != NULL) {
7598 RemoveInputSource(second.isr);
7608 if (appData.debugMode)
7609 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
7613 if (gameMode == MachinePlaysWhite ||
7614 gameMode == MachinePlaysBlack) {
7617 DisplayBothClocks();
7619 if (gameMode == PlayFromGameFile) {
7620 if (appData.timeDelay >= 0)
7622 } else if (gameMode == IcsExamining && pauseExamInvalid) {
7624 SendToICS(ics_prefix);
7625 SendToICS("refresh\n");
7626 } else if (currentMove < forwardMostMove) {
7627 ForwardInner(forwardMostMove);
7629 pauseExamInvalid = FALSE;
7635 pauseExamForwardMostMove = forwardMostMove;
7636 pauseExamInvalid = FALSE;
7639 case IcsPlayingWhite:
7640 case IcsPlayingBlack:
7644 case PlayFromGameFile:
7645 (void) StopLoadGameTimer();
7649 case BeginningOfGame:
7650 if (appData.icsActive) return;
7651 /* else fall through */
7652 case MachinePlaysWhite:
7653 case MachinePlaysBlack:
7654 case TwoMachinesPlay:
7655 if (forwardMostMove == 0)
7656 return; /* don't pause if no one has moved */
7657 if ((gameMode == MachinePlaysWhite &&
7658 !WhiteOnMove(forwardMostMove)) ||
7659 (gameMode == MachinePlaysBlack &&
7660 WhiteOnMove(forwardMostMove))) {
7673 char title[MSG_SIZ];
7675 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
7676 strcpy(title, "Edit comment");
7678 sprintf(title, "Edit comment on %d.%s%s", (currentMove - 1) / 2 + 1,
7679 WhiteOnMove(currentMove - 1) ? " " : ".. ",
7680 parseList[currentMove - 1]);
7683 EditCommentPopUp(currentMove, title, commentList[currentMove]);
7690 char *tags = PGNTags(&gameInfo);
7691 EditTagsPopUp(tags);
7698 if (appData.noChessProgram || gameMode == AnalyzeMode)
7701 if (gameMode != AnalyzeFile) {
7703 if (gameMode != EditGame) return;
7704 ResurrectChessProgram();
7705 SendToProgram("analyze\n", &first);
7706 first.analyzing = TRUE;
7707 /*first.maybeThinking = TRUE;*/
7708 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
7709 AnalysisPopUp("Analysis",
7710 "Starting analysis mode...\nIf this message stays up, your chess program does not support analysis.");
7712 gameMode = AnalyzeMode;
7717 StartAnalysisClock();
7718 GetTimeMark(&lastNodeCountTime);
7725 if (appData.noChessProgram || gameMode == AnalyzeFile)
7728 if (gameMode != AnalyzeMode) {
7730 if (gameMode != EditGame) return;
7731 ResurrectChessProgram();
7732 SendToProgram("analyze\n", &first);
7733 first.analyzing = TRUE;
7734 /*first.maybeThinking = TRUE;*/
7735 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
7736 AnalysisPopUp("Analysis",
7737 "Starting analysis mode...\nIf this message stays up, your chess program does not support analysis.");
7739 gameMode = AnalyzeFile;
7744 StartAnalysisClock();
7745 GetTimeMark(&lastNodeCountTime);
7754 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
7758 if (gameMode == PlayFromGameFile ||
7759 gameMode == TwoMachinesPlay ||
7760 gameMode == Training ||
7761 gameMode == AnalyzeMode ||
7762 gameMode == EndOfGame)
7765 if (gameMode == EditPosition)
7768 if (!WhiteOnMove(currentMove)) {
7769 DisplayError("It is not White's turn", 0);
7773 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
7776 if (gameMode == EditGame || gameMode == AnalyzeMode ||
7777 gameMode == AnalyzeFile)
7780 ResurrectChessProgram(); /* in case it isn't running */
7781 gameMode = MachinePlaysWhite;
7785 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
7787 if (first.sendName) {
7788 sprintf(buf, "name %s\n", gameInfo.black);
7789 SendToProgram(buf, &first);
7791 if (first.sendTime) {
7792 if (first.useColors) {
7793 SendToProgram("black\n", &first); /*gnu kludge*/
7795 SendTimeRemaining(&first, TRUE);
7797 if (first.useColors) {
7798 SendToProgram("white\ngo\n", &first);
7800 SendToProgram("go\n", &first);
7802 SetMachineThinkingEnables();
7803 first.maybeThinking = TRUE;
7806 if (appData.autoFlipView && !flipView) {
7807 flipView = !flipView;
7808 DrawPosition(FALSE, NULL);
7817 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
7821 if (gameMode == PlayFromGameFile ||
7822 gameMode == TwoMachinesPlay ||
7823 gameMode == Training ||
7824 gameMode == AnalyzeMode ||
7825 gameMode == EndOfGame)
7828 if (gameMode == EditPosition)
7831 if (WhiteOnMove(currentMove)) {
7832 DisplayError("It is not Black's turn", 0);
7836 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
7839 if (gameMode == EditGame || gameMode == AnalyzeMode ||
7840 gameMode == AnalyzeFile)
7843 ResurrectChessProgram(); /* in case it isn't running */
7844 gameMode = MachinePlaysBlack;
7848 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
7850 if (first.sendName) {
7851 sprintf(buf, "name %s\n", gameInfo.white);
7852 SendToProgram(buf, &first);
7854 if (first.sendTime) {
7855 if (first.useColors) {
7856 SendToProgram("white\n", &first); /*gnu kludge*/
7858 SendTimeRemaining(&first, FALSE);
7860 if (first.useColors) {
7861 SendToProgram("black\ngo\n", &first);
7863 SendToProgram("go\n", &first);
7865 SetMachineThinkingEnables();
7866 first.maybeThinking = TRUE;
7869 if (appData.autoFlipView && flipView) {
7870 flipView = !flipView;
7871 DrawPosition(FALSE, NULL);
7877 DisplayTwoMachinesTitle()
7880 if (appData.matchGames > 0) {
7881 if (first.twoMachinesColor[0] == 'w') {
7882 sprintf(buf, "%s vs. %s (%d-%d-%d)",
7883 gameInfo.white, gameInfo.black,
7884 first.matchWins, second.matchWins,
7885 matchGame - 1 - (first.matchWins + second.matchWins));
7887 sprintf(buf, "%s vs. %s (%d-%d-%d)",
7888 gameInfo.white, gameInfo.black,
7889 second.matchWins, first.matchWins,
7890 matchGame - 1 - (first.matchWins + second.matchWins));
7893 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
7899 TwoMachinesEvent P((void))
7903 ChessProgramState *onmove;
7905 if (appData.noChessProgram) return;
7908 case TwoMachinesPlay:
7910 case MachinePlaysWhite:
7911 case MachinePlaysBlack:
7912 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
7913 DisplayError("Wait until your turn,\nor select Move Now", 0);
7917 case BeginningOfGame:
7918 case PlayFromGameFile:
7921 if (gameMode != EditGame) return;
7935 forwardMostMove = currentMove;
7936 ResurrectChessProgram(); /* in case first program isn't running */
7938 if (second.pr == NULL) {
7939 StartChessProgram(&second);
7940 if (second.protocolVersion == 1) {
7941 TwoMachinesEventIfReady();
7943 /* kludge: allow timeout for initial "feature" command */
7945 DisplayMessage("", "Starting second chess program");
7946 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
7950 DisplayMessage("", "");
7951 InitChessProgram(&second);
7952 SendToProgram("force\n", &second);
7953 if (startedFromSetupPosition) {
7954 SendBoard(&second, backwardMostMove);
7956 for (i = backwardMostMove; i < forwardMostMove; i++) {
7957 SendMoveToProgram(i, &second);
7960 gameMode = TwoMachinesPlay;
7964 DisplayTwoMachinesTitle();
7966 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
7972 SendToProgram(first.computerString, &first);
7973 if (first.sendName) {
7974 sprintf(buf, "name %s\n", second.tidy);
7975 SendToProgram(buf, &first);
7977 SendToProgram(second.computerString, &second);
7978 if (second.sendName) {
7979 sprintf(buf, "name %s\n", first.tidy);
7980 SendToProgram(buf, &second);
7983 if (!first.sendTime || !second.sendTime) {
7985 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7986 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7988 if (onmove->sendTime) {
7989 if (onmove->useColors) {
7990 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
7992 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
7994 if (onmove->useColors) {
7995 SendToProgram(onmove->twoMachinesColor, onmove);
7997 SendToProgram("go\n", onmove);
7998 onmove->maybeThinking = TRUE;
7999 SetMachineThinkingEnables();
8007 if (gameMode == Training) {
8008 SetTrainingModeOff();
8009 gameMode = PlayFromGameFile;
8010 DisplayMessage("", "Training mode off");
8012 gameMode = Training;
8013 animateTraining = appData.animate;
8015 /* make sure we are not already at the end of the game */
8016 if (currentMove < forwardMostMove) {
8017 SetTrainingModeOn();
8018 DisplayMessage("", "Training mode on");
8020 gameMode = PlayFromGameFile;
8021 DisplayError("Already at end of game", 0);
8030 if (!appData.icsActive) return;
8032 case IcsPlayingWhite:
8033 case IcsPlayingBlack:
8036 case BeginningOfGame:
8070 SetTrainingModeOff();
8072 case MachinePlaysWhite:
8073 case MachinePlaysBlack:
8074 case BeginningOfGame:
8075 SendToProgram("force\n", &first);
8076 SetUserThinkingEnables();
8078 case PlayFromGameFile:
8079 (void) StopLoadGameTimer();
8080 if (gameFileFP != NULL) {
8090 SendToProgram("force\n", &first);
8092 case TwoMachinesPlay:
8093 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8094 ResurrectChessProgram();
8095 SetUserThinkingEnables();
8098 ResurrectChessProgram();
8100 case IcsPlayingBlack:
8101 case IcsPlayingWhite:
8102 DisplayError("Warning: You are still playing a game", 0);
8105 DisplayError("Warning: You are still observing a game", 0);
8108 DisplayError("Warning: You are still examining a game", 0);
8119 first.offeredDraw = second.offeredDraw = 0;
8121 if (gameMode == PlayFromGameFile) {
8122 whiteTimeRemaining = timeRemaining[0][currentMove];
8123 blackTimeRemaining = timeRemaining[1][currentMove];
8127 if (gameMode == MachinePlaysWhite ||
8128 gameMode == MachinePlaysBlack ||
8129 gameMode == TwoMachinesPlay ||
8130 gameMode == EndOfGame) {
8131 i = forwardMostMove;
8132 while (i > currentMove) {
8133 SendToProgram("undo\n", &first);
8136 whiteTimeRemaining = timeRemaining[0][currentMove];
8137 blackTimeRemaining = timeRemaining[1][currentMove];
8138 DisplayBothClocks();
8139 if (whiteFlag || blackFlag) {
8140 whiteFlag = blackFlag = 0;
8145 gameMode = EditGame;
8154 if (gameMode == EditPosition) {
8160 if (gameMode != EditGame) return;
8162 gameMode = EditPosition;
8165 if (currentMove > 0)
8166 CopyBoard(boards[0], boards[currentMove]);
8168 blackPlaysFirst = !WhiteOnMove(currentMove);
8170 currentMove = forwardMostMove = backwardMostMove = 0;
8171 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8178 if (first.analysisSupport && first.analyzing) {
8179 SendToProgram("exit\n", &first);
8180 first.analyzing = FALSE;
8183 thinkOutput[0] = NULLCHAR;
8189 startedFromSetupPosition = TRUE;
8190 InitChessProgram(&first);
8191 SendToProgram("force\n", &first);
8192 if (blackPlaysFirst) {
8193 strcpy(moveList[0], "");
8194 strcpy(parseList[0], "");
8195 currentMove = forwardMostMove = backwardMostMove = 1;
8196 CopyBoard(boards[1], boards[0]);
8198 currentMove = forwardMostMove = backwardMostMove = 0;
8200 SendBoard(&first, forwardMostMove);
8202 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8203 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8204 gameMode = EditGame;
8206 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8209 /* Pause for `ms' milliseconds */
8210 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
8220 } while (SubtractTimeMarks(&m2, &m1) < ms);
8223 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
8225 SendMultiLineToICS(buf)
8228 char temp[MSG_SIZ+1], *p;
8235 strncpy(temp, buf, len);
8240 if (*p == '\n' || *p == '\r')
8247 SendToPlayer(temp, strlen(temp));
8251 SetWhiteToPlayEvent()
8253 if (gameMode == EditPosition) {
8254 blackPlaysFirst = FALSE;
8255 DisplayBothClocks(); /* works because currentMove is 0 */
8256 } else if (gameMode == IcsExamining) {
8257 SendToICS(ics_prefix);
8258 SendToICS("tomove white\n");
8263 SetBlackToPlayEvent()
8265 if (gameMode == EditPosition) {
8266 blackPlaysFirst = TRUE;
8267 currentMove = 1; /* kludge */
8268 DisplayBothClocks();
8270 } else if (gameMode == IcsExamining) {
8271 SendToICS(ics_prefix);
8272 SendToICS("tomove black\n");
8277 EditPositionMenuEvent(selection, x, y)
8278 ChessSquare selection;
8283 if (gameMode != EditPosition && gameMode != IcsExamining) return;
8285 switch (selection) {
8287 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
8288 SendToICS(ics_prefix);
8289 SendToICS("bsetup clear\n");
8290 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
8291 SendToICS(ics_prefix);
8292 SendToICS("clearboard\n");
8294 for (x = 0; x < BOARD_SIZE; x++) {
8295 for (y = 0; y < BOARD_SIZE; y++) {
8296 if (gameMode == IcsExamining) {
8297 if (boards[currentMove][y][x] != EmptySquare) {
8298 sprintf(buf, "%sx@%c%c\n", ics_prefix,
8303 boards[0][y][x] = EmptySquare;
8308 if (gameMode == EditPosition) {
8309 DrawPosition(FALSE, boards[0]);
8314 SetWhiteToPlayEvent();
8318 SetBlackToPlayEvent();
8322 if (gameMode == IcsExamining) {
8323 sprintf(buf, "%sx@%c%c\n", ics_prefix, 'a' + x, '1' + y);
8326 boards[0][y][x] = EmptySquare;
8327 DrawPosition(FALSE, boards[0]);
8332 if (gameMode == IcsExamining) {
8333 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
8334 PieceToChar(selection), 'a' + x, '1' + y);
8337 boards[0][y][x] = selection;
8338 DrawPosition(FALSE, boards[0]);
8346 DropMenuEvent(selection, x, y)
8347 ChessSquare selection;
8353 case IcsPlayingWhite:
8354 case MachinePlaysBlack:
8355 if (!WhiteOnMove(currentMove)) {
8356 DisplayMoveError("It is Black's turn");
8359 moveType = WhiteDrop;
8361 case IcsPlayingBlack:
8362 case MachinePlaysWhite:
8363 if (WhiteOnMove(currentMove)) {
8364 DisplayMoveError("It is White's turn");
8367 moveType = BlackDrop;
8370 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
8376 if (moveType == BlackDrop && selection < BlackPawn) {
8377 selection = (ChessSquare) ((int) selection
8378 + (int) BlackPawn - (int) WhitePawn);
8380 if (boards[currentMove][y][x] != EmptySquare) {
8381 DisplayMoveError("That square is occupied");
8385 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
8391 /* Accept a pending offer of any kind from opponent */
8393 if (appData.icsActive) {
8394 SendToICS(ics_prefix);
8395 SendToICS("accept\n");
8396 } else if (cmailMsgLoaded) {
8397 if (currentMove == cmailOldMove &&
8398 commentList[cmailOldMove] != NULL &&
8399 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
8400 "Black offers a draw" : "White offers a draw")) {
8402 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8403 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
8405 DisplayError("There is no pending offer on this move", 0);
8406 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8409 /* Not used for offers from chess program */
8416 /* Decline a pending offer of any kind from opponent */
8418 if (appData.icsActive) {
8419 SendToICS(ics_prefix);
8420 SendToICS("decline\n");
8421 } else if (cmailMsgLoaded) {
8422 if (currentMove == cmailOldMove &&
8423 commentList[cmailOldMove] != NULL &&
8424 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
8425 "Black offers a draw" : "White offers a draw")) {
8427 AppendComment(cmailOldMove, "Draw declined");
8428 DisplayComment(cmailOldMove - 1, "Draw declined");
8431 DisplayError("There is no pending offer on this move", 0);
8434 /* Not used for offers from chess program */
8441 /* Issue ICS rematch command */
8442 if (appData.icsActive) {
8443 SendToICS(ics_prefix);
8444 SendToICS("rematch\n");
8451 /* Call your opponent's flag (claim a win on time) */
8452 if (appData.icsActive) {
8453 SendToICS(ics_prefix);
8454 SendToICS("flag\n");
8459 case MachinePlaysWhite:
8462 GameEnds(GameIsDrawn, "Both players ran out of time",
8465 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
8467 DisplayError("Your opponent is not out of time", 0);
8470 case MachinePlaysBlack:
8473 GameEnds(GameIsDrawn, "Both players ran out of time",
8476 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
8478 DisplayError("Your opponent is not out of time", 0);
8488 /* Offer draw or accept pending draw offer from opponent */
8490 if (appData.icsActive) {
8491 /* Note: tournament rules require draw offers to be
8492 made after you make your move but before you punch
8493 your clock. Currently ICS doesn't let you do that;
8494 instead, you immediately punch your clock after making
8495 a move, but you can offer a draw at any time. */
8497 SendToICS(ics_prefix);
8498 SendToICS("draw\n");
8499 } else if (cmailMsgLoaded) {
8500 if (currentMove == cmailOldMove &&
8501 commentList[cmailOldMove] != NULL &&
8502 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
8503 "Black offers a draw" : "White offers a draw")) {
8504 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8505 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
8506 } else if (currentMove == cmailOldMove + 1) {
8507 char *offer = WhiteOnMove(cmailOldMove) ?
8508 "White offers a draw" : "Black offers a draw";
8509 AppendComment(currentMove, offer);
8510 DisplayComment(currentMove - 1, offer);
8511 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
8513 DisplayError("You must make your move before offering a draw", 0);
8514 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8516 } else if (first.offeredDraw) {
8517 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8519 if (first.sendDrawOffers) {
8520 SendToProgram("draw\n", &first);
8521 userOfferedDraw = TRUE;
8529 /* Offer Adjourn or accept pending Adjourn offer from opponent */
8531 if (appData.icsActive) {
8532 SendToICS(ics_prefix);
8533 SendToICS("adjourn\n");
8535 /* Currently GNU Chess doesn't offer or accept Adjourns */
8543 /* Offer Abort or accept pending Abort offer from opponent */
8545 if (appData.icsActive) {
8546 SendToICS(ics_prefix);
8547 SendToICS("abort\n");
8549 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
8556 /* Resign. You can do this even if it's not your turn. */
8558 if (appData.icsActive) {
8559 SendToICS(ics_prefix);
8560 SendToICS("resign\n");
8563 case MachinePlaysWhite:
8564 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8566 case MachinePlaysBlack:
8567 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8570 if (cmailMsgLoaded) {
8572 if (WhiteOnMove(cmailOldMove)) {
8573 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8575 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8577 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
8588 StopObservingEvent()
8590 /* Stop observing current games */
8591 SendToICS(ics_prefix);
8592 SendToICS("unobserve\n");
8596 StopExaminingEvent()
8598 /* Stop observing current game */
8599 SendToICS(ics_prefix);
8600 SendToICS("unexamine\n");
8604 ForwardInner(target)
8609 if (appData.debugMode)
8610 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
8611 target, currentMove, forwardMostMove);
8613 if (gameMode == EditPosition)
8616 if (gameMode == PlayFromGameFile && !pausing)
8619 if (gameMode == IcsExamining && pausing)
8620 limit = pauseExamForwardMostMove;
8622 limit = forwardMostMove;
8624 if (target > limit) target = limit;
8626 if (target > 0 && moveList[target - 1][0]) {
8627 int fromX, fromY, toX, toY;
8628 toX = moveList[target - 1][2] - 'a';
8629 toY = moveList[target - 1][3] - '1';
8630 if (moveList[target - 1][1] == '@') {
8631 if (appData.highlightLastMove) {
8632 SetHighlights(-1, -1, toX, toY);
8635 fromX = moveList[target - 1][0] - 'a';
8636 fromY = moveList[target - 1][1] - '1';
8637 if (target == currentMove + 1) {
8638 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8640 if (appData.highlightLastMove) {
8641 SetHighlights(fromX, fromY, toX, toY);
8645 if (gameMode == EditGame || gameMode == AnalyzeMode ||
8646 gameMode == Training || gameMode == PlayFromGameFile ||
8647 gameMode == AnalyzeFile) {
8648 while (currentMove < target) {
8649 SendMoveToProgram(currentMove++, &first);
8652 currentMove = target;
8655 if (gameMode == EditGame || gameMode == EndOfGame) {
8656 whiteTimeRemaining = timeRemaining[0][currentMove];
8657 blackTimeRemaining = timeRemaining[1][currentMove];
8659 DisplayBothClocks();
8660 DisplayMove(currentMove - 1);
8661 DrawPosition(FALSE, boards[currentMove]);
8662 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8663 if (commentList[currentMove] && !matchMode && gameMode != Training) {
8664 DisplayComment(currentMove - 1, commentList[currentMove]);
8672 if (gameMode == IcsExamining && !pausing) {
8673 SendToICS(ics_prefix);
8674 SendToICS("forward\n");
8676 ForwardInner(currentMove + 1);
8683 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
8684 /* to optimze, we temporarily turn off analysis mode while we feed
8685 * the remaining moves to the engine. Otherwise we get analysis output
8688 if (first.analysisSupport) {
8689 SendToProgram("exit\nforce\n", &first);
8690 first.analyzing = FALSE;
8694 if (gameMode == IcsExamining && !pausing) {
8695 SendToICS(ics_prefix);
8696 SendToICS("forward 999999\n");
8698 ForwardInner(forwardMostMove);
8701 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
8702 /* we have fed all the moves, so reactivate analysis mode */
8703 SendToProgram("analyze\n", &first);
8704 first.analyzing = TRUE;
8705 /*first.maybeThinking = TRUE;*/
8706 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
8711 BackwardInner(target)
8714 if (appData.debugMode)
8715 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
8716 target, currentMove, forwardMostMove);
8718 if (gameMode == EditPosition) return;
8719 if (currentMove <= backwardMostMove) {
8721 DrawPosition(FALSE, boards[currentMove]);
8724 if (gameMode == PlayFromGameFile && !pausing)
8727 if (moveList[target][0]) {
8728 int fromX, fromY, toX, toY;
8729 toX = moveList[target][2] - 'a';
8730 toY = moveList[target][3] - '1';
8731 if (moveList[target][1] == '@') {
8732 if (appData.highlightLastMove) {
8733 SetHighlights(-1, -1, toX, toY);
8736 fromX = moveList[target][0] - 'a';
8737 fromY = moveList[target][1] - '1';
8738 if (target == currentMove - 1) {
8739 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
8741 if (appData.highlightLastMove) {
8742 SetHighlights(fromX, fromY, toX, toY);
8746 if (gameMode == EditGame || gameMode==AnalyzeMode ||
8747 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8748 while (currentMove > target) {
8749 SendToProgram("undo\n", &first);
8753 currentMove = target;
8756 if (gameMode == EditGame || gameMode == EndOfGame) {
8757 whiteTimeRemaining = timeRemaining[0][currentMove];
8758 blackTimeRemaining = timeRemaining[1][currentMove];
8760 DisplayBothClocks();
8761 DisplayMove(currentMove - 1);
8762 DrawPosition(FALSE, boards[currentMove]);
8763 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8764 if (commentList[currentMove] != NULL) {
8765 DisplayComment(currentMove - 1, commentList[currentMove]);
8772 if (gameMode == IcsExamining && !pausing) {
8773 SendToICS(ics_prefix);
8774 SendToICS("backward\n");
8776 BackwardInner(currentMove - 1);
8783 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
8784 /* to optimze, we temporarily turn off analysis mode while we undo
8785 * all the moves. Otherwise we get analysis output after each undo.
8787 if (first.analysisSupport) {
8788 SendToProgram("exit\nforce\n", &first);
8789 first.analyzing = FALSE;
8793 if (gameMode == IcsExamining && !pausing) {
8794 SendToICS(ics_prefix);
8795 SendToICS("backward 999999\n");
8797 BackwardInner(backwardMostMove);
8800 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
8801 /* we have fed all the moves, so reactivate analysis mode */
8802 SendToProgram("analyze\n", &first);
8803 first.analyzing = TRUE;
8804 /*first.maybeThinking = TRUE;*/
8805 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
8812 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
8813 if (to >= forwardMostMove) to = forwardMostMove;
8814 if (to <= backwardMostMove) to = backwardMostMove;
8815 if (to < currentMove) {
8825 if (gameMode != IcsExamining) {
8826 DisplayError("You are not examining a game", 0);
8830 DisplayError("You can't revert while pausing", 0);
8833 SendToICS(ics_prefix);
8834 SendToICS("revert\n");
8841 case MachinePlaysWhite:
8842 case MachinePlaysBlack:
8843 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
8844 DisplayError("Wait until your turn,\nor select Move Now", 0);
8847 if (forwardMostMove < 2) return;
8848 currentMove = forwardMostMove = forwardMostMove - 2;
8849 whiteTimeRemaining = timeRemaining[0][currentMove];
8850 blackTimeRemaining = timeRemaining[1][currentMove];
8851 DisplayBothClocks();
8852 DisplayMove(currentMove - 1);
8853 ClearHighlights();/*!! could figure this out*/
8854 DrawPosition(FALSE, boards[currentMove]);
8855 SendToProgram("remove\n", &first);
8856 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
8859 case BeginningOfGame:
8863 case IcsPlayingWhite:
8864 case IcsPlayingBlack:
8865 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
8866 SendToICS(ics_prefix);
8867 SendToICS("takeback 2\n");
8869 SendToICS(ics_prefix);
8870 SendToICS("takeback 1\n");
8879 ChessProgramState *cps;
8882 case MachinePlaysWhite:
8883 if (!WhiteOnMove(forwardMostMove)) {
8884 DisplayError("It is your turn", 0);
8889 case MachinePlaysBlack:
8890 if (WhiteOnMove(forwardMostMove)) {
8891 DisplayError("It is your turn", 0);
8896 case TwoMachinesPlay:
8897 if (WhiteOnMove(forwardMostMove) ==
8898 (first.twoMachinesColor[0] == 'w')) {
8904 case BeginningOfGame:
8908 SendToProgram("?\n", cps);
8915 if (gameMode != EditGame) return;
8922 if (forwardMostMove > currentMove) {
8923 if (gameInfo.resultDetails != NULL) {
8924 free(gameInfo.resultDetails);
8925 gameInfo.resultDetails = NULL;
8926 gameInfo.result = GameUnfinished;
8928 forwardMostMove = currentMove;
8929 HistorySet(parseList, backwardMostMove, forwardMostMove,
8937 if (appData.noChessProgram) return;
8939 case MachinePlaysWhite:
8940 if (WhiteOnMove(forwardMostMove)) {
8941 DisplayError("Wait until your turn", 0);
8945 case BeginningOfGame:
8946 case MachinePlaysBlack:
8947 if (!WhiteOnMove(forwardMostMove)) {
8948 DisplayError("Wait until your turn", 0);
8953 DisplayError("No hint available", 0);
8956 SendToProgram("hint\n", &first);
8957 hintRequested = TRUE;
8963 if (appData.noChessProgram) return;
8965 case MachinePlaysWhite:
8966 if (WhiteOnMove(forwardMostMove)) {
8967 DisplayError("Wait until your turn", 0);
8971 case BeginningOfGame:
8972 case MachinePlaysBlack:
8973 if (!WhiteOnMove(forwardMostMove)) {
8974 DisplayError("Wait until your turn", 0);
8981 case TwoMachinesPlay:
8986 SendToProgram("bk\n", &first);
8987 bookOutput[0] = NULLCHAR;
8988 bookRequested = TRUE;
8994 char *tags = PGNTags(&gameInfo);
8995 TagsPopUp(tags, CmailMsg());
8999 /* end button procedures */
9002 PrintPosition(fp, move)
9008 for (i = BOARD_SIZE - 1; i >= 0; i--) {
9009 for (j = 0; j < BOARD_SIZE; j++) {
9010 char c = PieceToChar(boards[move][i][j]);
9011 fputc(c == 'x' ? '.' : c, fp);
9012 fputc(j == BOARD_SIZE - 1 ? '\n' : ' ', fp);
9015 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
9016 fprintf(fp, "white to play\n");
9018 fprintf(fp, "black to play\n");
9025 if (gameInfo.white != NULL) {
9026 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
9032 /* Find last component of program's own name, using some heuristics */
9034 TidyProgramName(prog, host, buf)
9035 char *prog, *host, buf[MSG_SIZ];
9038 int local = (strcmp(host, "localhost") == 0);
9039 while (!local && (p = strchr(prog, ';')) != NULL) {
9041 while (*p == ' ') p++;
9044 if (*prog == '"' || *prog == '\'') {
9045 q = strchr(prog + 1, *prog);
9047 q = strchr(prog, ' ');
9049 if (q == NULL) q = prog + strlen(prog);
9051 while (p >= prog && *p != '/' && *p != '\\') p--;
9053 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
9054 memcpy(buf, p, q - p);
9055 buf[q - p] = NULLCHAR;
9063 TimeControlTagValue()
9066 if (!appData.clockMode) {
9068 } else if (movesPerSession > 0) {
9069 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
9070 } else if (timeIncrement == 0) {
9071 sprintf(buf, "%ld", timeControl/1000);
9073 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
9075 return StrSave(buf);
9081 /* This routine is used only for certain modes */
9082 VariantClass v = gameInfo.variant;
9083 ClearGameInfo(&gameInfo);
9084 gameInfo.variant = v;
9087 case MachinePlaysWhite:
9088 gameInfo.event = StrSave( appData.pgnEventHeader );
9089 gameInfo.site = StrSave(HostName());
9090 gameInfo.date = PGNDate();
9091 gameInfo.round = StrSave("-");
9092 gameInfo.white = StrSave(first.tidy);
9093 gameInfo.black = StrSave(UserName());
9094 gameInfo.timeControl = TimeControlTagValue();
9097 case MachinePlaysBlack:
9098 gameInfo.event = StrSave( appData.pgnEventHeader );
9099 gameInfo.site = StrSave(HostName());
9100 gameInfo.date = PGNDate();
9101 gameInfo.round = StrSave("-");
9102 gameInfo.white = StrSave(UserName());
9103 gameInfo.black = StrSave(first.tidy);
9104 gameInfo.timeControl = TimeControlTagValue();
9107 case TwoMachinesPlay:
9108 gameInfo.event = StrSave( appData.pgnEventHeader );
9109 gameInfo.site = StrSave(HostName());
9110 gameInfo.date = PGNDate();
9111 if (matchGame > 0) {
9113 sprintf(buf, "%d", matchGame);
9114 gameInfo.round = StrSave(buf);
9116 gameInfo.round = StrSave("-");
9118 if (first.twoMachinesColor[0] == 'w') {
9119 gameInfo.white = StrSave(first.tidy);
9120 gameInfo.black = StrSave(second.tidy);
9122 gameInfo.white = StrSave(second.tidy);
9123 gameInfo.black = StrSave(first.tidy);
9125 gameInfo.timeControl = TimeControlTagValue();
9129 gameInfo.event = StrSave("Edited game");
9130 gameInfo.site = StrSave(HostName());
9131 gameInfo.date = PGNDate();
9132 gameInfo.round = StrSave("-");
9133 gameInfo.white = StrSave("-");
9134 gameInfo.black = StrSave("-");
9138 gameInfo.event = StrSave("Edited position");
9139 gameInfo.site = StrSave(HostName());
9140 gameInfo.date = PGNDate();
9141 gameInfo.round = StrSave("-");
9142 gameInfo.white = StrSave("-");
9143 gameInfo.black = StrSave("-");
9146 case IcsPlayingWhite:
9147 case IcsPlayingBlack:
9152 case PlayFromGameFile:
9153 gameInfo.event = StrSave("Game from non-PGN file");
9154 gameInfo.site = StrSave(HostName());
9155 gameInfo.date = PGNDate();
9156 gameInfo.round = StrSave("-");
9157 gameInfo.white = StrSave("?");
9158 gameInfo.black = StrSave("?");
9167 ReplaceComment(index, text)
9173 while (*text == '\n') text++;
9175 while (len > 0 && text[len - 1] == '\n') len--;
9177 if (commentList[index] != NULL)
9178 free(commentList[index]);
9181 commentList[index] = NULL;
9184 commentList[index] = (char *) malloc(len + 2);
9185 strncpy(commentList[index], text, len);
9186 commentList[index][len] = '\n';
9187 commentList[index][len + 1] = NULLCHAR;
9200 if (ch == '\r') continue;
9202 } while (ch != '\0');
9206 AppendComment(index, text)
9214 while (*text == '\n') text++;
9216 while (len > 0 && text[len - 1] == '\n') len--;
9218 if (len == 0) return;
9220 if (commentList[index] != NULL) {
9221 old = commentList[index];
9222 oldlen = strlen(old);
9223 commentList[index] = (char *) malloc(oldlen + len + 2);
9224 strcpy(commentList[index], old);
9226 strncpy(&commentList[index][oldlen], text, len);
9227 commentList[index][oldlen + len] = '\n';
9228 commentList[index][oldlen + len + 1] = NULLCHAR;
9230 commentList[index] = (char *) malloc(len + 2);
9231 strncpy(commentList[index], text, len);
9232 commentList[index][len] = '\n';
9233 commentList[index][len + 1] = NULLCHAR;
9238 SendToProgram(message, cps)
9240 ChessProgramState *cps;
9242 int count, outCount, error;
9245 if (cps->pr == NULL) return;
9248 if (appData.debugMode) {
9251 fprintf(debugFP, "%ld >%-6s: %s",
9252 SubtractTimeMarks(&now, &programStartTime),
9253 cps->which, message);
9256 count = strlen(message);
9257 outCount = OutputToProcess(cps->pr, message, count, &error);
9258 if (outCount < count && !exiting) {
9259 sprintf(buf, "Error writing to %s chess program", cps->which);
9260 DisplayFatalError(buf, error, 1);
9265 ReceiveFromProgram(isr, closure, message, count, error)
9274 ChessProgramState *cps = (ChessProgramState *)closure;
9276 if (isr != cps->isr) return; /* Killed intentionally */
9280 "Error: %s chess program (%s) exited unexpectedly",
9281 cps->which, cps->program);
9282 RemoveInputSource(cps->isr);
9283 DisplayFatalError(buf, 0, 1);
9286 "Error reading from %s chess program (%s)",
9287 cps->which, cps->program);
9288 RemoveInputSource(cps->isr);
9290 /* [AS] Program is misbehaving badly... kill it */
9292 DestroyChildProcess( cps->pr, 9 );
9296 DisplayFatalError(buf, error, 1);
9298 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9302 if ((end_str = strchr(message, '\r')) != NULL)
9303 *end_str = NULLCHAR;
9304 if ((end_str = strchr(message, '\n')) != NULL)
9305 *end_str = NULLCHAR;
9307 if (appData.debugMode) {
9310 fprintf(debugFP, "%ld <%-6s: %s\n",
9311 SubtractTimeMarks(&now, &programStartTime),
9312 cps->which, message);
9314 HandleMachineMove(message, cps);
9319 SendTimeControl(cps, mps, tc, inc, sd, st)
9320 ChessProgramState *cps;
9321 int mps, inc, sd, st;
9325 int seconds = (tc / 1000) % 60;
9327 if( timeControl_2 > 0 ) {
9328 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
9334 /* Set exact time per move, normally using st command */
9335 if (cps->stKludge) {
9336 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
9339 sprintf(buf, "level 1 %d\n", st/60);
9341 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
9344 sprintf(buf, "st %d\n", st);
9347 /* Set conventional or incremental time control, using level command */
9349 /* Note old gnuchess bug -- minutes:seconds used to not work.
9350 Fixed in later versions, but still avoid :seconds
9351 when seconds is 0. */
9352 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
9354 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
9358 SendToProgram(buf, cps);
9360 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
9361 /* Orthogonally, limit search to given depth */
9363 if (cps->sdKludge) {
9364 sprintf(buf, "depth\n%d\n", sd);
9366 sprintf(buf, "sd %d\n", sd);
9368 SendToProgram(buf, cps);
9373 SendTimeRemaining(cps, machineWhite)
9374 ChessProgramState *cps;
9375 int /*boolean*/ machineWhite;
9377 char message[MSG_SIZ];
9380 /* Note: this routine must be called when the clocks are stopped
9381 or when they have *just* been set or switched; otherwise
9382 it will be off by the time since the current tick started.
9385 time = whiteTimeRemaining / 10;
9386 otime = blackTimeRemaining / 10;
9388 time = blackTimeRemaining / 10;
9389 otime = whiteTimeRemaining / 10;
9391 if (time <= 0) time = 1;
9392 if (otime <= 0) otime = 1;
9394 sprintf(message, "time %ld\notim %ld\n", time, otime);
9395 SendToProgram(message, cps);
9399 BoolFeature(p, name, loc, cps)
9403 ChessProgramState *cps;
9406 int len = strlen(name);
9408 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
9410 sscanf(*p, "%d", &val);
9412 while (**p && **p != ' ') (*p)++;
9413 sprintf(buf, "accepted %s\n", name);
9414 SendToProgram(buf, cps);
9421 IntFeature(p, name, loc, cps)
9425 ChessProgramState *cps;
9428 int len = strlen(name);
9429 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
9431 sscanf(*p, "%d", loc);
9432 while (**p && **p != ' ') (*p)++;
9433 sprintf(buf, "accepted %s\n", name);
9434 SendToProgram(buf, cps);
9441 StringFeature(p, name, loc, cps)
9445 ChessProgramState *cps;
9448 int len = strlen(name);
9449 if (strncmp((*p), name, len) == 0
9450 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
9452 sscanf(*p, "%[^\"]", loc);
9453 while (**p && **p != '\"') (*p)++;
9454 if (**p == '\"') (*p)++;
9455 sprintf(buf, "accepted %s\n", name);
9456 SendToProgram(buf, cps);
9463 FeatureDone(cps, val)
9464 ChessProgramState* cps;
9467 DelayedEventCallback cb = GetDelayedEvent();
9468 if ((cb == InitBackEnd3 && cps == &first) ||
9469 (cb == TwoMachinesEventIfReady && cps == &second)) {
9470 CancelDelayedEvent();
9471 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
9473 cps->initDone = val;
9476 /* Parse feature command from engine */
9478 ParseFeatures(args, cps)
9480 ChessProgramState *cps;
9488 while (*p == ' ') p++;
9489 if (*p == NULLCHAR) return;
9491 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
9492 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
9493 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
9494 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
9495 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
9496 if (BoolFeature(&p, "reuse", &val, cps)) {
9497 /* Engine can disable reuse, but can't enable it if user said no */
9498 if (!val) cps->reuse = FALSE;
9501 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
9502 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
9503 if (gameMode == TwoMachinesPlay) {
9504 DisplayTwoMachinesTitle();
9510 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
9511 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
9512 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
9513 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
9514 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
9515 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
9516 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
9517 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
9518 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
9519 if (IntFeature(&p, "done", &val, cps)) {
9520 FeatureDone(cps, val);
9523 /* Added by Tord: */
9524 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
9525 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
9526 /* End of additions by Tord */
9528 /* unknown feature: complain and skip */
9530 while (*q && *q != '=') q++;
9531 sprintf(buf, "rejected %.*s\n", q-p, p);
9532 SendToProgram(buf, cps);
9538 while (*p && *p != '\"') p++;
9539 if (*p == '\"') p++;
9541 while (*p && *p != ' ') p++;
9549 PeriodicUpdatesEvent(newState)
9552 if (newState == appData.periodicUpdates)
9555 appData.periodicUpdates=newState;
9557 /* Display type changes, so update it now */
9560 /* Get the ball rolling again... */
9562 AnalysisPeriodicEvent(1);
9563 StartAnalysisClock();
9568 PonderNextMoveEvent(newState)
9571 if (newState == appData.ponderNextMove) return;
9572 if (gameMode == EditPosition) EditPositionDone();
9574 SendToProgram("hard\n", &first);
9575 if (gameMode == TwoMachinesPlay) {
9576 SendToProgram("hard\n", &second);
9579 SendToProgram("easy\n", &first);
9580 thinkOutput[0] = NULLCHAR;
9581 if (gameMode == TwoMachinesPlay) {
9582 SendToProgram("easy\n", &second);
9585 appData.ponderNextMove = newState;
9589 ShowThinkingEvent(newState)
9592 if (newState == appData.showThinking) return;
9593 if (gameMode == EditPosition) EditPositionDone();
9595 SendToProgram("post\n", &first);
9596 if (gameMode == TwoMachinesPlay) {
9597 SendToProgram("post\n", &second);
9600 SendToProgram("nopost\n", &first);
9601 thinkOutput[0] = NULLCHAR;
9602 if (gameMode == TwoMachinesPlay) {
9603 SendToProgram("nopost\n", &second);
9606 appData.showThinking = newState;
9610 AskQuestionEvent(title, question, replyPrefix, which)
9611 char *title; char *question; char *replyPrefix; char *which;
9613 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
9614 if (pr == NoProc) return;
9615 AskQuestion(title, question, replyPrefix, pr);
9619 DisplayMove(moveNumber)
9622 char message[MSG_SIZ];
9624 char cpThinkOutput[MSG_SIZ];
9626 if (moveNumber == forwardMostMove - 1 ||
9627 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
9629 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
9631 if (strchr(cpThinkOutput, '\n')) {
9632 *strchr(cpThinkOutput, '\n') = NULLCHAR;
9635 *cpThinkOutput = NULLCHAR;
9638 /* [AS] Hide thinking from human user */
9639 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
9640 *cpThinkOutput = NULLCHAR;
9641 if( thinkOutput[0] != NULLCHAR ) {
9644 for( i=0; i<=hiddenThinkOutputState; i++ ) {
9645 cpThinkOutput[i] = '.';
9647 cpThinkOutput[i] = NULLCHAR;
9648 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
9652 if (moveNumber == forwardMostMove - 1 &&
9653 gameInfo.resultDetails != NULL) {
9654 if (gameInfo.resultDetails[0] == NULLCHAR) {
9655 sprintf(res, " %s", PGNResult(gameInfo.result));
9657 sprintf(res, " {%s} %s",
9658 gameInfo.resultDetails, PGNResult(gameInfo.result));
9664 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
9665 DisplayMessage(res, cpThinkOutput);
9667 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
9668 WhiteOnMove(moveNumber) ? " " : ".. ",
9669 parseList[moveNumber], res);
9670 DisplayMessage(message, cpThinkOutput);
9675 DisplayAnalysisText(text)
9680 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
9681 sprintf(buf, "Analysis (%s)", first.tidy);
9682 AnalysisPopUp(buf, text);
9690 while (*str && isspace(*str)) ++str;
9691 while (*str && !isspace(*str)) ++str;
9692 if (!*str) return 1;
9693 while (*str && isspace(*str)) ++str;
9694 if (!*str) return 1;
9702 char lst[MSG_SIZ / 2];
9704 static char *xtra[] = { "", " (--)", " (++)" };
9707 if (programStats.time == 0) {
9708 programStats.time = 1;
9711 if (programStats.got_only_move) {
9712 safeStrCpy(buf, programStats.movelist, sizeof(buf));
9714 safeStrCpy( lst, programStats.movelist, sizeof(lst));
9716 nps = (((double)programStats.nodes) /
9717 (((double)programStats.time)/100.0));
9719 cs = programStats.time % 100;
9720 s = programStats.time / 100;
9726 if (programStats.moves_left > 0 && appData.periodicUpdates) {
9727 if (programStats.move_name[0] != NULLCHAR) {
9728 sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",
9730 programStats.nr_moves-programStats.moves_left,
9731 programStats.nr_moves, programStats.move_name,
9732 ((float)programStats.score)/100.0, lst,
9734 xtra[programStats.got_fail] : "",
9735 programStats.nodes, (int)nps, h, m, s, cs);
9737 sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",
9739 programStats.nr_moves-programStats.moves_left,
9740 programStats.nr_moves, ((float)programStats.score)/100.0,
9743 xtra[programStats.got_fail] : "",
9744 programStats.nodes, (int)nps, h, m, s, cs);
9747 sprintf(buf, "depth=%d %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",
9749 ((float)programStats.score)/100.0,
9752 xtra[programStats.got_fail] : "",
9753 programStats.nodes, (int)nps, h, m, s, cs);
9756 DisplayAnalysisText(buf);
9760 DisplayComment(moveNumber, text)
9764 char title[MSG_SIZ];
9766 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
9767 strcpy(title, "Comment");
9769 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
9770 WhiteOnMove(moveNumber) ? " " : ".. ",
9771 parseList[moveNumber]);
9774 CommentPopUp(title, text);
9777 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
9778 * might be busy thinking or pondering. It can be omitted if your
9779 * gnuchess is configured to stop thinking immediately on any user
9780 * input. However, that gnuchess feature depends on the FIONREAD
9781 * ioctl, which does not work properly on some flavors of Unix.
9785 ChessProgramState *cps;
9788 if (!cps->useSigint) return;
9789 if (appData.noChessProgram || (cps->pr == NoProc)) return;
9791 case MachinePlaysWhite:
9792 case MachinePlaysBlack:
9793 case TwoMachinesPlay:
9794 case IcsPlayingWhite:
9795 case IcsPlayingBlack:
9798 /* Skip if we know it isn't thinking */
9799 if (!cps->maybeThinking) return;
9800 if (appData.debugMode)
9801 fprintf(debugFP, "Interrupting %s\n", cps->which);
9802 InterruptChildProcess(cps->pr);
9803 cps->maybeThinking = FALSE;
9808 #endif /*ATTENTION*/
9814 if (whiteTimeRemaining <= 0) {
9817 if (appData.icsActive) {
9818 if (appData.autoCallFlag &&
9819 gameMode == IcsPlayingBlack && !blackFlag) {
9820 SendToICS(ics_prefix);
9821 SendToICS("flag\n");
9825 DisplayTitle("Both flags fell");
9827 DisplayTitle("White's flag fell");
9828 if (appData.autoCallFlag) {
9829 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
9836 if (blackTimeRemaining <= 0) {
9839 if (appData.icsActive) {
9840 if (appData.autoCallFlag &&
9841 gameMode == IcsPlayingWhite && !whiteFlag) {
9842 SendToICS(ics_prefix);
9843 SendToICS("flag\n");
9847 DisplayTitle("Both flags fell");
9849 DisplayTitle("Black's flag fell");
9850 if (appData.autoCallFlag) {
9851 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
9864 if (!appData.clockMode || appData.icsActive ||
9865 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
9867 if (timeIncrement >= 0) {
9868 if (WhiteOnMove(forwardMostMove)) {
9869 blackTimeRemaining += timeIncrement;
9871 whiteTimeRemaining += timeIncrement;
9875 * add time to clocks when time control is achieved
9877 if (movesPerSession) {
9878 switch ((forwardMostMove + 1) % (movesPerSession * 2)) {
9880 /* White made time control */
9881 whiteTimeRemaining += GetTimeControlForWhite();
9884 /* Black made time control */
9885 blackTimeRemaining += GetTimeControlForBlack();
9896 int wom = gameMode == EditPosition ?
9897 !blackPlaysFirst : WhiteOnMove(currentMove);
9898 DisplayWhiteClock(whiteTimeRemaining, wom);
9899 DisplayBlackClock(blackTimeRemaining, !wom);
9903 /* Timekeeping seems to be a portability nightmare. I think everyone
9904 has ftime(), but I'm really not sure, so I'm including some ifdefs
9905 to use other calls if you don't. Clocks will be less accurate if
9906 you have neither ftime nor gettimeofday.
9909 /* Get the current time as a TimeMark */
9914 #if HAVE_GETTIMEOFDAY
9916 struct timeval timeVal;
9917 struct timezone timeZone;
9919 gettimeofday(&timeVal, &timeZone);
9920 tm->sec = (long) timeVal.tv_sec;
9921 tm->ms = (int) (timeVal.tv_usec / 1000L);
9923 #else /*!HAVE_GETTIMEOFDAY*/
9926 #include <sys/timeb.h>
9930 tm->sec = (long) timeB.time;
9931 tm->ms = (int) timeB.millitm;
9933 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
9934 tm->sec = (long) time(NULL);
9940 /* Return the difference in milliseconds between two
9941 time marks. We assume the difference will fit in a long!
9944 SubtractTimeMarks(tm2, tm1)
9945 TimeMark *tm2, *tm1;
9947 return 1000L*(tm2->sec - tm1->sec) +
9948 (long) (tm2->ms - tm1->ms);
9953 * Code to manage the game clocks.
9955 * In tournament play, black starts the clock and then white makes a move.
9956 * We give the human user a slight advantage if he is playing white---the
9957 * clocks don't run until he makes his first move, so it takes zero time.
9958 * Also, we don't account for network lag, so we could get out of sync
9959 * with GNU Chess's clock -- but then, referees are always right.
9962 static TimeMark tickStartTM;
9963 static long intendedTickLength;
9966 NextTickLength(timeRemaining)
9969 long nominalTickLength, nextTickLength;
9971 if (timeRemaining > 0L && timeRemaining <= 10000L)
9972 nominalTickLength = 100L;
9974 nominalTickLength = 1000L;
9975 nextTickLength = timeRemaining % nominalTickLength;
9976 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
9978 return nextTickLength;
9981 /* Stop clocks and reset to a fresh time control */
9985 (void) StopClockTimer();
9986 if (appData.icsActive) {
9987 whiteTimeRemaining = blackTimeRemaining = 0;
9989 whiteTimeRemaining = GetTimeControlForWhite();
9990 blackTimeRemaining = GetTimeControlForBlack();
9992 if (whiteFlag || blackFlag) {
9994 whiteFlag = blackFlag = FALSE;
9996 DisplayBothClocks();
9999 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
10001 /* Decrement running clock by amount of time that has passed */
10005 long timeRemaining;
10006 long lastTickLength, fudge;
10009 if (!appData.clockMode) return;
10010 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
10014 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
10016 /* Fudge if we woke up a little too soon */
10017 fudge = intendedTickLength - lastTickLength;
10018 if (fudge < 0 || fudge > FUDGE) fudge = 0;
10020 if (WhiteOnMove(forwardMostMove)) {
10021 timeRemaining = whiteTimeRemaining -= lastTickLength;
10022 DisplayWhiteClock(whiteTimeRemaining - fudge,
10023 WhiteOnMove(currentMove));
10025 timeRemaining = blackTimeRemaining -= lastTickLength;
10026 DisplayBlackClock(blackTimeRemaining - fudge,
10027 !WhiteOnMove(currentMove));
10030 if (CheckFlags()) return;
10033 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
10034 StartClockTimer(intendedTickLength);
10036 /* if the time remaining has fallen below the alarm threshold, sound the
10037 * alarm. if the alarm has sounded and (due to a takeback or time control
10038 * with increment) the time remaining has increased to a level above the
10039 * threshold, reset the alarm so it can sound again.
10042 if (appData.icsActive && appData.icsAlarm) {
10044 /* make sure we are dealing with the user's clock */
10045 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
10046 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
10049 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
10050 alarmSounded = FALSE;
10051 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
10053 alarmSounded = TRUE;
10059 /* A player has just moved, so stop the previously running
10060 clock and (if in clock mode) start the other one.
10061 We redisplay both clocks in case we're in ICS mode, because
10062 ICS gives us an update to both clocks after every move.
10063 Note that this routine is called *after* forwardMostMove
10064 is updated, so the last fractional tick must be subtracted
10065 from the color that is *not* on move now.
10070 long lastTickLength;
10072 int flagged = FALSE;
10076 if (StopClockTimer() && appData.clockMode) {
10077 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
10078 if (WhiteOnMove(forwardMostMove)) {
10079 blackTimeRemaining -= lastTickLength;
10081 whiteTimeRemaining -= lastTickLength;
10083 flagged = CheckFlags();
10085 CheckTimeControl();
10087 if (flagged || !appData.clockMode) return;
10089 switch (gameMode) {
10090 case MachinePlaysBlack:
10091 case MachinePlaysWhite:
10092 case BeginningOfGame:
10093 if (pausing) return;
10097 case PlayFromGameFile:
10106 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
10107 whiteTimeRemaining : blackTimeRemaining);
10108 StartClockTimer(intendedTickLength);
10112 /* Stop both clocks */
10116 long lastTickLength;
10119 if (!StopClockTimer()) return;
10120 if (!appData.clockMode) return;
10124 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
10125 if (WhiteOnMove(forwardMostMove)) {
10126 whiteTimeRemaining -= lastTickLength;
10127 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
10129 blackTimeRemaining -= lastTickLength;
10130 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
10135 /* Start clock of player on move. Time may have been reset, so
10136 if clock is already running, stop and restart it. */
10140 (void) StopClockTimer(); /* in case it was running already */
10141 DisplayBothClocks();
10142 if (CheckFlags()) return;
10144 if (!appData.clockMode) return;
10145 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
10147 GetTimeMark(&tickStartTM);
10148 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
10149 whiteTimeRemaining : blackTimeRemaining);
10150 StartClockTimer(intendedTickLength);
10157 long second, minute, hour, day;
10159 static char buf[32];
10161 if (ms > 0 && ms <= 9900) {
10162 /* convert milliseconds to tenths, rounding up */
10163 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
10165 sprintf(buf, " %03.1f ", tenths/10.0);
10169 /* convert milliseconds to seconds, rounding up */
10170 /* use floating point to avoid strangeness of integer division
10171 with negative dividends on many machines */
10172 second = (long) floor(((double) (ms + 999L)) / 1000.0);
10179 day = second / (60 * 60 * 24);
10180 second = second % (60 * 60 * 24);
10181 hour = second / (60 * 60);
10182 second = second % (60 * 60);
10183 minute = second / 60;
10184 second = second % 60;
10187 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
10188 sign, day, hour, minute, second);
10190 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
10192 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
10199 * This is necessary because some C libraries aren't ANSI C compliant yet.
10202 StrStr(string, match)
10203 char *string, *match;
10207 length = strlen(match);
10209 for (i = strlen(string) - length; i >= 0; i--, string++)
10210 if (!strncmp(match, string, length))
10217 StrCaseStr(string, match)
10218 char *string, *match;
10222 length = strlen(match);
10224 for (i = strlen(string) - length; i >= 0; i--, string++) {
10225 for (j = 0; j < length; j++) {
10226 if (ToLower(match[j]) != ToLower(string[j]))
10229 if (j == length) return string;
10243 c1 = ToLower(*s1++);
10244 c2 = ToLower(*s2++);
10245 if (c1 > c2) return 1;
10246 if (c1 < c2) return -1;
10247 if (c1 == NULLCHAR) return 0;
10256 return isupper(c) ? tolower(c) : c;
10264 return islower(c) ? toupper(c) : c;
10266 #endif /* !_amigados */
10274 if ((ret = (char *) malloc(strlen(s) + 1))) {
10281 StrSavePtr(s, savePtr)
10282 char *s, **savePtr;
10287 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
10288 strcpy(*savePtr, s);
10300 clock = time((time_t *)NULL);
10301 tm = localtime(&clock);
10302 sprintf(buf, "%04d.%02d.%02d",
10303 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
10304 return StrSave(buf);
10309 PositionToFEN(move, useFEN960)
10313 int i, j, fromX, fromY, toX, toY;
10319 whiteToPlay = (gameMode == EditPosition) ?
10320 !blackPlaysFirst : (move % 2 == 0);
10323 /* Piece placement data */
10324 for (i = BOARD_SIZE - 1; i >= 0; i--) {
10326 for (j = 0; j < BOARD_SIZE; j++) {
10327 if (boards[move][i][j] == EmptySquare) {
10330 if (emptycount > 0) {
10331 *p++ = '0' + emptycount;
10334 *p++ = PieceToChar(boards[move][i][j]);
10337 if (emptycount > 0) {
10338 *p++ = '0' + emptycount;
10346 *p++ = whiteToPlay ? 'w' : 'b';
10349 /* HACK: we don't keep track of castling availability, so fake it! */
10351 /* PUSH Fabien & Tord */
10353 /* Declare all potential FRC castling rights (conservative) */
10354 /* outermost rook on each side of the king */
10356 if( gameInfo.variant == VariantFischeRandom ) {
10361 /* White castling rights */
10363 for (fk = 1; fk < 7; fk++) {
10365 if (boards[move][0][fk] == WhiteKing) {
10367 for (fr = 7; fr > fk; fr--) { /* H side */
10368 if (boards[move][0][fr] == WhiteRook) {
10369 *p++ = useFEN960 ? 'A' + fr : 'K';
10374 for (fr = 0; fr < fk; fr++) { /* A side */
10375 if (boards[move][0][fr] == WhiteRook) {
10376 *p++ = useFEN960 ? 'A' + fr : 'Q';
10383 /* Black castling rights */
10385 for (fk = 1; fk < 7; fk++) {
10387 if (boards[move][7][fk] == BlackKing) {
10389 for (fr = 7; fr > fk; fr--) { /* H side */
10390 if (boards[move][7][fr] == BlackRook) {
10391 *p++ = useFEN960 ? 'a' + fr : 'k';
10396 for (fr = 0; fr < fk; fr++) { /* A side */
10397 if (boards[move][7][fr] == BlackRook) {
10398 *p++ = useFEN960 ? 'a' + fr : 'q';
10405 if (q == p) *p++ = '-'; /* No castling rights */
10410 if (boards[move][0][4] == WhiteKing) {
10411 if (boards[move][0][7] == WhiteRook) *p++ = 'K';
10412 if (boards[move][0][0] == WhiteRook) *p++ = 'Q';
10414 if (boards[move][7][4] == BlackKing) {
10415 if (boards[move][7][7] == BlackRook) *p++ = 'k';
10416 if (boards[move][7][0] == BlackRook) *p++ = 'q';
10418 if (q == p) *p++ = '-';
10422 /* POP Fabien & Tord */
10424 /* En passant target square */
10425 if (move > backwardMostMove) {
10426 fromX = moveList[move - 1][0] - 'a';
10427 fromY = moveList[move - 1][1] - '1';
10428 toX = moveList[move - 1][2] - 'a';
10429 toY = moveList[move - 1][3] - '1';
10430 if (fromY == (whiteToPlay ? 6 : 1) &&
10431 toY == (whiteToPlay ? 4 : 3) &&
10432 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
10434 /* 2-square pawn move just happened */
10436 *p++ = whiteToPlay ? '6' : '3';
10444 /* We don't keep track of halfmove clock for 50-move rule */
10448 /* Fullmove number */
10449 sprintf(p, "%d", (move / 2) + 1);
10451 return StrSave(buf);
10455 ParseFEN(board, blackPlaysFirst, fen)
10457 int *blackPlaysFirst;
10466 /* Piece placement data */
10467 for (i = BOARD_SIZE - 1; i >= 0; i--) {
10470 if (*p == '/' || *p == ' ') {
10471 if (*p == '/') p++;
10472 emptycount = BOARD_SIZE - j;
10473 while (emptycount--) board[i][j++] = EmptySquare;
10475 } else if (isdigit(*p)) {
10476 emptycount = *p++ - '0';
10477 if (j + emptycount > BOARD_SIZE) return FALSE;
10478 while (emptycount--) board[i][j++] = EmptySquare;
10479 } else if (isalpha(*p)) {
10480 if (j >= BOARD_SIZE) return FALSE;
10481 board[i][j++] = CharToPiece(*p++);
10487 while (*p == '/' || *p == ' ') p++;
10492 *blackPlaysFirst = FALSE;
10495 *blackPlaysFirst = TRUE;
10501 /* !!We ignore the rest of the FEN notation */
10506 EditPositionPasteFEN(char *fen)
10509 Board initial_position;
10511 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
10512 DisplayError("Bad FEN position in clipboard", 0);
10515 int savedBlackPlaysFirst = blackPlaysFirst;
10516 EditPositionEvent();
10517 blackPlaysFirst = savedBlackPlaysFirst;
10518 CopyBoard(boards[0], initial_position);
10519 EditPositionDone();
10520 DisplayBothClocks();
10521 DrawPosition(FALSE, boards[currentMove]);