2 * backend.c -- Common back end for X and Windows NT versions of
3 * XBoard $Id: backend.c,v 2.6 2003/11/28 09:37:36 mann Exp $
5 * Copyright 1991 by Digital Equipment Corporation, Maynard, Massachusetts.
6 * Enhancements Copyright 1992-2001 Free Software Foundation, Inc.
8 * The following terms apply to Digital Equipment Corporation's copyright
10 * ------------------------------------------------------------------------
13 * Permission to use, copy, modify, and distribute this software and its
14 * documentation for any purpose and without fee is hereby granted,
15 * provided that the above copyright notice appear in all copies and that
16 * both that copyright notice and this permission notice appear in
17 * supporting documentation, and that the name of Digital not be
18 * used in advertising or publicity pertaining to distribution of the
19 * software without specific, written prior permission.
21 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
22 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
23 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
24 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
25 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
26 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
28 * ------------------------------------------------------------------------
30 * The following terms apply to the enhanced version of XBoard distributed
31 * by the Free Software Foundation:
32 * ------------------------------------------------------------------------
33 * This program is free software; you can redistribute it and/or modify
34 * it under the terms of the GNU General Public License as published by
35 * the Free Software Foundation; either version 2 of the License, or
36 * (at your option) any later version.
38 * This program is distributed in the hope that it will be useful,
39 * but WITHOUT ANY WARRANTY; without even the implied warranty of
40 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
41 * GNU General Public License for more details.
43 * You should have received a copy of the GNU General Public License
44 * along with this program; if not, write to the Free Software
45 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
46 * ------------------------------------------------------------------------
48 * See the file ChangeLog for a revision history. */
50 /* [AS] Also useful here for debugging */
54 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
68 #include <sys/types.h>
75 #else /* not STDC_HEADERS */
78 # else /* not HAVE_STRING_H */
80 # endif /* not HAVE_STRING_H */
81 #endif /* not STDC_HEADERS */
84 # include <sys/fcntl.h>
85 #else /* not HAVE_SYS_FCNTL_H */
88 # endif /* HAVE_FCNTL_H */
89 #endif /* not HAVE_SYS_FCNTL_H */
91 #if TIME_WITH_SYS_TIME
92 # include <sys/time.h>
96 # include <sys/time.h>
102 #if defined(_amigados) && !defined(__GNUC__)
107 extern int gettimeofday(struct timeval *, struct timezone *);
115 #include "frontend.h"
122 #include "backendz.h"
124 /* A point in time */
126 long sec; /* Assuming this is >= 32 bits */
127 int ms; /* Assuming this is >= 16 bits */
130 /* Search stats from chessprogram */
132 char movelist[2*MSG_SIZ]; /* Last PV we were sent */
133 int depth; /* Current search depth */
134 int nr_moves; /* Total nr of root moves */
135 int moves_left; /* Moves remaining to be searched */
136 char move_name[MOVE_LEN]; /* Current move being searched, if provided */
137 unsigned long nodes; /* # of nodes searched */
138 int time; /* Search time (centiseconds) */
139 int score; /* Score (centipawns) */
140 int got_only_move; /* If last msg was "(only move)" */
141 int got_fail; /* 0 - nothing, 1 - got "--", 2 - got "++" */
142 int ok_to_send; /* handshaking between send & recv */
143 int line_is_book; /* 1 if movelist is book moves */
144 int seen_stat; /* 1 if we've seen the stat01: line */
147 int establish P((void));
148 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
149 char *buf, int count, int error));
150 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
151 char *buf, int count, int error));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
156 void InitPosition P((int redraw));
157 void HandleMachineMove P((char *message, ChessProgramState *cps));
158 int AutoPlayOneMove P((void));
159 int LoadGameOneMove P((ChessMove readAhead));
160 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
161 int LoadPositionFromFile P((char *filename, int n, char *title));
162 int SavePositionToFile P((char *filename));
163 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
165 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
166 void ShowMove P((int fromX, int fromY, int toX, int toY));
167 void FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
168 /*char*/int promoChar));
169 void BackwardInner P((int target));
170 void ForwardInner P((int target));
171 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
172 void EditPositionDone P((void));
173 void PrintOpponents P((FILE *fp));
174 void PrintPosition P((FILE *fp, int move));
175 void StartChessProgram P((ChessProgramState *cps));
176 void SendToProgram P((char *message, ChessProgramState *cps));
177 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
178 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
179 char *buf, int count, int error));
180 void SendTimeControl P((ChessProgramState *cps,
181 int mps, long tc, int inc, int sd, int st));
182 char *TimeControlTagValue P((void));
183 void Attention P((ChessProgramState *cps));
184 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
185 void ResurrectChessProgram P((void));
186 void DisplayComment P((int moveNumber, char *text));
187 void DisplayMove P((int moveNumber));
188 void DisplayAnalysis P((void));
190 void ParseGameHistory P((char *game));
191 void ParseBoard12 P((char *string));
192 void StartClocks P((void));
193 void SwitchClocks P((void));
194 void StopClocks P((void));
195 void ResetClocks P((void));
196 char *PGNDate P((void));
197 void SetGameInfo P((void));
198 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 void GetTimeMark P((TimeMark *));
208 long SubtractTimeMarks P((TimeMark *, TimeMark *));
209 int CheckFlags P((void));
210 long NextTickLength P((long));
211 void CheckTimeControl P((void));
212 void show_bytes P((FILE *, char *, int));
213 int string_to_rating P((char *str));
214 void ParseFeatures P((char* args, ChessProgramState *cps));
215 void InitBackEnd3 P((void));
216 void FeatureDone P((ChessProgramState* cps, int val));
217 void InitChessProgram P((ChessProgramState *cps));
219 void GetInfoFromComment( int, char * );
221 extern int tinyLayout, smallLayout;
222 static ChessProgramStats programStats;
224 /* States for ics_getting_history */
226 #define H_REQUESTED 1
227 #define H_GOT_REQ_HEADER 2
228 #define H_GOT_UNREQ_HEADER 3
229 #define H_GETTING_MOVES 4
230 #define H_GOT_UNWANTED_HEADER 5
232 /* whosays values for GameEnds */
239 /* Maximum number of games in a cmail message */
240 #define CMAIL_MAX_GAMES 20
242 /* Different types of move when calling RegisterMove */
244 #define CMAIL_RESIGN 1
246 #define CMAIL_ACCEPT 3
248 /* Different types of result to remember for each game */
249 #define CMAIL_NOT_RESULT 0
250 #define CMAIL_OLD_RESULT 1
251 #define CMAIL_NEW_RESULT 2
253 /* Telnet protocol constants */
264 static char * safeStrCpy( char * dst, const char * src, size_t count )
266 assert( dst != NULL );
267 assert( src != NULL );
270 strncpy( dst, src, count );
271 dst[ count-1 ] = '\0';
275 static char * safeStrCat( char * dst, const char * src, size_t count )
279 assert( dst != NULL );
280 assert( src != NULL );
283 dst_len = strlen(dst);
285 assert( count > dst_len ); /* Buffer size must be greater than current length */
287 safeStrCpy( dst + dst_len, src, count - dst_len );
292 /* Fake up flags for now, as we aren't keeping track of castling
297 int flags = F_ALL_CASTLE_OK;
298 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
299 switch (gameInfo.variant) {
301 case VariantGiveaway:
302 flags |= F_IGNORE_CHECK;
303 flags &= ~F_ALL_CASTLE_OK;
306 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
308 case VariantKriegspiel:
309 flags |= F_KRIEGSPIEL_CAPTURE;
311 case VariantNoCastle:
312 flags &= ~F_ALL_CASTLE_OK;
320 FILE *gameFileFP, *debugFP;
323 [AS] Note: sometimes, the sscanf() function is used to parse the input
324 into a fixed-size buffer. Because of this, we must be prepared to
325 receive strings as long as the size of the input buffer, which is currently
326 set to 4K for Windows and 8K for the rest.
327 So, we must either allocate sufficiently large buffers here, or
328 reduce the size of the input buffer in the input reading part.
331 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
332 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
333 char thinkOutput1[MSG_SIZ*10];
335 ChessProgramState first, second;
337 /* premove variables */
340 int premoveFromX = 0;
341 int premoveFromY = 0;
342 int premovePromoChar = 0;
344 Boolean alarmSounded;
345 /* end premove variables */
347 #define ICS_GENERIC 0
350 #define ICS_CHESSNET 3 /* not really supported */
351 int ics_type = ICS_GENERIC;
352 char *ics_prefix = "$";
354 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
355 int pauseExamForwardMostMove = 0;
356 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
357 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
358 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
359 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
360 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
361 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
362 int whiteFlag = FALSE, blackFlag = FALSE;
363 int userOfferedDraw = FALSE;
364 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
365 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
366 int cmailMoveType[CMAIL_MAX_GAMES];
367 long ics_clock_paused = 0;
368 ProcRef icsPR = NoProc, cmailPR = NoProc;
369 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
370 GameMode gameMode = BeginningOfGame;
371 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
372 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
373 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
374 int hiddenThinkOutputState = 0; /* [AS] */
375 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
376 int adjudicateLossPlies = 6;
377 char white_holding[64], black_holding[64];
378 TimeMark lastNodeCountTime;
379 long lastNodeCount=0;
380 int have_sent_ICS_logon = 0;
382 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
383 long timeControl_2; /* [AS] Allow separate time controls */
384 long timeRemaining[2][MAX_MOVES];
386 TimeMark programStartTime;
387 char ics_handle[MSG_SIZ];
388 int have_set_title = 0;
390 /* animateTraining preserves the state of appData.animate
391 * when Training mode is activated. This allows the
392 * response to be animated when appData.animate == TRUE and
393 * appData.animateDragging == TRUE.
395 Boolean animateTraining;
401 Board boards[MAX_MOVES];
402 Board initialPosition = {
403 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
404 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
405 { WhitePawn, WhitePawn, WhitePawn, WhitePawn,
406 WhitePawn, WhitePawn, WhitePawn, WhitePawn },
407 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
408 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
409 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
410 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
411 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
412 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
413 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
414 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
415 { BlackPawn, BlackPawn, BlackPawn, BlackPawn,
416 BlackPawn, BlackPawn, BlackPawn, BlackPawn },
417 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
418 BlackKing, BlackBishop, BlackKnight, BlackRook }
420 Board twoKingsPosition = {
421 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
422 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
423 { WhitePawn, WhitePawn, WhitePawn, WhitePawn,
424 WhitePawn, WhitePawn, WhitePawn, WhitePawn },
425 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
426 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
427 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
428 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
429 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
430 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
431 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
432 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
433 { BlackPawn, BlackPawn, BlackPawn, BlackPawn,
434 BlackPawn, BlackPawn, BlackPawn, BlackPawn },
435 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
436 BlackKing, BlackKing, BlackKnight, BlackRook }
440 /* Convert str to a rating. Checks for special cases of "----",
441 "++++", etc. Also strips ()'s */
443 string_to_rating(str)
446 while(*str && !isdigit(*str)) ++str;
448 return 0; /* One of the special "no rating" cases */
456 /* Init programStats */
457 programStats.movelist[0] = 0;
458 programStats.depth = 0;
459 programStats.nr_moves = 0;
460 programStats.moves_left = 0;
461 programStats.nodes = 0;
462 programStats.time = 100;
463 programStats.score = 0;
464 programStats.got_only_move = 0;
465 programStats.got_fail = 0;
466 programStats.line_is_book = 0;
472 int matched, min, sec;
474 GetTimeMark(&programStartTime);
477 programStats.ok_to_send = 1;
478 programStats.seen_stat = 0;
481 * Initialize game list
487 * Internet chess server status
489 if (appData.icsActive) {
490 appData.matchMode = FALSE;
491 appData.matchGames = 0;
493 appData.noChessProgram = !appData.zippyPlay;
495 appData.zippyPlay = FALSE;
496 appData.zippyTalk = FALSE;
497 appData.noChessProgram = TRUE;
499 if (*appData.icsHelper != NULLCHAR) {
500 appData.useTelnet = TRUE;
501 appData.telnetProgram = appData.icsHelper;
504 appData.zippyTalk = appData.zippyPlay = FALSE;
507 /* [AS] Initialize pv info list */
511 for( i=0; i<MAX_MOVES; i++ ) {
512 pvInfoList[i].depth = 0;
517 * Parse timeControl resource
519 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
520 appData.movesPerSession)) {
522 sprintf(buf, "bad timeControl option %s", appData.timeControl);
523 DisplayFatalError(buf, 0, 2);
527 * Parse searchTime resource
529 if (*appData.searchTime != NULLCHAR) {
530 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
532 searchTime = min * 60;
533 } else if (matched == 2) {
534 searchTime = min * 60 + sec;
537 sprintf(buf, "bad searchTime option %s", appData.searchTime);
538 DisplayFatalError(buf, 0, 2);
542 /* [AS] Adjudication threshold */
543 adjudicateLossThreshold = appData.adjudicateLossThreshold;
545 first.which = "first";
546 second.which = "second";
547 first.maybeThinking = second.maybeThinking = FALSE;
548 first.pr = second.pr = NoProc;
549 first.isr = second.isr = NULL;
550 first.sendTime = second.sendTime = 2;
551 first.sendDrawOffers = 1;
552 if (appData.firstPlaysBlack) {
553 first.twoMachinesColor = "black\n";
554 second.twoMachinesColor = "white\n";
556 first.twoMachinesColor = "white\n";
557 second.twoMachinesColor = "black\n";
559 first.program = appData.firstChessProgram;
560 second.program = appData.secondChessProgram;
561 first.host = appData.firstHost;
562 second.host = appData.secondHost;
563 first.dir = appData.firstDirectory;
564 second.dir = appData.secondDirectory;
565 first.other = &second;
566 second.other = &first;
567 first.initString = appData.initString;
568 second.initString = appData.secondInitString;
569 first.computerString = appData.firstComputerString;
570 second.computerString = appData.secondComputerString;
571 first.useSigint = second.useSigint = TRUE;
572 first.useSigterm = second.useSigterm = TRUE;
573 first.reuse = appData.reuseFirst;
574 second.reuse = appData.reuseSecond;
575 first.useSetboard = second.useSetboard = FALSE;
576 first.useSAN = second.useSAN = FALSE;
577 first.usePing = second.usePing = FALSE;
578 first.lastPing = second.lastPing = 0;
579 first.lastPong = second.lastPong = 0;
580 first.usePlayother = second.usePlayother = FALSE;
581 first.useColors = second.useColors = TRUE;
582 first.useUsermove = second.useUsermove = FALSE;
583 first.sendICS = second.sendICS = FALSE;
584 first.sendName = second.sendName = appData.icsActive;
585 first.sdKludge = second.sdKludge = FALSE;
586 first.stKludge = second.stKludge = FALSE;
587 TidyProgramName(first.program, first.host, first.tidy);
588 TidyProgramName(second.program, second.host, second.tidy);
589 first.matchWins = second.matchWins = 0;
590 strcpy(first.variants, appData.variant);
591 strcpy(second.variants, appData.variant);
592 first.analysisSupport = second.analysisSupport = 2; /* detect */
593 first.analyzing = second.analyzing = FALSE;
594 first.initDone = second.initDone = FALSE;
596 /* New features added by Tord: */
597 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
598 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
599 /* End of new features added by Tord. */
601 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
602 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
603 first.isUCI = appData.firstIsUCI; /* [AS] */
604 second.isUCI = appData.secondIsUCI; /* [AS] */
605 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
606 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
608 if (appData.firstProtocolVersion > PROTOVER ||
609 appData.firstProtocolVersion < 1) {
611 sprintf(buf, "protocol version %d not supported",
612 appData.firstProtocolVersion);
613 DisplayFatalError(buf, 0, 2);
615 first.protocolVersion = appData.firstProtocolVersion;
618 if (appData.secondProtocolVersion > PROTOVER ||
619 appData.secondProtocolVersion < 1) {
621 sprintf(buf, "protocol version %d not supported",
622 appData.secondProtocolVersion);
623 DisplayFatalError(buf, 0, 2);
625 second.protocolVersion = appData.secondProtocolVersion;
628 if (appData.icsActive) {
629 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
630 } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
631 appData.clockMode = FALSE;
632 first.sendTime = second.sendTime = 0;
636 /* Override some settings from environment variables, for backward
637 compatibility. Unfortunately it's not feasible to have the env
638 vars just set defaults, at least in xboard. Ugh.
640 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
645 if (appData.noChessProgram) {
646 programVersion = (char*) malloc(5 + strlen(PRODUCT) + strlen(VERSION)
647 + strlen(PATCHLEVEL));
648 sprintf(programVersion, "%s %s.%s", PRODUCT, VERSION, PATCHLEVEL);
652 while (*q != ' ' && *q != NULLCHAR) q++;
654 while (p > first.program && *(p-1) != '/') p--;
655 programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)
656 + strlen(PATCHLEVEL) + (q - p));
657 sprintf(programVersion, "%s %s.%s + ", PRODUCT, VERSION, PATCHLEVEL);
658 strncat(programVersion, p, q - p);
661 if (!appData.icsActive) {
663 /* Check for variants that are supported only in ICS mode,
664 or not at all. Some that are accepted here nevertheless
665 have bugs; see comments below.
667 VariantClass variant = StringToVariant(appData.variant);
669 case VariantBughouse: /* need four players and two boards */
670 case VariantKriegspiel: /* need to hide pieces and move details */
671 /* case VariantFischeRandom: (Fabien: moved below) */
672 sprintf(buf, "Variant %s supported only in ICS mode", appData.variant);
673 DisplayFatalError(buf, 0, 2);
677 case VariantLoadable:
687 sprintf(buf, "Unknown variant name %s", appData.variant);
688 DisplayFatalError(buf, 0, 2);
691 case VariantNormal: /* definitely works! */
692 case VariantWildCastle: /* pieces not automatically shuffled */
693 case VariantNoCastle: /* pieces not automatically shuffled */
694 case VariantFischeRandom: /* Fabien: pieces not automatically shuffled */
695 case VariantCrazyhouse: /* holdings not shown,
696 offboard interposition not understood */
697 case VariantLosers: /* should work except for win condition,
698 and doesn't know captures are mandatory */
699 case VariantSuicide: /* should work except for win condition,
700 and doesn't know captures are mandatory */
701 case VariantGiveaway: /* should work except for win condition,
702 and doesn't know captures are mandatory */
703 case VariantTwoKings: /* should work */
704 case VariantAtomic: /* should work except for win condition */
705 case Variant3Check: /* should work except for win condition */
706 case VariantShatranj: /* might work if TestLegality is off */
712 int NextIntegerFromString( char ** str, long * value )
717 while( *s == ' ' || *s == '\t' ) {
723 if( *s >= '0' && *s <= '9' ) {
724 while( *s >= '0' && *s <= '9' ) {
725 *value = *value * 10 + (*s - '0');
737 int NextTimeControlFromString( char ** str, long * value )
740 int result = NextIntegerFromString( str, &temp );
743 *value = temp * 60; /* Minutes */
746 result = NextIntegerFromString( str, &temp );
747 *value += temp; /* Seconds */
754 int GetTimeControlForWhite()
756 int result = timeControl;
761 int GetTimeControlForBlack()
763 int result = timeControl;
765 if( timeControl_2 > 0 ) {
766 result = timeControl_2;
773 ParseTimeControl(tc, ti, mps)
779 int matched, min, sec;
781 matched = sscanf(tc, "%d:%d", &min, &sec);
783 timeControl = min * 60 * 1000;
784 } else if (matched == 2) {
785 timeControl = (min * 60 + sec) * 1000;
793 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
798 /* Parse second time control */
801 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
809 timeControl_2 = tc2 * 1000;
819 timeControl = tc1 * 1000;
823 timeIncrement = ti * 1000; /* convert to ms */
827 movesPerSession = mps;
835 if (appData.debugMode) {
836 fprintf(debugFP, "%s\n", programVersion);
839 if (appData.matchGames > 0) {
840 appData.matchMode = TRUE;
841 } else if (appData.matchMode) {
842 appData.matchGames = 1;
845 if (appData.noChessProgram || first.protocolVersion == 1) {
848 /* kludge: allow timeout for initial "feature" commands */
850 DisplayMessage("", "Starting chess program");
851 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
856 InitBackEnd3 P((void))
858 GameMode initialMode;
862 InitChessProgram(&first);
864 if (appData.icsActive) {
867 if (*appData.icsCommPort != NULLCHAR) {
868 sprintf(buf, "Could not open comm port %s",
869 appData.icsCommPort);
871 sprintf(buf, "Could not connect to host %s, port %s",
872 appData.icsHost, appData.icsPort);
874 DisplayFatalError(buf, err, 1);
879 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
881 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
882 } else if (appData.noChessProgram) {
888 if (*appData.cmailGameName != NULLCHAR) {
890 OpenLoopback(&cmailPR);
892 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
896 DisplayMessage("", "");
897 if (StrCaseCmp(appData.initialMode, "") == 0) {
898 initialMode = BeginningOfGame;
899 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
900 initialMode = TwoMachinesPlay;
901 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
902 initialMode = AnalyzeFile;
903 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
904 initialMode = AnalyzeMode;
905 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
906 initialMode = MachinePlaysWhite;
907 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
908 initialMode = MachinePlaysBlack;
909 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
910 initialMode = EditGame;
911 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
912 initialMode = EditPosition;
913 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
914 initialMode = Training;
916 sprintf(buf, "Unknown initialMode %s", appData.initialMode);
917 DisplayFatalError(buf, 0, 2);
921 if (appData.matchMode) {
922 /* Set up machine vs. machine match */
923 if (appData.noChessProgram) {
924 DisplayFatalError("Can't have a match with no chess programs",
930 if (*appData.loadGameFile != NULLCHAR) {
931 if (!LoadGameFromFile(appData.loadGameFile,
932 appData.loadGameIndex,
933 appData.loadGameFile, FALSE)) {
934 DisplayFatalError("Bad game file", 0, 1);
937 } else if (*appData.loadPositionFile != NULLCHAR) {
938 if (!LoadPositionFromFile(appData.loadPositionFile,
939 appData.loadPositionIndex,
940 appData.loadPositionFile)) {
941 DisplayFatalError("Bad position file", 0, 1);
946 } else if (*appData.cmailGameName != NULLCHAR) {
947 /* Set up cmail mode */
948 ReloadCmailMsgEvent(TRUE);
950 /* Set up other modes */
951 if (initialMode == AnalyzeFile) {
952 if (*appData.loadGameFile == NULLCHAR) {
953 DisplayFatalError("AnalyzeFile mode requires a game file", 0, 1);
957 if (*appData.loadGameFile != NULLCHAR) {
958 (void) LoadGameFromFile(appData.loadGameFile,
959 appData.loadGameIndex,
960 appData.loadGameFile, TRUE);
961 } else if (*appData.loadPositionFile != NULLCHAR) {
962 (void) LoadPositionFromFile(appData.loadPositionFile,
963 appData.loadPositionIndex,
964 appData.loadPositionFile);
966 if (initialMode == AnalyzeMode) {
967 if (appData.noChessProgram) {
968 DisplayFatalError("Analysis mode requires a chess engine", 0, 2);
971 if (appData.icsActive) {
972 DisplayFatalError("Analysis mode does not work with ICS mode",0,2);
976 } else if (initialMode == AnalyzeFile) {
977 ShowThinkingEvent(TRUE);
979 AnalysisPeriodicEvent(1);
980 } else if (initialMode == MachinePlaysWhite) {
981 if (appData.noChessProgram) {
982 DisplayFatalError("MachineWhite mode requires a chess engine",
986 if (appData.icsActive) {
987 DisplayFatalError("MachineWhite mode does not work with ICS mode",
992 } else if (initialMode == MachinePlaysBlack) {
993 if (appData.noChessProgram) {
994 DisplayFatalError("MachineBlack mode requires a chess engine",
998 if (appData.icsActive) {
999 DisplayFatalError("MachineBlack mode does not work with ICS mode",
1003 MachineBlackEvent();
1004 } else if (initialMode == TwoMachinesPlay) {
1005 if (appData.noChessProgram) {
1006 DisplayFatalError("TwoMachines mode requires a chess engine",
1010 if (appData.icsActive) {
1011 DisplayFatalError("TwoMachines mode does not work with ICS mode",
1016 } else if (initialMode == EditGame) {
1018 } else if (initialMode == EditPosition) {
1019 EditPositionEvent();
1020 } else if (initialMode == Training) {
1021 if (*appData.loadGameFile == NULLCHAR) {
1022 DisplayFatalError("Training mode requires a game file", 0, 2);
1031 * Establish will establish a contact to a remote host.port.
1032 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1033 * used to talk to the host.
1034 * Returns 0 if okay, error code if not.
1041 if (*appData.icsCommPort != NULLCHAR) {
1042 /* Talk to the host through a serial comm port */
1043 return OpenCommPort(appData.icsCommPort, &icsPR);
1045 } else if (*appData.gateway != NULLCHAR) {
1046 if (*appData.remoteShell == NULLCHAR) {
1047 /* Use the rcmd protocol to run telnet program on a gateway host */
1048 sprintf(buf, "%s %s %s",
1049 appData.telnetProgram, appData.icsHost, appData.icsPort);
1050 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1053 /* Use the rsh program to run telnet program on a gateway host */
1054 if (*appData.remoteUser == NULLCHAR) {
1055 sprintf(buf, "%s %s %s %s %s", appData.remoteShell,
1056 appData.gateway, appData.telnetProgram,
1057 appData.icsHost, appData.icsPort);
1059 sprintf(buf, "%s %s -l %s %s %s %s",
1060 appData.remoteShell, appData.gateway,
1061 appData.remoteUser, appData.telnetProgram,
1062 appData.icsHost, appData.icsPort);
1064 return StartChildProcess(buf, "", &icsPR);
1067 } else if (appData.useTelnet) {
1068 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1071 /* TCP socket interface differs somewhat between
1072 Unix and NT; handle details in the front end.
1074 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1079 show_bytes(fp, buf, count)
1085 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1086 fprintf(fp, "\\%03o", *buf & 0xff);
1095 /* Returns an errno value */
1097 OutputMaybeTelnet(pr, message, count, outError)
1103 char buf[8192], *p, *q, *buflim;
1104 int left, newcount, outcount;
1106 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1107 *appData.gateway != NULLCHAR) {
1108 if (appData.debugMode) {
1109 fprintf(debugFP, ">ICS: ");
1110 show_bytes(debugFP, message, count);
1111 fprintf(debugFP, "\n");
1113 return OutputToProcess(pr, message, count, outError);
1116 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1123 if (appData.debugMode) {
1124 fprintf(debugFP, ">ICS: ");
1125 show_bytes(debugFP, buf, newcount);
1126 fprintf(debugFP, "\n");
1128 outcount = OutputToProcess(pr, buf, newcount, outError);
1129 if (outcount < newcount) return -1; /* to be sure */
1136 } else if (((unsigned char) *p) == TN_IAC) {
1137 *q++ = (char) TN_IAC;
1144 if (appData.debugMode) {
1145 fprintf(debugFP, ">ICS: ");
1146 show_bytes(debugFP, buf, newcount);
1147 fprintf(debugFP, "\n");
1149 outcount = OutputToProcess(pr, buf, newcount, outError);
1150 if (outcount < newcount) return -1; /* to be sure */
1155 read_from_player(isr, closure, message, count, error)
1162 int outError, outCount;
1163 static int gotEof = 0;
1165 /* Pass data read from player on to ICS */
1168 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1169 if (outCount < count) {
1170 DisplayFatalError("Error writing to ICS", outError, 1);
1172 } else if (count < 0) {
1173 RemoveInputSource(isr);
1174 DisplayFatalError("Error reading from keyboard", error, 1);
1175 } else if (gotEof++ > 0) {
1176 RemoveInputSource(isr);
1177 DisplayFatalError("Got end of file from keyboard", 0, 0);
1185 int count, outCount, outError;
1187 if (icsPR == NULL) return;
1190 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1191 if (outCount < count) {
1192 DisplayFatalError("Error writing to ICS", outError, 1);
1196 /* This is used for sending logon scripts to the ICS. Sending
1197 without a delay causes problems when using timestamp on ICC
1198 (at least on my machine). */
1200 SendToICSDelayed(s,msdelay)
1204 int count, outCount, outError;
1206 if (icsPR == NULL) return;
1209 if (appData.debugMode) {
1210 fprintf(debugFP, ">ICS: ");
1211 show_bytes(debugFP, s, count);
1212 fprintf(debugFP, "\n");
1214 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1216 if (outCount < count) {
1217 DisplayFatalError("Error writing to ICS", outError, 1);
1222 /* Remove all highlighting escape sequences in s
1223 Also deletes any suffix starting with '('
1226 StripHighlightAndTitle(s)
1229 static char retbuf[MSG_SIZ];
1232 while (*s != NULLCHAR) {
1233 while (*s == '\033') {
1234 while (*s != NULLCHAR && !isalpha(*s)) s++;
1235 if (*s != NULLCHAR) s++;
1237 while (*s != NULLCHAR && *s != '\033') {
1238 if (*s == '(' || *s == '[') {
1249 /* Remove all highlighting escape sequences in s */
1254 static char retbuf[MSG_SIZ];
1257 while (*s != NULLCHAR) {
1258 while (*s == '\033') {
1259 while (*s != NULLCHAR && !isalpha(*s)) s++;
1260 if (*s != NULLCHAR) s++;
1262 while (*s != NULLCHAR && *s != '\033') {
1270 char *variantNames[] = VARIANT_NAMES;
1275 return variantNames[v];
1279 /* Identify a variant from the strings the chess servers use or the
1280 PGN Variant tag names we use. */
1287 VariantClass v = VariantNormal;
1288 int i, found = FALSE;
1293 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1294 if (StrCaseStr(e, variantNames[i])) {
1295 v = (VariantClass) i;
1302 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1303 || StrCaseStr(e, "wild/fr")) {
1304 v = VariantFischeRandom;
1305 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1306 (i = 1, p = StrCaseStr(e, "w"))) {
1308 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1315 case 0: /* FICS only, actually */
1317 /* Castling legal even if K starts on d-file */
1318 v = VariantWildCastle;
1323 /* Castling illegal even if K & R happen to start in
1324 normal positions. */
1325 v = VariantNoCastle;
1338 /* Castling legal iff K & R start in normal positions */
1344 /* Special wilds for position setup; unclear what to do here */
1345 v = VariantLoadable;
1348 /* Bizarre ICC game */
1349 v = VariantTwoKings;
1352 v = VariantKriegspiel;
1358 v = VariantFischeRandom;
1361 v = VariantCrazyhouse;
1364 v = VariantBughouse;
1370 /* Not quite the same as FICS suicide! */
1371 v = VariantGiveaway;
1377 v = VariantShatranj;
1380 /* Temporary names for future ICC types. The name *will* change in
1381 the next xboard/WinBoard release after ICC defines it. */
1408 /* Found "wild" or "w" in the string but no number;
1409 must assume it's normal chess. */
1413 sprintf(buf, "Unknown wild type %d", wnum);
1414 DisplayError(buf, 0);
1420 if (appData.debugMode) {
1421 fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
1422 e, wnum, VariantName(v));
1427 static int leftover_start = 0, leftover_len = 0;
1428 char star_match[STAR_MATCH_N][MSG_SIZ];
1430 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1431 advance *index beyond it, and set leftover_start to the new value of
1432 *index; else return FALSE. If pattern contains the character '*', it
1433 matches any sequence of characters not containing '\r', '\n', or the
1434 character following the '*' (if any), and the matched sequence(s) are
1435 copied into star_match.
1438 looking_at(buf, index, pattern)
1443 char *bufp = &buf[*index], *patternp = pattern;
1445 char *matchp = star_match[0];
1448 if (*patternp == NULLCHAR) {
1449 *index = leftover_start = bufp - buf;
1453 if (*bufp == NULLCHAR) return FALSE;
1454 if (*patternp == '*') {
1455 if (*bufp == *(patternp + 1)) {
1457 matchp = star_match[++star_count];
1461 } else if (*bufp == '\n' || *bufp == '\r') {
1463 if (*patternp == NULLCHAR)
1468 *matchp++ = *bufp++;
1472 if (*patternp != *bufp) return FALSE;
1479 SendToPlayer(data, length)
1483 int error, outCount;
1484 outCount = OutputToProcess(NoProc, data, length, &error);
1485 if (outCount < length) {
1486 DisplayFatalError("Error writing to display", error, 1);
1491 PackHolding(packed, holding)
1503 switch (runlength) {
1514 sprintf(q, "%d", runlength);
1526 /* Telnet protocol requests from the front end */
1528 TelnetRequest(ddww, option)
1529 unsigned char ddww, option;
1531 unsigned char msg[3];
1532 int outCount, outError;
1534 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1536 if (appData.debugMode) {
1537 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1553 sprintf(buf1, "%d", ddww);
1562 sprintf(buf2, "%d", option);
1565 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1570 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1572 DisplayFatalError("Error writing to ICS", outError, 1);
1579 if (!appData.icsActive) return;
1580 TelnetRequest(TN_DO, TN_ECHO);
1586 if (!appData.icsActive) return;
1587 TelnetRequest(TN_DONT, TN_ECHO);
1590 static int loggedOn = FALSE;
1592 /*-- Game start info cache: --*/
1594 char gs_kind[MSG_SIZ];
1595 static char player1Name[128] = "";
1596 static char player2Name[128] = "";
1597 static int player1Rating = -1;
1598 static int player2Rating = -1;
1599 /*----------------------------*/
1601 ColorClass curColor = ColorNormal;
1604 read_from_ics(isr, closure, data, count, error)
1611 #define BUF_SIZE 8192
1612 #define STARTED_NONE 0
1613 #define STARTED_MOVES 1
1614 #define STARTED_BOARD 2
1615 #define STARTED_OBSERVE 3
1616 #define STARTED_HOLDINGS 4
1617 #define STARTED_CHATTER 5
1618 #define STARTED_COMMENT 6
1619 #define STARTED_MOVES_NOHIDE 7
1621 static int started = STARTED_NONE;
1622 static char parse[20000];
1623 static int parse_pos = 0;
1624 static char buf[BUF_SIZE + 1];
1625 static int firstTime = TRUE, intfSet = FALSE;
1626 static ColorClass prevColor = ColorNormal;
1627 static int savingComment = FALSE;
1636 if (appData.debugMode) {
1638 fprintf(debugFP, "<ICS: ");
1639 show_bytes(debugFP, data, count);
1640 fprintf(debugFP, "\n");
1646 /* If last read ended with a partial line that we couldn't parse,
1647 prepend it to the new read and try again. */
1648 if (leftover_len > 0) {
1649 for (i=0; i<leftover_len; i++)
1650 buf[i] = buf[leftover_start + i];
1653 /* Copy in new characters, removing nulls and \r's */
1654 buf_len = leftover_len;
1655 for (i = 0; i < count; i++) {
1656 if (data[i] != NULLCHAR && data[i] != '\r')
1657 buf[buf_len++] = data[i];
1660 buf[buf_len] = NULLCHAR;
1661 next_out = leftover_len;
1665 while (i < buf_len) {
1666 /* Deal with part of the TELNET option negotiation
1667 protocol. We refuse to do anything beyond the
1668 defaults, except that we allow the WILL ECHO option,
1669 which ICS uses to turn off password echoing when we are
1670 directly connected to it. We reject this option
1671 if localLineEditing mode is on (always on in xboard)
1672 and we are talking to port 23, which might be a real
1673 telnet server that will try to keep WILL ECHO on permanently.
1675 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
1676 static int remoteEchoOption = FALSE; /* telnet ECHO option */
1677 unsigned char option;
1679 switch ((unsigned char) buf[++i]) {
1681 if (appData.debugMode)
1682 fprintf(debugFP, "\n<WILL ");
1683 switch (option = (unsigned char) buf[++i]) {
1685 if (appData.debugMode)
1686 fprintf(debugFP, "ECHO ");
1687 /* Reply only if this is a change, according
1688 to the protocol rules. */
1689 if (remoteEchoOption) break;
1690 if (appData.localLineEditing &&
1691 atoi(appData.icsPort) == TN_PORT) {
1692 TelnetRequest(TN_DONT, TN_ECHO);
1695 TelnetRequest(TN_DO, TN_ECHO);
1696 remoteEchoOption = TRUE;
1700 if (appData.debugMode)
1701 fprintf(debugFP, "%d ", option);
1702 /* Whatever this is, we don't want it. */
1703 TelnetRequest(TN_DONT, option);
1708 if (appData.debugMode)
1709 fprintf(debugFP, "\n<WONT ");
1710 switch (option = (unsigned char) buf[++i]) {
1712 if (appData.debugMode)
1713 fprintf(debugFP, "ECHO ");
1714 /* Reply only if this is a change, according
1715 to the protocol rules. */
1716 if (!remoteEchoOption) break;
1718 TelnetRequest(TN_DONT, TN_ECHO);
1719 remoteEchoOption = FALSE;
1722 if (appData.debugMode)
1723 fprintf(debugFP, "%d ", (unsigned char) option);
1724 /* Whatever this is, it must already be turned
1725 off, because we never agree to turn on
1726 anything non-default, so according to the
1727 protocol rules, we don't reply. */
1732 if (appData.debugMode)
1733 fprintf(debugFP, "\n<DO ");
1734 switch (option = (unsigned char) buf[++i]) {
1736 /* Whatever this is, we refuse to do it. */
1737 if (appData.debugMode)
1738 fprintf(debugFP, "%d ", option);
1739 TelnetRequest(TN_WONT, option);
1744 if (appData.debugMode)
1745 fprintf(debugFP, "\n<DONT ");
1746 switch (option = (unsigned char) buf[++i]) {
1748 if (appData.debugMode)
1749 fprintf(debugFP, "%d ", option);
1750 /* Whatever this is, we are already not doing
1751 it, because we never agree to do anything
1752 non-default, so according to the protocol
1753 rules, we don't reply. */
1758 if (appData.debugMode)
1759 fprintf(debugFP, "\n<IAC ");
1760 /* Doubled IAC; pass it through */
1764 if (appData.debugMode)
1765 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
1766 /* Drop all other telnet commands on the floor */
1769 if (oldi > next_out)
1770 SendToPlayer(&buf[next_out], oldi - next_out);
1776 /* OK, this at least will *usually* work */
1777 if (!loggedOn && looking_at(buf, &i, "ics%")) {
1781 if (loggedOn && !intfSet) {
1782 if (ics_type == ICS_ICC) {
1784 "/set-quietly interface %s\n/set-quietly style 12\n",
1787 } else if (ics_type == ICS_CHESSNET) {
1788 sprintf(str, "/style 12\n");
1790 strcpy(str, "alias $ @\n$set interface ");
1791 strcat(str, programVersion);
1792 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
1794 strcat(str, "$iset nohighlight 1\n");
1796 strcat(str, "$iset lock 1\n$style 12\n");
1802 if (started == STARTED_COMMENT) {
1803 /* Accumulate characters in comment */
1804 parse[parse_pos++] = buf[i];
1805 if (buf[i] == '\n') {
1806 parse[parse_pos] = NULLCHAR;
1807 AppendComment(forwardMostMove, StripHighlight(parse));
1808 started = STARTED_NONE;
1810 /* Don't match patterns against characters in chatter */
1815 if (started == STARTED_CHATTER) {
1816 if (buf[i] != '\n') {
1817 /* Don't match patterns against characters in chatter */
1821 started = STARTED_NONE;
1824 /* Kludge to deal with rcmd protocol */
1825 if (firstTime && looking_at(buf, &i, "\001*")) {
1826 DisplayFatalError(&buf[1], 0, 1);
1832 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
1835 if (appData.debugMode)
1836 fprintf(debugFP, "ics_type %d\n", ics_type);
1839 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
1840 ics_type = ICS_FICS;
1842 if (appData.debugMode)
1843 fprintf(debugFP, "ics_type %d\n", ics_type);
1846 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
1847 ics_type = ICS_CHESSNET;
1849 if (appData.debugMode)
1850 fprintf(debugFP, "ics_type %d\n", ics_type);
1855 (looking_at(buf, &i, "\"*\" is *a registered name") ||
1856 looking_at(buf, &i, "Logging you in as \"*\"") ||
1857 looking_at(buf, &i, "will be \"*\""))) {
1858 strcpy(ics_handle, star_match[0]);
1862 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
1864 sprintf(buf, "%s@%s", ics_handle, appData.icsHost);
1865 DisplayIcsInteractionTitle(buf);
1866 have_set_title = TRUE;
1869 /* skip finger notes */
1870 if (started == STARTED_NONE &&
1871 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
1872 (buf[i] == '1' && buf[i+1] == '0')) &&
1873 buf[i+2] == ':' && buf[i+3] == ' ') {
1874 started = STARTED_CHATTER;
1879 /* skip formula vars */
1880 if (started == STARTED_NONE &&
1881 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
1882 started = STARTED_CHATTER;
1888 if (appData.zippyTalk || appData.zippyPlay) {
1890 if (ZippyControl(buf, &i) ||
1891 ZippyConverse(buf, &i) ||
1892 (appData.zippyPlay && ZippyMatch(buf, &i))) {
1898 if (/* Don't color "message" or "messages" output */
1899 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
1900 looking_at(buf, &i, "*. * at *:*: ") ||
1901 looking_at(buf, &i, "--* (*:*): ") ||
1902 /* Regular tells and says */
1903 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
1904 looking_at(buf, &i, "* (your partner) tells you: ") ||
1905 looking_at(buf, &i, "* says: ") ||
1906 /* Message notifications (same color as tells) */
1907 looking_at(buf, &i, "* has left a message ") ||
1908 looking_at(buf, &i, "* just sent you a message:\n") ||
1909 /* Whispers and kibitzes */
1910 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
1911 looking_at(buf, &i, "* kibitzes: ") ||
1913 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
1915 if (tkind == 1 && strchr(star_match[0], ':')) {
1916 /* Avoid "tells you:" spoofs in channels */
1919 if (star_match[0][0] == NULLCHAR ||
1920 strchr(star_match[0], ' ') ||
1921 (tkind == 3 && strchr(star_match[1], ' '))) {
1922 /* Reject bogus matches */
1925 if (appData.colorize) {
1926 if (oldi > next_out) {
1927 SendToPlayer(&buf[next_out], oldi - next_out);
1932 Colorize(ColorTell, FALSE);
1933 curColor = ColorTell;
1936 Colorize(ColorKibitz, FALSE);
1937 curColor = ColorKibitz;
1940 p = strrchr(star_match[1], '(');
1947 Colorize(ColorChannel1, FALSE);
1948 curColor = ColorChannel1;
1950 Colorize(ColorChannel, FALSE);
1951 curColor = ColorChannel;
1955 curColor = ColorNormal;
1959 if (started == STARTED_NONE && appData.autoComment &&
1960 (gameMode == IcsObserving ||
1961 gameMode == IcsPlayingWhite ||
1962 gameMode == IcsPlayingBlack)) {
1963 parse_pos = i - oldi;
1964 memcpy(parse, &buf[oldi], parse_pos);
1965 parse[parse_pos] = NULLCHAR;
1966 started = STARTED_COMMENT;
1967 savingComment = TRUE;
1969 started = STARTED_CHATTER;
1970 savingComment = FALSE;
1977 if (looking_at(buf, &i, "* s-shouts: ") ||
1978 looking_at(buf, &i, "* c-shouts: ")) {
1979 if (appData.colorize) {
1980 if (oldi > next_out) {
1981 SendToPlayer(&buf[next_out], oldi - next_out);
1984 Colorize(ColorSShout, FALSE);
1985 curColor = ColorSShout;
1988 started = STARTED_CHATTER;
1992 if (looking_at(buf, &i, "--->")) {
1997 if (looking_at(buf, &i, "* shouts: ") ||
1998 looking_at(buf, &i, "--> ")) {
1999 if (appData.colorize) {
2000 if (oldi > next_out) {
2001 SendToPlayer(&buf[next_out], oldi - next_out);
2004 Colorize(ColorShout, FALSE);
2005 curColor = ColorShout;
2008 started = STARTED_CHATTER;
2012 if (looking_at( buf, &i, "Challenge:")) {
2013 if (appData.colorize) {
2014 if (oldi > next_out) {
2015 SendToPlayer(&buf[next_out], oldi - next_out);
2018 Colorize(ColorChallenge, FALSE);
2019 curColor = ColorChallenge;
2025 if (looking_at(buf, &i, "* offers you") ||
2026 looking_at(buf, &i, "* offers to be") ||
2027 looking_at(buf, &i, "* would like to") ||
2028 looking_at(buf, &i, "* requests to") ||
2029 looking_at(buf, &i, "Your opponent offers") ||
2030 looking_at(buf, &i, "Your opponent requests")) {
2032 if (appData.colorize) {
2033 if (oldi > next_out) {
2034 SendToPlayer(&buf[next_out], oldi - next_out);
2037 Colorize(ColorRequest, FALSE);
2038 curColor = ColorRequest;
2043 if (looking_at(buf, &i, "* (*) seeking")) {
2044 if (appData.colorize) {
2045 if (oldi > next_out) {
2046 SendToPlayer(&buf[next_out], oldi - next_out);
2049 Colorize(ColorSeek, FALSE);
2050 curColor = ColorSeek;
2056 if (looking_at(buf, &i, "\\ ")) {
2057 if (prevColor != ColorNormal) {
2058 if (oldi > next_out) {
2059 SendToPlayer(&buf[next_out], oldi - next_out);
2062 Colorize(prevColor, TRUE);
2063 curColor = prevColor;
2065 if (savingComment) {
2066 parse_pos = i - oldi;
2067 memcpy(parse, &buf[oldi], parse_pos);
2068 parse[parse_pos] = NULLCHAR;
2069 started = STARTED_COMMENT;
2071 started = STARTED_CHATTER;
2076 if (looking_at(buf, &i, "Black Strength :") ||
2077 looking_at(buf, &i, "<<< style 10 board >>>") ||
2078 looking_at(buf, &i, "<10>") ||
2079 looking_at(buf, &i, "#@#")) {
2080 /* Wrong board style */
2082 SendToICS(ics_prefix);
2083 SendToICS("set style 12\n");
2084 SendToICS(ics_prefix);
2085 SendToICS("refresh\n");
2089 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2091 have_sent_ICS_logon = 1;
2095 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2096 (looking_at(buf, &i, "\n<12> ") ||
2097 looking_at(buf, &i, "<12> "))) {
2099 if (oldi > next_out) {
2100 SendToPlayer(&buf[next_out], oldi - next_out);
2103 started = STARTED_BOARD;
2108 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2109 looking_at(buf, &i, "<b1> ")) {
2110 if (oldi > next_out) {
2111 SendToPlayer(&buf[next_out], oldi - next_out);
2114 started = STARTED_HOLDINGS;
2119 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2121 /* Header for a move list -- first line */
2123 switch (ics_getting_history) {
2127 case BeginningOfGame:
2128 /* User typed "moves" or "oldmoves" while we
2129 were idle. Pretend we asked for these
2130 moves and soak them up so user can step
2131 through them and/or save them.
2134 gameMode = IcsObserving;
2137 ics_getting_history = H_GOT_UNREQ_HEADER;
2139 case EditGame: /*?*/
2140 case EditPosition: /*?*/
2141 /* Should above feature work in these modes too? */
2142 /* For now it doesn't */
2143 ics_getting_history = H_GOT_UNWANTED_HEADER;
2146 ics_getting_history = H_GOT_UNWANTED_HEADER;
2151 /* Is this the right one? */
2152 if (gameInfo.white && gameInfo.black &&
2153 strcmp(gameInfo.white, star_match[0]) == 0 &&
2154 strcmp(gameInfo.black, star_match[2]) == 0) {
2156 ics_getting_history = H_GOT_REQ_HEADER;
2159 case H_GOT_REQ_HEADER:
2160 case H_GOT_UNREQ_HEADER:
2161 case H_GOT_UNWANTED_HEADER:
2162 case H_GETTING_MOVES:
2163 /* Should not happen */
2164 DisplayError("Error gathering move list: two headers", 0);
2165 ics_getting_history = H_FALSE;
2169 /* Save player ratings into gameInfo if needed */
2170 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2171 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2172 (gameInfo.whiteRating == -1 ||
2173 gameInfo.blackRating == -1)) {
2175 gameInfo.whiteRating = string_to_rating(star_match[1]);
2176 gameInfo.blackRating = string_to_rating(star_match[3]);
2177 if (appData.debugMode)
2178 fprintf(debugFP, "Ratings from header: W %d, B %d\n",
2179 gameInfo.whiteRating, gameInfo.blackRating);
2184 if (looking_at(buf, &i,
2185 "* * match, initial time: * minute*, increment: * second")) {
2186 /* Header for a move list -- second line */
2187 /* Initial board will follow if this is a wild game */
2189 if (gameInfo.event != NULL) free(gameInfo.event);
2190 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2191 gameInfo.event = StrSave(str);
2192 gameInfo.variant = StringToVariant(gameInfo.event);
2196 if (looking_at(buf, &i, "Move ")) {
2197 /* Beginning of a move list */
2198 switch (ics_getting_history) {
2200 /* Normally should not happen */
2201 /* Maybe user hit reset while we were parsing */
2204 /* Happens if we are ignoring a move list that is not
2205 * the one we just requested. Common if the user
2206 * tries to observe two games without turning off
2209 case H_GETTING_MOVES:
2210 /* Should not happen */
2211 DisplayError("Error gathering move list: nested", 0);
2212 ics_getting_history = H_FALSE;
2214 case H_GOT_REQ_HEADER:
2215 ics_getting_history = H_GETTING_MOVES;
2216 started = STARTED_MOVES;
2218 if (oldi > next_out) {
2219 SendToPlayer(&buf[next_out], oldi - next_out);
2222 case H_GOT_UNREQ_HEADER:
2223 ics_getting_history = H_GETTING_MOVES;
2224 started = STARTED_MOVES_NOHIDE;
2227 case H_GOT_UNWANTED_HEADER:
2228 ics_getting_history = H_FALSE;
2234 if (looking_at(buf, &i, "% ") ||
2235 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2236 && looking_at(buf, &i, "}*"))) {
2237 savingComment = FALSE;
2240 case STARTED_MOVES_NOHIDE:
2241 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2242 parse[parse_pos + i - oldi] = NULLCHAR;
2243 ParseGameHistory(parse);
2245 if (appData.zippyPlay && first.initDone) {
2246 FeedMovesToProgram(&first, forwardMostMove);
2247 if (gameMode == IcsPlayingWhite) {
2248 if (WhiteOnMove(forwardMostMove)) {
2249 if (first.sendTime) {
2250 if (first.useColors) {
2251 SendToProgram("black\n", &first);
2253 SendTimeRemaining(&first, TRUE);
2255 if (first.useColors) {
2256 SendToProgram("white\ngo\n", &first);
2258 SendToProgram("go\n", &first);
2260 first.maybeThinking = TRUE;
2262 if (first.usePlayother) {
2263 if (first.sendTime) {
2264 SendTimeRemaining(&first, TRUE);
2266 SendToProgram("playother\n", &first);
2272 } else if (gameMode == IcsPlayingBlack) {
2273 if (!WhiteOnMove(forwardMostMove)) {
2274 if (first.sendTime) {
2275 if (first.useColors) {
2276 SendToProgram("white\n", &first);
2278 SendTimeRemaining(&first, FALSE);
2280 if (first.useColors) {
2281 SendToProgram("black\ngo\n", &first);
2283 SendToProgram("go\n", &first);
2285 first.maybeThinking = TRUE;
2287 if (first.usePlayother) {
2288 if (first.sendTime) {
2289 SendTimeRemaining(&first, FALSE);
2291 SendToProgram("playother\n", &first);
2300 if (gameMode == IcsObserving && ics_gamenum == -1) {
2301 /* Moves came from oldmoves or moves command
2302 while we weren't doing anything else.
2304 currentMove = forwardMostMove;
2305 ClearHighlights();/*!!could figure this out*/
2306 flipView = appData.flipView;
2307 DrawPosition(FALSE, boards[currentMove]);
2308 DisplayBothClocks();
2309 sprintf(str, "%s vs. %s",
2310 gameInfo.white, gameInfo.black);
2314 /* Moves were history of an active game */
2315 if (gameInfo.resultDetails != NULL) {
2316 free(gameInfo.resultDetails);
2317 gameInfo.resultDetails = NULL;
2320 HistorySet(parseList, backwardMostMove,
2321 forwardMostMove, currentMove-1);
2322 DisplayMove(currentMove - 1);
2323 if (started == STARTED_MOVES) next_out = i;
2324 started = STARTED_NONE;
2325 ics_getting_history = H_FALSE;
2328 case STARTED_OBSERVE:
2329 started = STARTED_NONE;
2330 SendToICS(ics_prefix);
2331 SendToICS("refresh\n");
2340 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2341 started == STARTED_HOLDINGS ||
2342 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2343 /* Accumulate characters in move list or board */
2344 parse[parse_pos++] = buf[i];
2347 /* Start of game messages. Mostly we detect start of game
2348 when the first board image arrives. On some versions
2349 of the ICS, though, we need to do a "refresh" after starting
2350 to observe in order to get the current board right away. */
2351 if (looking_at(buf, &i, "Adding game * to observation list")) {
2352 started = STARTED_OBSERVE;
2356 /* Handle auto-observe */
2357 if (appData.autoObserve &&
2358 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2359 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2361 /* Choose the player that was highlighted, if any. */
2362 if (star_match[0][0] == '\033' ||
2363 star_match[1][0] != '\033') {
2364 player = star_match[0];
2366 player = star_match[2];
2368 sprintf(str, "%sobserve %s\n",
2369 ics_prefix, StripHighlightAndTitle(player));
2372 /* Save ratings from notify string */
2373 strcpy(player1Name, star_match[0]);
2374 player1Rating = string_to_rating(star_match[1]);
2375 strcpy(player2Name, star_match[2]);
2376 player2Rating = string_to_rating(star_match[3]);
2378 if (appData.debugMode)
2380 "Ratings from 'Game notification:' %s %d, %s %d\n",
2381 player1Name, player1Rating,
2382 player2Name, player2Rating);
2387 /* Deal with automatic examine mode after a game,
2388 and with IcsObserving -> IcsExamining transition */
2389 if (looking_at(buf, &i, "Entering examine mode for game *") ||
2390 looking_at(buf, &i, "has made you an examiner of game *")) {
2392 int gamenum = atoi(star_match[0]);
2393 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2394 gamenum == ics_gamenum) {
2395 /* We were already playing or observing this game;
2396 no need to refetch history */
2397 gameMode = IcsExamining;
2399 pauseExamForwardMostMove = forwardMostMove;
2400 } else if (currentMove < forwardMostMove) {
2401 ForwardInner(forwardMostMove);
2404 /* I don't think this case really can happen */
2405 SendToICS(ics_prefix);
2406 SendToICS("refresh\n");
2411 /* Error messages */
2412 if (ics_user_moved) {
2413 if (looking_at(buf, &i, "Illegal move") ||
2414 looking_at(buf, &i, "Not a legal move") ||
2415 looking_at(buf, &i, "Your king is in check") ||
2416 looking_at(buf, &i, "It isn't your turn") ||
2417 looking_at(buf, &i, "It is not your move")) {
2420 if (forwardMostMove > backwardMostMove) {
2421 currentMove = --forwardMostMove;
2422 DisplayMove(currentMove - 1); /* before DMError */
2423 DisplayMoveError("Illegal move (rejected by ICS)");
2424 DrawPosition(FALSE, boards[currentMove]);
2426 DisplayBothClocks();
2432 if (looking_at(buf, &i, "still have time") ||
2433 looking_at(buf, &i, "not out of time") ||
2434 looking_at(buf, &i, "either player is out of time") ||
2435 looking_at(buf, &i, "has timeseal; checking")) {
2436 /* We must have called his flag a little too soon */
2437 whiteFlag = blackFlag = FALSE;
2441 if (looking_at(buf, &i, "added * seconds to") ||
2442 looking_at(buf, &i, "seconds were added to")) {
2443 /* Update the clocks */
2444 SendToICS(ics_prefix);
2445 SendToICS("refresh\n");
2449 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
2450 ics_clock_paused = TRUE;
2455 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
2456 ics_clock_paused = FALSE;
2461 /* Grab player ratings from the Creating: message.
2462 Note we have to check for the special case when
2463 the ICS inserts things like [white] or [black]. */
2464 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
2465 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
2467 0 player 1 name (not necessarily white)
2469 2 empty, white, or black (IGNORED)
2470 3 player 2 name (not necessarily black)
2473 The names/ratings are sorted out when the game
2474 actually starts (below).
2476 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
2477 player1Rating = string_to_rating(star_match[1]);
2478 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
2479 player2Rating = string_to_rating(star_match[4]);
2481 if (appData.debugMode)
2483 "Ratings from 'Creating:' %s %d, %s %d\n",
2484 player1Name, player1Rating,
2485 player2Name, player2Rating);
2490 /* Improved generic start/end-of-game messages */
2491 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
2492 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
2493 /* If tkind == 0: */
2494 /* star_match[0] is the game number */
2495 /* [1] is the white player's name */
2496 /* [2] is the black player's name */
2497 /* For end-of-game: */
2498 /* [3] is the reason for the game end */
2499 /* [4] is a PGN end game-token, preceded by " " */
2500 /* For start-of-game: */
2501 /* [3] begins with "Creating" or "Continuing" */
2502 /* [4] is " *" or empty (don't care). */
2503 int gamenum = atoi(star_match[0]);
2504 char *whitename, *blackname, *why, *endtoken;
2505 ChessMove endtype = (ChessMove) 0;
2508 whitename = star_match[1];
2509 blackname = star_match[2];
2510 why = star_match[3];
2511 endtoken = star_match[4];
2513 whitename = star_match[1];
2514 blackname = star_match[3];
2515 why = star_match[5];
2516 endtoken = star_match[6];
2519 /* Game start messages */
2520 if (strncmp(why, "Creating ", 9) == 0 ||
2521 strncmp(why, "Continuing ", 11) == 0) {
2522 gs_gamenum = gamenum;
2523 strcpy(gs_kind, strchr(why, ' ') + 1);
2525 if (appData.zippyPlay) {
2526 ZippyGameStart(whitename, blackname);
2532 /* Game end messages */
2533 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
2534 ics_gamenum != gamenum) {
2537 while (endtoken[0] == ' ') endtoken++;
2538 switch (endtoken[0]) {
2541 endtype = GameUnfinished;
2544 endtype = BlackWins;
2547 if (endtoken[1] == '/')
2548 endtype = GameIsDrawn;
2550 endtype = WhiteWins;
2553 GameEnds(endtype, why, GE_ICS);
2555 if (appData.zippyPlay && first.initDone) {
2556 ZippyGameEnd(endtype, why);
2557 if (first.pr == NULL) {
2558 /* Start the next process early so that we'll
2559 be ready for the next challenge */
2560 StartChessProgram(&first);
2562 /* Send "new" early, in case this command takes
2563 a long time to finish, so that we'll be ready
2564 for the next challenge. */
2571 if (looking_at(buf, &i, "Removing game * from observation") ||
2572 looking_at(buf, &i, "no longer observing game *") ||
2573 looking_at(buf, &i, "Game * (*) has no examiners")) {
2574 if (gameMode == IcsObserving &&
2575 atoi(star_match[0]) == ics_gamenum)
2580 ics_user_moved = FALSE;
2585 if (looking_at(buf, &i, "no longer examining game *")) {
2586 if (gameMode == IcsExamining &&
2587 atoi(star_match[0]) == ics_gamenum)
2591 ics_user_moved = FALSE;
2596 /* Advance leftover_start past any newlines we find,
2597 so only partial lines can get reparsed */
2598 if (looking_at(buf, &i, "\n")) {
2599 prevColor = curColor;
2600 if (curColor != ColorNormal) {
2601 if (oldi > next_out) {
2602 SendToPlayer(&buf[next_out], oldi - next_out);
2605 Colorize(ColorNormal, FALSE);
2606 curColor = ColorNormal;
2608 if (started == STARTED_BOARD) {
2609 started = STARTED_NONE;
2610 parse[parse_pos] = NULLCHAR;
2611 ParseBoard12(parse);
2614 /* Send premove here */
2615 if (appData.premove) {
2617 if (currentMove == 0 &&
2618 gameMode == IcsPlayingWhite &&
2619 appData.premoveWhite) {
2620 sprintf(str, "%s%s\n", ics_prefix,
2621 appData.premoveWhiteText);
2622 if (appData.debugMode)
2623 fprintf(debugFP, "Sending premove:\n");
2625 } else if (currentMove == 1 &&
2626 gameMode == IcsPlayingBlack &&
2627 appData.premoveBlack) {
2628 sprintf(str, "%s%s\n", ics_prefix,
2629 appData.premoveBlackText);
2630 if (appData.debugMode)
2631 fprintf(debugFP, "Sending premove:\n");
2633 } else if (gotPremove) {
2635 ClearPremoveHighlights();
2636 if (appData.debugMode)
2637 fprintf(debugFP, "Sending premove:\n");
2638 UserMoveEvent(premoveFromX, premoveFromY,
2639 premoveToX, premoveToY,
2644 /* Usually suppress following prompt */
2645 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
2646 if (looking_at(buf, &i, "*% ")) {
2647 savingComment = FALSE;
2651 } else if (started == STARTED_HOLDINGS) {
2653 char new_piece[MSG_SIZ];
2654 started = STARTED_NONE;
2655 parse[parse_pos] = NULLCHAR;
2656 if (appData.debugMode)
2657 fprintf(debugFP, "Parsing holdings: %s\n", parse);
2658 if (sscanf(parse, " game %d", &gamenum) == 1 &&
2659 gamenum == ics_gamenum) {
2660 if (gameInfo.variant == VariantNormal) {
2661 gameInfo.variant = VariantCrazyhouse; /*temp guess*/
2662 /* Get a move list just to see the header, which
2663 will tell us whether this is really bug or zh */
2664 if (ics_getting_history == H_FALSE) {
2665 ics_getting_history = H_REQUESTED;
2666 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
2670 new_piece[0] = NULLCHAR;
2671 sscanf(parse, "game %d white [%s black [%s <- %s",
2672 &gamenum, white_holding, black_holding,
2674 white_holding[strlen(white_holding)-1] = NULLCHAR;
2675 black_holding[strlen(black_holding)-1] = NULLCHAR;
2677 if (appData.zippyPlay && first.initDone) {
2678 ZippyHoldings(white_holding, black_holding,
2682 if (tinyLayout || smallLayout) {
2683 char wh[16], bh[16];
2684 PackHolding(wh, white_holding);
2685 PackHolding(bh, black_holding);
2686 sprintf(str, "[%s-%s] %s-%s", wh, bh,
2687 gameInfo.white, gameInfo.black);
2689 sprintf(str, "%s [%s] vs. %s [%s]",
2690 gameInfo.white, white_holding,
2691 gameInfo.black, black_holding);
2693 DrawPosition(FALSE, NULL);
2696 /* Suppress following prompt */
2697 if (looking_at(buf, &i, "*% ")) {
2698 savingComment = FALSE;
2705 i++; /* skip unparsed character and loop back */
2708 if (started != STARTED_MOVES && started != STARTED_BOARD &&
2709 started != STARTED_HOLDINGS && i > next_out) {
2710 SendToPlayer(&buf[next_out], i - next_out);
2714 leftover_len = buf_len - leftover_start;
2715 /* if buffer ends with something we couldn't parse,
2716 reparse it after appending the next read */
2718 } else if (count == 0) {
2719 RemoveInputSource(isr);
2720 DisplayFatalError("Connection closed by ICS", 0, 0);
2722 DisplayFatalError("Error reading from ICS", error, 1);
2727 /* Board style 12 looks like this:
2729 <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
2731 * The "<12> " is stripped before it gets to this routine. The two
2732 * trailing 0's (flip state and clock ticking) are later addition, and
2733 * some chess servers may not have them, or may have only the first.
2734 * Additional trailing fields may be added in the future.
2737 #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"
2739 #define RELATION_OBSERVING_PLAYED 0
2740 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
2741 #define RELATION_PLAYING_MYMOVE 1
2742 #define RELATION_PLAYING_NOTMYMOVE -1
2743 #define RELATION_EXAMINING 2
2744 #define RELATION_ISOLATED_BOARD -3
2745 #define RELATION_STARTING_POSITION -4 /* FICS only */
2748 ParseBoard12(string)
2751 GameMode newGameMode;
2752 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
2753 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
2754 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
2755 char to_play, board_chars[72];
2756 char move_str[500], str[500], elapsed_time[500];
2757 char black[32], white[32];
2759 int prevMove = currentMove;
2762 int fromX, fromY, toX, toY;
2765 fromX = fromY = toX = toY = -1;
2769 if (appData.debugMode)
2770 fprintf(debugFP, "Parsing board: %s\n", string);
2772 move_str[0] = NULLCHAR;
2773 elapsed_time[0] = NULLCHAR;
2774 n = sscanf(string, PATTERN, board_chars, &to_play, &double_push,
2775 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
2776 &gamenum, white, black, &relation, &basetime, &increment,
2777 &white_stren, &black_stren, &white_time, &black_time,
2778 &moveNum, str, elapsed_time, move_str, &ics_flip,
2782 sprintf(str, "Failed to parse board string:\n\"%s\"", string);
2783 DisplayError(str, 0);
2787 /* Convert the move number to internal form */
2788 moveNum = (moveNum - 1) * 2;
2789 if (to_play == 'B') moveNum++;
2790 if (moveNum >= MAX_MOVES) {
2791 DisplayFatalError("Game too long; increase MAX_MOVES and recompile",
2797 case RELATION_OBSERVING_PLAYED:
2798 case RELATION_OBSERVING_STATIC:
2799 if (gamenum == -1) {
2800 /* Old ICC buglet */
2801 relation = RELATION_OBSERVING_STATIC;
2803 newGameMode = IcsObserving;
2805 case RELATION_PLAYING_MYMOVE:
2806 case RELATION_PLAYING_NOTMYMOVE:
2808 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
2809 IcsPlayingWhite : IcsPlayingBlack;
2811 case RELATION_EXAMINING:
2812 newGameMode = IcsExamining;
2814 case RELATION_ISOLATED_BOARD:
2816 /* Just display this board. If user was doing something else,
2817 we will forget about it until the next board comes. */
2818 newGameMode = IcsIdle;
2820 case RELATION_STARTING_POSITION:
2821 newGameMode = gameMode;
2825 /* Modify behavior for initial board display on move listing
2828 switch (ics_getting_history) {
2832 case H_GOT_REQ_HEADER:
2833 case H_GOT_UNREQ_HEADER:
2834 /* This is the initial position of the current game */
2835 gamenum = ics_gamenum;
2836 moveNum = 0; /* old ICS bug workaround */
2837 if (to_play == 'B') {
2838 startedFromSetupPosition = TRUE;
2839 blackPlaysFirst = TRUE;
2841 if (forwardMostMove == 0) forwardMostMove = 1;
2842 if (backwardMostMove == 0) backwardMostMove = 1;
2843 if (currentMove == 0) currentMove = 1;
2845 newGameMode = gameMode;
2846 relation = RELATION_STARTING_POSITION; /* ICC needs this */
2848 case H_GOT_UNWANTED_HEADER:
2849 /* This is an initial board that we don't want */
2851 case H_GETTING_MOVES:
2852 /* Should not happen */
2853 DisplayError("Error gathering move list: extra board", 0);
2854 ics_getting_history = H_FALSE;
2858 /* Take action if this is the first board of a new game, or of a
2859 different game than is currently being displayed. */
2860 if (gamenum != ics_gamenum || newGameMode != gameMode ||
2861 relation == RELATION_ISOLATED_BOARD) {
2863 /* Forget the old game and get the history (if any) of the new one */
2864 if (gameMode != BeginningOfGame) {
2868 if (appData.autoRaiseBoard) BoardToTop();
2870 if (gamenum == -1) {
2871 newGameMode = IcsIdle;
2872 } else if (moveNum > 0 && newGameMode != IcsIdle &&
2873 appData.getMoveList) {
2874 /* Need to get game history */
2875 ics_getting_history = H_REQUESTED;
2876 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
2880 /* Initially flip the board to have black on the bottom if playing
2881 black or if the ICS flip flag is set, but let the user change
2882 it with the Flip View button. */
2883 flipView = appData.autoFlipView ?
2884 (newGameMode == IcsPlayingBlack) || ics_flip :
2887 /* Done with values from previous mode; copy in new ones */
2888 gameMode = newGameMode;
2890 ics_gamenum = gamenum;
2891 if (gamenum == gs_gamenum) {
2892 int klen = strlen(gs_kind);
2893 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
2894 sprintf(str, "ICS %s", gs_kind);
2895 gameInfo.event = StrSave(str);
2897 gameInfo.event = StrSave("ICS game");
2899 gameInfo.site = StrSave(appData.icsHost);
2900 gameInfo.date = PGNDate();
2901 gameInfo.round = StrSave("-");
2902 gameInfo.white = StrSave(white);
2903 gameInfo.black = StrSave(black);
2904 timeControl = basetime * 60 * 1000;
2906 timeIncrement = increment * 1000;
2907 movesPerSession = 0;
2908 gameInfo.timeControl = TimeControlTagValue();
2909 gameInfo.variant = StringToVariant(gameInfo.event);
2910 gameInfo.outOfBook = NULL;
2912 /* Do we have the ratings? */
2913 if (strcmp(player1Name, white) == 0 &&
2914 strcmp(player2Name, black) == 0) {
2915 if (appData.debugMode)
2916 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
2917 player1Rating, player2Rating);
2918 gameInfo.whiteRating = player1Rating;
2919 gameInfo.blackRating = player2Rating;
2920 } else if (strcmp(player2Name, white) == 0 &&
2921 strcmp(player1Name, black) == 0) {
2922 if (appData.debugMode)
2923 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
2924 player2Rating, player1Rating);
2925 gameInfo.whiteRating = player2Rating;
2926 gameInfo.blackRating = player1Rating;
2928 player1Name[0] = player2Name[0] = NULLCHAR;
2930 /* Silence shouts if requested */
2931 if (appData.quietPlay &&
2932 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
2933 SendToICS(ics_prefix);
2934 SendToICS("set shout 0\n");
2938 /* Deal with midgame name changes */
2940 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
2941 if (gameInfo.white) free(gameInfo.white);
2942 gameInfo.white = StrSave(white);
2944 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
2945 if (gameInfo.black) free(gameInfo.black);
2946 gameInfo.black = StrSave(black);
2950 /* Throw away game result if anything actually changes in examine mode */
2951 if (gameMode == IcsExamining && !newGame) {
2952 gameInfo.result = GameUnfinished;
2953 if (gameInfo.resultDetails != NULL) {
2954 free(gameInfo.resultDetails);
2955 gameInfo.resultDetails = NULL;
2959 /* In pausing && IcsExamining mode, we ignore boards coming
2960 in if they are in a different variation than we are. */
2961 if (pauseExamInvalid) return;
2962 if (pausing && gameMode == IcsExamining) {
2963 if (moveNum <= pauseExamForwardMostMove) {
2964 pauseExamInvalid = TRUE;
2965 forwardMostMove = pauseExamForwardMostMove;
2970 /* Parse the board */
2971 for (k = 0; k < 8; k++)
2972 for (j = 0; j < 8; j++)
2973 board[k][j] = CharToPiece(board_chars[(7-k)*9 + j]);
2974 CopyBoard(boards[moveNum], board);
2976 startedFromSetupPosition =
2977 !CompareBoards(board, initialPosition);
2980 if (ics_getting_history == H_GOT_REQ_HEADER ||
2981 ics_getting_history == H_GOT_UNREQ_HEADER) {
2982 /* This was an initial position from a move list, not
2983 the current position */
2987 /* Update currentMove and known move number limits */
2988 newMove = newGame || moveNum > forwardMostMove;
2990 forwardMostMove = backwardMostMove = currentMove = moveNum;
2991 if (gameMode == IcsExamining && moveNum == 0) {
2992 /* Workaround for ICS limitation: we are not told the wild
2993 type when starting to examine a game. But if we ask for
2994 the move list, the move list header will tell us */
2995 ics_getting_history = H_REQUESTED;
2996 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
2999 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3000 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3001 forwardMostMove = moveNum;
3002 if (!pausing || currentMove > forwardMostMove)
3003 currentMove = forwardMostMove;
3005 /* New part of history that is not contiguous with old part */
3006 if (pausing && gameMode == IcsExamining) {
3007 pauseExamInvalid = TRUE;
3008 forwardMostMove = pauseExamForwardMostMove;
3011 forwardMostMove = backwardMostMove = currentMove = moveNum;
3012 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3013 ics_getting_history = H_REQUESTED;
3014 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3019 /* Update the clocks */
3020 if (strchr(elapsed_time, '.')) {
3022 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3023 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3025 /* Time is in seconds */
3026 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3027 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3032 if (appData.zippyPlay && newGame &&
3033 gameMode != IcsObserving && gameMode != IcsIdle &&
3034 gameMode != IcsExamining)
3035 ZippyFirstBoard(moveNum, basetime, increment);
3038 /* Put the move on the move list, first converting
3039 to canonical algebraic form. */
3041 if (moveNum <= backwardMostMove) {
3042 /* We don't know what the board looked like before
3044 strcpy(parseList[moveNum - 1], move_str);
3045 strcat(parseList[moveNum - 1], " ");
3046 strcat(parseList[moveNum - 1], elapsed_time);
3047 moveList[moveNum - 1][0] = NULLCHAR;
3048 } else if (ParseOneMove(move_str, moveNum - 1, &moveType,
3049 &fromX, &fromY, &toX, &toY, &promoChar)) {
3050 (void) CoordsToAlgebraic(boards[moveNum - 1],
3051 PosFlags(moveNum - 1), EP_UNKNOWN,
3052 fromY, fromX, toY, toX, promoChar,
3053 parseList[moveNum-1]);
3054 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN)){
3060 strcat(parseList[moveNum - 1], "+");
3063 strcat(parseList[moveNum - 1], "#");
3066 strcat(parseList[moveNum - 1], " ");
3067 strcat(parseList[moveNum - 1], elapsed_time);
3068 /* currentMoveString is set as a side-effect of ParseOneMove */
3069 strcpy(moveList[moveNum - 1], currentMoveString);
3070 strcat(moveList[moveNum - 1], "\n");
3071 } else if (strcmp(move_str, "none") == 0) {
3072 /* Again, we don't know what the board looked like;
3073 this is really the start of the game. */
3074 parseList[moveNum - 1][0] = NULLCHAR;
3075 moveList[moveNum - 1][0] = NULLCHAR;
3076 backwardMostMove = moveNum;
3077 startedFromSetupPosition = TRUE;
3078 fromX = fromY = toX = toY = -1;
3080 /* Move from ICS was illegal!? Punt. */
3082 if (appData.testLegality && appData.debugMode) {
3083 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
3084 DisplayError(str, 0);
3087 strcpy(parseList[moveNum - 1], move_str);
3088 strcat(parseList[moveNum - 1], " ");
3089 strcat(parseList[moveNum - 1], elapsed_time);
3090 moveList[moveNum - 1][0] = NULLCHAR;
3091 fromX = fromY = toX = toY = -1;
3095 /* Send move to chess program (BEFORE animating it). */
3096 if (appData.zippyPlay && !newGame && newMove &&
3097 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3099 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3100 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3101 if (moveList[moveNum - 1][0] == NULLCHAR) {
3102 sprintf(str, "Couldn't parse move \"%s\" from ICS",
3104 DisplayError(str, 0);
3106 if (first.sendTime) {
3107 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3109 SendMoveToProgram(moveNum - 1, &first);
3112 if (first.useColors) {
3113 SendToProgram(gameMode == IcsPlayingWhite ?
3115 "black\ngo\n", &first);
3117 SendToProgram("go\n", &first);
3119 first.maybeThinking = TRUE;
3122 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3123 if (moveList[moveNum - 1][0] == NULLCHAR) {
3124 sprintf(str, "Couldn't parse move \"%s\" from ICS", move_str);
3125 DisplayError(str, 0);
3127 SendMoveToProgram(moveNum - 1, &first);
3134 if (moveNum > 0 && !gotPremove) {
3135 /* If move comes from a remote source, animate it. If it
3136 isn't remote, it will have already been animated. */
3137 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3138 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3140 if (!pausing && appData.highlightLastMove) {
3141 SetHighlights(fromX, fromY, toX, toY);
3145 /* Start the clocks */
3146 whiteFlag = blackFlag = FALSE;
3147 appData.clockMode = !(basetime == 0 && increment == 0);
3149 ics_clock_paused = TRUE;
3151 } else if (ticking == 1) {
3152 ics_clock_paused = FALSE;
3154 if (gameMode == IcsIdle ||
3155 relation == RELATION_OBSERVING_STATIC ||
3156 relation == RELATION_EXAMINING ||
3158 DisplayBothClocks();
3162 /* Display opponents and material strengths */
3163 if (gameInfo.variant != VariantBughouse &&
3164 gameInfo.variant != VariantCrazyhouse) {
3165 if (tinyLayout || smallLayout) {
3166 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3167 gameInfo.white, white_stren, gameInfo.black, black_stren,
3168 basetime, increment);
3170 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3171 gameInfo.white, white_stren, gameInfo.black, black_stren,
3172 basetime, increment);
3178 /* Display the board */
3181 if (appData.premove)
3183 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3184 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3185 ClearPremoveHighlights();
3187 DrawPosition(FALSE, boards[currentMove]);
3188 DisplayMove(moveNum - 1);
3189 if (appData.ringBellAfterMoves && !ics_user_moved)
3193 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3200 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3201 ics_getting_history = H_REQUESTED;
3202 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3208 AnalysisPeriodicEvent(force)
3211 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3212 && !force) || !appData.periodicUpdates)
3215 /* Send . command to Crafty to collect stats */
3216 SendToProgram(".\n", &first);
3218 /* Don't send another until we get a response (this makes
3219 us stop sending to old Crafty's which don't understand
3220 the "." command (sending illegal cmds resets node count & time,
3221 which looks bad)) */
3222 programStats.ok_to_send = 0;
3226 SendMoveToProgram(moveNum, cps)
3228 ChessProgramState *cps;
3231 if (cps->useUsermove) {
3232 SendToProgram("usermove ", cps);
3236 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
3237 int len = space - parseList[moveNum];
3238 memcpy(buf, parseList[moveNum], len);
3240 buf[len] = NULLCHAR;
3242 sprintf(buf, "%s\n", parseList[moveNum]);
3244 SendToProgram(buf, cps);
3246 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
3247 * the engine. It would be nice to have a better way to identify castle
3249 if(gameInfo.variant == VariantFischeRandom && cps->useOOCastle) {
3250 int fromX = moveList[moveNum][0] - 'a';
3251 int fromY = moveList[moveNum][1] - '1';
3252 int toX = moveList[moveNum][2] - 'a';
3253 int toY = moveList[moveNum][3] - '1';
3254 if((boards[currentMove][fromY][fromX] == WhiteKing
3255 && boards[currentMove][toY][toX] == WhiteRook)
3256 || (boards[currentMove][fromY][fromX] == BlackKing
3257 && boards[currentMove][toY][toX] == BlackRook)) {
3258 if(toX > fromX) SendToProgram("O-O\n", cps);
3259 else SendToProgram("O-O-O\n", cps);
3261 else SendToProgram(moveList[moveNum], cps);
3263 else SendToProgram(moveList[moveNum], cps);
3264 /* End of additions by Tord */
3269 SendMoveToICS(moveType, fromX, fromY, toX, toY)
3271 int fromX, fromY, toX, toY;
3273 char user_move[MSG_SIZ];
3277 sprintf(user_move, "say Internal error; bad moveType %d (%d,%d-%d,%d)",
3278 (int)moveType, fromX, fromY, toX, toY);
3279 DisplayError(user_move + strlen("say "), 0);
3281 case WhiteKingSideCastle:
3282 case BlackKingSideCastle:
3283 case WhiteQueenSideCastleWild:
3284 case BlackQueenSideCastleWild:
3286 case WhiteHSideCastleFR:
3287 case BlackHSideCastleFR:
3289 sprintf(user_move, "o-o\n");
3291 case WhiteQueenSideCastle:
3292 case BlackQueenSideCastle:
3293 case WhiteKingSideCastleWild:
3294 case BlackKingSideCastleWild:
3296 case WhiteASideCastleFR:
3297 case BlackASideCastleFR:
3299 sprintf(user_move, "o-o-o\n");
3301 case WhitePromotionQueen:
3302 case BlackPromotionQueen:
3303 case WhitePromotionRook:
3304 case BlackPromotionRook:
3305 case WhitePromotionBishop:
3306 case BlackPromotionBishop:
3307 case WhitePromotionKnight:
3308 case BlackPromotionKnight:
3309 case WhitePromotionKing:
3310 case BlackPromotionKing:
3311 sprintf(user_move, "%c%c%c%c=%c\n",
3312 'a' + fromX, '1' + fromY, 'a' + toX, '1' + toY,
3313 PieceToChar(PromoPiece(moveType)));
3317 sprintf(user_move, "%c@%c%c\n",
3318 ToUpper(PieceToChar((ChessSquare) fromX)),
3319 'a' + toX, '1' + toY);
3322 case WhiteCapturesEnPassant:
3323 case BlackCapturesEnPassant:
3324 case IllegalMove: /* could be a variant we don't quite understand */
3325 sprintf(user_move, "%c%c%c%c\n",
3326 'a' + fromX, '1' + fromY, 'a' + toX, '1' + toY);
3329 SendToICS(user_move);
3333 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
3338 if (rf == DROP_RANK) {
3339 sprintf(move, "%c@%c%c\n",
3340 ToUpper(PieceToChar((ChessSquare) ff)), 'a' + ft, '1' + rt);
3342 if (promoChar == 'x' || promoChar == NULLCHAR) {
3343 sprintf(move, "%c%c%c%c\n",
3344 'a' + ff, '1' + rf, 'a' + ft, '1' + rt);
3346 sprintf(move, "%c%c%c%c%c\n",
3347 'a' + ff, '1' + rf, 'a' + ft, '1' + rt, promoChar);
3353 ProcessICSInitScript(f)
3358 while (fgets(buf, MSG_SIZ, f)) {
3359 SendToICSDelayed(buf,(long)appData.msLoginDelay);
3366 /* Parser for moves from gnuchess, ICS, or user typein box */
3368 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
3371 ChessMove *moveType;
3372 int *fromX, *fromY, *toX, *toY;
3375 *moveType = yylexstr(moveNum, move);
3376 switch (*moveType) {
3377 case WhitePromotionQueen:
3378 case BlackPromotionQueen:
3379 case WhitePromotionRook:
3380 case BlackPromotionRook:
3381 case WhitePromotionBishop:
3382 case BlackPromotionBishop:
3383 case WhitePromotionKnight:
3384 case BlackPromotionKnight:
3385 case WhitePromotionKing:
3386 case BlackPromotionKing:
3388 case WhiteCapturesEnPassant:
3389 case BlackCapturesEnPassant:
3390 case WhiteKingSideCastle:
3391 case WhiteQueenSideCastle:
3392 case BlackKingSideCastle:
3393 case BlackQueenSideCastle:
3394 case WhiteKingSideCastleWild:
3395 case WhiteQueenSideCastleWild:
3396 case BlackKingSideCastleWild:
3397 case BlackQueenSideCastleWild:
3398 /* Code added by Tord: */
3399 case WhiteHSideCastleFR:
3400 case WhiteASideCastleFR:
3401 case BlackHSideCastleFR:
3402 case BlackASideCastleFR:
3403 /* End of code added by Tord */
3404 case IllegalMove: /* bug or odd chess variant */
3405 *fromX = currentMoveString[0] - 'a';
3406 *fromY = currentMoveString[1] - '1';
3407 *toX = currentMoveString[2] - 'a';
3408 *toY = currentMoveString[3] - '1';
3409 *promoChar = currentMoveString[4];
3410 if (*fromX < 0 || *fromX > 7 || *fromY < 0 || *fromY > 7 ||
3411 *toX < 0 || *toX > 7 || *toY < 0 || *toY > 7) {
3412 *fromX = *fromY = *toX = *toY = 0;
3415 if (appData.testLegality) {
3416 return (*moveType != IllegalMove);
3418 return !(fromX == fromY && toX == toY);
3423 *fromX = *moveType == WhiteDrop ?
3424 (int) CharToPiece(ToUpper(currentMoveString[0])) :
3425 (int) CharToPiece(ToLower(currentMoveString[0]));
3427 *toX = currentMoveString[2] - 'a';
3428 *toY = currentMoveString[3] - '1';
3429 *promoChar = NULLCHAR;
3433 case ImpossibleMove:
3434 case (ChessMove) 0: /* end of file */
3444 *fromX = *fromY = *toX = *toY = 0;
3445 *promoChar = NULLCHAR;
3450 /* [AS] FRC game initialization */
3451 static int FindEmptySquare( Board board, int n )
3456 while( board[0][i] != EmptySquare ) i++;
3466 static void ShuffleFRC( Board board )
3472 for( i=0; i<8; i++ ) {
3473 board[0][i] = EmptySquare;
3476 board[0][(rand() % 4)*2 ] = WhiteBishop; /* On dark square */
3477 board[0][(rand() % 4)*2+1] = WhiteBishop; /* On lite square */
3478 board[0][FindEmptySquare(board, rand() % 6)] = WhiteQueen;
3479 board[0][FindEmptySquare(board, rand() % 5)] = WhiteKnight;
3480 board[0][FindEmptySquare(board, rand() % 4)] = WhiteKnight;
3481 board[0][FindEmptySquare(board, 0)] = WhiteRook;
3482 board[0][FindEmptySquare(board, 0)] = WhiteKing;
3483 board[0][FindEmptySquare(board, 0)] = WhiteRook;
3485 for( i=0; i<8; i++ ) {
3486 board[7][i] = board[0][i] + BlackPawn - WhitePawn;
3490 static unsigned char FRC_KnightTable[10] = {
3491 0x00, 0x01, 0x02, 0x03, 0x11, 0x12, 0x13, 0x22, 0x23, 0x33
3494 static void SetupFRC( Board board, int pos_index )
3497 unsigned char knights;
3499 /* Bring the position index into a safe range (just in case...) */
3500 if( pos_index < 0 ) pos_index = 0;
3504 /* Clear the board */
3505 for( i=0; i<8; i++ ) {
3506 board[0][i] = EmptySquare;
3509 /* Place bishops and queen */
3510 board[0][ (pos_index % 4)*2 + 1 ] = WhiteBishop; /* On lite square */
3513 board[0][ (pos_index % 4)*2 ] = WhiteBishop; /* On dark square */
3516 board[0][ FindEmptySquare(board, pos_index % 6) ] = WhiteQueen;
3520 knights = FRC_KnightTable[ pos_index ];
3522 board[0][ FindEmptySquare(board, knights / 16) ] = WhiteKnight;
3523 board[0][ FindEmptySquare(board, knights % 16) ] = WhiteKnight;
3525 /* Place rooks and king */
3526 board[0][ FindEmptySquare(board, 0) ] = WhiteRook;
3527 board[0][ FindEmptySquare(board, 0) ] = WhiteKing;
3528 board[0][ FindEmptySquare(board, 0) ] = WhiteRook;
3530 /* Mirror piece placement for black */
3531 for( i=0; i<8; i++ ) {
3532 board[7][i] = board[0][i] + BlackPawn - WhitePawn;
3537 InitPosition(redraw)
3540 currentMove = forwardMostMove = backwardMostMove = 0;
3542 /* [AS] Initialize pv info list */
3546 for( i=0; i<MAX_MOVES; i++ ) {
3547 pvInfoList[i].depth = 0;
3551 switch (gameInfo.variant) {
3553 CopyBoard(boards[0], initialPosition);
3555 case VariantTwoKings:
3556 CopyBoard(boards[0], twoKingsPosition);
3557 startedFromSetupPosition = TRUE;
3559 case VariantWildCastle:
3560 CopyBoard(boards[0], initialPosition);
3561 /* !!?shuffle with kings guaranteed to be on d or e file */
3563 case VariantNoCastle:
3564 CopyBoard(boards[0], initialPosition);
3565 /* !!?unconstrained back-rank shuffle */
3567 case VariantFischeRandom:
3568 CopyBoard(boards[0], initialPosition);
3569 if( appData.defaultFrcPosition < 0 ) {
3570 ShuffleFRC( boards[0] );
3573 SetupFRC( boards[0], appData.defaultFrcPosition );
3579 DrawPosition(TRUE, boards[currentMove]);
3583 SendBoard(cps, moveNum)
3584 ChessProgramState *cps;
3587 char message[MSG_SIZ];
3589 if (cps->useSetboard) {
3590 char* fen = PositionToFEN(moveNum, cps->useFEN960);
3591 sprintf(message, "setboard %s\n", fen);
3592 SendToProgram(message, cps);
3598 /* Kludge to set black to move, avoiding the troublesome and now
3599 * deprecated "black" command.
3601 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
3603 SendToProgram("edit\n", cps);
3604 SendToProgram("#\n", cps);
3605 for (i = BOARD_SIZE - 1; i >= 0; i--) {
3606 bp = &boards[moveNum][i][0];
3607 for (j = 0; j < BOARD_SIZE; j++, bp++) {
3608 if ((int) *bp < (int) BlackPawn) {
3609 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
3611 SendToProgram(message, cps);
3616 SendToProgram("c\n", cps);
3617 for (i = BOARD_SIZE - 1; i >= 0; i--) {
3618 bp = &boards[moveNum][i][0];
3619 for (j = 0; j < BOARD_SIZE; j++, bp++) {
3620 if (((int) *bp != (int) EmptySquare)
3621 && ((int) *bp >= (int) BlackPawn)) {
3622 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
3624 SendToProgram(message, cps);
3629 SendToProgram(".\n", cps);
3634 IsPromotion(fromX, fromY, toX, toY)
3635 int fromX, fromY, toX, toY;
3637 return gameMode != EditPosition &&
3638 fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0 &&
3639 ((boards[currentMove][fromY][fromX] == WhitePawn && toY == 7) ||
3640 (boards[currentMove][fromY][fromX] == BlackPawn && toY == 0));
3645 PieceForSquare (x, y)
3649 if (x < 0 || x >= BOARD_SIZE || y < 0 || y >= BOARD_SIZE)
3652 return boards[currentMove][y][x];
3656 OKToStartUserMove(x, y)
3659 ChessSquare from_piece;
3662 if (matchMode) return FALSE;
3663 if (gameMode == EditPosition) return TRUE;
3665 if (x >= 0 && y >= 0)
3666 from_piece = boards[currentMove][y][x];
3668 from_piece = EmptySquare;
3670 if (from_piece == EmptySquare) return FALSE;
3672 white_piece = (int)from_piece >= (int)WhitePawn &&
3673 (int)from_piece <= (int)WhiteKing;
3676 case PlayFromGameFile:
3678 case TwoMachinesPlay:
3686 case MachinePlaysWhite:
3687 case IcsPlayingBlack:
3688 if (appData.zippyPlay) return FALSE;
3690 DisplayMoveError("You are playing Black");
3695 case MachinePlaysBlack:
3696 case IcsPlayingWhite:
3697 if (appData.zippyPlay) return FALSE;
3699 DisplayMoveError("You are playing White");
3705 if (!white_piece && WhiteOnMove(currentMove)) {
3706 DisplayMoveError("It is White's turn");
3709 if (white_piece && !WhiteOnMove(currentMove)) {
3710 DisplayMoveError("It is Black's turn");
3713 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
3714 /* Editing correspondence game history */
3715 /* Could disallow this or prompt for confirmation */
3718 if (currentMove < forwardMostMove) {
3719 /* Discarding moves */
3720 /* Could prompt for confirmation here,
3721 but I don't think that's such a good idea */
3722 forwardMostMove = currentMove;
3726 case BeginningOfGame:
3727 if (appData.icsActive) return FALSE;
3728 if (!appData.noChessProgram) {
3730 DisplayMoveError("You are playing White");
3737 if (!white_piece && WhiteOnMove(currentMove)) {
3738 DisplayMoveError("It is White's turn");
3741 if (white_piece && !WhiteOnMove(currentMove)) {
3742 DisplayMoveError("It is Black's turn");
3751 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
3752 && gameMode != AnalyzeFile && gameMode != Training) {
3753 DisplayMoveError("Displayed position is not current");
3759 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
3760 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
3761 int lastLoadGameUseList = FALSE;
3762 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
3763 ChessMove lastLoadGameStart = (ChessMove) 0;
3767 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
3768 int fromX, fromY, toX, toY;
3773 if (fromX < 0 || fromY < 0) return;
3774 if ((fromX == toX) && (fromY == toY)) {
3778 /* Check if the user is playing in turn. This is complicated because we
3779 let the user "pick up" a piece before it is his turn. So the piece he
3780 tried to pick up may have been captured by the time he puts it down!
3781 Therefore we use the color the user is supposed to be playing in this
3782 test, not the color of the piece that is currently on the starting
3783 square---except in EditGame mode, where the user is playing both
3784 sides; fortunately there the capture race can't happen. (It can
3785 now happen in IcsExamining mode, but that's just too bad. The user
3786 will get a somewhat confusing message in that case.)
3790 case PlayFromGameFile:
3792 case TwoMachinesPlay:
3796 /* We switched into a game mode where moves are not accepted,
3797 perhaps while the mouse button was down. */
3800 case MachinePlaysWhite:
3801 /* User is moving for Black */
3802 if (WhiteOnMove(currentMove)) {
3803 DisplayMoveError("It is White's turn");
3808 case MachinePlaysBlack:
3809 /* User is moving for White */
3810 if (!WhiteOnMove(currentMove)) {
3811 DisplayMoveError("It is Black's turn");
3818 case BeginningOfGame:
3821 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
3822 (int) boards[currentMove][fromY][fromX] <= (int) BlackKing) {
3823 /* User is moving for Black */
3824 if (WhiteOnMove(currentMove)) {
3825 DisplayMoveError("It is White's turn");
3829 /* User is moving for White */
3830 if (!WhiteOnMove(currentMove)) {
3831 DisplayMoveError("It is Black's turn");
3837 case IcsPlayingBlack:
3838 /* User is moving for Black */
3839 if (WhiteOnMove(currentMove)) {
3840 if (!appData.premove) {
3841 DisplayMoveError("It is White's turn");
3842 } else if (toX >= 0 && toY >= 0) {
3845 premoveFromX = fromX;
3846 premoveFromY = fromY;
3847 premovePromoChar = promoChar;
3849 if (appData.debugMode)
3850 fprintf(debugFP, "Got premove: fromX %d,"
3851 "fromY %d, toX %d, toY %d\n",
3852 fromX, fromY, toX, toY);
3858 case IcsPlayingWhite:
3859 /* User is moving for White */
3860 if (!WhiteOnMove(currentMove)) {
3861 if (!appData.premove) {
3862 DisplayMoveError("It is Black's turn");
3863 } else if (toX >= 0 && toY >= 0) {
3866 premoveFromX = fromX;
3867 premoveFromY = fromY;
3868 premovePromoChar = promoChar;
3870 if (appData.debugMode)
3871 fprintf(debugFP, "Got premove: fromX %d,"
3872 "fromY %d, toX %d, toY %d\n",
3873 fromX, fromY, toX, toY);
3883 if (toX == -2 || toY == -2) {
3884 boards[0][fromY][fromX] = EmptySquare;
3885 DrawPosition(FALSE, boards[currentMove]);
3886 } else if (toX >= 0 && toY >= 0) {
3887 boards[0][toY][toX] = boards[0][fromY][fromX];
3888 boards[0][fromY][fromX] = EmptySquare;
3889 DrawPosition(FALSE, boards[currentMove]);
3894 if (toX < 0 || toY < 0) return;
3895 userOfferedDraw = FALSE;
3897 if (appData.testLegality) {
3898 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
3899 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar);
3900 if (moveType == IllegalMove || moveType == ImpossibleMove) {
3901 DisplayMoveError("Illegal move");
3905 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
3908 if (gameMode == Training) {
3909 /* compare the move played on the board to the next move in the
3910 * game. If they match, display the move and the opponent's response.
3911 * If they don't match, display an error message.
3915 CopyBoard(testBoard, boards[currentMove]);
3916 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
3918 if (CompareBoards(testBoard, boards[currentMove+1])) {
3919 ForwardInner(currentMove+1);
3921 /* Autoplay the opponent's response.
3922 * if appData.animate was TRUE when Training mode was entered,
3923 * the response will be animated.
3925 saveAnimate = appData.animate;
3926 appData.animate = animateTraining;
3927 ForwardInner(currentMove+1);
3928 appData.animate = saveAnimate;
3930 /* check for the end of the game */
3931 if (currentMove >= forwardMostMove) {
3932 gameMode = PlayFromGameFile;
3934 SetTrainingModeOff();
3935 DisplayInformation("End of game");
3938 DisplayError("Incorrect move", 0);
3943 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
3946 /* Common tail of UserMoveEvent and DropMenuEvent */
3948 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
3950 int fromX, fromY, toX, toY;
3951 /*char*/int promoChar;
3953 /* Ok, now we know that the move is good, so we can kill
3954 the previous line in Analysis Mode */
3955 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
3956 forwardMostMove = currentMove;
3959 /* If we need the chess program but it's dead, restart it */
3960 ResurrectChessProgram();
3962 /* A user move restarts a paused game*/
3966 thinkOutput[0] = NULLCHAR;
3968 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
3970 if (gameMode == BeginningOfGame) {
3971 if (appData.noChessProgram) {
3972 gameMode = EditGame;
3976 gameMode = MachinePlaysBlack;
3978 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
3980 if (first.sendName) {
3981 sprintf(buf, "name %s\n", gameInfo.white);
3982 SendToProgram(buf, &first);
3988 /* Relay move to ICS or chess engine */
3989 if (appData.icsActive) {
3990 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
3991 gameMode == IcsExamining) {
3992 SendMoveToICS(moveType, fromX, fromY, toX, toY);
3996 if (first.sendTime && (gameMode == BeginningOfGame ||
3997 gameMode == MachinePlaysWhite ||
3998 gameMode == MachinePlaysBlack)) {
3999 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
4001 SendMoveToProgram(forwardMostMove-1, &first);
4002 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
4003 first.maybeThinking = TRUE;
4005 if (currentMove == cmailOldMove + 1) {
4006 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
4010 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
4014 switch (MateTest(boards[currentMove], PosFlags(currentMove),
4020 if (WhiteOnMove(currentMove)) {
4021 GameEnds(BlackWins, "Black mates", GE_PLAYER);
4023 GameEnds(WhiteWins, "White mates", GE_PLAYER);
4027 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
4032 case MachinePlaysBlack:
4033 case MachinePlaysWhite:
4034 /* disable certain menu options while machine is thinking */
4035 SetMachineThinkingEnables();
4043 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
4045 char * hint = lastHint;
4046 FrontEndProgramStats stats;
4048 stats.which = cps == &first ? 0 : 1;
4049 stats.depth = cpstats->depth;
4050 stats.nodes = cpstats->nodes;
4051 stats.score = cpstats->score;
4052 stats.time = cpstats->time;
4053 stats.pv = cpstats->movelist;
4054 stats.hint = lastHint;
4055 stats.an_move_index = 0;
4056 stats.an_move_count = 0;
4058 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
4059 stats.hint = cpstats->move_name;
4060 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
4061 stats.an_move_count = cpstats->nr_moves;
4064 SetProgramStats( &stats );
4068 HandleMachineMove(message, cps)
4070 ChessProgramState *cps;
4072 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
4073 char realname[MSG_SIZ];
4074 int fromX, fromY, toX, toY;
4081 * Kludge to ignore BEL characters
4083 while (*message == '\007') message++;
4086 * Look for book output
4088 if (cps == &first && bookRequested) {
4089 if (message[0] == '\t' || message[0] == ' ') {
4090 /* Part of the book output is here; append it */
4091 strcat(bookOutput, message);
4092 strcat(bookOutput, " \n");
4094 } else if (bookOutput[0] != NULLCHAR) {
4095 /* All of book output has arrived; display it */
4096 char *p = bookOutput;
4097 while (*p != NULLCHAR) {
4098 if (*p == '\t') *p = ' ';
4101 DisplayInformation(bookOutput);
4102 bookRequested = FALSE;
4103 /* Fall through to parse the current output */
4108 * Look for machine move.
4110 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
4111 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
4113 /* This method is only useful on engines that support ping */
4114 if (cps->lastPing != cps->lastPong) {
4115 if (gameMode == BeginningOfGame) {
4116 /* Extra move from before last new; ignore */
4117 if (appData.debugMode) {
4118 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
4121 if (appData.debugMode) {
4122 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
4123 cps->which, gameMode);
4126 SendToProgram("undo\n", cps);
4132 case BeginningOfGame:
4133 /* Extra move from before last reset; ignore */
4134 if (appData.debugMode) {
4135 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
4142 /* Extra move after we tried to stop. The mode test is
4143 not a reliable way of detecting this problem, but it's
4144 the best we can do on engines that don't support ping.
4146 if (appData.debugMode) {
4147 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
4148 cps->which, gameMode);
4150 SendToProgram("undo\n", cps);
4153 case MachinePlaysWhite:
4154 case IcsPlayingWhite:
4155 machineWhite = TRUE;
4158 case MachinePlaysBlack:
4159 case IcsPlayingBlack:
4160 machineWhite = FALSE;
4163 case TwoMachinesPlay:
4164 machineWhite = (cps->twoMachinesColor[0] == 'w');
4167 if (WhiteOnMove(forwardMostMove) != machineWhite) {
4168 if (appData.debugMode) {
4170 "Ignoring move out of turn by %s, gameMode %d"
4171 ", forwardMost %d\n",
4172 cps->which, gameMode, forwardMostMove);
4177 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
4178 &fromX, &fromY, &toX, &toY, &promoChar)) {
4179 /* Machine move could not be parsed; ignore it. */
4180 sprintf(buf1, "Illegal move \"%s\" from %s machine",
4181 machineMove, cps->which);
4182 DisplayError(buf1, 0);
4183 if (gameMode == TwoMachinesPlay) {
4184 GameEnds(machineWhite ? BlackWins : WhiteWins,
4185 "Forfeit due to illegal move", GE_XBOARD);
4190 hintRequested = FALSE;
4191 lastHint[0] = NULLCHAR;
4192 bookRequested = FALSE;
4193 /* Program may be pondering now */
4194 cps->maybeThinking = TRUE;
4195 if (cps->sendTime == 2) cps->sendTime = 1;
4196 if (cps->offeredDraw) cps->offeredDraw--;
4199 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
4201 SendMoveToICS(moveType, fromX, fromY, toX, toY);
4205 /* currentMoveString is set as a side-effect of ParseOneMove */
4206 strcpy(machineMove, currentMoveString);
4207 strcat(machineMove, "\n");
4208 strcpy(moveList[forwardMostMove], machineMove);
4210 /* [AS] Save move info and clear stats for next move */
4211 pvInfoList[ forwardMostMove ].score = programStats.score;
4212 pvInfoList[ forwardMostMove ].depth = programStats.depth;
4213 pvInfoList[ forwardMostMove ].time = -1;
4214 ClearProgramStats();
4215 thinkOutput[0] = NULLCHAR;
4216 hiddenThinkOutputState = 0;
4218 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
4220 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
4221 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
4224 while( count < adjudicateLossPlies ) {
4225 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
4228 score = -score; /* Flip score for winning side */
4231 if( score > adjudicateLossThreshold ) {
4238 if( count >= adjudicateLossPlies ) {
4239 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
4241 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
4242 "Xboard adjudication",
4249 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
4250 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
4252 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
4257 if (gameMode == TwoMachinesPlay) {
4258 if (cps->other->sendTime) {
4259 SendTimeRemaining(cps->other,
4260 cps->other->twoMachinesColor[0] == 'w');
4262 SendMoveToProgram(forwardMostMove-1, cps->other);
4265 if (cps->other->useColors) {
4266 SendToProgram(cps->other->twoMachinesColor, cps->other);
4268 SendToProgram("go\n", cps->other);
4270 cps->other->maybeThinking = TRUE;
4273 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
4275 if (!pausing && appData.ringBellAfterMoves) {
4280 * Reenable menu items that were disabled while
4281 * machine was thinking
4283 if (gameMode != TwoMachinesPlay)
4284 SetUserThinkingEnables();
4289 /* Set special modes for chess engines. Later something general
4290 * could be added here; for now there is just one kludge feature,
4291 * needed because Crafty 15.10 and earlier don't ignore SIGINT
4292 * when "xboard" is given as an interactive command.
4294 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
4295 cps->useSigint = FALSE;
4296 cps->useSigterm = FALSE;
4300 * Look for communication commands
4302 if (!strncmp(message, "telluser ", 9)) {
4303 DisplayNote(message + 9);
4306 if (!strncmp(message, "tellusererror ", 14)) {
4307 DisplayError(message + 14, 0);
4310 if (!strncmp(message, "tellopponent ", 13)) {
4311 if (appData.icsActive) {
4313 sprintf(buf1, "%ssay %s\n", ics_prefix, message + 13);
4317 DisplayNote(message + 13);
4321 if (!strncmp(message, "tellothers ", 11)) {
4322 if (appData.icsActive) {
4324 sprintf(buf1, "%swhisper %s\n", ics_prefix, message + 11);
4330 if (!strncmp(message, "tellall ", 8)) {
4331 if (appData.icsActive) {
4333 sprintf(buf1, "%skibitz %s\n", ics_prefix, message + 8);
4337 DisplayNote(message + 8);
4341 if (strncmp(message, "warning", 7) == 0) {
4342 /* Undocumented feature, use tellusererror in new code */
4343 DisplayError(message, 0);
4346 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
4347 strcpy(realname, cps->tidy);
4348 strcat(realname, " query");
4349 AskQuestion(realname, buf2, buf1, cps->pr);
4352 /* Commands from the engine directly to ICS. We don't allow these to be
4353 * sent until we are logged on. Crafty kibitzes have been known to
4354 * interfere with the login process.
4357 if (!strncmp(message, "tellics ", 8)) {
4358 SendToICS(message + 8);
4362 if (!strncmp(message, "tellicsnoalias ", 15)) {
4363 SendToICS(ics_prefix);
4364 SendToICS(message + 15);
4368 /* The following are for backward compatibility only */
4369 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
4370 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
4371 SendToICS(ics_prefix);
4377 if (strncmp(message, "feature ", 8) == 0) {
4378 ParseFeatures(message+8, cps);
4380 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
4384 * If the move is illegal, cancel it and redraw the board.
4385 * Also deal with other error cases. Matching is rather loose
4386 * here to accommodate engines written before the spec.
4388 if (strncmp(message + 1, "llegal move", 11) == 0 ||
4389 strncmp(message, "Error", 5) == 0) {
4390 if (StrStr(message, "name") ||
4391 StrStr(message, "rating") || StrStr(message, "?") ||
4392 StrStr(message, "result") || StrStr(message, "board") ||
4393 StrStr(message, "bk") || StrStr(message, "computer") ||
4394 StrStr(message, "variant") || StrStr(message, "hint") ||
4395 StrStr(message, "random") || StrStr(message, "depth") ||
4396 StrStr(message, "accepted")) {
4399 if (StrStr(message, "protover")) {
4400 /* Program is responding to input, so it's apparently done
4401 initializing, and this error message indicates it is
4402 protocol version 1. So we don't need to wait any longer
4403 for it to initialize and send feature commands. */
4404 FeatureDone(cps, 1);
4405 cps->protocolVersion = 1;
4408 cps->maybeThinking = FALSE;
4410 if (StrStr(message, "draw")) {
4411 /* Program doesn't have "draw" command */
4412 cps->sendDrawOffers = 0;
4415 if (cps->sendTime != 1 &&
4416 (StrStr(message, "time") || StrStr(message, "otim"))) {
4417 /* Program apparently doesn't have "time" or "otim" command */
4421 if (StrStr(message, "analyze")) {
4422 cps->analysisSupport = FALSE;
4423 cps->analyzing = FALSE;
4425 sprintf(buf2, "%s does not support analysis", cps->tidy);
4426 DisplayError(buf2, 0);
4429 if (StrStr(message, "(no matching move)st")) {
4430 /* Special kludge for GNU Chess 4 only */
4431 cps->stKludge = TRUE;
4432 SendTimeControl(cps, movesPerSession, timeControl,
4433 timeIncrement, appData.searchDepth,
4437 if (StrStr(message, "(no matching move)sd")) {
4438 /* Special kludge for GNU Chess 4 only */
4439 cps->sdKludge = TRUE;
4440 SendTimeControl(cps, movesPerSession, timeControl,
4441 timeIncrement, appData.searchDepth,
4445 if (!StrStr(message, "llegal")) return;
4446 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
4447 gameMode == IcsIdle) return;
4448 if (forwardMostMove <= backwardMostMove) return;
4450 /* Following removed: it caused a bug where a real illegal move
4451 message in analyze mored would be ignored. */
4452 if (cps == &first && programStats.ok_to_send == 0) {
4453 /* Bogus message from Crafty responding to "." This filtering
4454 can miss some of the bad messages, but fortunately the bug
4455 is fixed in current Crafty versions, so it doesn't matter. */
4459 if (pausing) PauseEvent();
4460 if (gameMode == PlayFromGameFile) {
4461 /* Stop reading this game file */
4462 gameMode = EditGame;
4465 currentMove = --forwardMostMove;
4466 DisplayMove(currentMove-1); /* before DisplayMoveError */
4468 DisplayBothClocks();
4469 sprintf(buf1, "Illegal move \"%s\" (rejected by %s chess program)",
4470 parseList[currentMove], cps->which);
4471 DisplayMoveError(buf1);
4472 DrawPosition(FALSE, boards[currentMove]);
4475 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
4476 /* Program has a broken "time" command that
4477 outputs a string not ending in newline.
4483 * If chess program startup fails, exit with an error message.
4484 * Attempts to recover here are futile.
4486 if ((StrStr(message, "unknown host") != NULL)
4487 || (StrStr(message, "No remote directory") != NULL)
4488 || (StrStr(message, "not found") != NULL)
4489 || (StrStr(message, "No such file") != NULL)
4490 || (StrStr(message, "can't alloc") != NULL)
4491 || (StrStr(message, "Permission denied") != NULL)) {
4493 cps->maybeThinking = FALSE;
4494 sprintf(buf1, "Failed to start %s chess program %s on %s: %s\n",
4495 cps->which, cps->program, cps->host, message);
4496 RemoveInputSource(cps->isr);
4497 DisplayFatalError(buf1, 0, 1);
4502 * Look for hint output
4504 if (sscanf(message, "Hint: %s", buf1) == 1) {
4505 if (cps == &first && hintRequested) {
4506 hintRequested = FALSE;
4507 if (ParseOneMove(buf1, forwardMostMove, &moveType,
4508 &fromX, &fromY, &toX, &toY, &promoChar)) {
4509 (void) CoordsToAlgebraic(boards[forwardMostMove],
4510 PosFlags(forwardMostMove), EP_UNKNOWN,
4511 fromY, fromX, toY, toX, promoChar, buf1);
4512 sprintf(buf2, "Hint: %s", buf1);
4513 DisplayInformation(buf2);
4515 /* Hint move could not be parsed!? */
4517 "Illegal hint move \"%s\"\nfrom %s chess program",
4519 DisplayError(buf2, 0);
4522 strcpy(lastHint, buf1);
4528 * Ignore other messages if game is not in progress
4530 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
4531 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
4534 * look for win, lose, draw, or draw offer
4536 if (strncmp(message, "1-0", 3) == 0) {
4537 char *p, *q, *r = "";
4538 p = strchr(message, '{');
4546 GameEnds(WhiteWins, r, GE_ENGINE);
4548 } else if (strncmp(message, "0-1", 3) == 0) {
4549 char *p, *q, *r = "";
4550 p = strchr(message, '{');
4558 /* Kludge for Arasan 4.1 bug */
4559 if (strcmp(r, "Black resigns") == 0) {
4560 GameEnds(WhiteWins, r, GE_ENGINE);
4563 GameEnds(BlackWins, r, GE_ENGINE);
4565 } else if (strncmp(message, "1/2", 3) == 0) {
4566 char *p, *q, *r = "";
4567 p = strchr(message, '{');
4575 GameEnds(GameIsDrawn, r, GE_ENGINE);
4578 } else if (strncmp(message, "White resign", 12) == 0) {
4579 GameEnds(BlackWins, "White resigns", GE_ENGINE);
4581 } else if (strncmp(message, "Black resign", 12) == 0) {
4582 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
4584 } else if (strncmp(message, "White", 5) == 0 &&
4585 message[5] != '(' &&
4586 StrStr(message, "Black") == NULL) {
4587 GameEnds(WhiteWins, "White mates", GE_ENGINE);
4589 } else if (strncmp(message, "Black", 5) == 0 &&
4590 message[5] != '(') {
4591 GameEnds(BlackWins, "Black mates", GE_ENGINE);
4593 } else if (strcmp(message, "resign") == 0 ||
4594 strcmp(message, "computer resigns") == 0) {
4596 case MachinePlaysBlack:
4597 case IcsPlayingBlack:
4598 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
4600 case MachinePlaysWhite:
4601 case IcsPlayingWhite:
4602 GameEnds(BlackWins, "White resigns", GE_ENGINE);
4604 case TwoMachinesPlay:
4605 if (cps->twoMachinesColor[0] == 'w')
4606 GameEnds(BlackWins, "White resigns", GE_ENGINE);
4608 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
4615 } else if (strncmp(message, "opponent mates", 14) == 0) {
4617 case MachinePlaysBlack:
4618 case IcsPlayingBlack:
4619 GameEnds(WhiteWins, "White mates", GE_ENGINE);
4621 case MachinePlaysWhite:
4622 case IcsPlayingWhite:
4623 GameEnds(BlackWins, "Black mates", GE_ENGINE);
4625 case TwoMachinesPlay:
4626 if (cps->twoMachinesColor[0] == 'w')
4627 GameEnds(BlackWins, "Black mates", GE_ENGINE);
4629 GameEnds(WhiteWins, "White mates", GE_ENGINE);
4636 } else if (strncmp(message, "computer mates", 14) == 0) {
4638 case MachinePlaysBlack:
4639 case IcsPlayingBlack:
4640 GameEnds(BlackWins, "Black mates", GE_ENGINE);
4642 case MachinePlaysWhite:
4643 case IcsPlayingWhite:
4644 GameEnds(WhiteWins, "White mates", GE_ENGINE);
4646 case TwoMachinesPlay:
4647 if (cps->twoMachinesColor[0] == 'w')
4648 GameEnds(WhiteWins, "White mates", GE_ENGINE);
4650 GameEnds(BlackWins, "Black mates", GE_ENGINE);
4657 } else if (strncmp(message, "checkmate", 9) == 0) {
4658 if (WhiteOnMove(forwardMostMove)) {
4659 GameEnds(BlackWins, "Black mates", GE_ENGINE);
4661 GameEnds(WhiteWins, "White mates", GE_ENGINE);
4664 } else if (strstr(message, "Draw") != NULL ||
4665 strstr(message, "game is a draw") != NULL) {
4666 GameEnds(GameIsDrawn, "Draw", GE_ENGINE);
4668 } else if (strstr(message, "offer") != NULL &&
4669 strstr(message, "draw") != NULL) {
4671 if (appData.zippyPlay && first.initDone) {
4672 /* Relay offer to ICS */
4673 SendToICS(ics_prefix);
4674 SendToICS("draw\n");
4677 cps->offeredDraw = 2; /* valid until this engine moves twice */
4678 if (gameMode == TwoMachinesPlay) {
4679 if (cps->other->offeredDraw) {
4680 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
4682 if (cps->other->sendDrawOffers) {
4683 SendToProgram("draw\n", cps->other);
4686 } else if (gameMode == MachinePlaysWhite ||
4687 gameMode == MachinePlaysBlack) {
4688 if (userOfferedDraw) {
4689 DisplayInformation("Machine accepts your draw offer");
4690 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
4692 DisplayInformation("Machine offers a draw\nSelect Action / Draw to agree");
4699 * Look for thinking output
4701 if ( appData.showThinking) {
4702 int plylev, mvleft, mvtot, curscore, time;
4703 char mvname[MOVE_LEN];
4704 unsigned long nodes;
4707 int prefixHint = FALSE;
4708 mvname[0] = NULLCHAR;
4711 case MachinePlaysBlack:
4712 case IcsPlayingBlack:
4713 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
4715 case MachinePlaysWhite:
4716 case IcsPlayingWhite:
4717 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
4722 case TwoMachinesPlay:
4723 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
4734 if (sscanf(message, "%d%c %d %d %lu %[^\n]\n",
4735 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
4737 if (plyext != ' ' && plyext != '\t') {
4741 /* [AS] Negate score if machine is playing black and reporting absolute scores */
4742 if( cps->scoreIsAbsolute &&
4743 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
4745 curscore = -curscore;
4749 programStats.depth = plylev;
4750 programStats.nodes = nodes;
4751 programStats.time = time;
4752 programStats.score = curscore;
4753 programStats.got_only_move = 0;
4755 /* Buffer overflow protection */
4756 if (buf1[0] != NULLCHAR) {
4757 if (strlen(buf1) >= sizeof(programStats.movelist)
4758 && appData.debugMode) {
4760 "PV is too long; using the first %d bytes.\n",
4761 sizeof(programStats.movelist) - 1);
4764 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
4766 sprintf(programStats.movelist, " no PV\n");
4769 if (programStats.seen_stat) {
4770 programStats.ok_to_send = 1;
4773 if (strchr(programStats.movelist, '(') != NULL) {
4774 programStats.line_is_book = 1;
4775 programStats.nr_moves = 0;
4776 programStats.moves_left = 0;
4778 programStats.line_is_book = 0;
4781 SendProgramStatsToFrontend( cps, &programStats );
4784 [AS] Protect the thinkOutput buffer from overflow... this
4785 is only useful if buf1 hasn't overflowed first!
4787 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
4789 (gameMode == TwoMachinesPlay ?
4790 ToUpper(cps->twoMachinesColor[0]) : ' '),
4791 ((double) curscore) / 100.0,
4792 prefixHint ? lastHint : "",
4793 prefixHint ? " " : "" );
4795 if( buf1[0] != NULLCHAR ) {
4796 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
4798 if( strlen(buf1) > max_len ) {
4799 if( appData.debugMode) {
4800 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
4802 buf1[max_len+1] = '\0';
4805 strcat( thinkOutput, buf1 );
4808 if (currentMove == forwardMostMove || gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
4809 DisplayMove(currentMove - 1);
4814 } else if ((p=StrStr(message, "(only move)")) != NULL) {
4815 /* crafty (9.25+) says "(only move) <move>"
4816 * if there is only 1 legal move
4818 sscanf(p, "(only move) %s", buf1);
4819 sprintf(thinkOutput, "%s (only move)", buf1);
4820 sprintf(programStats.movelist, "%s (only move)", buf1);
4821 programStats.depth = 1;
4822 programStats.nr_moves = 1;
4823 programStats.moves_left = 1;
4824 programStats.nodes = 1;
4825 programStats.time = 1;
4826 programStats.got_only_move = 1;
4828 /* Not really, but we also use this member to
4829 mean "line isn't going to change" (Crafty
4830 isn't searching, so stats won't change) */
4831 programStats.line_is_book = 1;
4833 SendProgramStatsToFrontend( cps, &programStats );
4835 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || gameMode == AnalyzeFile) {
4836 DisplayMove(currentMove - 1);
4840 } else if (sscanf(message,"stat01: %d %lu %d %d %d %s",
4841 &time, &nodes, &plylev, &mvleft,
4842 &mvtot, mvname) >= 5) {
4843 /* The stat01: line is from Crafty (9.29+) in response
4844 to the "." command */
4845 programStats.seen_stat = 1;
4846 cps->maybeThinking = TRUE;
4848 if (programStats.got_only_move || !appData.periodicUpdates)
4851 programStats.depth = plylev;
4852 programStats.time = time;
4853 programStats.nodes = nodes;
4854 programStats.moves_left = mvleft;
4855 programStats.nr_moves = mvtot;
4856 strcpy(programStats.move_name, mvname);
4857 programStats.ok_to_send = 1;
4858 programStats.movelist[0] = '\0';
4860 SendProgramStatsToFrontend( cps, &programStats );
4865 } else if (strncmp(message,"++",2) == 0) {
4866 /* Crafty 9.29+ outputs this */
4867 programStats.got_fail = 2;
4870 } else if (strncmp(message,"--",2) == 0) {
4871 /* Crafty 9.29+ outputs this */
4872 programStats.got_fail = 1;
4875 } else if (thinkOutput[0] != NULLCHAR &&
4876 strncmp(message, " ", 4) == 0) {
4877 unsigned message_len;
4880 while (*p && *p == ' ') p++;
4882 message_len = strlen( p );
4884 /* [AS] Avoid buffer overflow */
4885 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
4886 strcat(thinkOutput, " ");
4887 strcat(thinkOutput, p);
4890 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
4891 strcat(programStats.movelist, " ");
4892 strcat(programStats.movelist, p);
4895 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || gameMode == AnalyzeFile) {
4896 DisplayMove(currentMove - 1);
4905 if (sscanf(message, "%d%c %d %d %lu %[^\n]\n",
4906 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
4908 ChessProgramStats cpstats;
4910 if (plyext != ' ' && plyext != '\t') {
4914 /* [AS] Negate score if machine is playing black and reporting absolute scores */
4915 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
4916 curscore = -curscore;
4919 cpstats.depth = plylev;
4920 cpstats.nodes = nodes;
4921 cpstats.time = time;
4922 cpstats.score = curscore;
4923 cpstats.got_only_move = 0;
4924 cpstats.movelist[0] = '\0';
4926 if (buf1[0] != NULLCHAR) {
4927 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
4930 cpstats.ok_to_send = 0;
4931 cpstats.line_is_book = 0;
4932 cpstats.nr_moves = 0;
4933 cpstats.moves_left = 0;
4935 SendProgramStatsToFrontend( cps, &cpstats );
4942 /* Parse a game score from the character string "game", and
4943 record it as the history of the current game. The game
4944 score is NOT assumed to start from the standard position.
4945 The display is not updated in any way.
4948 ParseGameHistory(game)
4952 int fromX, fromY, toX, toY, boardIndex;
4957 if (appData.debugMode)
4958 fprintf(debugFP, "Parsing game history: %s\n", game);
4960 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
4961 gameInfo.site = StrSave(appData.icsHost);
4962 gameInfo.date = PGNDate();
4963 gameInfo.round = StrSave("-");
4965 /* Parse out names of players */
4966 while (*game == ' ') game++;
4968 while (*game != ' ') *p++ = *game++;
4970 gameInfo.white = StrSave(buf);
4971 while (*game == ' ') game++;
4973 while (*game != ' ' && *game != '\n') *p++ = *game++;
4975 gameInfo.black = StrSave(buf);
4978 boardIndex = blackPlaysFirst ? 1 : 0;
4981 yyboardindex = boardIndex;
4982 moveType = (ChessMove) yylex();
4984 case WhitePromotionQueen:
4985 case BlackPromotionQueen:
4986 case WhitePromotionRook:
4987 case BlackPromotionRook:
4988 case WhitePromotionBishop:
4989 case BlackPromotionBishop:
4990 case WhitePromotionKnight:
4991 case BlackPromotionKnight:
4992 case WhitePromotionKing:
4993 case BlackPromotionKing:
4995 case WhiteCapturesEnPassant:
4996 case BlackCapturesEnPassant:
4997 case WhiteKingSideCastle:
4998 case WhiteQueenSideCastle:
4999 case BlackKingSideCastle:
5000 case BlackQueenSideCastle:
5001 case WhiteKingSideCastleWild:
5002 case WhiteQueenSideCastleWild:
5003 case BlackKingSideCastleWild:
5004 case BlackQueenSideCastleWild:
5006 case WhiteHSideCastleFR:
5007 case WhiteASideCastleFR:
5008 case BlackHSideCastleFR:
5009 case BlackASideCastleFR:
5011 case IllegalMove: /* maybe suicide chess, etc. */
5012 fromX = currentMoveString[0] - 'a';
5013 fromY = currentMoveString[1] - '1';
5014 toX = currentMoveString[2] - 'a';
5015 toY = currentMoveString[3] - '1';
5016 promoChar = currentMoveString[4];
5020 fromX = moveType == WhiteDrop ?
5021 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5022 (int) CharToPiece(ToLower(currentMoveString[0]));
5024 toX = currentMoveString[2] - 'a';
5025 toY = currentMoveString[3] - '1';
5026 promoChar = NULLCHAR;
5030 sprintf(buf, "Ambiguous move in ICS output: \"%s\"", yy_text);
5031 DisplayError(buf, 0);
5033 case ImpossibleMove:
5035 sprintf(buf, "Illegal move in ICS output: \"%s\"", yy_text);
5036 DisplayError(buf, 0);
5038 case (ChessMove) 0: /* end of file */
5039 if (boardIndex < backwardMostMove) {
5040 /* Oops, gap. How did that happen? */
5041 DisplayError("Gap in move list", 0);
5044 backwardMostMove = blackPlaysFirst ? 1 : 0;
5045 if (boardIndex > forwardMostMove) {
5046 forwardMostMove = boardIndex;
5050 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
5051 strcat(parseList[boardIndex-1], " ");
5052 strcat(parseList[boardIndex-1], yy_text);
5064 case GameUnfinished:
5065 if (gameMode == IcsExamining) {
5066 if (boardIndex < backwardMostMove) {
5067 /* Oops, gap. How did that happen? */
5070 backwardMostMove = blackPlaysFirst ? 1 : 0;
5073 gameInfo.result = moveType;
5074 p = strchr(yy_text, '{');
5075 if (p == NULL) p = strchr(yy_text, '(');
5078 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
5080 q = strchr(p, *p == '{' ? '}' : ')');
5081 if (q != NULL) *q = NULLCHAR;
5084 gameInfo.resultDetails = StrSave(p);
5087 if (boardIndex >= forwardMostMove &&
5088 !(gameMode == IcsObserving && ics_gamenum == -1)) {
5089 backwardMostMove = blackPlaysFirst ? 1 : 0;
5092 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
5093 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
5094 parseList[boardIndex]);
5095 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
5096 /* currentMoveString is set as a side-effect of yylex */
5097 strcpy(moveList[boardIndex], currentMoveString);
5098 strcat(moveList[boardIndex], "\n");
5100 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
5101 switch (MateTest(boards[boardIndex],
5102 PosFlags(boardIndex), EP_UNKNOWN)) {
5108 strcat(parseList[boardIndex - 1], "+");
5111 strcat(parseList[boardIndex - 1], "#");
5118 /* Apply a move to the given board */
5120 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
5121 int fromX, fromY, toX, toY;
5125 ChessSquare captured = board[toY][toX];
5126 if (fromY == DROP_RANK) {
5128 board[toY][toX] = (ChessSquare) fromX;
5129 } else if (fromX == toX && fromY == toY) {
5133 /* Code added by Tord: */
5134 /* FRC castling assumed when king captures friendly rook. */
5135 else if (board[fromY][fromX] == WhiteKing &&
5136 board[toY][toX] == WhiteRook) {
5137 board[fromY][fromX] = EmptySquare;
5138 board[toY][toX] = EmptySquare;
5140 board[0][6] = WhiteKing; board[0][5] = WhiteRook;
5142 board[0][2] = WhiteKing; board[0][3] = WhiteRook;
5144 } else if (board[fromY][fromX] == BlackKing &&
5145 board[toY][toX] == BlackRook) {
5146 board[fromY][fromX] = EmptySquare;
5147 board[toY][toX] = EmptySquare;
5149 board[7][6] = BlackKing; board[7][5] = BlackRook;
5151 board[7][2] = BlackKing; board[7][3] = BlackRook;
5153 /* End of code added by Tord */
5155 } else if (fromY == 0 && fromX == 4
5156 && board[fromY][fromX] == WhiteKing
5157 && toY == 0 && toX == 6) {
5158 board[fromY][fromX] = EmptySquare;
5159 board[toY][toX] = WhiteKing;
5160 board[fromY][7] = EmptySquare;
5161 board[toY][5] = WhiteRook;
5162 } else if (fromY == 0 && fromX == 4
5163 && board[fromY][fromX] == WhiteKing
5164 && toY == 0 && toX == 2) {
5165 board[fromY][fromX] = EmptySquare;
5166 board[toY][toX] = WhiteKing;
5167 board[fromY][0] = EmptySquare;
5168 board[toY][3] = WhiteRook;
5169 } else if (fromY == 0 && fromX == 3
5170 && board[fromY][fromX] == WhiteKing
5171 && toY == 0 && toX == 5) {
5172 board[fromY][fromX] = EmptySquare;
5173 board[toY][toX] = WhiteKing;
5174 board[fromY][7] = EmptySquare;
5175 board[toY][4] = WhiteRook;
5176 } else if (fromY == 0 && fromX == 3
5177 && board[fromY][fromX] == WhiteKing
5178 && toY == 0 && toX == 1) {
5179 board[fromY][fromX] = EmptySquare;
5180 board[toY][toX] = WhiteKing;
5181 board[fromY][0] = EmptySquare;
5182 board[toY][2] = WhiteRook;
5183 } else if (board[fromY][fromX] == WhitePawn
5185 /* white pawn promotion */
5186 board[7][toX] = CharToPiece(ToUpper(promoChar));
5187 if (board[7][toX] == EmptySquare) {
5188 board[7][toX] = WhiteQueen;
5190 board[fromY][fromX] = EmptySquare;
5191 } else if ((fromY == 4)
5193 && (board[fromY][fromX] == WhitePawn)
5194 && (board[toY][toX] == EmptySquare)) {
5195 board[fromY][fromX] = EmptySquare;
5196 board[toY][toX] = WhitePawn;
5197 captured = board[toY - 1][toX];
5198 board[toY - 1][toX] = EmptySquare;
5199 } else if (fromY == 7 && fromX == 4
5200 && board[fromY][fromX] == BlackKing
5201 && toY == 7 && toX == 6) {
5202 board[fromY][fromX] = EmptySquare;
5203 board[toY][toX] = BlackKing;
5204 board[fromY][7] = EmptySquare;
5205 board[toY][5] = BlackRook;
5206 } else if (fromY == 7 && fromX == 4
5207 && board[fromY][fromX] == BlackKing
5208 && toY == 7 && toX == 2) {
5209 board[fromY][fromX] = EmptySquare;
5210 board[toY][toX] = BlackKing;
5211 board[fromY][0] = EmptySquare;
5212 board[toY][3] = BlackRook;
5213 } else if (fromY == 7 && fromX == 3
5214 && board[fromY][fromX] == BlackKing
5215 && toY == 7 && toX == 5) {
5216 board[fromY][fromX] = EmptySquare;
5217 board[toY][toX] = BlackKing;
5218 board[fromY][7] = EmptySquare;
5219 board[toY][4] = BlackRook;
5220 } else if (fromY == 7 && fromX == 3
5221 && board[fromY][fromX] == BlackKing
5222 && toY == 7 && toX == 1) {
5223 board[fromY][fromX] = EmptySquare;
5224 board[toY][toX] = BlackKing;
5225 board[fromY][0] = EmptySquare;
5226 board[toY][2] = BlackRook;
5227 } else if (board[fromY][fromX] == BlackPawn
5229 /* black pawn promotion */
5230 board[0][toX] = CharToPiece(ToLower(promoChar));
5231 if (board[0][toX] == EmptySquare) {
5232 board[0][toX] = BlackQueen;
5234 board[fromY][fromX] = EmptySquare;
5235 } else if ((fromY == 3)
5237 && (board[fromY][fromX] == BlackPawn)
5238 && (board[toY][toX] == EmptySquare)) {
5239 board[fromY][fromX] = EmptySquare;
5240 board[toY][toX] = BlackPawn;
5241 captured = board[toY + 1][toX];
5242 board[toY + 1][toX] = EmptySquare;
5244 board[toY][toX] = board[fromY][fromX];
5245 board[fromY][fromX] = EmptySquare;
5247 if (gameInfo.variant == VariantCrazyhouse) {
5249 /* !!A lot more code needs to be written to support holdings */
5250 if (fromY == DROP_RANK) {
5251 /* Delete from holdings */
5252 if (holdings[(int) fromX] > 0) holdings[(int) fromX]--;
5254 if (captured != EmptySquare) {
5255 /* Add to holdings */
5256 if (captured < BlackPawn) {
5257 holdings[(int)captured - (int)BlackPawn + (int)WhitePawn]++;
5259 holdings[(int)captured - (int)WhitePawn + (int)BlackPawn]++;
5263 } else if (gameInfo.variant == VariantAtomic) {
5264 if (captured != EmptySquare) {
5266 for (y = toY-1; y <= toY+1; y++) {
5267 for (x = toX-1; x <= toX+1; x++) {
5268 if (y >= 0 && y <= 7 && x >= 0 && x <= 7 &&
5269 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
5270 board[y][x] = EmptySquare;
5274 board[toY][toX] = EmptySquare;
5279 /* Updates forwardMostMove */
5281 MakeMove(fromX, fromY, toX, toY, promoChar)
5282 int fromX, fromY, toX, toY;
5286 if (forwardMostMove >= MAX_MOVES) {
5287 DisplayFatalError("Game too long; increase MAX_MOVES and recompile",
5292 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
5293 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
5294 if (commentList[forwardMostMove] != NULL) {
5295 free(commentList[forwardMostMove]);
5296 commentList[forwardMostMove] = NULL;
5298 CopyBoard(boards[forwardMostMove], boards[forwardMostMove - 1]);
5299 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove]);
5300 gameInfo.result = GameUnfinished;
5301 if (gameInfo.resultDetails != NULL) {
5302 free(gameInfo.resultDetails);
5303 gameInfo.resultDetails = NULL;
5305 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
5306 moveList[forwardMostMove - 1]);
5307 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
5308 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
5309 fromY, fromX, toY, toX, promoChar,
5310 parseList[forwardMostMove - 1]);
5311 switch (MateTest(boards[forwardMostMove],
5312 PosFlags(forwardMostMove), EP_UNKNOWN)){
5318 strcat(parseList[forwardMostMove - 1], "+");
5321 strcat(parseList[forwardMostMove - 1], "#");
5326 /* Updates currentMove if not pausing */
5328 ShowMove(fromX, fromY, toX, toY)
5330 int instant = (gameMode == PlayFromGameFile) ?
5331 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
5332 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
5334 if (forwardMostMove == currentMove + 1) {
5335 AnimateMove(boards[forwardMostMove - 1],
5336 fromX, fromY, toX, toY);
5338 if (appData.highlightLastMove) {
5339 SetHighlights(fromX, fromY, toX, toY);
5342 currentMove = forwardMostMove;
5345 if (instant) return;
5347 DisplayMove(currentMove - 1);
5348 DrawPosition(FALSE, boards[currentMove]);
5349 DisplayBothClocks();
5350 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
5355 InitChessProgram(cps)
5356 ChessProgramState *cps;
5359 if (appData.noChessProgram) return;
5360 hintRequested = FALSE;
5361 bookRequested = FALSE;
5362 SendToProgram(cps->initString, cps);
5363 if (gameInfo.variant != VariantNormal &&
5364 gameInfo.variant != VariantLoadable) {
5365 char *v = VariantName(gameInfo.variant);
5366 if (StrStr(cps->variants, v) == NULL) {
5367 sprintf(buf, "Variant %s not supported by %s", v, cps->tidy);
5368 DisplayFatalError(buf, 0, 1);
5371 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
5372 SendToProgram(buf, cps);
5375 sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-");
5376 SendToProgram(buf, cps);
5378 cps->maybeThinking = FALSE;
5379 cps->offeredDraw = 0;
5380 if (!appData.icsActive) {
5381 SendTimeControl(cps, movesPerSession, timeControl,
5382 timeIncrement, appData.searchDepth,
5385 if (appData.showThinking) {
5386 SendToProgram("post\n", cps);
5388 SendToProgram("hard\n", cps);
5389 if (!appData.ponderNextMove) {
5390 /* Warning: "easy" is a toggle in GNU Chess, so don't send
5391 it without being sure what state we are in first. "hard"
5392 is not a toggle, so that one is OK.
5394 SendToProgram("easy\n", cps);
5397 sprintf(buf, "ping %d\n", ++cps->lastPing);
5398 SendToProgram(buf, cps);
5400 cps->initDone = TRUE;
5405 StartChessProgram(cps)
5406 ChessProgramState *cps;
5411 if (appData.noChessProgram) return;
5412 cps->initDone = FALSE;
5414 if (strcmp(cps->host, "localhost") == 0) {
5415 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
5416 } else if (*appData.remoteShell == NULLCHAR) {
5417 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
5419 if (*appData.remoteUser == NULLCHAR) {
5420 sprintf(buf, "%s %s %s", appData.remoteShell, cps->host,
5423 sprintf(buf, "%s %s -l %s %s", appData.remoteShell,
5424 cps->host, appData.remoteUser, cps->program);
5426 err = StartChildProcess(buf, "", &cps->pr);
5430 sprintf(buf, "Startup failure on '%s'", cps->program);
5431 DisplayFatalError(buf, err, 1);
5437 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
5438 if (cps->protocolVersion > 1) {
5439 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
5440 SendToProgram(buf, cps);
5442 SendToProgram("xboard\n", cps);
5448 TwoMachinesEventIfReady P((void))
5450 if (first.lastPing != first.lastPong) {
5451 DisplayMessage("", "Waiting for first chess program");
5452 ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000);
5455 if (second.lastPing != second.lastPong) {
5456 DisplayMessage("", "Waiting for second chess program");
5457 ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000);
5465 NextMatchGame P((void))
5468 if (*appData.loadGameFile != NULLCHAR) {
5469 LoadGameFromFile(appData.loadGameFile,
5470 appData.loadGameIndex,
5471 appData.loadGameFile, FALSE);
5472 } else if (*appData.loadPositionFile != NULLCHAR) {
5473 LoadPositionFromFile(appData.loadPositionFile,
5474 appData.loadPositionIndex,
5475 appData.loadPositionFile);
5477 TwoMachinesEventIfReady();
5480 void UserAdjudicationEvent( int result )
5482 ChessMove gameResult = GameIsDrawn;
5485 gameResult = WhiteWins;
5487 else if( result < 0 ) {
5488 gameResult = BlackWins;
5491 if( gameMode == TwoMachinesPlay ) {
5492 GameEnds( gameResult, "User adjudication", GE_XBOARD );
5498 GameEnds(result, resultDetails, whosays)
5500 char *resultDetails;
5503 GameMode nextGameMode;
5506 if (appData.debugMode) {
5507 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
5508 result, resultDetails ? resultDetails : "(null)", whosays);
5511 if (appData.icsActive && whosays == GE_ENGINE) {
5512 /* If we are playing on ICS, the server decides when the
5513 game is over, but the engine can offer to draw, claim
5517 if (appData.zippyPlay && first.initDone) {
5518 if (result == GameIsDrawn) {
5519 /* In case draw still needs to be claimed */
5520 SendToICS(ics_prefix);
5521 SendToICS("draw\n");
5522 } else if (StrCaseStr(resultDetails, "resign")) {
5523 SendToICS(ics_prefix);
5524 SendToICS("resign\n");
5531 /* If we're loading the game from a file, stop */
5532 if (whosays == GE_FILE) {
5533 (void) StopLoadGameTimer();
5537 /* Cancel draw offers */
5538 first.offeredDraw = second.offeredDraw = 0;
5540 /* If this is an ICS game, only ICS can really say it's done;
5541 if not, anyone can. */
5542 isIcsGame = (gameMode == IcsPlayingWhite ||
5543 gameMode == IcsPlayingBlack ||
5544 gameMode == IcsObserving ||
5545 gameMode == IcsExamining);
5547 if (!isIcsGame || whosays == GE_ICS) {
5548 /* OK -- not an ICS game, or ICS said it was done */
5550 if (!isIcsGame && !appData.noChessProgram)
5551 SetUserThinkingEnables();
5553 if (resultDetails != NULL) {
5554 gameInfo.result = result;
5555 gameInfo.resultDetails = StrSave(resultDetails);
5557 /* Tell program how game ended in case it is learning */
5558 if (gameMode == MachinePlaysWhite ||
5559 gameMode == MachinePlaysBlack ||
5560 gameMode == TwoMachinesPlay ||
5561 gameMode == IcsPlayingWhite ||
5562 gameMode == IcsPlayingBlack ||
5563 gameMode == BeginningOfGame) {
5565 sprintf(buf, "result %s {%s}\n", PGNResult(result),
5567 if (first.pr != NoProc) {
5568 SendToProgram(buf, &first);
5570 if (second.pr != NoProc &&
5571 gameMode == TwoMachinesPlay) {
5572 SendToProgram(buf, &second);
5576 /* display last move only if game was not loaded from file */
5577 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
5578 DisplayMove(currentMove - 1);
5580 if (forwardMostMove != 0) {
5581 if (gameMode != PlayFromGameFile && gameMode != EditGame) {
5582 if (*appData.saveGameFile != NULLCHAR) {
5583 SaveGameToFile(appData.saveGameFile, TRUE);
5584 } else if (appData.autoSaveGames) {
5587 if (*appData.savePositionFile != NULLCHAR) {
5588 SavePositionToFile(appData.savePositionFile);
5594 if (appData.icsActive) {
5595 if (appData.quietPlay &&
5596 (gameMode == IcsPlayingWhite ||
5597 gameMode == IcsPlayingBlack)) {
5598 SendToICS(ics_prefix);
5599 SendToICS("set shout 1\n");
5601 nextGameMode = IcsIdle;
5602 ics_user_moved = FALSE;
5603 /* clean up premove. It's ugly when the game has ended and the
5604 * premove highlights are still on the board.
5608 ClearPremoveHighlights();
5609 DrawPosition(FALSE, boards[currentMove]);
5611 if (whosays == GE_ICS) {
5614 if (gameMode == IcsPlayingWhite)
5616 else if(gameMode == IcsPlayingBlack)
5620 if (gameMode == IcsPlayingBlack)
5622 else if(gameMode == IcsPlayingWhite)
5629 PlayIcsUnfinishedSound();
5632 } else if (gameMode == EditGame ||
5633 gameMode == PlayFromGameFile ||
5634 gameMode == AnalyzeMode ||
5635 gameMode == AnalyzeFile) {
5636 nextGameMode = gameMode;
5638 nextGameMode = EndOfGame;
5643 nextGameMode = gameMode;
5646 if (appData.noChessProgram) {
5647 gameMode = nextGameMode;
5653 /* Put first chess program into idle state */
5654 if (first.pr != NoProc &&
5655 (gameMode == MachinePlaysWhite ||
5656 gameMode == MachinePlaysBlack ||
5657 gameMode == TwoMachinesPlay ||
5658 gameMode == IcsPlayingWhite ||
5659 gameMode == IcsPlayingBlack ||
5660 gameMode == BeginningOfGame)) {
5661 SendToProgram("force\n", &first);
5662 if (first.usePing) {
5664 sprintf(buf, "ping %d\n", ++first.lastPing);
5665 SendToProgram(buf, &first);
5668 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
5669 /* Kill off first chess program */
5670 if (first.isr != NULL)
5671 RemoveInputSource(first.isr);
5674 if (first.pr != NoProc) {
5676 DoSleep( appData.delayBeforeQuit );
5677 SendToProgram("quit\n", &first);
5678 DoSleep( appData.delayAfterQuit );
5679 DestroyChildProcess(first.pr, first.useSigterm);
5684 /* Put second chess program into idle state */
5685 if (second.pr != NoProc &&
5686 gameMode == TwoMachinesPlay) {
5687 SendToProgram("force\n", &second);
5688 if (second.usePing) {
5690 sprintf(buf, "ping %d\n", ++second.lastPing);
5691 SendToProgram(buf, &second);
5694 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
5695 /* Kill off second chess program */
5696 if (second.isr != NULL)
5697 RemoveInputSource(second.isr);
5700 if (second.pr != NoProc) {
5701 DoSleep( appData.delayBeforeQuit );
5702 SendToProgram("quit\n", &second);
5703 DoSleep( appData.delayAfterQuit );
5704 DestroyChildProcess(second.pr, second.useSigterm);
5709 if (matchMode && gameMode == TwoMachinesPlay) {
5712 if (first.twoMachinesColor[0] == 'w') {
5719 if (first.twoMachinesColor[0] == 'b') {
5728 if (matchGame < appData.matchGames) {
5730 tmp = first.twoMachinesColor;
5731 first.twoMachinesColor = second.twoMachinesColor;
5732 second.twoMachinesColor = tmp;
5733 gameMode = nextGameMode;
5735 ScheduleDelayedEvent(NextMatchGame, 10000);
5739 gameMode = nextGameMode;
5740 sprintf(buf, "Match %s vs. %s: final score %d-%d-%d",
5741 first.tidy, second.tidy,
5742 first.matchWins, second.matchWins,
5743 appData.matchGames - (first.matchWins + second.matchWins));
5744 DisplayFatalError(buf, 0, 0);
5747 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
5748 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
5750 gameMode = nextGameMode;
5754 /* Assumes program was just initialized (initString sent).
5755 Leaves program in force mode. */
5757 FeedMovesToProgram(cps, upto)
5758 ChessProgramState *cps;
5763 if (appData.debugMode)
5764 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
5765 startedFromSetupPosition ? "position and " : "",
5766 backwardMostMove, upto, cps->which);
5767 SendToProgram("force\n", cps);
5768 if (startedFromSetupPosition) {
5769 SendBoard(cps, backwardMostMove);
5771 for (i = backwardMostMove; i < upto; i++) {
5772 SendMoveToProgram(i, cps);
5778 ResurrectChessProgram()
5780 /* The chess program may have exited.
5781 If so, restart it and feed it all the moves made so far. */
5783 if (appData.noChessProgram || first.pr != NoProc) return;
5785 StartChessProgram(&first);
5786 InitChessProgram(&first);
5787 FeedMovesToProgram(&first, currentMove);
5789 if (!first.sendTime) {
5790 /* can't tell gnuchess what its clock should read,
5791 so we bow to its notion. */
5793 timeRemaining[0][currentMove] = whiteTimeRemaining;
5794 timeRemaining[1][currentMove] = blackTimeRemaining;
5797 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
5798 first.analysisSupport) {
5799 SendToProgram("analyze\n", &first);
5800 first.analyzing = TRUE;
5813 if (appData.debugMode) {
5814 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
5815 redraw, init, gameMode);
5818 pausing = pauseExamInvalid = FALSE;
5819 startedFromSetupPosition = blackPlaysFirst = FALSE;
5821 whiteFlag = blackFlag = FALSE;
5822 userOfferedDraw = FALSE;
5823 hintRequested = bookRequested = FALSE;
5824 first.maybeThinking = FALSE;
5825 second.maybeThinking = FALSE;
5826 thinkOutput[0] = NULLCHAR;
5827 lastHint[0] = NULLCHAR;
5828 ClearGameInfo(&gameInfo);
5829 gameInfo.variant = StringToVariant(appData.variant);
5830 ics_user_moved = ics_clock_paused = FALSE;
5831 ics_getting_history = H_FALSE;
5833 white_holding[0] = black_holding[0] = NULLCHAR;
5834 ClearProgramStats();
5838 flipView = appData.flipView;
5839 ClearPremoveHighlights();
5841 alarmSounded = FALSE;
5843 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
5845 gameMode = BeginningOfGame;
5847 InitPosition(redraw);
5848 for (i = 0; i < MAX_MOVES; i++) {
5849 if (commentList[i] != NULL) {
5850 free(commentList[i]);
5851 commentList[i] = NULL;
5855 timeRemaining[0][0] = whiteTimeRemaining;
5856 timeRemaining[1][0] = blackTimeRemaining;
5857 if (first.pr == NULL) {
5858 StartChessProgram(&first);
5860 if (init) InitChessProgram(&first);
5862 DisplayMessage("", "");
5863 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5870 if (!AutoPlayOneMove())
5872 if (matchMode || appData.timeDelay == 0)
5874 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
5876 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
5885 int fromX, fromY, toX, toY;
5887 if (appData.debugMode) {
5888 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
5891 if (gameMode != PlayFromGameFile)
5894 if (currentMove >= forwardMostMove) {
5895 gameMode = EditGame;
5898 /* [AS] Clear current move marker at the end of a game */
5899 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
5904 toX = moveList[currentMove][2] - 'a';
5905 toY = moveList[currentMove][3] - '1';
5907 if (moveList[currentMove][1] == '@') {
5908 if (appData.highlightLastMove) {
5909 SetHighlights(-1, -1, toX, toY);
5912 fromX = moveList[currentMove][0] - 'a';
5913 fromY = moveList[currentMove][1] - '1';
5915 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
5917 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
5919 if (appData.highlightLastMove) {
5920 SetHighlights(fromX, fromY, toX, toY);
5923 DisplayMove(currentMove);
5924 SendMoveToProgram(currentMove++, &first);
5925 DisplayBothClocks();
5926 DrawPosition(FALSE, boards[currentMove]);
5927 if (commentList[currentMove] != NULL) {
5928 DisplayComment(currentMove - 1, commentList[currentMove]);
5935 LoadGameOneMove(readAhead)
5936 ChessMove readAhead;
5938 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
5939 char promoChar = NULLCHAR;
5944 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
5945 gameMode != AnalyzeMode && gameMode != Training) {
5950 yyboardindex = forwardMostMove;
5951 if (readAhead != (ChessMove)0) {
5952 moveType = readAhead;
5954 if (gameFileFP == NULL)
5956 moveType = (ChessMove) yylex();
5962 if (appData.debugMode)
5963 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
5965 if (*p == '{' || *p == '[' || *p == '(') {
5966 p[strlen(p) - 1] = NULLCHAR;
5970 /* append the comment but don't display it */
5971 while (*p == '\n') p++;
5972 AppendComment(currentMove, p);
5975 case WhiteCapturesEnPassant:
5976 case BlackCapturesEnPassant:
5977 case WhitePromotionQueen:
5978 case BlackPromotionQueen:
5979 case WhitePromotionRook:
5980 case BlackPromotionRook:
5981 case WhitePromotionBishop:
5982 case BlackPromotionBishop:
5983 case WhitePromotionKnight:
5984 case BlackPromotionKnight:
5985 case WhitePromotionKing:
5986 case BlackPromotionKing:
5988 case WhiteKingSideCastle:
5989 case WhiteQueenSideCastle:
5990 case BlackKingSideCastle:
5991 case BlackQueenSideCastle:
5992 case WhiteKingSideCastleWild:
5993 case WhiteQueenSideCastleWild:
5994 case BlackKingSideCastleWild:
5995 case BlackQueenSideCastleWild:
5997 case WhiteHSideCastleFR:
5998 case WhiteASideCastleFR:
5999 case BlackHSideCastleFR:
6000 case BlackASideCastleFR:
6002 if (appData.debugMode)
6003 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
6004 fromX = currentMoveString[0] - 'a';
6005 fromY = currentMoveString[1] - '1';
6006 toX = currentMoveString[2] - 'a';
6007 toY = currentMoveString[3] - '1';
6008 promoChar = currentMoveString[4];
6013 if (appData.debugMode)
6014 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
6015 fromX = moveType == WhiteDrop ?
6016 (int) CharToPiece(ToUpper(currentMoveString[0])) :
6017 (int) CharToPiece(ToLower(currentMoveString[0]));
6019 toX = currentMoveString[2] - 'a';
6020 toY = currentMoveString[3] - '1';
6026 case GameUnfinished:
6027 if (appData.debugMode)
6028 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
6029 p = strchr(yy_text, '{');
6030 if (p == NULL) p = strchr(yy_text, '(');
6033 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
6035 q = strchr(p, *p == '{' ? '}' : ')');
6036 if (q != NULL) *q = NULLCHAR;
6039 GameEnds(moveType, p, GE_FILE);
6041 if (cmailMsgLoaded) {
6043 flipView = WhiteOnMove(currentMove);
6044 if (moveType == GameUnfinished) flipView = !flipView;
6045 if (appData.debugMode)
6046 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
6050 case (ChessMove) 0: /* end of file */
6051 if (appData.debugMode)
6052 fprintf(debugFP, "Parser hit end of file\n");
6053 switch (MateTest(boards[currentMove], PosFlags(currentMove),
6059 if (WhiteOnMove(currentMove)) {
6060 GameEnds(BlackWins, "Black mates", GE_FILE);
6062 GameEnds(WhiteWins, "White mates", GE_FILE);
6066 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
6073 if (lastLoadGameStart == GNUChessGame) {
6074 /* GNUChessGames have numbers, but they aren't move numbers */
6075 if (appData.debugMode)
6076 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
6077 yy_text, (int) moveType);
6078 return LoadGameOneMove((ChessMove)0); /* tail recursion */
6080 /* else fall thru */
6085 /* Reached start of next game in file */
6086 if (appData.debugMode)
6087 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
6088 switch (MateTest(boards[currentMove], PosFlags(currentMove),
6094 if (WhiteOnMove(currentMove)) {
6095 GameEnds(BlackWins, "Black mates", GE_FILE);
6097 GameEnds(WhiteWins, "White mates", GE_FILE);
6101 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
6107 case PositionDiagram: /* should not happen; ignore */
6108 case ElapsedTime: /* ignore */
6109 case NAG: /* ignore */
6110 if (appData.debugMode)
6111 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
6112 yy_text, (int) moveType);
6113 return LoadGameOneMove((ChessMove)0); /* tail recursion */
6116 if (appData.testLegality) {
6117 if (appData.debugMode)
6118 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
6119 sprintf(move, "Illegal move: %d.%s%s",
6120 (forwardMostMove / 2) + 1,
6121 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
6122 DisplayError(move, 0);
6125 if (appData.debugMode)
6126 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
6127 yy_text, currentMoveString);
6128 fromX = currentMoveString[0] - 'a';
6129 fromY = currentMoveString[1] - '1';
6130 toX = currentMoveString[2] - 'a';
6131 toY = currentMoveString[3] - '1';
6132 promoChar = currentMoveString[4];
6137 if (appData.debugMode)
6138 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
6139 sprintf(move, "Ambiguous move: %d.%s%s",
6140 (forwardMostMove / 2) + 1,
6141 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
6142 DisplayError(move, 0);
6147 case ImpossibleMove:
6148 if (appData.debugMode)
6149 fprintf(debugFP, "Parsed ImpossibleMove: %s\n", yy_text);
6150 sprintf(move, "Illegal move: %d.%s%s",
6151 (forwardMostMove / 2) + 1,
6152 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
6153 DisplayError(move, 0);
6159 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
6160 DrawPosition(FALSE, boards[currentMove]);
6161 DisplayBothClocks();
6162 if (!appData.matchMode && commentList[currentMove] != NULL)
6163 DisplayComment(currentMove - 1, commentList[currentMove]);
6165 (void) StopLoadGameTimer();
6167 cmailOldMove = forwardMostMove;
6170 /* currentMoveString is set as a side-effect of yylex */
6171 strcat(currentMoveString, "\n");
6172 strcpy(moveList[forwardMostMove], currentMoveString);
6174 thinkOutput[0] = NULLCHAR;
6175 MakeMove(fromX, fromY, toX, toY, promoChar);
6176 currentMove = forwardMostMove;
6181 /* Load the nth game from the given file */
6183 LoadGameFromFile(filename, n, title, useList)
6187 /*Boolean*/ int useList;
6192 if (strcmp(filename, "-") == 0) {
6196 f = fopen(filename, "rb");
6198 sprintf(buf, "Can't open \"%s\"", filename);
6199 DisplayError(buf, errno);
6203 if (fseek(f, 0, 0) == -1) {
6204 /* f is not seekable; probably a pipe */
6207 if (useList && n == 0) {
6208 int error = GameListBuild(f);
6210 DisplayError("Cannot build game list", error);
6211 } else if (!ListEmpty(&gameList) &&
6212 ((ListGame *) gameList.tailPred)->number > 1) {
6213 GameListPopUp(f, title);
6220 return LoadGame(f, n, title, FALSE);
6225 MakeRegisteredMove()
6227 int fromX, fromY, toX, toY;
6229 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
6230 switch (cmailMoveType[lastLoadGameNumber - 1]) {
6233 if (appData.debugMode)
6234 fprintf(debugFP, "Restoring %s for game %d\n",
6235 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
6237 thinkOutput[0] = NULLCHAR;
6238 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
6239 fromX = cmailMove[lastLoadGameNumber - 1][0] - 'a';
6240 fromY = cmailMove[lastLoadGameNumber - 1][1] - '1';
6241 toX = cmailMove[lastLoadGameNumber - 1][2] - 'a';
6242 toY = cmailMove[lastLoadGameNumber - 1][3] - '1';
6243 promoChar = cmailMove[lastLoadGameNumber - 1][4];
6244 MakeMove(fromX, fromY, toX, toY, promoChar);
6245 ShowMove(fromX, fromY, toX, toY);
6247 switch (MateTest(boards[currentMove], PosFlags(currentMove),
6254 if (WhiteOnMove(currentMove)) {
6255 GameEnds(BlackWins, "Black mates", GE_PLAYER);
6257 GameEnds(WhiteWins, "White mates", GE_PLAYER);
6262 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6269 if (WhiteOnMove(currentMove)) {
6270 GameEnds(BlackWins, "White resigns", GE_PLAYER);
6272 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
6277 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
6288 /* Wrapper around LoadGame for use when a Cmail message is loaded */
6290 CmailLoadGame(f, gameNumber, title, useList)
6298 if (gameNumber > nCmailGames) {
6299 DisplayError("No more games in this message", 0);
6302 if (f == lastLoadGameFP) {
6303 int offset = gameNumber - lastLoadGameNumber;
6305 cmailMsg[0] = NULLCHAR;
6306 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
6307 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
6308 nCmailMovesRegistered--;
6310 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6311 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
6312 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
6315 if (! RegisterMove()) return FALSE;
6319 retVal = LoadGame(f, gameNumber, title, useList);
6321 /* Make move registered during previous look at this game, if any */
6322 MakeRegisteredMove();
6324 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
6325 commentList[currentMove]
6326 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
6327 DisplayComment(currentMove - 1, commentList[currentMove]);
6333 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
6338 int gameNumber = lastLoadGameNumber + offset;
6339 if (lastLoadGameFP == NULL) {
6340 DisplayError("No game has been loaded yet", 0);
6343 if (gameNumber <= 0) {
6344 DisplayError("Can't back up any further", 0);
6347 if (cmailMsgLoaded) {
6348 return CmailLoadGame(lastLoadGameFP, gameNumber,
6349 lastLoadGameTitle, lastLoadGameUseList);
6351 return LoadGame(lastLoadGameFP, gameNumber,
6352 lastLoadGameTitle, lastLoadGameUseList);
6358 /* Load the nth game from open file f */
6360 LoadGame(f, gameNumber, title, useList)
6368 int gn = gameNumber;
6369 ListGame *lg = NULL;
6372 GameMode oldGameMode;
6374 if (appData.debugMode)
6375 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
6377 if (gameMode == Training )
6378 SetTrainingModeOff();
6380 oldGameMode = gameMode;
6381 if (gameMode != BeginningOfGame) {
6386 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
6387 fclose(lastLoadGameFP);
6391 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
6394 fseek(f, lg->offset, 0);
6395 GameListHighlight(gameNumber);
6399 DisplayError("Game number out of range", 0);
6404 if (fseek(f, 0, 0) == -1) {
6405 if (f == lastLoadGameFP ?
6406 gameNumber == lastLoadGameNumber + 1 :
6410 DisplayError("Can't seek on game file", 0);
6416 lastLoadGameNumber = gameNumber;
6417 strcpy(lastLoadGameTitle, title);
6418 lastLoadGameUseList = useList;
6423 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
6424 sprintf(buf, "%s vs. %s", lg->gameInfo.white,
6425 lg->gameInfo.black);
6427 } else if (*title != NULLCHAR) {
6428 if (gameNumber > 1) {
6429 sprintf(buf, "%s %d", title, gameNumber);
6432 DisplayTitle(title);
6436 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
6437 gameMode = PlayFromGameFile;
6441 currentMove = forwardMostMove = backwardMostMove = 0;
6442 CopyBoard(boards[0], initialPosition);
6446 * Skip the first gn-1 games in the file.
6447 * Also skip over anything that precedes an identifiable
6448 * start of game marker, to avoid being confused by
6449 * garbage at the start of the file. Currently
6450 * recognized start of game markers are the move number "1",
6451 * the pattern "gnuchess .* game", the pattern
6452 * "^[#;%] [^ ]* game file", and a PGN tag block.
6453 * A game that starts with one of the latter two patterns
6454 * will also have a move number 1, possibly
6455 * following a position diagram.
6456 * 5-4-02: Let's try being more lenient and allowing a game to
6457 * start with an unnumbered move. Does that break anything?
6459 cm = lastLoadGameStart = (ChessMove) 0;
6461 yyboardindex = forwardMostMove;
6462 cm = (ChessMove) yylex();
6465 if (cmailMsgLoaded) {
6466 nCmailGames = CMAIL_MAX_GAMES - gn;
6469 DisplayError("Game not found in file", 0);
6476 lastLoadGameStart = cm;
6480 switch (lastLoadGameStart) {
6487 gn--; /* count this game */
6488 lastLoadGameStart = cm;
6497 switch (lastLoadGameStart) {
6502 gn--; /* count this game */
6503 lastLoadGameStart = cm;
6506 lastLoadGameStart = cm; /* game counted already */
6514 yyboardindex = forwardMostMove;
6515 cm = (ChessMove) yylex();
6516 } while (cm == PGNTag || cm == Comment);
6523 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
6524 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
6525 != CMAIL_OLD_RESULT) {
6527 cmailResult[ CMAIL_MAX_GAMES
6528 - gn - 1] = CMAIL_OLD_RESULT;
6534 /* Only a NormalMove can be at the start of a game
6535 * without a position diagram. */
6536 if (lastLoadGameStart == (ChessMove) 0) {
6538 lastLoadGameStart = MoveNumberOne;
6547 if (appData.debugMode)
6548 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
6550 if (cm == XBoardGame) {
6551 /* Skip any header junk before position diagram and/or move 1 */
6553 yyboardindex = forwardMostMove;
6554 cm = (ChessMove) yylex();
6556 if (cm == (ChessMove) 0 ||
6557 cm == GNUChessGame || cm == XBoardGame) {
6558 /* Empty game; pretend end-of-file and handle later */
6563 if (cm == MoveNumberOne || cm == PositionDiagram ||
6564 cm == PGNTag || cm == Comment)
6567 } else if (cm == GNUChessGame) {
6568 if (gameInfo.event != NULL) {
6569 free(gameInfo.event);
6571 gameInfo.event = StrSave(yy_text);
6574 startedFromSetupPosition = FALSE;
6575 while (cm == PGNTag) {
6576 if (appData.debugMode)
6577 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
6578 err = ParsePGNTag(yy_text, &gameInfo);
6579 if (!err) numPGNTags++;
6581 if (gameInfo.fen != NULL) {
6582 Board initial_position;
6583 startedFromSetupPosition = TRUE;
6584 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
6586 DisplayError("Bad FEN position in file", 0);
6589 CopyBoard(boards[0], initial_position);
6590 if (blackPlaysFirst) {
6591 currentMove = forwardMostMove = backwardMostMove = 1;
6592 CopyBoard(boards[1], initial_position);
6593 strcpy(moveList[0], "");
6594 strcpy(parseList[0], "");
6595 timeRemaining[0][1] = whiteTimeRemaining;
6596 timeRemaining[1][1] = blackTimeRemaining;
6597 if (commentList[0] != NULL) {
6598 commentList[1] = commentList[0];
6599 commentList[0] = NULL;
6602 currentMove = forwardMostMove = backwardMostMove = 0;
6604 yyboardindex = forwardMostMove;
6606 gameInfo.fen = NULL;
6609 yyboardindex = forwardMostMove;
6610 cm = (ChessMove) yylex();
6612 /* Handle comments interspersed among the tags */
6613 while (cm == Comment) {
6615 if (appData.debugMode)
6616 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
6618 if (*p == '{' || *p == '[' || *p == '(') {
6619 p[strlen(p) - 1] = NULLCHAR;
6622 while (*p == '\n') p++;
6623 AppendComment(currentMove, p);
6624 yyboardindex = forwardMostMove;
6625 cm = (ChessMove) yylex();
6629 /* don't rely on existence of Event tag since if game was
6630 * pasted from clipboard the Event tag may not exist
6632 if (numPGNTags > 0){
6634 if (gameInfo.variant == VariantNormal) {
6635 gameInfo.variant = StringToVariant(gameInfo.event);
6638 if( appData.autoDisplayTags ) {
6639 tags = PGNTags(&gameInfo);
6640 TagsPopUp(tags, CmailMsg());
6645 /* Make something up, but don't display it now */
6650 if (cm == PositionDiagram) {
6653 Board initial_position;
6655 if (appData.debugMode)
6656 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
6658 if (!startedFromSetupPosition) {
6660 for (i = BOARD_SIZE - 1; i >= 0; i--)
6661 for (j = 0; j < BOARD_SIZE; p++)
6671 initial_position[i][j++] = CharToPiece(*p);
6674 while (*p == ' ' || *p == '\t' ||
6675 *p == '\n' || *p == '\r') p++;
6677 if (strncmp(p, "black", strlen("black"))==0)
6678 blackPlaysFirst = TRUE;
6680 blackPlaysFirst = FALSE;
6681 startedFromSetupPosition = TRUE;
6683 CopyBoard(boards[0], initial_position);
6684 if (blackPlaysFirst) {
6685 currentMove = forwardMostMove = backwardMostMove = 1;
6686 CopyBoard(boards[1], initial_position);
6687 strcpy(moveList[0], "");
6688 strcpy(parseList[0], "");
6689 timeRemaining[0][1] = whiteTimeRemaining;
6690 timeRemaining[1][1] = blackTimeRemaining;
6691 if (commentList[0] != NULL) {
6692 commentList[1] = commentList[0];
6693 commentList[0] = NULL;
6696 currentMove = forwardMostMove = backwardMostMove = 0;
6699 yyboardindex = forwardMostMove;
6700 cm = (ChessMove) yylex();
6703 if (first.pr == NoProc) {
6704 StartChessProgram(&first);
6706 InitChessProgram(&first);
6707 SendToProgram("force\n", &first);
6708 if (startedFromSetupPosition) {
6709 SendBoard(&first, forwardMostMove);
6710 DisplayBothClocks();
6713 while (cm == Comment) {
6715 if (appData.debugMode)
6716 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
6718 if (*p == '{' || *p == '[' || *p == '(') {
6719 p[strlen(p) - 1] = NULLCHAR;
6722 while (*p == '\n') p++;
6723 AppendComment(currentMove, p);
6724 yyboardindex = forwardMostMove;
6725 cm = (ChessMove) yylex();
6728 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
6729 cm == WhiteWins || cm == BlackWins ||
6730 cm == GameIsDrawn || cm == GameUnfinished) {
6731 DisplayMessage("", "No moves in game");
6732 if (cmailMsgLoaded) {
6733 if (appData.debugMode)
6734 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
6738 DrawPosition(FALSE, boards[currentMove]);
6739 DisplayBothClocks();
6740 gameMode = EditGame;
6747 if (commentList[currentMove] != NULL) {
6748 if (!matchMode && (pausing || appData.timeDelay != 0)) {
6749 DisplayComment(currentMove - 1, commentList[currentMove]);
6752 if (!matchMode && appData.timeDelay != 0)
6753 DrawPosition(FALSE, boards[currentMove]);
6755 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
6756 programStats.ok_to_send = 1;
6759 /* if the first token after the PGN tags is a move
6760 * and not move number 1, retrieve it from the parser
6762 if (cm != MoveNumberOne)
6763 LoadGameOneMove(cm);
6765 /* load the remaining moves from the file */
6766 while (LoadGameOneMove((ChessMove)0)) {
6767 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
6768 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
6771 /* rewind to the start of the game */
6772 currentMove = backwardMostMove;
6774 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
6776 if (oldGameMode == AnalyzeFile ||
6777 oldGameMode == AnalyzeMode) {
6781 if (matchMode || appData.timeDelay == 0) {
6783 gameMode = EditGame;
6785 } else if (appData.timeDelay > 0) {
6789 if (appData.debugMode)
6790 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
6794 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
6796 ReloadPosition(offset)
6799 int positionNumber = lastLoadPositionNumber + offset;
6800 if (lastLoadPositionFP == NULL) {
6801 DisplayError("No position has been loaded yet", 0);
6804 if (positionNumber <= 0) {
6805 DisplayError("Can't back up any further", 0);
6808 return LoadPosition(lastLoadPositionFP, positionNumber,
6809 lastLoadPositionTitle);
6812 /* Load the nth position from the given file */
6814 LoadPositionFromFile(filename, n, title)
6822 if (strcmp(filename, "-") == 0) {
6823 return LoadPosition(stdin, n, "stdin");
6825 f = fopen(filename, "rb");
6827 sprintf(buf, "Can't open \"%s\"", filename);
6828 DisplayError(buf, errno);
6831 return LoadPosition(f, n, title);
6836 /* Load the nth position from the given open file, and close it */
6838 LoadPosition(f, positionNumber, title)
6843 char *p, line[MSG_SIZ];
6844 Board initial_position;
6845 int i, j, fenMode, pn;
6847 if (gameMode == Training )
6848 SetTrainingModeOff();
6850 if (gameMode != BeginningOfGame) {
6853 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
6854 fclose(lastLoadPositionFP);
6856 if (positionNumber == 0) positionNumber = 1;
6857 lastLoadPositionFP = f;
6858 lastLoadPositionNumber = positionNumber;
6859 strcpy(lastLoadPositionTitle, title);
6860 if (first.pr == NoProc) {
6861 StartChessProgram(&first);
6862 InitChessProgram(&first);
6864 pn = positionNumber;
6865 if (positionNumber < 0) {
6866 /* Negative position number means to seek to that byte offset */
6867 if (fseek(f, -positionNumber, 0) == -1) {
6868 DisplayError("Can't seek on position file", 0);
6873 if (fseek(f, 0, 0) == -1) {
6874 if (f == lastLoadPositionFP ?
6875 positionNumber == lastLoadPositionNumber + 1 :
6876 positionNumber == 1) {
6879 DisplayError("Can't seek on position file", 0);
6884 /* See if this file is FEN or old-style xboard */
6885 if (fgets(line, MSG_SIZ, f) == NULL) {
6886 DisplayError("Position not found in file", 0);
6894 case 'p': case 'n': case 'b': case 'r': case 'q': case 'k':
6895 case 'P': case 'N': case 'B': case 'R': case 'Q': case 'K':
6896 case '1': case '2': case '3': case '4': case '5': case '6':
6903 if (fenMode || line[0] == '#') pn--;
6905 /* skip postions before number pn */
6906 if (fgets(line, MSG_SIZ, f) == NULL) {
6908 DisplayError("Position not found in file", 0);
6911 if (fenMode || line[0] == '#') pn--;
6916 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
6917 DisplayError("Bad FEN position in file", 0);
6921 (void) fgets(line, MSG_SIZ, f);
6922 (void) fgets(line, MSG_SIZ, f);
6924 for (i = BOARD_SIZE - 1; i >= 0; i--) {
6925 (void) fgets(line, MSG_SIZ, f);
6926 for (p = line, j = 0; j < BOARD_SIZE; p++) {
6929 initial_position[i][j++] = CharToPiece(*p);
6933 blackPlaysFirst = FALSE;
6935 (void) fgets(line, MSG_SIZ, f);
6936 if (strncmp(line, "black", strlen("black"))==0)
6937 blackPlaysFirst = TRUE;
6940 startedFromSetupPosition = TRUE;
6942 SendToProgram("force\n", &first);
6943 CopyBoard(boards[0], initial_position);
6944 if (blackPlaysFirst) {
6945 currentMove = forwardMostMove = backwardMostMove = 1;
6946 strcpy(moveList[0], "");
6947 strcpy(parseList[0], "");
6948 CopyBoard(boards[1], initial_position);
6949 DisplayMessage("", "Black to play");
6951 currentMove = forwardMostMove = backwardMostMove = 0;
6952 DisplayMessage("", "White to play");
6954 SendBoard(&first, forwardMostMove);
6956 if (positionNumber > 1) {
6957 sprintf(line, "%s %d", title, positionNumber);
6960 DisplayTitle(title);
6962 gameMode = EditGame;
6965 timeRemaining[0][1] = whiteTimeRemaining;
6966 timeRemaining[1][1] = blackTimeRemaining;
6967 DrawPosition(FALSE, boards[currentMove]);
6974 CopyPlayerNameIntoFileName(dest, src)
6977 while (*src != NULLCHAR && *src != ',') {
6982 *(*dest)++ = *src++;
6987 char *DefaultFileName(ext)
6990 static char def[MSG_SIZ];
6993 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
6995 CopyPlayerNameIntoFileName(&p, gameInfo.white);
6997 CopyPlayerNameIntoFileName(&p, gameInfo.black);
7006 /* Save the current game to the given file */
7008 SaveGameToFile(filename, append)
7015 if (strcmp(filename, "-") == 0) {
7016 return SaveGame(stdout, 0, NULL);
7018 f = fopen(filename, append ? "a" : "w");
7020 sprintf(buf, "Can't open \"%s\"", filename);
7021 DisplayError(buf, errno);
7024 return SaveGame(f, 0, NULL);
7033 static char buf[MSG_SIZ];
7036 p = strchr(str, ' ');
7037 if (p == NULL) return str;
7038 strncpy(buf, str, p - str);
7039 buf[p - str] = NULLCHAR;
7043 #define PGN_MAX_LINE 75
7045 #define PGN_SIDE_WHITE 0
7046 #define PGN_SIDE_BLACK 1
7049 static int FindFirstMoveOutOfBook( int side )
7053 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
7054 int index = backwardMostMove;
7055 int has_book_hit = 0;
7057 if( (index % 2) != side ) {
7061 while( index < forwardMostMove ) {
7062 /* Check to see if engine is in book */
7063 int depth = pvInfoList[index].depth;
7064 int score = pvInfoList[index].score;
7070 else if( score == 0 && depth == 63 ) {
7071 in_book = 1; /* Zappa */
7073 else if( score == 2 && depth == 99 ) {
7074 in_book = 1; /* Abrok */
7077 has_book_hit += in_book;
7093 void GetOutOfBookInfo( char * buf )
7097 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
7099 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
7100 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
7104 if( oob[0] >= 0 || oob[1] >= 0 ) {
7105 for( i=0; i<2; i++ ) {
7109 if( i > 0 && oob[0] >= 0 ) {
7113 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
7114 sprintf( buf+strlen(buf), "%s%.2f",
7115 pvInfoList[idx].score >= 0 ? "+" : "",
7116 pvInfoList[idx].score / 100.0 );
7122 /* Save game in PGN style and close the file */
7127 int i, offset, linelen, newblock;
7131 int movelen, numlen, blank;
7132 char move_buffer[100]; /* [AS] Buffer for move+PV info */
7134 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
7136 tm = time((time_t *) NULL);
7138 PrintPGNTags(f, &gameInfo);
7140 if (backwardMostMove > 0 || startedFromSetupPosition) {
7141 char *fen = PositionToFEN(backwardMostMove, 1);
7142 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
7143 fprintf(f, "\n{--------------\n");
7144 PrintPosition(f, backwardMostMove);
7145 fprintf(f, "--------------}\n");
7149 /* [AS] Out of book annotation */
7150 if( appData.saveOutOfBookInfo ) {
7153 GetOutOfBookInfo( buf );
7155 if( buf[0] != '\0' ) {
7156 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
7163 i = backwardMostMove;
7167 while (i < forwardMostMove) {
7168 /* Print comments preceding this move */
7169 if (commentList[i] != NULL) {
7170 if (linelen > 0) fprintf(f, "\n");
7171 fprintf(f, "{\n%s}\n", commentList[i]);
7176 /* Format move number */
7178 sprintf(numtext, "%d.", (i - offset)/2 + 1);
7181 sprintf(numtext, "%d...", (i - offset)/2 + 1);
7183 numtext[0] = NULLCHAR;
7186 numlen = strlen(numtext);
7189 /* Print move number */
7190 blank = linelen > 0 && numlen > 0;
7191 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
7200 fprintf(f, numtext);
7204 movetext = SavePart(parseList[i]);
7206 /* [AS] Add PV info if present */
7207 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
7208 sprintf( move_buffer, "%s {%s%.2f/%d}",
7210 pvInfoList[i].score >= 0 ? "+" : "",
7211 pvInfoList[i].score / 100.0,
7212 pvInfoList[i].depth );
7213 movetext = move_buffer;
7216 movelen = strlen(movetext);
7219 blank = linelen > 0 && movelen > 0;
7220 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
7229 fprintf(f, movetext);
7235 /* Start a new line */
7236 if (linelen > 0) fprintf(f, "\n");
7238 /* Print comments after last move */
7239 if (commentList[i] != NULL) {
7240 fprintf(f, "{\n%s}\n", commentList[i]);
7244 if (gameInfo.resultDetails != NULL &&
7245 gameInfo.resultDetails[0] != NULLCHAR) {
7246 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
7247 PGNResult(gameInfo.result));
7249 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
7256 /* Save game in old style and close the file */
7264 tm = time((time_t *) NULL);
7266 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
7269 if (backwardMostMove > 0 || startedFromSetupPosition) {
7270 fprintf(f, "\n[--------------\n");
7271 PrintPosition(f, backwardMostMove);
7272 fprintf(f, "--------------]\n");
7277 i = backwardMostMove;
7278 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
7280 while (i < forwardMostMove) {
7281 if (commentList[i] != NULL) {
7282 fprintf(f, "[%s]\n", commentList[i]);
7286 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
7289 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
7291 if (commentList[i] != NULL) {
7295 if (i >= forwardMostMove) {
7299 fprintf(f, "%s\n", parseList[i]);
7304 if (commentList[i] != NULL) {
7305 fprintf(f, "[%s]\n", commentList[i]);
7308 /* This isn't really the old style, but it's close enough */
7309 if (gameInfo.resultDetails != NULL &&
7310 gameInfo.resultDetails[0] != NULLCHAR) {
7311 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
7312 gameInfo.resultDetails);
7314 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
7321 /* Save the current game to open file f and close the file */
7323 SaveGame(f, dummy, dummy2)
7328 if (gameMode == EditPosition) EditPositionDone();
7329 if (appData.oldSaveStyle)
7330 return SaveGameOldStyle(f);
7332 return SaveGamePGN(f);
7335 /* Save the current position to the given file */
7337 SavePositionToFile(filename)
7343 if (strcmp(filename, "-") == 0) {
7344 return SavePosition(stdout, 0, NULL);
7346 f = fopen(filename, "a");
7348 sprintf(buf, "Can't open \"%s\"", filename);
7349 DisplayError(buf, errno);
7352 SavePosition(f, 0, NULL);
7358 /* Save the current position to the given open file and close the file */
7360 SavePosition(f, dummy, dummy2)
7368 if (appData.oldSaveStyle) {
7369 tm = time((time_t *) NULL);
7371 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
7373 fprintf(f, "[--------------\n");
7374 PrintPosition(f, currentMove);
7375 fprintf(f, "--------------]\n");
7377 fen = PositionToFEN(currentMove, 1);
7378 fprintf(f, "%s\n", fen);
7386 ReloadCmailMsgEvent(unregister)
7390 static char *inFilename = NULL;
7391 static char *outFilename;
7393 struct stat inbuf, outbuf;
7396 /* Any registered moves are unregistered if unregister is set, */
7397 /* i.e. invoked by the signal handler */
7399 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
7400 cmailMoveRegistered[i] = FALSE;
7401 if (cmailCommentList[i] != NULL) {
7402 free(cmailCommentList[i]);
7403 cmailCommentList[i] = NULL;
7406 nCmailMovesRegistered = 0;
7409 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
7410 cmailResult[i] = CMAIL_NOT_RESULT;
7414 if (inFilename == NULL) {
7415 /* Because the filenames are static they only get malloced once */
7416 /* and they never get freed */
7417 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
7418 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
7420 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
7421 sprintf(outFilename, "%s.out", appData.cmailGameName);
7424 status = stat(outFilename, &outbuf);
7426 cmailMailedMove = FALSE;
7428 status = stat(inFilename, &inbuf);
7429 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
7432 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
7433 counts the games, notes how each one terminated, etc.
7435 It would be nice to remove this kludge and instead gather all
7436 the information while building the game list. (And to keep it
7437 in the game list nodes instead of having a bunch of fixed-size
7438 parallel arrays.) Note this will require getting each game's
7439 termination from the PGN tags, as the game list builder does
7440 not process the game moves. --mann
7442 cmailMsgLoaded = TRUE;
7443 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
7445 /* Load first game in the file or popup game menu */
7446 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
7456 char string[MSG_SIZ];
7458 if ( cmailMailedMove
7459 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
7460 return TRUE; /* Allow free viewing */
7463 /* Unregister move to ensure that we don't leave RegisterMove */
7464 /* with the move registered when the conditions for registering no */
7466 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
7467 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
7468 nCmailMovesRegistered --;
7470 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
7472 free(cmailCommentList[lastLoadGameNumber - 1]);
7473 cmailCommentList[lastLoadGameNumber - 1] = NULL;
7477 if (cmailOldMove == -1) {
7478 DisplayError("You have edited the game history.\nUse Reload Same Game and make your move again.", 0);
7482 if (currentMove > cmailOldMove + 1) {
7483 DisplayError("You have entered too many moves.\nBack up to the correct position and try again.", 0);
7487 if (currentMove < cmailOldMove) {
7488 DisplayError("Displayed position is not current.\nStep forward to the correct position and try again.", 0);
7492 if (forwardMostMove > currentMove) {
7493 /* Silently truncate extra moves */
7497 if ( (currentMove == cmailOldMove + 1)
7498 || ( (currentMove == cmailOldMove)
7499 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
7500 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
7501 if (gameInfo.result != GameUnfinished) {
7502 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
7505 if (commentList[currentMove] != NULL) {
7506 cmailCommentList[lastLoadGameNumber - 1]
7507 = StrSave(commentList[currentMove]);
7509 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
7511 if (appData.debugMode)
7512 fprintf(debugFP, "Saving %s for game %d\n",
7513 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
7516 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
7518 f = fopen(string, "w");
7519 if (appData.oldSaveStyle) {
7520 SaveGameOldStyle(f); /* also closes the file */
7522 sprintf(string, "%s.pos.out", appData.cmailGameName);
7523 f = fopen(string, "w");
7524 SavePosition(f, 0, NULL); /* also closes the file */
7526 fprintf(f, "{--------------\n");
7527 PrintPosition(f, currentMove);
7528 fprintf(f, "--------------}\n\n");
7530 SaveGame(f, 0, NULL); /* also closes the file*/
7533 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
7534 nCmailMovesRegistered ++;
7535 } else if (nCmailGames == 1) {
7536 DisplayError("You have not made a move yet", 0);
7547 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
7548 FILE *commandOutput;
7549 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
7550 int nBytes = 0; /* Suppress warnings on uninitialized variables */
7556 if (! cmailMsgLoaded) {
7557 DisplayError("The cmail message is not loaded.\nUse Reload CMail Message and make your move again.", 0);
7561 if (nCmailGames == nCmailResults) {
7562 DisplayError("No unfinished games", 0);
7566 #if CMAIL_PROHIBIT_REMAIL
7567 if (cmailMailedMove) {
7568 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);
7569 DisplayError(msg, 0);
7574 if (! (cmailMailedMove || RegisterMove())) return;
7576 if ( cmailMailedMove
7577 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
7578 sprintf(string, partCommandString,
7579 appData.debugMode ? " -v" : "", appData.cmailGameName);
7580 commandOutput = popen(string, "rb");
7582 if (commandOutput == NULL) {
7583 DisplayError("Failed to invoke cmail", 0);
7585 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
7586 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
7589 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
7590 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
7591 nBytes = MSG_SIZ - 1;
7593 (void) memcpy(msg, buffer, nBytes);
7595 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
7597 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
7598 cmailMailedMove = TRUE; /* Prevent >1 moves */
7601 for (i = 0; i < nCmailGames; i ++) {
7602 if (cmailResult[i] == CMAIL_NOT_RESULT) {
7607 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
7609 sprintf(buffer, "%s/%s.%s.archive",
7611 appData.cmailGameName,
7613 LoadGameFromFile(buffer, 1, buffer, FALSE);
7614 cmailMsgLoaded = FALSE;
7618 DisplayInformation(msg);
7619 pclose(commandOutput);
7622 if ((*cmailMsg) != '\0') {
7623 DisplayInformation(cmailMsg);
7637 int prependComma = 0;
7639 char string[MSG_SIZ]; /* Space for game-list */
7642 if (!cmailMsgLoaded) return "";
7644 if (cmailMailedMove) {
7645 sprintf(cmailMsg, "Waiting for reply from opponent\n");
7647 /* Create a list of games left */
7648 sprintf(string, "[");
7649 for (i = 0; i < nCmailGames; i ++) {
7650 if (! ( cmailMoveRegistered[i]
7651 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
7653 sprintf(number, ",%d", i + 1);
7655 sprintf(number, "%d", i + 1);
7659 strcat(string, number);
7662 strcat(string, "]");
7664 if (nCmailMovesRegistered + nCmailResults == 0) {
7665 switch (nCmailGames) {
7668 "Still need to make move for game\n");
7673 "Still need to make moves for both games\n");
7678 "Still need to make moves for all %d games\n",
7683 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
7686 "Still need to make a move for game %s\n",
7691 if (nCmailResults == nCmailGames) {
7692 sprintf(cmailMsg, "No unfinished games\n");
7694 sprintf(cmailMsg, "Ready to send mail\n");
7700 "Still need to make moves for games %s\n",
7712 if (gameMode == Training)
7713 SetTrainingModeOff();
7716 cmailMsgLoaded = FALSE;
7717 if (appData.icsActive) {
7718 SendToICS(ics_prefix);
7719 SendToICS("refresh\n");
7723 static int exiting = 0;
7731 /* Give up on clean exit */
7735 /* Keep trying for clean exit */
7739 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
7741 if (telnetISR != NULL) {
7742 RemoveInputSource(telnetISR);
7744 if (icsPR != NoProc) {
7745 DestroyChildProcess(icsPR, TRUE);
7747 /* Save game if resource set and not already saved by GameEnds() */
7748 if (gameInfo.resultDetails == NULL && forwardMostMove > 0) {
7749 if (*appData.saveGameFile != NULLCHAR) {
7750 SaveGameToFile(appData.saveGameFile, TRUE);
7751 } else if (appData.autoSaveGames) {
7754 if (*appData.savePositionFile != NULLCHAR) {
7755 SavePositionToFile(appData.savePositionFile);
7758 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
7760 /* Kill off chess programs */
7761 if (first.pr != NoProc) {
7764 DoSleep( appData.delayBeforeQuit );
7765 SendToProgram("quit\n", &first);
7766 DoSleep( appData.delayAfterQuit );
7767 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
7769 if (second.pr != NoProc) {
7770 DoSleep( appData.delayBeforeQuit );
7771 SendToProgram("quit\n", &second);
7772 DoSleep( appData.delayAfterQuit );
7773 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
7775 if (first.isr != NULL) {
7776 RemoveInputSource(first.isr);
7778 if (second.isr != NULL) {
7779 RemoveInputSource(second.isr);
7789 if (appData.debugMode)
7790 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
7794 if (gameMode == MachinePlaysWhite ||
7795 gameMode == MachinePlaysBlack) {
7798 DisplayBothClocks();
7800 if (gameMode == PlayFromGameFile) {
7801 if (appData.timeDelay >= 0)
7803 } else if (gameMode == IcsExamining && pauseExamInvalid) {
7805 SendToICS(ics_prefix);
7806 SendToICS("refresh\n");
7807 } else if (currentMove < forwardMostMove) {
7808 ForwardInner(forwardMostMove);
7810 pauseExamInvalid = FALSE;
7816 pauseExamForwardMostMove = forwardMostMove;
7817 pauseExamInvalid = FALSE;
7820 case IcsPlayingWhite:
7821 case IcsPlayingBlack:
7825 case PlayFromGameFile:
7826 (void) StopLoadGameTimer();
7830 case BeginningOfGame:
7831 if (appData.icsActive) return;
7832 /* else fall through */
7833 case MachinePlaysWhite:
7834 case MachinePlaysBlack:
7835 case TwoMachinesPlay:
7836 if (forwardMostMove == 0)
7837 return; /* don't pause if no one has moved */
7838 if ((gameMode == MachinePlaysWhite &&
7839 !WhiteOnMove(forwardMostMove)) ||
7840 (gameMode == MachinePlaysBlack &&
7841 WhiteOnMove(forwardMostMove))) {
7854 char title[MSG_SIZ];
7856 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
7857 strcpy(title, "Edit comment");
7859 sprintf(title, "Edit comment on %d.%s%s", (currentMove - 1) / 2 + 1,
7860 WhiteOnMove(currentMove - 1) ? " " : ".. ",
7861 parseList[currentMove - 1]);
7864 EditCommentPopUp(currentMove, title, commentList[currentMove]);
7871 char *tags = PGNTags(&gameInfo);
7872 EditTagsPopUp(tags);
7879 if (appData.noChessProgram || gameMode == AnalyzeMode)
7882 if (gameMode != AnalyzeFile) {
7884 if (gameMode != EditGame) return;
7885 ResurrectChessProgram();
7886 SendToProgram("analyze\n", &first);
7887 first.analyzing = TRUE;
7888 /*first.maybeThinking = TRUE;*/
7889 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
7890 AnalysisPopUp("Analysis",
7891 "Starting analysis mode...\nIf this message stays up, your chess program does not support analysis.");
7893 gameMode = AnalyzeMode;
7898 StartAnalysisClock();
7899 GetTimeMark(&lastNodeCountTime);
7906 if (appData.noChessProgram || gameMode == AnalyzeFile)
7909 if (gameMode != AnalyzeMode) {
7911 if (gameMode != EditGame) return;
7912 ResurrectChessProgram();
7913 SendToProgram("analyze\n", &first);
7914 first.analyzing = TRUE;
7915 /*first.maybeThinking = TRUE;*/
7916 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
7917 AnalysisPopUp("Analysis",
7918 "Starting analysis mode...\nIf this message stays up, your chess program does not support analysis.");
7920 gameMode = AnalyzeFile;
7925 StartAnalysisClock();
7926 GetTimeMark(&lastNodeCountTime);
7935 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
7939 if (gameMode == PlayFromGameFile ||
7940 gameMode == TwoMachinesPlay ||
7941 gameMode == Training ||
7942 gameMode == AnalyzeMode ||
7943 gameMode == EndOfGame)
7946 if (gameMode == EditPosition)
7949 if (!WhiteOnMove(currentMove)) {
7950 DisplayError("It is not White's turn", 0);
7954 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
7957 if (gameMode == EditGame || gameMode == AnalyzeMode ||
7958 gameMode == AnalyzeFile)
7961 ResurrectChessProgram(); /* in case it isn't running */
7962 gameMode = MachinePlaysWhite;
7966 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
7968 if (first.sendName) {
7969 sprintf(buf, "name %s\n", gameInfo.black);
7970 SendToProgram(buf, &first);
7972 if (first.sendTime) {
7973 if (first.useColors) {
7974 SendToProgram("black\n", &first); /*gnu kludge*/
7976 SendTimeRemaining(&first, TRUE);
7978 if (first.useColors) {
7979 SendToProgram("white\ngo\n", &first);
7981 SendToProgram("go\n", &first);
7983 SetMachineThinkingEnables();
7984 first.maybeThinking = TRUE;
7987 if (appData.autoFlipView && !flipView) {
7988 flipView = !flipView;
7989 DrawPosition(FALSE, NULL);
7998 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
8002 if (gameMode == PlayFromGameFile ||
8003 gameMode == TwoMachinesPlay ||
8004 gameMode == Training ||
8005 gameMode == AnalyzeMode ||
8006 gameMode == EndOfGame)
8009 if (gameMode == EditPosition)
8012 if (WhiteOnMove(currentMove)) {
8013 DisplayError("It is not Black's turn", 0);
8017 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
8020 if (gameMode == EditGame || gameMode == AnalyzeMode ||
8021 gameMode == AnalyzeFile)
8024 ResurrectChessProgram(); /* in case it isn't running */
8025 gameMode = MachinePlaysBlack;
8029 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
8031 if (first.sendName) {
8032 sprintf(buf, "name %s\n", gameInfo.white);
8033 SendToProgram(buf, &first);
8035 if (first.sendTime) {
8036 if (first.useColors) {
8037 SendToProgram("white\n", &first); /*gnu kludge*/
8039 SendTimeRemaining(&first, FALSE);
8041 if (first.useColors) {
8042 SendToProgram("black\ngo\n", &first);
8044 SendToProgram("go\n", &first);
8046 SetMachineThinkingEnables();
8047 first.maybeThinking = TRUE;
8050 if (appData.autoFlipView && flipView) {
8051 flipView = !flipView;
8052 DrawPosition(FALSE, NULL);
8058 DisplayTwoMachinesTitle()
8061 if (appData.matchGames > 0) {
8062 if (first.twoMachinesColor[0] == 'w') {
8063 sprintf(buf, "%s vs. %s (%d-%d-%d)",
8064 gameInfo.white, gameInfo.black,
8065 first.matchWins, second.matchWins,
8066 matchGame - 1 - (first.matchWins + second.matchWins));
8068 sprintf(buf, "%s vs. %s (%d-%d-%d)",
8069 gameInfo.white, gameInfo.black,
8070 second.matchWins, first.matchWins,
8071 matchGame - 1 - (first.matchWins + second.matchWins));
8074 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
8080 TwoMachinesEvent P((void))
8084 ChessProgramState *onmove;
8086 if (appData.noChessProgram) return;
8089 case TwoMachinesPlay:
8091 case MachinePlaysWhite:
8092 case MachinePlaysBlack:
8093 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
8094 DisplayError("Wait until your turn,\nor select Move Now", 0);
8098 case BeginningOfGame:
8099 case PlayFromGameFile:
8102 if (gameMode != EditGame) return;
8116 forwardMostMove = currentMove;
8117 ResurrectChessProgram(); /* in case first program isn't running */
8119 if (second.pr == NULL) {
8120 StartChessProgram(&second);
8121 if (second.protocolVersion == 1) {
8122 TwoMachinesEventIfReady();
8124 /* kludge: allow timeout for initial "feature" command */
8126 DisplayMessage("", "Starting second chess program");
8127 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
8131 DisplayMessage("", "");
8132 InitChessProgram(&second);
8133 SendToProgram("force\n", &second);
8134 if (startedFromSetupPosition) {
8135 SendBoard(&second, backwardMostMove);
8137 for (i = backwardMostMove; i < forwardMostMove; i++) {
8138 SendMoveToProgram(i, &second);
8141 gameMode = TwoMachinesPlay;
8145 DisplayTwoMachinesTitle();
8147 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
8153 SendToProgram(first.computerString, &first);
8154 if (first.sendName) {
8155 sprintf(buf, "name %s\n", second.tidy);
8156 SendToProgram(buf, &first);
8158 SendToProgram(second.computerString, &second);
8159 if (second.sendName) {
8160 sprintf(buf, "name %s\n", first.tidy);
8161 SendToProgram(buf, &second);
8164 if (!first.sendTime || !second.sendTime) {
8166 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8167 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8169 if (onmove->sendTime) {
8170 if (onmove->useColors) {
8171 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
8173 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
8175 if (onmove->useColors) {
8176 SendToProgram(onmove->twoMachinesColor, onmove);
8178 SendToProgram("go\n", onmove);
8179 onmove->maybeThinking = TRUE;
8180 SetMachineThinkingEnables();
8188 if (gameMode == Training) {
8189 SetTrainingModeOff();
8190 gameMode = PlayFromGameFile;
8191 DisplayMessage("", "Training mode off");
8193 gameMode = Training;
8194 animateTraining = appData.animate;
8196 /* make sure we are not already at the end of the game */
8197 if (currentMove < forwardMostMove) {
8198 SetTrainingModeOn();
8199 DisplayMessage("", "Training mode on");
8201 gameMode = PlayFromGameFile;
8202 DisplayError("Already at end of game", 0);
8211 if (!appData.icsActive) return;
8213 case IcsPlayingWhite:
8214 case IcsPlayingBlack:
8217 case BeginningOfGame:
8251 SetTrainingModeOff();
8253 case MachinePlaysWhite:
8254 case MachinePlaysBlack:
8255 case BeginningOfGame:
8256 SendToProgram("force\n", &first);
8257 SetUserThinkingEnables();
8259 case PlayFromGameFile:
8260 (void) StopLoadGameTimer();
8261 if (gameFileFP != NULL) {
8271 SendToProgram("force\n", &first);
8273 case TwoMachinesPlay:
8274 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8275 ResurrectChessProgram();
8276 SetUserThinkingEnables();
8279 ResurrectChessProgram();
8281 case IcsPlayingBlack:
8282 case IcsPlayingWhite:
8283 DisplayError("Warning: You are still playing a game", 0);
8286 DisplayError("Warning: You are still observing a game", 0);
8289 DisplayError("Warning: You are still examining a game", 0);
8300 first.offeredDraw = second.offeredDraw = 0;
8302 if (gameMode == PlayFromGameFile) {
8303 whiteTimeRemaining = timeRemaining[0][currentMove];
8304 blackTimeRemaining = timeRemaining[1][currentMove];
8308 if (gameMode == MachinePlaysWhite ||
8309 gameMode == MachinePlaysBlack ||
8310 gameMode == TwoMachinesPlay ||
8311 gameMode == EndOfGame) {
8312 i = forwardMostMove;
8313 while (i > currentMove) {
8314 SendToProgram("undo\n", &first);
8317 whiteTimeRemaining = timeRemaining[0][currentMove];
8318 blackTimeRemaining = timeRemaining[1][currentMove];
8319 DisplayBothClocks();
8320 if (whiteFlag || blackFlag) {
8321 whiteFlag = blackFlag = 0;
8326 gameMode = EditGame;
8335 if (gameMode == EditPosition) {
8341 if (gameMode != EditGame) return;
8343 gameMode = EditPosition;
8346 if (currentMove > 0)
8347 CopyBoard(boards[0], boards[currentMove]);
8349 blackPlaysFirst = !WhiteOnMove(currentMove);
8351 currentMove = forwardMostMove = backwardMostMove = 0;
8352 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8359 if (first.analysisSupport && first.analyzing) {
8360 SendToProgram("exit\n", &first);
8361 first.analyzing = FALSE;
8364 thinkOutput[0] = NULLCHAR;
8370 startedFromSetupPosition = TRUE;
8371 InitChessProgram(&first);
8372 SendToProgram("force\n", &first);
8373 if (blackPlaysFirst) {
8374 strcpy(moveList[0], "");
8375 strcpy(parseList[0], "");
8376 currentMove = forwardMostMove = backwardMostMove = 1;
8377 CopyBoard(boards[1], boards[0]);
8379 currentMove = forwardMostMove = backwardMostMove = 0;
8381 SendBoard(&first, forwardMostMove);
8383 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8384 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8385 gameMode = EditGame;
8387 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8388 ClearHighlights(); /* [AS] */
8391 /* Pause for `ms' milliseconds */
8392 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
8402 } while (SubtractTimeMarks(&m2, &m1) < ms);
8405 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
8407 SendMultiLineToICS(buf)
8410 char temp[MSG_SIZ+1], *p;
8417 strncpy(temp, buf, len);
8422 if (*p == '\n' || *p == '\r')
8429 SendToPlayer(temp, strlen(temp));
8433 SetWhiteToPlayEvent()
8435 if (gameMode == EditPosition) {
8436 blackPlaysFirst = FALSE;
8437 DisplayBothClocks(); /* works because currentMove is 0 */
8438 } else if (gameMode == IcsExamining) {
8439 SendToICS(ics_prefix);
8440 SendToICS("tomove white\n");
8445 SetBlackToPlayEvent()
8447 if (gameMode == EditPosition) {
8448 blackPlaysFirst = TRUE;
8449 currentMove = 1; /* kludge */
8450 DisplayBothClocks();
8452 } else if (gameMode == IcsExamining) {
8453 SendToICS(ics_prefix);
8454 SendToICS("tomove black\n");
8459 EditPositionMenuEvent(selection, x, y)
8460 ChessSquare selection;
8465 if (gameMode != EditPosition && gameMode != IcsExamining) return;
8467 switch (selection) {
8469 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
8470 SendToICS(ics_prefix);
8471 SendToICS("bsetup clear\n");
8472 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
8473 SendToICS(ics_prefix);
8474 SendToICS("clearboard\n");
8476 for (x = 0; x < BOARD_SIZE; x++) {
8477 for (y = 0; y < BOARD_SIZE; y++) {
8478 if (gameMode == IcsExamining) {
8479 if (boards[currentMove][y][x] != EmptySquare) {
8480 sprintf(buf, "%sx@%c%c\n", ics_prefix,
8485 boards[0][y][x] = EmptySquare;
8490 if (gameMode == EditPosition) {
8491 DrawPosition(FALSE, boards[0]);
8496 SetWhiteToPlayEvent();
8500 SetBlackToPlayEvent();
8504 if (gameMode == IcsExamining) {
8505 sprintf(buf, "%sx@%c%c\n", ics_prefix, 'a' + x, '1' + y);
8508 boards[0][y][x] = EmptySquare;
8509 DrawPosition(FALSE, boards[0]);
8514 if (gameMode == IcsExamining) {
8515 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
8516 PieceToChar(selection), 'a' + x, '1' + y);
8519 boards[0][y][x] = selection;
8520 DrawPosition(FALSE, boards[0]);
8528 DropMenuEvent(selection, x, y)
8529 ChessSquare selection;
8535 case IcsPlayingWhite:
8536 case MachinePlaysBlack:
8537 if (!WhiteOnMove(currentMove)) {
8538 DisplayMoveError("It is Black's turn");
8541 moveType = WhiteDrop;
8543 case IcsPlayingBlack:
8544 case MachinePlaysWhite:
8545 if (WhiteOnMove(currentMove)) {
8546 DisplayMoveError("It is White's turn");
8549 moveType = BlackDrop;
8552 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
8558 if (moveType == BlackDrop && selection < BlackPawn) {
8559 selection = (ChessSquare) ((int) selection
8560 + (int) BlackPawn - (int) WhitePawn);
8562 if (boards[currentMove][y][x] != EmptySquare) {
8563 DisplayMoveError("That square is occupied");
8567 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
8573 /* Accept a pending offer of any kind from opponent */
8575 if (appData.icsActive) {
8576 SendToICS(ics_prefix);
8577 SendToICS("accept\n");
8578 } else if (cmailMsgLoaded) {
8579 if (currentMove == cmailOldMove &&
8580 commentList[cmailOldMove] != NULL &&
8581 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
8582 "Black offers a draw" : "White offers a draw")) {
8584 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8585 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
8587 DisplayError("There is no pending offer on this move", 0);
8588 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8591 /* Not used for offers from chess program */
8598 /* Decline a pending offer of any kind from opponent */
8600 if (appData.icsActive) {
8601 SendToICS(ics_prefix);
8602 SendToICS("decline\n");
8603 } else if (cmailMsgLoaded) {
8604 if (currentMove == cmailOldMove &&
8605 commentList[cmailOldMove] != NULL &&
8606 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
8607 "Black offers a draw" : "White offers a draw")) {
8609 AppendComment(cmailOldMove, "Draw declined");
8610 DisplayComment(cmailOldMove - 1, "Draw declined");
8613 DisplayError("There is no pending offer on this move", 0);
8616 /* Not used for offers from chess program */
8623 /* Issue ICS rematch command */
8624 if (appData.icsActive) {
8625 SendToICS(ics_prefix);
8626 SendToICS("rematch\n");
8633 /* Call your opponent's flag (claim a win on time) */
8634 if (appData.icsActive) {
8635 SendToICS(ics_prefix);
8636 SendToICS("flag\n");
8641 case MachinePlaysWhite:
8644 GameEnds(GameIsDrawn, "Both players ran out of time",
8647 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
8649 DisplayError("Your opponent is not out of time", 0);
8652 case MachinePlaysBlack:
8655 GameEnds(GameIsDrawn, "Both players ran out of time",
8658 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
8660 DisplayError("Your opponent is not out of time", 0);
8670 /* Offer draw or accept pending draw offer from opponent */
8672 if (appData.icsActive) {
8673 /* Note: tournament rules require draw offers to be
8674 made after you make your move but before you punch
8675 your clock. Currently ICS doesn't let you do that;
8676 instead, you immediately punch your clock after making
8677 a move, but you can offer a draw at any time. */
8679 SendToICS(ics_prefix);
8680 SendToICS("draw\n");
8681 } else if (cmailMsgLoaded) {
8682 if (currentMove == cmailOldMove &&
8683 commentList[cmailOldMove] != NULL &&
8684 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
8685 "Black offers a draw" : "White offers a draw")) {
8686 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8687 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
8688 } else if (currentMove == cmailOldMove + 1) {
8689 char *offer = WhiteOnMove(cmailOldMove) ?
8690 "White offers a draw" : "Black offers a draw";
8691 AppendComment(currentMove, offer);
8692 DisplayComment(currentMove - 1, offer);
8693 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
8695 DisplayError("You must make your move before offering a draw", 0);
8696 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8698 } else if (first.offeredDraw) {
8699 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8701 if (first.sendDrawOffers) {
8702 SendToProgram("draw\n", &first);
8703 userOfferedDraw = TRUE;
8711 /* Offer Adjourn or accept pending Adjourn offer from opponent */
8713 if (appData.icsActive) {
8714 SendToICS(ics_prefix);
8715 SendToICS("adjourn\n");
8717 /* Currently GNU Chess doesn't offer or accept Adjourns */
8725 /* Offer Abort or accept pending Abort offer from opponent */
8727 if (appData.icsActive) {
8728 SendToICS(ics_prefix);
8729 SendToICS("abort\n");
8731 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
8738 /* Resign. You can do this even if it's not your turn. */
8740 if (appData.icsActive) {
8741 SendToICS(ics_prefix);
8742 SendToICS("resign\n");
8745 case MachinePlaysWhite:
8746 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8748 case MachinePlaysBlack:
8749 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8752 if (cmailMsgLoaded) {
8754 if (WhiteOnMove(cmailOldMove)) {
8755 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8757 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8759 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
8770 StopObservingEvent()
8772 /* Stop observing current games */
8773 SendToICS(ics_prefix);
8774 SendToICS("unobserve\n");
8778 StopExaminingEvent()
8780 /* Stop observing current game */
8781 SendToICS(ics_prefix);
8782 SendToICS("unexamine\n");
8786 ForwardInner(target)
8791 if (appData.debugMode)
8792 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
8793 target, currentMove, forwardMostMove);
8795 if (gameMode == EditPosition)
8798 if (gameMode == PlayFromGameFile && !pausing)
8801 if (gameMode == IcsExamining && pausing)
8802 limit = pauseExamForwardMostMove;
8804 limit = forwardMostMove;
8806 if (target > limit) target = limit;
8808 if (target > 0 && moveList[target - 1][0]) {
8809 int fromX, fromY, toX, toY;
8810 toX = moveList[target - 1][2] - 'a';
8811 toY = moveList[target - 1][3] - '1';
8812 if (moveList[target - 1][1] == '@') {
8813 if (appData.highlightLastMove) {
8814 SetHighlights(-1, -1, toX, toY);
8817 fromX = moveList[target - 1][0] - 'a';
8818 fromY = moveList[target - 1][1] - '1';
8819 if (target == currentMove + 1) {
8820 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8822 if (appData.highlightLastMove) {
8823 SetHighlights(fromX, fromY, toX, toY);
8827 if (gameMode == EditGame || gameMode == AnalyzeMode ||
8828 gameMode == Training || gameMode == PlayFromGameFile ||
8829 gameMode == AnalyzeFile) {
8830 while (currentMove < target) {
8831 SendMoveToProgram(currentMove++, &first);
8834 currentMove = target;
8837 if (gameMode == EditGame || gameMode == EndOfGame) {
8838 whiteTimeRemaining = timeRemaining[0][currentMove];
8839 blackTimeRemaining = timeRemaining[1][currentMove];
8841 DisplayBothClocks();
8842 DisplayMove(currentMove - 1);
8843 DrawPosition(FALSE, boards[currentMove]);
8844 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8845 if (commentList[currentMove] && !matchMode && gameMode != Training) {
8846 DisplayComment(currentMove - 1, commentList[currentMove]);
8854 if (gameMode == IcsExamining && !pausing) {
8855 SendToICS(ics_prefix);
8856 SendToICS("forward\n");
8858 ForwardInner(currentMove + 1);
8865 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
8866 /* to optimze, we temporarily turn off analysis mode while we feed
8867 * the remaining moves to the engine. Otherwise we get analysis output
8870 if (first.analysisSupport) {
8871 SendToProgram("exit\nforce\n", &first);
8872 first.analyzing = FALSE;
8876 if (gameMode == IcsExamining && !pausing) {
8877 SendToICS(ics_prefix);
8878 SendToICS("forward 999999\n");
8880 ForwardInner(forwardMostMove);
8883 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
8884 /* we have fed all the moves, so reactivate analysis mode */
8885 SendToProgram("analyze\n", &first);
8886 first.analyzing = TRUE;
8887 /*first.maybeThinking = TRUE;*/
8888 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
8893 BackwardInner(target)
8896 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
8898 if (appData.debugMode)
8899 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
8900 target, currentMove, forwardMostMove);
8902 if (gameMode == EditPosition) return;
8903 if (currentMove <= backwardMostMove) {
8905 DrawPosition(full_redraw, boards[currentMove]);
8908 if (gameMode == PlayFromGameFile && !pausing)
8911 if (moveList[target][0]) {
8912 int fromX, fromY, toX, toY;
8913 toX = moveList[target][2] - 'a';
8914 toY = moveList[target][3] - '1';
8915 if (moveList[target][1] == '@') {
8916 if (appData.highlightLastMove) {
8917 SetHighlights(-1, -1, toX, toY);
8920 fromX = moveList[target][0] - 'a';
8921 fromY = moveList[target][1] - '1';
8922 if (target == currentMove - 1) {
8923 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
8925 if (appData.highlightLastMove) {
8926 SetHighlights(fromX, fromY, toX, toY);
8930 if (gameMode == EditGame || gameMode==AnalyzeMode ||
8931 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8932 while (currentMove > target) {
8933 SendToProgram("undo\n", &first);
8937 currentMove = target;
8940 if (gameMode == EditGame || gameMode == EndOfGame) {
8941 whiteTimeRemaining = timeRemaining[0][currentMove];
8942 blackTimeRemaining = timeRemaining[1][currentMove];
8944 DisplayBothClocks();
8945 DisplayMove(currentMove - 1);
8946 DrawPosition(full_redraw, boards[currentMove]);
8947 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8948 if (commentList[currentMove] != NULL) {
8949 DisplayComment(currentMove - 1, commentList[currentMove]);
8956 if (gameMode == IcsExamining && !pausing) {
8957 SendToICS(ics_prefix);
8958 SendToICS("backward\n");
8960 BackwardInner(currentMove - 1);
8967 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
8968 /* to optimze, we temporarily turn off analysis mode while we undo
8969 * all the moves. Otherwise we get analysis output after each undo.
8971 if (first.analysisSupport) {
8972 SendToProgram("exit\nforce\n", &first);
8973 first.analyzing = FALSE;
8977 if (gameMode == IcsExamining && !pausing) {
8978 SendToICS(ics_prefix);
8979 SendToICS("backward 999999\n");
8981 BackwardInner(backwardMostMove);
8984 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
8985 /* we have fed all the moves, so reactivate analysis mode */
8986 SendToProgram("analyze\n", &first);
8987 first.analyzing = TRUE;
8988 /*first.maybeThinking = TRUE;*/
8989 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
8996 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
8997 if (to >= forwardMostMove) to = forwardMostMove;
8998 if (to <= backwardMostMove) to = backwardMostMove;
8999 if (to < currentMove) {
9009 if (gameMode != IcsExamining) {
9010 DisplayError("You are not examining a game", 0);
9014 DisplayError("You can't revert while pausing", 0);
9017 SendToICS(ics_prefix);
9018 SendToICS("revert\n");
9025 case MachinePlaysWhite:
9026 case MachinePlaysBlack:
9027 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
9028 DisplayError("Wait until your turn,\nor select Move Now", 0);
9031 if (forwardMostMove < 2) return;
9032 currentMove = forwardMostMove = forwardMostMove - 2;
9033 whiteTimeRemaining = timeRemaining[0][currentMove];
9034 blackTimeRemaining = timeRemaining[1][currentMove];
9035 DisplayBothClocks();
9036 DisplayMove(currentMove - 1);
9037 ClearHighlights();/*!! could figure this out*/
9038 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
9039 SendToProgram("remove\n", &first);
9040 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
9043 case BeginningOfGame:
9047 case IcsPlayingWhite:
9048 case IcsPlayingBlack:
9049 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
9050 SendToICS(ics_prefix);
9051 SendToICS("takeback 2\n");
9053 SendToICS(ics_prefix);
9054 SendToICS("takeback 1\n");
9063 ChessProgramState *cps;
9066 case MachinePlaysWhite:
9067 if (!WhiteOnMove(forwardMostMove)) {
9068 DisplayError("It is your turn", 0);
9073 case MachinePlaysBlack:
9074 if (WhiteOnMove(forwardMostMove)) {
9075 DisplayError("It is your turn", 0);
9080 case TwoMachinesPlay:
9081 if (WhiteOnMove(forwardMostMove) ==
9082 (first.twoMachinesColor[0] == 'w')) {
9088 case BeginningOfGame:
9092 SendToProgram("?\n", cps);
9099 if (gameMode != EditGame) return;
9106 if (forwardMostMove > currentMove) {
9107 if (gameInfo.resultDetails != NULL) {
9108 free(gameInfo.resultDetails);
9109 gameInfo.resultDetails = NULL;
9110 gameInfo.result = GameUnfinished;
9112 forwardMostMove = currentMove;
9113 HistorySet(parseList, backwardMostMove, forwardMostMove,
9121 if (appData.noChessProgram) return;
9123 case MachinePlaysWhite:
9124 if (WhiteOnMove(forwardMostMove)) {
9125 DisplayError("Wait until your turn", 0);
9129 case BeginningOfGame:
9130 case MachinePlaysBlack:
9131 if (!WhiteOnMove(forwardMostMove)) {
9132 DisplayError("Wait until your turn", 0);
9137 DisplayError("No hint available", 0);
9140 SendToProgram("hint\n", &first);
9141 hintRequested = TRUE;
9147 if (appData.noChessProgram) return;
9149 case MachinePlaysWhite:
9150 if (WhiteOnMove(forwardMostMove)) {
9151 DisplayError("Wait until your turn", 0);
9155 case BeginningOfGame:
9156 case MachinePlaysBlack:
9157 if (!WhiteOnMove(forwardMostMove)) {
9158 DisplayError("Wait until your turn", 0);
9165 case TwoMachinesPlay:
9170 SendToProgram("bk\n", &first);
9171 bookOutput[0] = NULLCHAR;
9172 bookRequested = TRUE;
9178 char *tags = PGNTags(&gameInfo);
9179 TagsPopUp(tags, CmailMsg());
9183 /* end button procedures */
9186 PrintPosition(fp, move)
9192 for (i = BOARD_SIZE - 1; i >= 0; i--) {
9193 for (j = 0; j < BOARD_SIZE; j++) {
9194 char c = PieceToChar(boards[move][i][j]);
9195 fputc(c == 'x' ? '.' : c, fp);
9196 fputc(j == BOARD_SIZE - 1 ? '\n' : ' ', fp);
9199 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
9200 fprintf(fp, "white to play\n");
9202 fprintf(fp, "black to play\n");
9209 if (gameInfo.white != NULL) {
9210 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
9216 /* Find last component of program's own name, using some heuristics */
9218 TidyProgramName(prog, host, buf)
9219 char *prog, *host, buf[MSG_SIZ];
9222 int local = (strcmp(host, "localhost") == 0);
9223 while (!local && (p = strchr(prog, ';')) != NULL) {
9225 while (*p == ' ') p++;
9228 if (*prog == '"' || *prog == '\'') {
9229 q = strchr(prog + 1, *prog);
9231 q = strchr(prog, ' ');
9233 if (q == NULL) q = prog + strlen(prog);
9235 while (p >= prog && *p != '/' && *p != '\\') p--;
9237 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
9238 memcpy(buf, p, q - p);
9239 buf[q - p] = NULLCHAR;
9247 TimeControlTagValue()
9250 if (!appData.clockMode) {
9252 } else if (movesPerSession > 0) {
9253 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
9254 } else if (timeIncrement == 0) {
9255 sprintf(buf, "%ld", timeControl/1000);
9257 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
9259 return StrSave(buf);
9265 /* This routine is used only for certain modes */
9266 VariantClass v = gameInfo.variant;
9267 ClearGameInfo(&gameInfo);
9268 gameInfo.variant = v;
9271 case MachinePlaysWhite:
9272 gameInfo.event = StrSave( appData.pgnEventHeader );
9273 gameInfo.site = StrSave(HostName());
9274 gameInfo.date = PGNDate();
9275 gameInfo.round = StrSave("-");
9276 gameInfo.white = StrSave(first.tidy);
9277 gameInfo.black = StrSave(UserName());
9278 gameInfo.timeControl = TimeControlTagValue();
9281 case MachinePlaysBlack:
9282 gameInfo.event = StrSave( appData.pgnEventHeader );
9283 gameInfo.site = StrSave(HostName());
9284 gameInfo.date = PGNDate();
9285 gameInfo.round = StrSave("-");
9286 gameInfo.white = StrSave(UserName());
9287 gameInfo.black = StrSave(first.tidy);
9288 gameInfo.timeControl = TimeControlTagValue();
9291 case TwoMachinesPlay:
9292 gameInfo.event = StrSave( appData.pgnEventHeader );
9293 gameInfo.site = StrSave(HostName());
9294 gameInfo.date = PGNDate();
9295 if (matchGame > 0) {
9297 sprintf(buf, "%d", matchGame);
9298 gameInfo.round = StrSave(buf);
9300 gameInfo.round = StrSave("-");
9302 if (first.twoMachinesColor[0] == 'w') {
9303 gameInfo.white = StrSave(first.tidy);
9304 gameInfo.black = StrSave(second.tidy);
9306 gameInfo.white = StrSave(second.tidy);
9307 gameInfo.black = StrSave(first.tidy);
9309 gameInfo.timeControl = TimeControlTagValue();
9313 gameInfo.event = StrSave("Edited game");
9314 gameInfo.site = StrSave(HostName());
9315 gameInfo.date = PGNDate();
9316 gameInfo.round = StrSave("-");
9317 gameInfo.white = StrSave("-");
9318 gameInfo.black = StrSave("-");
9322 gameInfo.event = StrSave("Edited position");
9323 gameInfo.site = StrSave(HostName());
9324 gameInfo.date = PGNDate();
9325 gameInfo.round = StrSave("-");
9326 gameInfo.white = StrSave("-");
9327 gameInfo.black = StrSave("-");
9330 case IcsPlayingWhite:
9331 case IcsPlayingBlack:
9336 case PlayFromGameFile:
9337 gameInfo.event = StrSave("Game from non-PGN file");
9338 gameInfo.site = StrSave(HostName());
9339 gameInfo.date = PGNDate();
9340 gameInfo.round = StrSave("-");
9341 gameInfo.white = StrSave("?");
9342 gameInfo.black = StrSave("?");
9351 ReplaceComment(index, text)
9357 while (*text == '\n') text++;
9359 while (len > 0 && text[len - 1] == '\n') len--;
9361 if (commentList[index] != NULL)
9362 free(commentList[index]);
9365 commentList[index] = NULL;
9368 commentList[index] = (char *) malloc(len + 2);
9369 strncpy(commentList[index], text, len);
9370 commentList[index][len] = '\n';
9371 commentList[index][len + 1] = NULLCHAR;
9384 if (ch == '\r') continue;
9386 } while (ch != '\0');
9390 AppendComment(index, text)
9397 GetInfoFromComment( index, text );
9400 while (*text == '\n') text++;
9402 while (len > 0 && text[len - 1] == '\n') len--;
9404 if (len == 0) return;
9406 if (commentList[index] != NULL) {
9407 old = commentList[index];
9408 oldlen = strlen(old);
9409 commentList[index] = (char *) malloc(oldlen + len + 2);
9410 strcpy(commentList[index], old);
9412 strncpy(&commentList[index][oldlen], text, len);
9413 commentList[index][oldlen + len] = '\n';
9414 commentList[index][oldlen + len + 1] = NULLCHAR;
9416 commentList[index] = (char *) malloc(len + 2);
9417 strncpy(commentList[index], text, len);
9418 commentList[index][len] = '\n';
9419 commentList[index][len + 1] = NULLCHAR;
9423 static char * FindStr( char * text, char * sub_text )
9425 char * result = strstr( text, sub_text );
9427 if( result != NULL ) {
9428 result += strlen( sub_text );
9434 /* [AS] Try to extract PV info from PGN comment */
9435 void GetInfoFromComment( int index, char * text )
9437 if( text != NULL && index > 0 ) {
9441 char * s_eval = FindStr( text, "[%eval " );
9442 char * s_emt = FindStr( text, "[%emt " );
9444 if( s_eval != NULL || s_emt != NULL ) {
9448 if( s_eval != NULL ) {
9449 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
9453 if( delim != ']' ) {
9458 if( s_emt != NULL ) {
9462 /* We expect something like: [+|-]nnn.nn/dd */
9463 char * sep = strchr( text, '/' );
9466 if( sep == NULL || sep < (text+4) ) {
9470 if( sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
9474 if( score_lo < 0 || score_lo >= 100 ) {
9478 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
9489 pvInfoList[index-1].depth = depth;
9490 pvInfoList[index-1].score = score;
9491 pvInfoList[index-1].time = time;
9496 SendToProgram(message, cps)
9498 ChessProgramState *cps;
9500 int count, outCount, error;
9503 if (cps->pr == NULL) return;
9506 if (appData.debugMode) {
9509 fprintf(debugFP, "%ld >%-6s: %s",
9510 SubtractTimeMarks(&now, &programStartTime),
9511 cps->which, message);
9514 count = strlen(message);
9515 outCount = OutputToProcess(cps->pr, message, count, &error);
9516 if (outCount < count && !exiting) {
9517 sprintf(buf, "Error writing to %s chess program", cps->which);
9518 DisplayFatalError(buf, error, 1);
9523 ReceiveFromProgram(isr, closure, message, count, error)
9532 ChessProgramState *cps = (ChessProgramState *)closure;
9534 if (isr != cps->isr) return; /* Killed intentionally */
9538 "Error: %s chess program (%s) exited unexpectedly",
9539 cps->which, cps->program);
9540 RemoveInputSource(cps->isr);
9541 DisplayFatalError(buf, 0, 1);
9544 "Error reading from %s chess program (%s)",
9545 cps->which, cps->program);
9546 RemoveInputSource(cps->isr);
9548 /* [AS] Program is misbehaving badly... kill it */
9550 DestroyChildProcess( cps->pr, 9 );
9554 DisplayFatalError(buf, error, 1);
9556 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9560 if ((end_str = strchr(message, '\r')) != NULL)
9561 *end_str = NULLCHAR;
9562 if ((end_str = strchr(message, '\n')) != NULL)
9563 *end_str = NULLCHAR;
9565 if (appData.debugMode) {
9568 fprintf(debugFP, "%ld <%-6s: %s\n",
9569 SubtractTimeMarks(&now, &programStartTime),
9570 cps->which, message);
9572 HandleMachineMove(message, cps);
9577 SendTimeControl(cps, mps, tc, inc, sd, st)
9578 ChessProgramState *cps;
9579 int mps, inc, sd, st;
9583 int seconds = (tc / 1000) % 60;
9585 if( timeControl_2 > 0 ) {
9586 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
9592 /* Set exact time per move, normally using st command */
9593 if (cps->stKludge) {
9594 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
9597 sprintf(buf, "level 1 %d\n", st/60);
9599 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
9602 sprintf(buf, "st %d\n", st);
9605 /* Set conventional or incremental time control, using level command */
9607 /* Note old gnuchess bug -- minutes:seconds used to not work.
9608 Fixed in later versions, but still avoid :seconds
9609 when seconds is 0. */
9610 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
9612 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
9616 SendToProgram(buf, cps);
9618 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
9619 /* Orthogonally, limit search to given depth */
9621 if (cps->sdKludge) {
9622 sprintf(buf, "depth\n%d\n", sd);
9624 sprintf(buf, "sd %d\n", sd);
9626 SendToProgram(buf, cps);
9631 SendTimeRemaining(cps, machineWhite)
9632 ChessProgramState *cps;
9633 int /*boolean*/ machineWhite;
9635 char message[MSG_SIZ];
9638 /* Note: this routine must be called when the clocks are stopped
9639 or when they have *just* been set or switched; otherwise
9640 it will be off by the time since the current tick started.
9643 time = whiteTimeRemaining / 10;
9644 otime = blackTimeRemaining / 10;
9646 time = blackTimeRemaining / 10;
9647 otime = whiteTimeRemaining / 10;
9649 if (time <= 0) time = 1;
9650 if (otime <= 0) otime = 1;
9652 sprintf(message, "time %ld\n", time);
9653 SendToProgram(message, cps);
9655 sprintf(message, "otim %ld\n", otime);
9656 SendToProgram(message, cps);
9660 BoolFeature(p, name, loc, cps)
9664 ChessProgramState *cps;
9667 int len = strlen(name);
9669 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
9671 sscanf(*p, "%d", &val);
9673 while (**p && **p != ' ') (*p)++;
9674 sprintf(buf, "accepted %s\n", name);
9675 SendToProgram(buf, cps);
9682 IntFeature(p, name, loc, cps)
9686 ChessProgramState *cps;
9689 int len = strlen(name);
9690 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
9692 sscanf(*p, "%d", loc);
9693 while (**p && **p != ' ') (*p)++;
9694 sprintf(buf, "accepted %s\n", name);
9695 SendToProgram(buf, cps);
9702 StringFeature(p, name, loc, cps)
9706 ChessProgramState *cps;
9709 int len = strlen(name);
9710 if (strncmp((*p), name, len) == 0
9711 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
9713 sscanf(*p, "%[^\"]", loc);
9714 while (**p && **p != '\"') (*p)++;
9715 if (**p == '\"') (*p)++;
9716 sprintf(buf, "accepted %s\n", name);
9717 SendToProgram(buf, cps);
9724 FeatureDone(cps, val)
9725 ChessProgramState* cps;
9728 DelayedEventCallback cb = GetDelayedEvent();
9729 if ((cb == InitBackEnd3 && cps == &first) ||
9730 (cb == TwoMachinesEventIfReady && cps == &second)) {
9731 CancelDelayedEvent();
9732 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
9734 cps->initDone = val;
9737 /* Parse feature command from engine */
9739 ParseFeatures(args, cps)
9741 ChessProgramState *cps;
9749 while (*p == ' ') p++;
9750 if (*p == NULLCHAR) return;
9752 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
9753 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
9754 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
9755 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
9756 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
9757 if (BoolFeature(&p, "reuse", &val, cps)) {
9758 /* Engine can disable reuse, but can't enable it if user said no */
9759 if (!val) cps->reuse = FALSE;
9762 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
9763 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
9764 if (gameMode == TwoMachinesPlay) {
9765 DisplayTwoMachinesTitle();
9771 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
9772 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
9773 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
9774 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
9775 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
9776 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
9777 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
9778 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
9779 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
9780 if (IntFeature(&p, "done", &val, cps)) {
9781 FeatureDone(cps, val);
9784 /* Added by Tord: */
9785 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
9786 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
9787 /* End of additions by Tord */
9789 /* unknown feature: complain and skip */
9791 while (*q && *q != '=') q++;
9792 sprintf(buf, "rejected %.*s\n", q-p, p);
9793 SendToProgram(buf, cps);
9799 while (*p && *p != '\"') p++;
9800 if (*p == '\"') p++;
9802 while (*p && *p != ' ') p++;
9810 PeriodicUpdatesEvent(newState)
9813 if (newState == appData.periodicUpdates)
9816 appData.periodicUpdates=newState;
9818 /* Display type changes, so update it now */
9821 /* Get the ball rolling again... */
9823 AnalysisPeriodicEvent(1);
9824 StartAnalysisClock();
9829 PonderNextMoveEvent(newState)
9832 if (newState == appData.ponderNextMove) return;
9833 if (gameMode == EditPosition) EditPositionDone();
9835 SendToProgram("hard\n", &first);
9836 if (gameMode == TwoMachinesPlay) {
9837 SendToProgram("hard\n", &second);
9840 SendToProgram("easy\n", &first);
9841 thinkOutput[0] = NULLCHAR;
9842 if (gameMode == TwoMachinesPlay) {
9843 SendToProgram("easy\n", &second);
9846 appData.ponderNextMove = newState;
9850 ShowThinkingEvent(newState)
9853 if (newState == appData.showThinking) return;
9854 if (gameMode == EditPosition) EditPositionDone();
9856 SendToProgram("post\n", &first);
9857 if (gameMode == TwoMachinesPlay) {
9858 SendToProgram("post\n", &second);
9861 SendToProgram("nopost\n", &first);
9862 thinkOutput[0] = NULLCHAR;
9863 if (gameMode == TwoMachinesPlay) {
9864 SendToProgram("nopost\n", &second);
9867 appData.showThinking = newState;
9871 AskQuestionEvent(title, question, replyPrefix, which)
9872 char *title; char *question; char *replyPrefix; char *which;
9874 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
9875 if (pr == NoProc) return;
9876 AskQuestion(title, question, replyPrefix, pr);
9880 DisplayMove(moveNumber)
9883 char message[MSG_SIZ];
9885 char cpThinkOutput[MSG_SIZ];
9887 if (moveNumber == forwardMostMove - 1 ||
9888 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
9890 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
9892 if (strchr(cpThinkOutput, '\n')) {
9893 *strchr(cpThinkOutput, '\n') = NULLCHAR;
9896 *cpThinkOutput = NULLCHAR;
9899 /* [AS] Hide thinking from human user */
9900 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
9901 *cpThinkOutput = NULLCHAR;
9902 if( thinkOutput[0] != NULLCHAR ) {
9905 for( i=0; i<=hiddenThinkOutputState; i++ ) {
9906 cpThinkOutput[i] = '.';
9908 cpThinkOutput[i] = NULLCHAR;
9909 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
9913 if (moveNumber == forwardMostMove - 1 &&
9914 gameInfo.resultDetails != NULL) {
9915 if (gameInfo.resultDetails[0] == NULLCHAR) {
9916 sprintf(res, " %s", PGNResult(gameInfo.result));
9918 sprintf(res, " {%s} %s",
9919 gameInfo.resultDetails, PGNResult(gameInfo.result));
9925 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
9926 DisplayMessage(res, cpThinkOutput);
9928 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
9929 WhiteOnMove(moveNumber) ? " " : ".. ",
9930 parseList[moveNumber], res);
9931 DisplayMessage(message, cpThinkOutput);
9936 DisplayAnalysisText(text)
9941 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
9942 sprintf(buf, "Analysis (%s)", first.tidy);
9943 AnalysisPopUp(buf, text);
9951 while (*str && isspace(*str)) ++str;
9952 while (*str && !isspace(*str)) ++str;
9953 if (!*str) return 1;
9954 while (*str && isspace(*str)) ++str;
9955 if (!*str) return 1;
9963 char lst[MSG_SIZ / 2];
9965 static char *xtra[] = { "", " (--)", " (++)" };
9968 if (programStats.time == 0) {
9969 programStats.time = 1;
9972 if (programStats.got_only_move) {
9973 safeStrCpy(buf, programStats.movelist, sizeof(buf));
9975 safeStrCpy( lst, programStats.movelist, sizeof(lst));
9977 nps = (((double)programStats.nodes) /
9978 (((double)programStats.time)/100.0));
9980 cs = programStats.time % 100;
9981 s = programStats.time / 100;
9987 if (programStats.moves_left > 0 && appData.periodicUpdates) {
9988 if (programStats.move_name[0] != NULLCHAR) {
9989 sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",
9991 programStats.nr_moves-programStats.moves_left,
9992 programStats.nr_moves, programStats.move_name,
9993 ((float)programStats.score)/100.0, lst,
9995 xtra[programStats.got_fail] : "",
9996 programStats.nodes, (int)nps, h, m, s, cs);
9998 sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",
10000 programStats.nr_moves-programStats.moves_left,
10001 programStats.nr_moves, ((float)programStats.score)/100.0,
10003 only_one_move(lst)?
10004 xtra[programStats.got_fail] : "",
10005 programStats.nodes, (int)nps, h, m, s, cs);
10008 sprintf(buf, "depth=%d %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",
10009 programStats.depth,
10010 ((float)programStats.score)/100.0,
10012 only_one_move(lst)?
10013 xtra[programStats.got_fail] : "",
10014 programStats.nodes, (int)nps, h, m, s, cs);
10017 DisplayAnalysisText(buf);
10021 DisplayComment(moveNumber, text)
10025 char title[MSG_SIZ];
10027 if( appData.autoDisplayComment ) {
10028 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
10029 strcpy(title, "Comment");
10031 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
10032 WhiteOnMove(moveNumber) ? " " : ".. ",
10033 parseList[moveNumber]);
10036 CommentPopUp(title, text);
10040 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
10041 * might be busy thinking or pondering. It can be omitted if your
10042 * gnuchess is configured to stop thinking immediately on any user
10043 * input. However, that gnuchess feature depends on the FIONREAD
10044 * ioctl, which does not work properly on some flavors of Unix.
10048 ChessProgramState *cps;
10051 if (!cps->useSigint) return;
10052 if (appData.noChessProgram || (cps->pr == NoProc)) return;
10053 switch (gameMode) {
10054 case MachinePlaysWhite:
10055 case MachinePlaysBlack:
10056 case TwoMachinesPlay:
10057 case IcsPlayingWhite:
10058 case IcsPlayingBlack:
10061 /* Skip if we know it isn't thinking */
10062 if (!cps->maybeThinking) return;
10063 if (appData.debugMode)
10064 fprintf(debugFP, "Interrupting %s\n", cps->which);
10065 InterruptChildProcess(cps->pr);
10066 cps->maybeThinking = FALSE;
10071 #endif /*ATTENTION*/
10077 if (whiteTimeRemaining <= 0) {
10080 if (appData.icsActive) {
10081 if (appData.autoCallFlag &&
10082 gameMode == IcsPlayingBlack && !blackFlag) {
10083 SendToICS(ics_prefix);
10084 SendToICS("flag\n");
10088 DisplayTitle("Both flags fell");
10090 DisplayTitle("White's flag fell");
10091 if (appData.autoCallFlag) {
10092 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
10099 if (blackTimeRemaining <= 0) {
10102 if (appData.icsActive) {
10103 if (appData.autoCallFlag &&
10104 gameMode == IcsPlayingWhite && !whiteFlag) {
10105 SendToICS(ics_prefix);
10106 SendToICS("flag\n");
10110 DisplayTitle("Both flags fell");
10112 DisplayTitle("Black's flag fell");
10113 if (appData.autoCallFlag) {
10114 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
10127 if (!appData.clockMode || appData.icsActive ||
10128 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
10130 if (timeIncrement >= 0) {
10131 if (WhiteOnMove(forwardMostMove)) {
10132 blackTimeRemaining += timeIncrement;
10134 whiteTimeRemaining += timeIncrement;
10138 * add time to clocks when time control is achieved
10140 if (movesPerSession) {
10141 switch ((forwardMostMove + 1) % (movesPerSession * 2)) {
10143 /* White made time control */
10144 whiteTimeRemaining += GetTimeControlForWhite();
10147 /* Black made time control */
10148 blackTimeRemaining += GetTimeControlForBlack();
10157 DisplayBothClocks()
10159 int wom = gameMode == EditPosition ?
10160 !blackPlaysFirst : WhiteOnMove(currentMove);
10161 DisplayWhiteClock(whiteTimeRemaining, wom);
10162 DisplayBlackClock(blackTimeRemaining, !wom);
10166 /* Timekeeping seems to be a portability nightmare. I think everyone
10167 has ftime(), but I'm really not sure, so I'm including some ifdefs
10168 to use other calls if you don't. Clocks will be less accurate if
10169 you have neither ftime nor gettimeofday.
10172 /* Get the current time as a TimeMark */
10177 #if HAVE_GETTIMEOFDAY
10179 struct timeval timeVal;
10180 struct timezone timeZone;
10182 gettimeofday(&timeVal, &timeZone);
10183 tm->sec = (long) timeVal.tv_sec;
10184 tm->ms = (int) (timeVal.tv_usec / 1000L);
10186 #else /*!HAVE_GETTIMEOFDAY*/
10189 #include <sys/timeb.h>
10190 struct timeb timeB;
10193 tm->sec = (long) timeB.time;
10194 tm->ms = (int) timeB.millitm;
10196 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
10197 tm->sec = (long) time(NULL);
10203 /* Return the difference in milliseconds between two
10204 time marks. We assume the difference will fit in a long!
10207 SubtractTimeMarks(tm2, tm1)
10208 TimeMark *tm2, *tm1;
10210 return 1000L*(tm2->sec - tm1->sec) +
10211 (long) (tm2->ms - tm1->ms);
10216 * Code to manage the game clocks.
10218 * In tournament play, black starts the clock and then white makes a move.
10219 * We give the human user a slight advantage if he is playing white---the
10220 * clocks don't run until he makes his first move, so it takes zero time.
10221 * Also, we don't account for network lag, so we could get out of sync
10222 * with GNU Chess's clock -- but then, referees are always right.
10225 static TimeMark tickStartTM;
10226 static long intendedTickLength;
10229 NextTickLength(timeRemaining)
10230 long timeRemaining;
10232 long nominalTickLength, nextTickLength;
10234 if (timeRemaining > 0L && timeRemaining <= 10000L)
10235 nominalTickLength = 100L;
10237 nominalTickLength = 1000L;
10238 nextTickLength = timeRemaining % nominalTickLength;
10239 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
10241 return nextTickLength;
10244 /* Stop clocks and reset to a fresh time control */
10248 (void) StopClockTimer();
10249 if (appData.icsActive) {
10250 whiteTimeRemaining = blackTimeRemaining = 0;
10252 whiteTimeRemaining = GetTimeControlForWhite();
10253 blackTimeRemaining = GetTimeControlForBlack();
10255 if (whiteFlag || blackFlag) {
10257 whiteFlag = blackFlag = FALSE;
10259 DisplayBothClocks();
10262 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
10264 /* Decrement running clock by amount of time that has passed */
10268 long timeRemaining;
10269 long lastTickLength, fudge;
10272 if (!appData.clockMode) return;
10273 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
10277 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
10279 /* Fudge if we woke up a little too soon */
10280 fudge = intendedTickLength - lastTickLength;
10281 if (fudge < 0 || fudge > FUDGE) fudge = 0;
10283 if (WhiteOnMove(forwardMostMove)) {
10284 timeRemaining = whiteTimeRemaining -= lastTickLength;
10285 DisplayWhiteClock(whiteTimeRemaining - fudge,
10286 WhiteOnMove(currentMove));
10288 timeRemaining = blackTimeRemaining -= lastTickLength;
10289 DisplayBlackClock(blackTimeRemaining - fudge,
10290 !WhiteOnMove(currentMove));
10293 if (CheckFlags()) return;
10296 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
10297 StartClockTimer(intendedTickLength);
10299 /* if the time remaining has fallen below the alarm threshold, sound the
10300 * alarm. if the alarm has sounded and (due to a takeback or time control
10301 * with increment) the time remaining has increased to a level above the
10302 * threshold, reset the alarm so it can sound again.
10305 if (appData.icsActive && appData.icsAlarm) {
10307 /* make sure we are dealing with the user's clock */
10308 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
10309 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
10312 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
10313 alarmSounded = FALSE;
10314 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
10316 alarmSounded = TRUE;
10322 /* A player has just moved, so stop the previously running
10323 clock and (if in clock mode) start the other one.
10324 We redisplay both clocks in case we're in ICS mode, because
10325 ICS gives us an update to both clocks after every move.
10326 Note that this routine is called *after* forwardMostMove
10327 is updated, so the last fractional tick must be subtracted
10328 from the color that is *not* on move now.
10333 long lastTickLength;
10335 int flagged = FALSE;
10339 if (StopClockTimer() && appData.clockMode) {
10340 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
10341 if (WhiteOnMove(forwardMostMove)) {
10342 blackTimeRemaining -= lastTickLength;
10344 whiteTimeRemaining -= lastTickLength;
10346 flagged = CheckFlags();
10348 CheckTimeControl();
10350 if (flagged || !appData.clockMode) return;
10352 switch (gameMode) {
10353 case MachinePlaysBlack:
10354 case MachinePlaysWhite:
10355 case BeginningOfGame:
10356 if (pausing) return;
10360 case PlayFromGameFile:
10369 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
10370 whiteTimeRemaining : blackTimeRemaining);
10371 StartClockTimer(intendedTickLength);
10375 /* Stop both clocks */
10379 long lastTickLength;
10382 if (!StopClockTimer()) return;
10383 if (!appData.clockMode) return;
10387 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
10388 if (WhiteOnMove(forwardMostMove)) {
10389 whiteTimeRemaining -= lastTickLength;
10390 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
10392 blackTimeRemaining -= lastTickLength;
10393 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
10398 /* Start clock of player on move. Time may have been reset, so
10399 if clock is already running, stop and restart it. */
10403 (void) StopClockTimer(); /* in case it was running already */
10404 DisplayBothClocks();
10405 if (CheckFlags()) return;
10407 if (!appData.clockMode) return;
10408 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
10410 GetTimeMark(&tickStartTM);
10411 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
10412 whiteTimeRemaining : blackTimeRemaining);
10413 StartClockTimer(intendedTickLength);
10420 long second, minute, hour, day;
10422 static char buf[32];
10424 if (ms > 0 && ms <= 9900) {
10425 /* convert milliseconds to tenths, rounding up */
10426 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
10428 sprintf(buf, " %03.1f ", tenths/10.0);
10432 /* convert milliseconds to seconds, rounding up */
10433 /* use floating point to avoid strangeness of integer division
10434 with negative dividends on many machines */
10435 second = (long) floor(((double) (ms + 999L)) / 1000.0);
10442 day = second / (60 * 60 * 24);
10443 second = second % (60 * 60 * 24);
10444 hour = second / (60 * 60);
10445 second = second % (60 * 60);
10446 minute = second / 60;
10447 second = second % 60;
10450 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
10451 sign, day, hour, minute, second);
10453 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
10455 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
10462 * This is necessary because some C libraries aren't ANSI C compliant yet.
10465 StrStr(string, match)
10466 char *string, *match;
10470 length = strlen(match);
10472 for (i = strlen(string) - length; i >= 0; i--, string++)
10473 if (!strncmp(match, string, length))
10480 StrCaseStr(string, match)
10481 char *string, *match;
10485 length = strlen(match);
10487 for (i = strlen(string) - length; i >= 0; i--, string++) {
10488 for (j = 0; j < length; j++) {
10489 if (ToLower(match[j]) != ToLower(string[j]))
10492 if (j == length) return string;
10506 c1 = ToLower(*s1++);
10507 c2 = ToLower(*s2++);
10508 if (c1 > c2) return 1;
10509 if (c1 < c2) return -1;
10510 if (c1 == NULLCHAR) return 0;
10519 return isupper(c) ? tolower(c) : c;
10527 return islower(c) ? toupper(c) : c;
10529 #endif /* !_amigados */
10537 if ((ret = (char *) malloc(strlen(s) + 1))) {
10544 StrSavePtr(s, savePtr)
10545 char *s, **savePtr;
10550 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
10551 strcpy(*savePtr, s);
10563 clock = time((time_t *)NULL);
10564 tm = localtime(&clock);
10565 sprintf(buf, "%04d.%02d.%02d",
10566 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
10567 return StrSave(buf);
10572 PositionToFEN(move, useFEN960)
10576 int i, j, fromX, fromY, toX, toY;
10582 whiteToPlay = (gameMode == EditPosition) ?
10583 !blackPlaysFirst : (move % 2 == 0);
10586 /* Piece placement data */
10587 for (i = BOARD_SIZE - 1; i >= 0; i--) {
10589 for (j = 0; j < BOARD_SIZE; j++) {
10590 if (boards[move][i][j] == EmptySquare) {
10593 if (emptycount > 0) {
10594 *p++ = '0' + emptycount;
10597 *p++ = PieceToChar(boards[move][i][j]);
10600 if (emptycount > 0) {
10601 *p++ = '0' + emptycount;
10609 *p++ = whiteToPlay ? 'w' : 'b';
10612 /* HACK: we don't keep track of castling availability, so fake it! */
10614 /* PUSH Fabien & Tord */
10616 /* Declare all potential FRC castling rights (conservative) */
10617 /* outermost rook on each side of the king */
10619 if( gameInfo.variant == VariantFischeRandom ) {
10624 /* White castling rights */
10626 for (fk = 1; fk < 7; fk++) {
10628 if (boards[move][0][fk] == WhiteKing) {
10630 for (fr = 7; fr > fk; fr--) { /* H side */
10631 if (boards[move][0][fr] == WhiteRook) {
10632 *p++ = useFEN960 ? 'A' + fr : 'K';
10637 for (fr = 0; fr < fk; fr++) { /* A side */
10638 if (boards[move][0][fr] == WhiteRook) {
10639 *p++ = useFEN960 ? 'A' + fr : 'Q';
10646 /* Black castling rights */
10648 for (fk = 1; fk < 7; fk++) {
10650 if (boards[move][7][fk] == BlackKing) {
10652 for (fr = 7; fr > fk; fr--) { /* H side */
10653 if (boards[move][7][fr] == BlackRook) {
10654 *p++ = useFEN960 ? 'a' + fr : 'k';
10659 for (fr = 0; fr < fk; fr++) { /* A side */
10660 if (boards[move][7][fr] == BlackRook) {
10661 *p++ = useFEN960 ? 'a' + fr : 'q';
10668 if (q == p) *p++ = '-'; /* No castling rights */
10673 if (boards[move][0][4] == WhiteKing) {
10674 if (boards[move][0][7] == WhiteRook) *p++ = 'K';
10675 if (boards[move][0][0] == WhiteRook) *p++ = 'Q';
10677 if (boards[move][7][4] == BlackKing) {
10678 if (boards[move][7][7] == BlackRook) *p++ = 'k';
10679 if (boards[move][7][0] == BlackRook) *p++ = 'q';
10681 if (q == p) *p++ = '-';
10685 /* POP Fabien & Tord */
10687 /* En passant target square */
10688 if (move > backwardMostMove) {
10689 fromX = moveList[move - 1][0] - 'a';
10690 fromY = moveList[move - 1][1] - '1';
10691 toX = moveList[move - 1][2] - 'a';
10692 toY = moveList[move - 1][3] - '1';
10693 if (fromY == (whiteToPlay ? 6 : 1) &&
10694 toY == (whiteToPlay ? 4 : 3) &&
10695 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
10697 /* 2-square pawn move just happened */
10699 *p++ = whiteToPlay ? '6' : '3';
10707 /* We don't keep track of halfmove clock for 50-move rule */
10711 /* Fullmove number */
10712 sprintf(p, "%d", (move / 2) + 1);
10714 return StrSave(buf);
10718 ParseFEN(board, blackPlaysFirst, fen)
10720 int *blackPlaysFirst;
10729 /* Piece placement data */
10730 for (i = BOARD_SIZE - 1; i >= 0; i--) {
10733 if (*p == '/' || *p == ' ') {
10734 if (*p == '/') p++;
10735 emptycount = BOARD_SIZE - j;
10736 while (emptycount--) board[i][j++] = EmptySquare;
10738 } else if (isdigit(*p)) {
10739 emptycount = *p++ - '0';
10740 if (j + emptycount > BOARD_SIZE) return FALSE;
10741 while (emptycount--) board[i][j++] = EmptySquare;
10742 } else if (isalpha(*p)) {
10743 if (j >= BOARD_SIZE) return FALSE;
10744 board[i][j++] = CharToPiece(*p++);
10750 while (*p == '/' || *p == ' ') p++;
10755 *blackPlaysFirst = FALSE;
10758 *blackPlaysFirst = TRUE;
10764 /* !!We ignore the rest of the FEN notation */
10769 EditPositionPasteFEN(char *fen)
10772 Board initial_position;
10774 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
10775 DisplayError("Bad FEN position in clipboard", 0);
10778 int savedBlackPlaysFirst = blackPlaysFirst;
10779 EditPositionEvent();
10780 blackPlaysFirst = savedBlackPlaysFirst;
10781 CopyBoard(boards[0], initial_position);
10782 EditPositionDone();
10783 DisplayBothClocks();
10784 DrawPosition(FALSE, boards[currentMove]);