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 )
4045 SetProgramStats( cps == &first ? 0 : 1,
4050 programStats.movelist,
4055 HandleMachineMove(message, cps)
4057 ChessProgramState *cps;
4059 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
4060 char realname[MSG_SIZ];
4061 int fromX, fromY, toX, toY;
4068 * Kludge to ignore BEL characters
4070 while (*message == '\007') message++;
4073 * Look for book output
4075 if (cps == &first && bookRequested) {
4076 if (message[0] == '\t' || message[0] == ' ') {
4077 /* Part of the book output is here; append it */
4078 strcat(bookOutput, message);
4079 strcat(bookOutput, " \n");
4081 } else if (bookOutput[0] != NULLCHAR) {
4082 /* All of book output has arrived; display it */
4083 char *p = bookOutput;
4084 while (*p != NULLCHAR) {
4085 if (*p == '\t') *p = ' ';
4088 DisplayInformation(bookOutput);
4089 bookRequested = FALSE;
4090 /* Fall through to parse the current output */
4095 * Look for machine move.
4097 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
4098 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
4100 /* This method is only useful on engines that support ping */
4101 if (cps->lastPing != cps->lastPong) {
4102 if (gameMode == BeginningOfGame) {
4103 /* Extra move from before last new; ignore */
4104 if (appData.debugMode) {
4105 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
4108 if (appData.debugMode) {
4109 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
4110 cps->which, gameMode);
4112 SendToProgram("undo\n", cps);
4118 case BeginningOfGame:
4119 /* Extra move from before last reset; ignore */
4120 if (appData.debugMode) {
4121 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
4128 /* Extra move after we tried to stop. The mode test is
4129 not a reliable way of detecting this problem, but it's
4130 the best we can do on engines that don't support ping.
4132 if (appData.debugMode) {
4133 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
4134 cps->which, gameMode);
4136 SendToProgram("undo\n", cps);
4139 case MachinePlaysWhite:
4140 case IcsPlayingWhite:
4141 machineWhite = TRUE;
4144 case MachinePlaysBlack:
4145 case IcsPlayingBlack:
4146 machineWhite = FALSE;
4149 case TwoMachinesPlay:
4150 machineWhite = (cps->twoMachinesColor[0] == 'w');
4153 if (WhiteOnMove(forwardMostMove) != machineWhite) {
4154 if (appData.debugMode) {
4156 "Ignoring move out of turn by %s, gameMode %d"
4157 ", forwardMost %d\n",
4158 cps->which, gameMode, forwardMostMove);
4163 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
4164 &fromX, &fromY, &toX, &toY, &promoChar)) {
4165 /* Machine move could not be parsed; ignore it. */
4166 sprintf(buf1, "Illegal move \"%s\" from %s machine",
4167 machineMove, cps->which);
4168 DisplayError(buf1, 0);
4169 if (gameMode == TwoMachinesPlay) {
4170 GameEnds(machineWhite ? BlackWins : WhiteWins,
4171 "Forfeit due to illegal move", GE_XBOARD);
4176 hintRequested = FALSE;
4177 lastHint[0] = NULLCHAR;
4178 bookRequested = FALSE;
4179 /* Program may be pondering now */
4180 cps->maybeThinking = TRUE;
4181 if (cps->sendTime == 2) cps->sendTime = 1;
4182 if (cps->offeredDraw) cps->offeredDraw--;
4185 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
4187 SendMoveToICS(moveType, fromX, fromY, toX, toY);
4191 /* currentMoveString is set as a side-effect of ParseOneMove */
4192 strcpy(machineMove, currentMoveString);
4193 strcat(machineMove, "\n");
4194 strcpy(moveList[forwardMostMove], machineMove);
4196 /* [AS] Save move info and clear stats for next move */
4197 pvInfoList[ forwardMostMove ].score = programStats.score;
4198 pvInfoList[ forwardMostMove ].depth = programStats.depth;
4199 pvInfoList[ forwardMostMove ].time = -1;
4200 ClearProgramStats();
4201 thinkOutput[0] = NULLCHAR;
4202 hiddenThinkOutputState = 0;
4204 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
4206 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
4207 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
4210 while( count < adjudicateLossPlies ) {
4211 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
4214 score = -score; /* Flip score for winning side */
4217 if( score > adjudicateLossThreshold ) {
4224 if( count >= adjudicateLossPlies ) {
4225 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
4227 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
4228 "Xboard adjudication",
4235 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
4236 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
4238 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
4243 if (gameMode == TwoMachinesPlay) {
4244 if (cps->other->sendTime) {
4245 SendTimeRemaining(cps->other,
4246 cps->other->twoMachinesColor[0] == 'w');
4248 SendMoveToProgram(forwardMostMove-1, cps->other);
4251 if (cps->other->useColors) {
4252 SendToProgram(cps->other->twoMachinesColor, cps->other);
4254 SendToProgram("go\n", cps->other);
4256 cps->other->maybeThinking = TRUE;
4259 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
4261 if (!pausing && appData.ringBellAfterMoves) {
4266 * Reenable menu items that were disabled while
4267 * machine was thinking
4269 if (gameMode != TwoMachinesPlay)
4270 SetUserThinkingEnables();
4275 /* Set special modes for chess engines. Later something general
4276 * could be added here; for now there is just one kludge feature,
4277 * needed because Crafty 15.10 and earlier don't ignore SIGINT
4278 * when "xboard" is given as an interactive command.
4280 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
4281 cps->useSigint = FALSE;
4282 cps->useSigterm = FALSE;
4286 * Look for communication commands
4288 if (!strncmp(message, "telluser ", 9)) {
4289 DisplayNote(message + 9);
4292 if (!strncmp(message, "tellusererror ", 14)) {
4293 DisplayError(message + 14, 0);
4296 if (!strncmp(message, "tellopponent ", 13)) {
4297 if (appData.icsActive) {
4299 sprintf(buf1, "%ssay %s\n", ics_prefix, message + 13);
4303 DisplayNote(message + 13);
4307 if (!strncmp(message, "tellothers ", 11)) {
4308 if (appData.icsActive) {
4310 sprintf(buf1, "%swhisper %s\n", ics_prefix, message + 11);
4316 if (!strncmp(message, "tellall ", 8)) {
4317 if (appData.icsActive) {
4319 sprintf(buf1, "%skibitz %s\n", ics_prefix, message + 8);
4323 DisplayNote(message + 8);
4327 if (strncmp(message, "warning", 7) == 0) {
4328 /* Undocumented feature, use tellusererror in new code */
4329 DisplayError(message, 0);
4332 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
4333 strcpy(realname, cps->tidy);
4334 strcat(realname, " query");
4335 AskQuestion(realname, buf2, buf1, cps->pr);
4338 /* Commands from the engine directly to ICS. We don't allow these to be
4339 * sent until we are logged on. Crafty kibitzes have been known to
4340 * interfere with the login process.
4343 if (!strncmp(message, "tellics ", 8)) {
4344 SendToICS(message + 8);
4348 if (!strncmp(message, "tellicsnoalias ", 15)) {
4349 SendToICS(ics_prefix);
4350 SendToICS(message + 15);
4354 /* The following are for backward compatibility only */
4355 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
4356 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
4357 SendToICS(ics_prefix);
4363 if (strncmp(message, "feature ", 8) == 0) {
4364 ParseFeatures(message+8, cps);
4366 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
4370 * If the move is illegal, cancel it and redraw the board.
4371 * Also deal with other error cases. Matching is rather loose
4372 * here to accommodate engines written before the spec.
4374 if (strncmp(message + 1, "llegal move", 11) == 0 ||
4375 strncmp(message, "Error", 5) == 0) {
4376 if (StrStr(message, "name") ||
4377 StrStr(message, "rating") || StrStr(message, "?") ||
4378 StrStr(message, "result") || StrStr(message, "board") ||
4379 StrStr(message, "bk") || StrStr(message, "computer") ||
4380 StrStr(message, "variant") || StrStr(message, "hint") ||
4381 StrStr(message, "random") || StrStr(message, "depth") ||
4382 StrStr(message, "accepted")) {
4385 if (StrStr(message, "protover")) {
4386 /* Program is responding to input, so it's apparently done
4387 initializing, and this error message indicates it is
4388 protocol version 1. So we don't need to wait any longer
4389 for it to initialize and send feature commands. */
4390 FeatureDone(cps, 1);
4391 cps->protocolVersion = 1;
4394 cps->maybeThinking = FALSE;
4396 if (StrStr(message, "draw")) {
4397 /* Program doesn't have "draw" command */
4398 cps->sendDrawOffers = 0;
4401 if (cps->sendTime != 1 &&
4402 (StrStr(message, "time") || StrStr(message, "otim"))) {
4403 /* Program apparently doesn't have "time" or "otim" command */
4407 if (StrStr(message, "analyze")) {
4408 cps->analysisSupport = FALSE;
4409 cps->analyzing = FALSE;
4411 sprintf(buf2, "%s does not support analysis", cps->tidy);
4412 DisplayError(buf2, 0);
4415 if (StrStr(message, "(no matching move)st")) {
4416 /* Special kludge for GNU Chess 4 only */
4417 cps->stKludge = TRUE;
4418 SendTimeControl(cps, movesPerSession, timeControl,
4419 timeIncrement, appData.searchDepth,
4423 if (StrStr(message, "(no matching move)sd")) {
4424 /* Special kludge for GNU Chess 4 only */
4425 cps->sdKludge = TRUE;
4426 SendTimeControl(cps, movesPerSession, timeControl,
4427 timeIncrement, appData.searchDepth,
4431 if (!StrStr(message, "llegal")) return;
4432 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
4433 gameMode == IcsIdle) return;
4434 if (forwardMostMove <= backwardMostMove) return;
4436 /* Following removed: it caused a bug where a real illegal move
4437 message in analyze mored would be ignored. */
4438 if (cps == &first && programStats.ok_to_send == 0) {
4439 /* Bogus message from Crafty responding to "." This filtering
4440 can miss some of the bad messages, but fortunately the bug
4441 is fixed in current Crafty versions, so it doesn't matter. */
4445 if (pausing) PauseEvent();
4446 if (gameMode == PlayFromGameFile) {
4447 /* Stop reading this game file */
4448 gameMode = EditGame;
4451 currentMove = --forwardMostMove;
4452 DisplayMove(currentMove-1); /* before DisplayMoveError */
4454 DisplayBothClocks();
4455 sprintf(buf1, "Illegal move \"%s\" (rejected by %s chess program)",
4456 parseList[currentMove], cps->which);
4457 DisplayMoveError(buf1);
4458 DrawPosition(FALSE, boards[currentMove]);
4461 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
4462 /* Program has a broken "time" command that
4463 outputs a string not ending in newline.
4469 * If chess program startup fails, exit with an error message.
4470 * Attempts to recover here are futile.
4472 if ((StrStr(message, "unknown host") != NULL)
4473 || (StrStr(message, "No remote directory") != NULL)
4474 || (StrStr(message, "not found") != NULL)
4475 || (StrStr(message, "No such file") != NULL)
4476 || (StrStr(message, "can't alloc") != NULL)
4477 || (StrStr(message, "Permission denied") != NULL)) {
4479 cps->maybeThinking = FALSE;
4480 sprintf(buf1, "Failed to start %s chess program %s on %s: %s\n",
4481 cps->which, cps->program, cps->host, message);
4482 RemoveInputSource(cps->isr);
4483 DisplayFatalError(buf1, 0, 1);
4488 * Look for hint output
4490 if (sscanf(message, "Hint: %s", buf1) == 1) {
4491 if (cps == &first && hintRequested) {
4492 hintRequested = FALSE;
4493 if (ParseOneMove(buf1, forwardMostMove, &moveType,
4494 &fromX, &fromY, &toX, &toY, &promoChar)) {
4495 (void) CoordsToAlgebraic(boards[forwardMostMove],
4496 PosFlags(forwardMostMove), EP_UNKNOWN,
4497 fromY, fromX, toY, toX, promoChar, buf1);
4498 sprintf(buf2, "Hint: %s", buf1);
4499 DisplayInformation(buf2);
4501 /* Hint move could not be parsed!? */
4503 "Illegal hint move \"%s\"\nfrom %s chess program",
4505 DisplayError(buf2, 0);
4508 strcpy(lastHint, buf1);
4514 * Ignore other messages if game is not in progress
4516 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
4517 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
4520 * look for win, lose, draw, or draw offer
4522 if (strncmp(message, "1-0", 3) == 0) {
4523 char *p, *q, *r = "";
4524 p = strchr(message, '{');
4532 GameEnds(WhiteWins, r, GE_ENGINE);
4534 } else if (strncmp(message, "0-1", 3) == 0) {
4535 char *p, *q, *r = "";
4536 p = strchr(message, '{');
4544 /* Kludge for Arasan 4.1 bug */
4545 if (strcmp(r, "Black resigns") == 0) {
4546 GameEnds(WhiteWins, r, GE_ENGINE);
4549 GameEnds(BlackWins, r, GE_ENGINE);
4551 } else if (strncmp(message, "1/2", 3) == 0) {
4552 char *p, *q, *r = "";
4553 p = strchr(message, '{');
4561 GameEnds(GameIsDrawn, r, GE_ENGINE);
4564 } else if (strncmp(message, "White resign", 12) == 0) {
4565 GameEnds(BlackWins, "White resigns", GE_ENGINE);
4567 } else if (strncmp(message, "Black resign", 12) == 0) {
4568 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
4570 } else if (strncmp(message, "White", 5) == 0 &&
4571 message[5] != '(' &&
4572 StrStr(message, "Black") == NULL) {
4573 GameEnds(WhiteWins, "White mates", GE_ENGINE);
4575 } else if (strncmp(message, "Black", 5) == 0 &&
4576 message[5] != '(') {
4577 GameEnds(BlackWins, "Black mates", GE_ENGINE);
4579 } else if (strcmp(message, "resign") == 0 ||
4580 strcmp(message, "computer resigns") == 0) {
4582 case MachinePlaysBlack:
4583 case IcsPlayingBlack:
4584 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
4586 case MachinePlaysWhite:
4587 case IcsPlayingWhite:
4588 GameEnds(BlackWins, "White resigns", GE_ENGINE);
4590 case TwoMachinesPlay:
4591 if (cps->twoMachinesColor[0] == 'w')
4592 GameEnds(BlackWins, "White resigns", GE_ENGINE);
4594 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
4601 } else if (strncmp(message, "opponent mates", 14) == 0) {
4603 case MachinePlaysBlack:
4604 case IcsPlayingBlack:
4605 GameEnds(WhiteWins, "White mates", GE_ENGINE);
4607 case MachinePlaysWhite:
4608 case IcsPlayingWhite:
4609 GameEnds(BlackWins, "Black mates", GE_ENGINE);
4611 case TwoMachinesPlay:
4612 if (cps->twoMachinesColor[0] == 'w')
4613 GameEnds(BlackWins, "Black mates", GE_ENGINE);
4615 GameEnds(WhiteWins, "White mates", GE_ENGINE);
4622 } else if (strncmp(message, "computer mates", 14) == 0) {
4624 case MachinePlaysBlack:
4625 case IcsPlayingBlack:
4626 GameEnds(BlackWins, "Black mates", GE_ENGINE);
4628 case MachinePlaysWhite:
4629 case IcsPlayingWhite:
4630 GameEnds(WhiteWins, "White mates", GE_ENGINE);
4632 case TwoMachinesPlay:
4633 if (cps->twoMachinesColor[0] == 'w')
4634 GameEnds(WhiteWins, "White mates", GE_ENGINE);
4636 GameEnds(BlackWins, "Black mates", GE_ENGINE);
4643 } else if (strncmp(message, "checkmate", 9) == 0) {
4644 if (WhiteOnMove(forwardMostMove)) {
4645 GameEnds(BlackWins, "Black mates", GE_ENGINE);
4647 GameEnds(WhiteWins, "White mates", GE_ENGINE);
4650 } else if (strstr(message, "Draw") != NULL ||
4651 strstr(message, "game is a draw") != NULL) {
4652 GameEnds(GameIsDrawn, "Draw", GE_ENGINE);
4654 } else if (strstr(message, "offer") != NULL &&
4655 strstr(message, "draw") != NULL) {
4657 if (appData.zippyPlay && first.initDone) {
4658 /* Relay offer to ICS */
4659 SendToICS(ics_prefix);
4660 SendToICS("draw\n");
4663 cps->offeredDraw = 2; /* valid until this engine moves twice */
4664 if (gameMode == TwoMachinesPlay) {
4665 if (cps->other->offeredDraw) {
4666 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
4668 if (cps->other->sendDrawOffers) {
4669 SendToProgram("draw\n", cps->other);
4672 } else if (gameMode == MachinePlaysWhite ||
4673 gameMode == MachinePlaysBlack) {
4674 if (userOfferedDraw) {
4675 DisplayInformation("Machine accepts your draw offer");
4676 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
4678 DisplayInformation("Machine offers a draw\nSelect Action / Draw to agree");
4685 * Look for thinking output
4687 if ( appData.showThinking) {
4688 int plylev, mvleft, mvtot, curscore, time;
4689 char mvname[MOVE_LEN];
4690 unsigned long nodes;
4693 int prefixHint = FALSE;
4694 mvname[0] = NULLCHAR;
4697 case MachinePlaysBlack:
4698 case IcsPlayingBlack:
4699 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
4701 case MachinePlaysWhite:
4702 case IcsPlayingWhite:
4703 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
4708 case TwoMachinesPlay:
4709 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
4720 if (sscanf(message, "%d%c %d %d %lu %[^\n]\n",
4721 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
4723 if (plyext != ' ' && plyext != '\t') {
4727 /* [AS] Negate score if machine is playing black and reporting absolute scores */
4728 if( cps->scoreIsAbsolute &&
4729 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
4731 curscore = -curscore;
4735 programStats.depth = plylev;
4736 programStats.nodes = nodes;
4737 programStats.time = time;
4738 programStats.score = curscore;
4739 programStats.got_only_move = 0;
4741 /* Buffer overflow protection */
4742 if (buf1[0] != NULLCHAR) {
4743 if (strlen(buf1) >= sizeof(programStats.movelist)
4744 && appData.debugMode) {
4746 "PV is too long; using the first %d bytes.\n",
4747 sizeof(programStats.movelist) - 1);
4750 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
4752 sprintf(programStats.movelist, " no PV\n");
4755 if (programStats.seen_stat) {
4756 programStats.ok_to_send = 1;
4759 if (strchr(programStats.movelist, '(') != NULL) {
4760 programStats.line_is_book = 1;
4761 programStats.nr_moves = 0;
4762 programStats.moves_left = 0;
4764 programStats.line_is_book = 0;
4767 SendProgramStatsToFrontend( cps );
4770 [AS] Protect the thinkOutput buffer from overflow... this
4771 is only useful if buf1 hasn't overflowed first!
4773 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
4775 (gameMode == TwoMachinesPlay ?
4776 ToUpper(cps->twoMachinesColor[0]) : ' '),
4777 ((double) curscore) / 100.0,
4778 prefixHint ? lastHint : "",
4779 prefixHint ? " " : "" );
4781 if( buf1[0] != NULLCHAR ) {
4782 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
4784 if( strlen(buf1) > max_len ) {
4785 if( appData.debugMode) {
4786 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
4788 buf1[max_len+1] = '\0';
4791 strcat( thinkOutput, buf1 );
4794 if (currentMove == forwardMostMove || gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
4795 DisplayMove(currentMove - 1);
4800 } else if ((p=StrStr(message, "(only move)")) != NULL) {
4801 /* crafty (9.25+) says "(only move) <move>"
4802 * if there is only 1 legal move
4804 sscanf(p, "(only move) %s", buf1);
4805 sprintf(thinkOutput, "%s (only move)", buf1);
4806 sprintf(programStats.movelist, "%s (only move)", buf1);
4807 programStats.depth = 1;
4808 programStats.nr_moves = 1;
4809 programStats.moves_left = 1;
4810 programStats.nodes = 1;
4811 programStats.time = 1;
4812 programStats.got_only_move = 1;
4814 /* Not really, but we also use this member to
4815 mean "line isn't going to change" (Crafty
4816 isn't searching, so stats won't change) */
4817 programStats.line_is_book = 1;
4819 SendProgramStatsToFrontend( cps );
4821 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || gameMode == AnalyzeFile) {
4822 DisplayMove(currentMove - 1);
4826 } else if (sscanf(message,"stat01: %d %lu %d %d %d %s",
4827 &time, &nodes, &plylev, &mvleft,
4828 &mvtot, mvname) >= 5) {
4829 /* The stat01: line is from Crafty (9.29+) in response
4830 to the "." command */
4831 programStats.seen_stat = 1;
4832 cps->maybeThinking = TRUE;
4834 if (programStats.got_only_move || !appData.periodicUpdates)
4837 programStats.depth = plylev;
4838 programStats.time = time;
4839 programStats.nodes = nodes;
4840 programStats.moves_left = mvleft;
4841 programStats.nr_moves = mvtot;
4842 strcpy(programStats.move_name, mvname);
4843 programStats.ok_to_send = 1;
4845 SendProgramStatsToFrontend( cps );
4850 } else if (strncmp(message,"++",2) == 0) {
4851 /* Crafty 9.29+ outputs this */
4852 programStats.got_fail = 2;
4855 } else if (strncmp(message,"--",2) == 0) {
4856 /* Crafty 9.29+ outputs this */
4857 programStats.got_fail = 1;
4860 } else if (thinkOutput[0] != NULLCHAR &&
4861 strncmp(message, " ", 4) == 0) {
4862 unsigned message_len;
4865 while (*p && *p == ' ') p++;
4867 message_len = strlen( p );
4869 /* [AS] Avoid buffer overflow */
4870 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
4871 strcat(thinkOutput, " ");
4872 strcat(thinkOutput, p);
4875 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
4876 strcat(programStats.movelist, " ");
4877 strcat(programStats.movelist, p);
4880 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || gameMode == AnalyzeFile) {
4881 DisplayMove(currentMove - 1);
4890 if (sscanf(message, "%d%c %d %d %lu %[^\n]\n",
4891 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
4893 if (plyext != ' ' && plyext != '\t') {
4897 /* [AS] Negate score if machine is playing black and reporting absolute scores */
4898 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
4899 curscore = -curscore;
4902 programStats.depth = plylev;
4903 programStats.nodes = nodes;
4904 programStats.time = time;
4905 programStats.score = curscore;
4906 programStats.got_only_move = 0;
4907 programStats.movelist[0] = '\0';
4909 if (buf1[0] != NULLCHAR) {
4910 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
4913 programStats.ok_to_send = 0;
4914 programStats.line_is_book = 0;
4915 programStats.nr_moves = 0;
4916 programStats.moves_left = 0;
4918 SendProgramStatsToFrontend( cps );
4925 /* Parse a game score from the character string "game", and
4926 record it as the history of the current game. The game
4927 score is NOT assumed to start from the standard position.
4928 The display is not updated in any way.
4931 ParseGameHistory(game)
4935 int fromX, fromY, toX, toY, boardIndex;
4940 if (appData.debugMode)
4941 fprintf(debugFP, "Parsing game history: %s\n", game);
4943 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
4944 gameInfo.site = StrSave(appData.icsHost);
4945 gameInfo.date = PGNDate();
4946 gameInfo.round = StrSave("-");
4948 /* Parse out names of players */
4949 while (*game == ' ') game++;
4951 while (*game != ' ') *p++ = *game++;
4953 gameInfo.white = StrSave(buf);
4954 while (*game == ' ') game++;
4956 while (*game != ' ' && *game != '\n') *p++ = *game++;
4958 gameInfo.black = StrSave(buf);
4961 boardIndex = blackPlaysFirst ? 1 : 0;
4964 yyboardindex = boardIndex;
4965 moveType = (ChessMove) yylex();
4967 case WhitePromotionQueen:
4968 case BlackPromotionQueen:
4969 case WhitePromotionRook:
4970 case BlackPromotionRook:
4971 case WhitePromotionBishop:
4972 case BlackPromotionBishop:
4973 case WhitePromotionKnight:
4974 case BlackPromotionKnight:
4975 case WhitePromotionKing:
4976 case BlackPromotionKing:
4978 case WhiteCapturesEnPassant:
4979 case BlackCapturesEnPassant:
4980 case WhiteKingSideCastle:
4981 case WhiteQueenSideCastle:
4982 case BlackKingSideCastle:
4983 case BlackQueenSideCastle:
4984 case WhiteKingSideCastleWild:
4985 case WhiteQueenSideCastleWild:
4986 case BlackKingSideCastleWild:
4987 case BlackQueenSideCastleWild:
4989 case WhiteHSideCastleFR:
4990 case WhiteASideCastleFR:
4991 case BlackHSideCastleFR:
4992 case BlackASideCastleFR:
4994 case IllegalMove: /* maybe suicide chess, etc. */
4995 fromX = currentMoveString[0] - 'a';
4996 fromY = currentMoveString[1] - '1';
4997 toX = currentMoveString[2] - 'a';
4998 toY = currentMoveString[3] - '1';
4999 promoChar = currentMoveString[4];
5003 fromX = moveType == WhiteDrop ?
5004 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5005 (int) CharToPiece(ToLower(currentMoveString[0]));
5007 toX = currentMoveString[2] - 'a';
5008 toY = currentMoveString[3] - '1';
5009 promoChar = NULLCHAR;
5013 sprintf(buf, "Ambiguous move in ICS output: \"%s\"", yy_text);
5014 DisplayError(buf, 0);
5016 case ImpossibleMove:
5018 sprintf(buf, "Illegal move in ICS output: \"%s\"", yy_text);
5019 DisplayError(buf, 0);
5021 case (ChessMove) 0: /* end of file */
5022 if (boardIndex < backwardMostMove) {
5023 /* Oops, gap. How did that happen? */
5024 DisplayError("Gap in move list", 0);
5027 backwardMostMove = blackPlaysFirst ? 1 : 0;
5028 if (boardIndex > forwardMostMove) {
5029 forwardMostMove = boardIndex;
5033 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
5034 strcat(parseList[boardIndex-1], " ");
5035 strcat(parseList[boardIndex-1], yy_text);
5047 case GameUnfinished:
5048 if (gameMode == IcsExamining) {
5049 if (boardIndex < backwardMostMove) {
5050 /* Oops, gap. How did that happen? */
5053 backwardMostMove = blackPlaysFirst ? 1 : 0;
5056 gameInfo.result = moveType;
5057 p = strchr(yy_text, '{');
5058 if (p == NULL) p = strchr(yy_text, '(');
5061 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
5063 q = strchr(p, *p == '{' ? '}' : ')');
5064 if (q != NULL) *q = NULLCHAR;
5067 gameInfo.resultDetails = StrSave(p);
5070 if (boardIndex >= forwardMostMove &&
5071 !(gameMode == IcsObserving && ics_gamenum == -1)) {
5072 backwardMostMove = blackPlaysFirst ? 1 : 0;
5075 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
5076 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
5077 parseList[boardIndex]);
5078 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
5079 /* currentMoveString is set as a side-effect of yylex */
5080 strcpy(moveList[boardIndex], currentMoveString);
5081 strcat(moveList[boardIndex], "\n");
5083 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
5084 switch (MateTest(boards[boardIndex],
5085 PosFlags(boardIndex), EP_UNKNOWN)) {
5091 strcat(parseList[boardIndex - 1], "+");
5094 strcat(parseList[boardIndex - 1], "#");
5101 /* Apply a move to the given board */
5103 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
5104 int fromX, fromY, toX, toY;
5108 ChessSquare captured = board[toY][toX];
5109 if (fromY == DROP_RANK) {
5111 board[toY][toX] = (ChessSquare) fromX;
5112 } else if (fromX == toX && fromY == toY) {
5116 /* Code added by Tord: */
5117 /* FRC castling assumed when king captures friendly rook. */
5118 else if (board[fromY][fromX] == WhiteKing &&
5119 board[toY][toX] == WhiteRook) {
5120 board[fromY][fromX] = EmptySquare;
5121 board[toY][toX] = EmptySquare;
5123 board[0][6] = WhiteKing; board[0][5] = WhiteRook;
5125 board[0][2] = WhiteKing; board[0][3] = WhiteRook;
5127 } else if (board[fromY][fromX] == BlackKing &&
5128 board[toY][toX] == BlackRook) {
5129 board[fromY][fromX] = EmptySquare;
5130 board[toY][toX] = EmptySquare;
5132 board[7][6] = BlackKing; board[7][5] = BlackRook;
5134 board[7][2] = BlackKing; board[7][3] = BlackRook;
5136 /* End of code added by Tord */
5138 } else if (fromY == 0 && fromX == 4
5139 && board[fromY][fromX] == WhiteKing
5140 && toY == 0 && toX == 6) {
5141 board[fromY][fromX] = EmptySquare;
5142 board[toY][toX] = WhiteKing;
5143 board[fromY][7] = EmptySquare;
5144 board[toY][5] = WhiteRook;
5145 } else if (fromY == 0 && fromX == 4
5146 && board[fromY][fromX] == WhiteKing
5147 && toY == 0 && toX == 2) {
5148 board[fromY][fromX] = EmptySquare;
5149 board[toY][toX] = WhiteKing;
5150 board[fromY][0] = EmptySquare;
5151 board[toY][3] = WhiteRook;
5152 } else if (fromY == 0 && fromX == 3
5153 && board[fromY][fromX] == WhiteKing
5154 && toY == 0 && toX == 5) {
5155 board[fromY][fromX] = EmptySquare;
5156 board[toY][toX] = WhiteKing;
5157 board[fromY][7] = EmptySquare;
5158 board[toY][4] = WhiteRook;
5159 } else if (fromY == 0 && fromX == 3
5160 && board[fromY][fromX] == WhiteKing
5161 && toY == 0 && toX == 1) {
5162 board[fromY][fromX] = EmptySquare;
5163 board[toY][toX] = WhiteKing;
5164 board[fromY][0] = EmptySquare;
5165 board[toY][2] = WhiteRook;
5166 } else if (board[fromY][fromX] == WhitePawn
5168 /* white pawn promotion */
5169 board[7][toX] = CharToPiece(ToUpper(promoChar));
5170 if (board[7][toX] == EmptySquare) {
5171 board[7][toX] = WhiteQueen;
5173 board[fromY][fromX] = EmptySquare;
5174 } else if ((fromY == 4)
5176 && (board[fromY][fromX] == WhitePawn)
5177 && (board[toY][toX] == EmptySquare)) {
5178 board[fromY][fromX] = EmptySquare;
5179 board[toY][toX] = WhitePawn;
5180 captured = board[toY - 1][toX];
5181 board[toY - 1][toX] = EmptySquare;
5182 } else if (fromY == 7 && fromX == 4
5183 && board[fromY][fromX] == BlackKing
5184 && toY == 7 && toX == 6) {
5185 board[fromY][fromX] = EmptySquare;
5186 board[toY][toX] = BlackKing;
5187 board[fromY][7] = EmptySquare;
5188 board[toY][5] = BlackRook;
5189 } else if (fromY == 7 && fromX == 4
5190 && board[fromY][fromX] == BlackKing
5191 && toY == 7 && toX == 2) {
5192 board[fromY][fromX] = EmptySquare;
5193 board[toY][toX] = BlackKing;
5194 board[fromY][0] = EmptySquare;
5195 board[toY][3] = BlackRook;
5196 } else if (fromY == 7 && fromX == 3
5197 && board[fromY][fromX] == BlackKing
5198 && toY == 7 && toX == 5) {
5199 board[fromY][fromX] = EmptySquare;
5200 board[toY][toX] = BlackKing;
5201 board[fromY][7] = EmptySquare;
5202 board[toY][4] = BlackRook;
5203 } else if (fromY == 7 && fromX == 3
5204 && board[fromY][fromX] == BlackKing
5205 && toY == 7 && toX == 1) {
5206 board[fromY][fromX] = EmptySquare;
5207 board[toY][toX] = BlackKing;
5208 board[fromY][0] = EmptySquare;
5209 board[toY][2] = BlackRook;
5210 } else if (board[fromY][fromX] == BlackPawn
5212 /* black pawn promotion */
5213 board[0][toX] = CharToPiece(ToLower(promoChar));
5214 if (board[0][toX] == EmptySquare) {
5215 board[0][toX] = BlackQueen;
5217 board[fromY][fromX] = EmptySquare;
5218 } else if ((fromY == 3)
5220 && (board[fromY][fromX] == BlackPawn)
5221 && (board[toY][toX] == EmptySquare)) {
5222 board[fromY][fromX] = EmptySquare;
5223 board[toY][toX] = BlackPawn;
5224 captured = board[toY + 1][toX];
5225 board[toY + 1][toX] = EmptySquare;
5227 board[toY][toX] = board[fromY][fromX];
5228 board[fromY][fromX] = EmptySquare;
5230 if (gameInfo.variant == VariantCrazyhouse) {
5232 /* !!A lot more code needs to be written to support holdings */
5233 if (fromY == DROP_RANK) {
5234 /* Delete from holdings */
5235 if (holdings[(int) fromX] > 0) holdings[(int) fromX]--;
5237 if (captured != EmptySquare) {
5238 /* Add to holdings */
5239 if (captured < BlackPawn) {
5240 holdings[(int)captured - (int)BlackPawn + (int)WhitePawn]++;
5242 holdings[(int)captured - (int)WhitePawn + (int)BlackPawn]++;
5246 } else if (gameInfo.variant == VariantAtomic) {
5247 if (captured != EmptySquare) {
5249 for (y = toY-1; y <= toY+1; y++) {
5250 for (x = toX-1; x <= toX+1; x++) {
5251 if (y >= 0 && y <= 7 && x >= 0 && x <= 7 &&
5252 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
5253 board[y][x] = EmptySquare;
5257 board[toY][toX] = EmptySquare;
5262 /* Updates forwardMostMove */
5264 MakeMove(fromX, fromY, toX, toY, promoChar)
5265 int fromX, fromY, toX, toY;
5269 if (forwardMostMove >= MAX_MOVES) {
5270 DisplayFatalError("Game too long; increase MAX_MOVES and recompile",
5275 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
5276 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
5277 if (commentList[forwardMostMove] != NULL) {
5278 free(commentList[forwardMostMove]);
5279 commentList[forwardMostMove] = NULL;
5281 CopyBoard(boards[forwardMostMove], boards[forwardMostMove - 1]);
5282 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove]);
5283 gameInfo.result = GameUnfinished;
5284 if (gameInfo.resultDetails != NULL) {
5285 free(gameInfo.resultDetails);
5286 gameInfo.resultDetails = NULL;
5288 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
5289 moveList[forwardMostMove - 1]);
5290 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
5291 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
5292 fromY, fromX, toY, toX, promoChar,
5293 parseList[forwardMostMove - 1]);
5294 switch (MateTest(boards[forwardMostMove],
5295 PosFlags(forwardMostMove), EP_UNKNOWN)){
5301 strcat(parseList[forwardMostMove - 1], "+");
5304 strcat(parseList[forwardMostMove - 1], "#");
5309 /* Updates currentMove if not pausing */
5311 ShowMove(fromX, fromY, toX, toY)
5313 int instant = (gameMode == PlayFromGameFile) ?
5314 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
5315 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
5317 if (forwardMostMove == currentMove + 1) {
5318 AnimateMove(boards[forwardMostMove - 1],
5319 fromX, fromY, toX, toY);
5321 if (appData.highlightLastMove) {
5322 SetHighlights(fromX, fromY, toX, toY);
5325 currentMove = forwardMostMove;
5328 if (instant) return;
5330 DisplayMove(currentMove - 1);
5331 DrawPosition(FALSE, boards[currentMove]);
5332 DisplayBothClocks();
5333 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
5338 InitChessProgram(cps)
5339 ChessProgramState *cps;
5342 if (appData.noChessProgram) return;
5343 hintRequested = FALSE;
5344 bookRequested = FALSE;
5345 SendToProgram(cps->initString, cps);
5346 if (gameInfo.variant != VariantNormal &&
5347 gameInfo.variant != VariantLoadable) {
5348 char *v = VariantName(gameInfo.variant);
5349 if (StrStr(cps->variants, v) == NULL) {
5350 sprintf(buf, "Variant %s not supported by %s", v, cps->tidy);
5351 DisplayFatalError(buf, 0, 1);
5354 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
5355 SendToProgram(buf, cps);
5358 sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-");
5359 SendToProgram(buf, cps);
5361 cps->maybeThinking = FALSE;
5362 cps->offeredDraw = 0;
5363 if (!appData.icsActive) {
5364 SendTimeControl(cps, movesPerSession, timeControl,
5365 timeIncrement, appData.searchDepth,
5368 if (appData.showThinking) {
5369 SendToProgram("post\n", cps);
5371 SendToProgram("hard\n", cps);
5372 if (!appData.ponderNextMove) {
5373 /* Warning: "easy" is a toggle in GNU Chess, so don't send
5374 it without being sure what state we are in first. "hard"
5375 is not a toggle, so that one is OK.
5377 SendToProgram("easy\n", cps);
5380 sprintf(buf, "ping %d\n", ++cps->lastPing);
5381 SendToProgram(buf, cps);
5383 cps->initDone = TRUE;
5388 StartChessProgram(cps)
5389 ChessProgramState *cps;
5394 if (appData.noChessProgram) return;
5395 cps->initDone = FALSE;
5397 if (strcmp(cps->host, "localhost") == 0) {
5398 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
5399 } else if (*appData.remoteShell == NULLCHAR) {
5400 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
5402 if (*appData.remoteUser == NULLCHAR) {
5403 sprintf(buf, "%s %s %s", appData.remoteShell, cps->host,
5406 sprintf(buf, "%s %s -l %s %s", appData.remoteShell,
5407 cps->host, appData.remoteUser, cps->program);
5409 err = StartChildProcess(buf, "", &cps->pr);
5413 sprintf(buf, "Startup failure on '%s'", cps->program);
5414 DisplayFatalError(buf, err, 1);
5420 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
5421 if (cps->protocolVersion > 1) {
5422 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
5423 SendToProgram(buf, cps);
5425 SendToProgram("xboard\n", cps);
5431 TwoMachinesEventIfReady P((void))
5433 if (first.lastPing != first.lastPong) {
5434 DisplayMessage("", "Waiting for first chess program");
5435 ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000);
5438 if (second.lastPing != second.lastPong) {
5439 DisplayMessage("", "Waiting for second chess program");
5440 ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000);
5448 NextMatchGame P((void))
5451 if (*appData.loadGameFile != NULLCHAR) {
5452 LoadGameFromFile(appData.loadGameFile,
5453 appData.loadGameIndex,
5454 appData.loadGameFile, FALSE);
5455 } else if (*appData.loadPositionFile != NULLCHAR) {
5456 LoadPositionFromFile(appData.loadPositionFile,
5457 appData.loadPositionIndex,
5458 appData.loadPositionFile);
5460 TwoMachinesEventIfReady();
5463 void UserAdjudicationEvent( int result )
5465 ChessMove gameResult = GameIsDrawn;
5468 gameResult = WhiteWins;
5470 else if( result < 0 ) {
5471 gameResult = BlackWins;
5474 if( gameMode == TwoMachinesPlay ) {
5475 GameEnds( gameResult, "User adjudication", GE_XBOARD );
5481 GameEnds(result, resultDetails, whosays)
5483 char *resultDetails;
5486 GameMode nextGameMode;
5489 if (appData.debugMode) {
5490 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
5491 result, resultDetails ? resultDetails : "(null)", whosays);
5494 if (appData.icsActive && whosays == GE_ENGINE) {
5495 /* If we are playing on ICS, the server decides when the
5496 game is over, but the engine can offer to draw, claim
5500 if (appData.zippyPlay && first.initDone) {
5501 if (result == GameIsDrawn) {
5502 /* In case draw still needs to be claimed */
5503 SendToICS(ics_prefix);
5504 SendToICS("draw\n");
5505 } else if (StrCaseStr(resultDetails, "resign")) {
5506 SendToICS(ics_prefix);
5507 SendToICS("resign\n");
5514 /* If we're loading the game from a file, stop */
5515 if (whosays == GE_FILE) {
5516 (void) StopLoadGameTimer();
5520 /* Cancel draw offers */
5521 first.offeredDraw = second.offeredDraw = 0;
5523 /* If this is an ICS game, only ICS can really say it's done;
5524 if not, anyone can. */
5525 isIcsGame = (gameMode == IcsPlayingWhite ||
5526 gameMode == IcsPlayingBlack ||
5527 gameMode == IcsObserving ||
5528 gameMode == IcsExamining);
5530 if (!isIcsGame || whosays == GE_ICS) {
5531 /* OK -- not an ICS game, or ICS said it was done */
5533 if (!isIcsGame && !appData.noChessProgram)
5534 SetUserThinkingEnables();
5536 if (resultDetails != NULL) {
5537 gameInfo.result = result;
5538 gameInfo.resultDetails = StrSave(resultDetails);
5540 /* Tell program how game ended in case it is learning */
5541 if (gameMode == MachinePlaysWhite ||
5542 gameMode == MachinePlaysBlack ||
5543 gameMode == TwoMachinesPlay ||
5544 gameMode == IcsPlayingWhite ||
5545 gameMode == IcsPlayingBlack ||
5546 gameMode == BeginningOfGame) {
5548 sprintf(buf, "result %s {%s}\n", PGNResult(result),
5550 if (first.pr != NoProc) {
5551 SendToProgram(buf, &first);
5553 if (second.pr != NoProc &&
5554 gameMode == TwoMachinesPlay) {
5555 SendToProgram(buf, &second);
5559 /* display last move only if game was not loaded from file */
5560 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
5561 DisplayMove(currentMove - 1);
5563 if (forwardMostMove != 0) {
5564 if (gameMode != PlayFromGameFile && gameMode != EditGame) {
5565 if (*appData.saveGameFile != NULLCHAR) {
5566 SaveGameToFile(appData.saveGameFile, TRUE);
5567 } else if (appData.autoSaveGames) {
5570 if (*appData.savePositionFile != NULLCHAR) {
5571 SavePositionToFile(appData.savePositionFile);
5577 if (appData.icsActive) {
5578 if (appData.quietPlay &&
5579 (gameMode == IcsPlayingWhite ||
5580 gameMode == IcsPlayingBlack)) {
5581 SendToICS(ics_prefix);
5582 SendToICS("set shout 1\n");
5584 nextGameMode = IcsIdle;
5585 ics_user_moved = FALSE;
5586 /* clean up premove. It's ugly when the game has ended and the
5587 * premove highlights are still on the board.
5591 ClearPremoveHighlights();
5592 DrawPosition(FALSE, boards[currentMove]);
5594 if (whosays == GE_ICS) {
5597 if (gameMode == IcsPlayingWhite)
5599 else if(gameMode == IcsPlayingBlack)
5603 if (gameMode == IcsPlayingBlack)
5605 else if(gameMode == IcsPlayingWhite)
5612 PlayIcsUnfinishedSound();
5615 } else if (gameMode == EditGame ||
5616 gameMode == PlayFromGameFile ||
5617 gameMode == AnalyzeMode ||
5618 gameMode == AnalyzeFile) {
5619 nextGameMode = gameMode;
5621 nextGameMode = EndOfGame;
5626 nextGameMode = gameMode;
5629 if (appData.noChessProgram) {
5630 gameMode = nextGameMode;
5636 /* Put first chess program into idle state */
5637 if (first.pr != NoProc &&
5638 (gameMode == MachinePlaysWhite ||
5639 gameMode == MachinePlaysBlack ||
5640 gameMode == TwoMachinesPlay ||
5641 gameMode == IcsPlayingWhite ||
5642 gameMode == IcsPlayingBlack ||
5643 gameMode == BeginningOfGame)) {
5644 SendToProgram("force\n", &first);
5645 if (first.usePing) {
5647 sprintf(buf, "ping %d\n", ++first.lastPing);
5648 SendToProgram(buf, &first);
5651 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
5652 /* Kill off first chess program */
5653 if (first.isr != NULL)
5654 RemoveInputSource(first.isr);
5657 if (first.pr != NoProc) {
5659 DoSleep( appData.delayBeforeQuit );
5660 SendToProgram("quit\n", &first);
5661 DoSleep( appData.delayAfterQuit );
5662 DestroyChildProcess(first.pr, first.useSigterm);
5667 /* Put second chess program into idle state */
5668 if (second.pr != NoProc &&
5669 gameMode == TwoMachinesPlay) {
5670 SendToProgram("force\n", &second);
5671 if (second.usePing) {
5673 sprintf(buf, "ping %d\n", ++second.lastPing);
5674 SendToProgram(buf, &second);
5677 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
5678 /* Kill off second chess program */
5679 if (second.isr != NULL)
5680 RemoveInputSource(second.isr);
5683 if (second.pr != NoProc) {
5684 DoSleep( appData.delayBeforeQuit );
5685 SendToProgram("quit\n", &second);
5686 DoSleep( appData.delayAfterQuit );
5687 DestroyChildProcess(second.pr, second.useSigterm);
5692 if (matchMode && gameMode == TwoMachinesPlay) {
5695 if (first.twoMachinesColor[0] == 'w') {
5702 if (first.twoMachinesColor[0] == 'b') {
5711 if (matchGame < appData.matchGames) {
5713 tmp = first.twoMachinesColor;
5714 first.twoMachinesColor = second.twoMachinesColor;
5715 second.twoMachinesColor = tmp;
5716 gameMode = nextGameMode;
5718 ScheduleDelayedEvent(NextMatchGame, 10000);
5722 gameMode = nextGameMode;
5723 sprintf(buf, "Match %s vs. %s: final score %d-%d-%d",
5724 first.tidy, second.tidy,
5725 first.matchWins, second.matchWins,
5726 appData.matchGames - (first.matchWins + second.matchWins));
5727 DisplayFatalError(buf, 0, 0);
5730 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
5731 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
5733 gameMode = nextGameMode;
5737 /* Assumes program was just initialized (initString sent).
5738 Leaves program in force mode. */
5740 FeedMovesToProgram(cps, upto)
5741 ChessProgramState *cps;
5746 if (appData.debugMode)
5747 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
5748 startedFromSetupPosition ? "position and " : "",
5749 backwardMostMove, upto, cps->which);
5750 SendToProgram("force\n", cps);
5751 if (startedFromSetupPosition) {
5752 SendBoard(cps, backwardMostMove);
5754 for (i = backwardMostMove; i < upto; i++) {
5755 SendMoveToProgram(i, cps);
5761 ResurrectChessProgram()
5763 /* The chess program may have exited.
5764 If so, restart it and feed it all the moves made so far. */
5766 if (appData.noChessProgram || first.pr != NoProc) return;
5768 StartChessProgram(&first);
5769 InitChessProgram(&first);
5770 FeedMovesToProgram(&first, currentMove);
5772 if (!first.sendTime) {
5773 /* can't tell gnuchess what its clock should read,
5774 so we bow to its notion. */
5776 timeRemaining[0][currentMove] = whiteTimeRemaining;
5777 timeRemaining[1][currentMove] = blackTimeRemaining;
5780 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
5781 first.analysisSupport) {
5782 SendToProgram("analyze\n", &first);
5783 first.analyzing = TRUE;
5796 if (appData.debugMode) {
5797 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
5798 redraw, init, gameMode);
5801 pausing = pauseExamInvalid = FALSE;
5802 startedFromSetupPosition = blackPlaysFirst = FALSE;
5804 whiteFlag = blackFlag = FALSE;
5805 userOfferedDraw = FALSE;
5806 hintRequested = bookRequested = FALSE;
5807 first.maybeThinking = FALSE;
5808 second.maybeThinking = FALSE;
5809 thinkOutput[0] = NULLCHAR;
5810 lastHint[0] = NULLCHAR;
5811 ClearGameInfo(&gameInfo);
5812 gameInfo.variant = StringToVariant(appData.variant);
5813 ics_user_moved = ics_clock_paused = FALSE;
5814 ics_getting_history = H_FALSE;
5816 white_holding[0] = black_holding[0] = NULLCHAR;
5817 ClearProgramStats();
5821 flipView = appData.flipView;
5822 ClearPremoveHighlights();
5824 alarmSounded = FALSE;
5826 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
5828 gameMode = BeginningOfGame;
5830 InitPosition(redraw);
5831 for (i = 0; i < MAX_MOVES; i++) {
5832 if (commentList[i] != NULL) {
5833 free(commentList[i]);
5834 commentList[i] = NULL;
5838 timeRemaining[0][0] = whiteTimeRemaining;
5839 timeRemaining[1][0] = blackTimeRemaining;
5840 if (first.pr == NULL) {
5841 StartChessProgram(&first);
5843 if (init) InitChessProgram(&first);
5845 DisplayMessage("", "");
5846 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5853 if (!AutoPlayOneMove())
5855 if (matchMode || appData.timeDelay == 0)
5857 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
5859 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
5868 int fromX, fromY, toX, toY;
5870 if (appData.debugMode) {
5871 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
5874 if (gameMode != PlayFromGameFile)
5877 if (currentMove >= forwardMostMove) {
5878 gameMode = EditGame;
5881 /* [AS] Clear current move marker at the end of a game */
5882 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
5887 toX = moveList[currentMove][2] - 'a';
5888 toY = moveList[currentMove][3] - '1';
5890 if (moveList[currentMove][1] == '@') {
5891 if (appData.highlightLastMove) {
5892 SetHighlights(-1, -1, toX, toY);
5895 fromX = moveList[currentMove][0] - 'a';
5896 fromY = moveList[currentMove][1] - '1';
5898 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
5900 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
5902 if (appData.highlightLastMove) {
5903 SetHighlights(fromX, fromY, toX, toY);
5906 DisplayMove(currentMove);
5907 SendMoveToProgram(currentMove++, &first);
5908 DisplayBothClocks();
5909 DrawPosition(FALSE, boards[currentMove]);
5910 if (commentList[currentMove] != NULL) {
5911 DisplayComment(currentMove - 1, commentList[currentMove]);
5918 LoadGameOneMove(readAhead)
5919 ChessMove readAhead;
5921 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
5922 char promoChar = NULLCHAR;
5927 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
5928 gameMode != AnalyzeMode && gameMode != Training) {
5933 yyboardindex = forwardMostMove;
5934 if (readAhead != (ChessMove)0) {
5935 moveType = readAhead;
5937 if (gameFileFP == NULL)
5939 moveType = (ChessMove) yylex();
5945 if (appData.debugMode)
5946 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
5948 if (*p == '{' || *p == '[' || *p == '(') {
5949 p[strlen(p) - 1] = NULLCHAR;
5953 /* append the comment but don't display it */
5954 while (*p == '\n') p++;
5955 AppendComment(currentMove, p);
5958 case WhiteCapturesEnPassant:
5959 case BlackCapturesEnPassant:
5960 case WhitePromotionQueen:
5961 case BlackPromotionQueen:
5962 case WhitePromotionRook:
5963 case BlackPromotionRook:
5964 case WhitePromotionBishop:
5965 case BlackPromotionBishop:
5966 case WhitePromotionKnight:
5967 case BlackPromotionKnight:
5968 case WhitePromotionKing:
5969 case BlackPromotionKing:
5971 case WhiteKingSideCastle:
5972 case WhiteQueenSideCastle:
5973 case BlackKingSideCastle:
5974 case BlackQueenSideCastle:
5975 case WhiteKingSideCastleWild:
5976 case WhiteQueenSideCastleWild:
5977 case BlackKingSideCastleWild:
5978 case BlackQueenSideCastleWild:
5980 case WhiteHSideCastleFR:
5981 case WhiteASideCastleFR:
5982 case BlackHSideCastleFR:
5983 case BlackASideCastleFR:
5985 if (appData.debugMode)
5986 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
5987 fromX = currentMoveString[0] - 'a';
5988 fromY = currentMoveString[1] - '1';
5989 toX = currentMoveString[2] - 'a';
5990 toY = currentMoveString[3] - '1';
5991 promoChar = currentMoveString[4];
5996 if (appData.debugMode)
5997 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
5998 fromX = moveType == WhiteDrop ?
5999 (int) CharToPiece(ToUpper(currentMoveString[0])) :
6000 (int) CharToPiece(ToLower(currentMoveString[0]));
6002 toX = currentMoveString[2] - 'a';
6003 toY = currentMoveString[3] - '1';
6009 case GameUnfinished:
6010 if (appData.debugMode)
6011 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
6012 p = strchr(yy_text, '{');
6013 if (p == NULL) p = strchr(yy_text, '(');
6016 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
6018 q = strchr(p, *p == '{' ? '}' : ')');
6019 if (q != NULL) *q = NULLCHAR;
6022 GameEnds(moveType, p, GE_FILE);
6024 if (cmailMsgLoaded) {
6026 flipView = WhiteOnMove(currentMove);
6027 if (moveType == GameUnfinished) flipView = !flipView;
6028 if (appData.debugMode)
6029 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
6033 case (ChessMove) 0: /* end of file */
6034 if (appData.debugMode)
6035 fprintf(debugFP, "Parser hit end of file\n");
6036 switch (MateTest(boards[currentMove], PosFlags(currentMove),
6042 if (WhiteOnMove(currentMove)) {
6043 GameEnds(BlackWins, "Black mates", GE_FILE);
6045 GameEnds(WhiteWins, "White mates", GE_FILE);
6049 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
6056 if (lastLoadGameStart == GNUChessGame) {
6057 /* GNUChessGames have numbers, but they aren't move numbers */
6058 if (appData.debugMode)
6059 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
6060 yy_text, (int) moveType);
6061 return LoadGameOneMove((ChessMove)0); /* tail recursion */
6063 /* else fall thru */
6068 /* Reached start of next game in file */
6069 if (appData.debugMode)
6070 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
6071 switch (MateTest(boards[currentMove], PosFlags(currentMove),
6077 if (WhiteOnMove(currentMove)) {
6078 GameEnds(BlackWins, "Black mates", GE_FILE);
6080 GameEnds(WhiteWins, "White mates", GE_FILE);
6084 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
6090 case PositionDiagram: /* should not happen; ignore */
6091 case ElapsedTime: /* ignore */
6092 case NAG: /* ignore */
6093 if (appData.debugMode)
6094 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
6095 yy_text, (int) moveType);
6096 return LoadGameOneMove((ChessMove)0); /* tail recursion */
6099 if (appData.testLegality) {
6100 if (appData.debugMode)
6101 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
6102 sprintf(move, "Illegal move: %d.%s%s",
6103 (forwardMostMove / 2) + 1,
6104 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
6105 DisplayError(move, 0);
6108 if (appData.debugMode)
6109 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
6110 yy_text, currentMoveString);
6111 fromX = currentMoveString[0] - 'a';
6112 fromY = currentMoveString[1] - '1';
6113 toX = currentMoveString[2] - 'a';
6114 toY = currentMoveString[3] - '1';
6115 promoChar = currentMoveString[4];
6120 if (appData.debugMode)
6121 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
6122 sprintf(move, "Ambiguous move: %d.%s%s",
6123 (forwardMostMove / 2) + 1,
6124 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
6125 DisplayError(move, 0);
6130 case ImpossibleMove:
6131 if (appData.debugMode)
6132 fprintf(debugFP, "Parsed ImpossibleMove: %s\n", yy_text);
6133 sprintf(move, "Illegal move: %d.%s%s",
6134 (forwardMostMove / 2) + 1,
6135 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
6136 DisplayError(move, 0);
6142 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
6143 DrawPosition(FALSE, boards[currentMove]);
6144 DisplayBothClocks();
6145 if (!appData.matchMode && commentList[currentMove] != NULL)
6146 DisplayComment(currentMove - 1, commentList[currentMove]);
6148 (void) StopLoadGameTimer();
6150 cmailOldMove = forwardMostMove;
6153 /* currentMoveString is set as a side-effect of yylex */
6154 strcat(currentMoveString, "\n");
6155 strcpy(moveList[forwardMostMove], currentMoveString);
6157 thinkOutput[0] = NULLCHAR;
6158 MakeMove(fromX, fromY, toX, toY, promoChar);
6159 currentMove = forwardMostMove;
6164 /* Load the nth game from the given file */
6166 LoadGameFromFile(filename, n, title, useList)
6170 /*Boolean*/ int useList;
6175 if (strcmp(filename, "-") == 0) {
6179 f = fopen(filename, "rb");
6181 sprintf(buf, "Can't open \"%s\"", filename);
6182 DisplayError(buf, errno);
6186 if (fseek(f, 0, 0) == -1) {
6187 /* f is not seekable; probably a pipe */
6190 if (useList && n == 0) {
6191 int error = GameListBuild(f);
6193 DisplayError("Cannot build game list", error);
6194 } else if (!ListEmpty(&gameList) &&
6195 ((ListGame *) gameList.tailPred)->number > 1) {
6196 GameListPopUp(f, title);
6203 return LoadGame(f, n, title, FALSE);
6208 MakeRegisteredMove()
6210 int fromX, fromY, toX, toY;
6212 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
6213 switch (cmailMoveType[lastLoadGameNumber - 1]) {
6216 if (appData.debugMode)
6217 fprintf(debugFP, "Restoring %s for game %d\n",
6218 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
6220 thinkOutput[0] = NULLCHAR;
6221 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
6222 fromX = cmailMove[lastLoadGameNumber - 1][0] - 'a';
6223 fromY = cmailMove[lastLoadGameNumber - 1][1] - '1';
6224 toX = cmailMove[lastLoadGameNumber - 1][2] - 'a';
6225 toY = cmailMove[lastLoadGameNumber - 1][3] - '1';
6226 promoChar = cmailMove[lastLoadGameNumber - 1][4];
6227 MakeMove(fromX, fromY, toX, toY, promoChar);
6228 ShowMove(fromX, fromY, toX, toY);
6230 switch (MateTest(boards[currentMove], PosFlags(currentMove),
6237 if (WhiteOnMove(currentMove)) {
6238 GameEnds(BlackWins, "Black mates", GE_PLAYER);
6240 GameEnds(WhiteWins, "White mates", GE_PLAYER);
6245 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6252 if (WhiteOnMove(currentMove)) {
6253 GameEnds(BlackWins, "White resigns", GE_PLAYER);
6255 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
6260 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
6271 /* Wrapper around LoadGame for use when a Cmail message is loaded */
6273 CmailLoadGame(f, gameNumber, title, useList)
6281 if (gameNumber > nCmailGames) {
6282 DisplayError("No more games in this message", 0);
6285 if (f == lastLoadGameFP) {
6286 int offset = gameNumber - lastLoadGameNumber;
6288 cmailMsg[0] = NULLCHAR;
6289 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
6290 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
6291 nCmailMovesRegistered--;
6293 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6294 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
6295 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
6298 if (! RegisterMove()) return FALSE;
6302 retVal = LoadGame(f, gameNumber, title, useList);
6304 /* Make move registered during previous look at this game, if any */
6305 MakeRegisteredMove();
6307 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
6308 commentList[currentMove]
6309 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
6310 DisplayComment(currentMove - 1, commentList[currentMove]);
6316 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
6321 int gameNumber = lastLoadGameNumber + offset;
6322 if (lastLoadGameFP == NULL) {
6323 DisplayError("No game has been loaded yet", 0);
6326 if (gameNumber <= 0) {
6327 DisplayError("Can't back up any further", 0);
6330 if (cmailMsgLoaded) {
6331 return CmailLoadGame(lastLoadGameFP, gameNumber,
6332 lastLoadGameTitle, lastLoadGameUseList);
6334 return LoadGame(lastLoadGameFP, gameNumber,
6335 lastLoadGameTitle, lastLoadGameUseList);
6341 /* Load the nth game from open file f */
6343 LoadGame(f, gameNumber, title, useList)
6351 int gn = gameNumber;
6352 ListGame *lg = NULL;
6355 GameMode oldGameMode;
6357 if (appData.debugMode)
6358 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
6360 if (gameMode == Training )
6361 SetTrainingModeOff();
6363 oldGameMode = gameMode;
6364 if (gameMode != BeginningOfGame) {
6369 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
6370 fclose(lastLoadGameFP);
6374 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
6377 fseek(f, lg->offset, 0);
6378 GameListHighlight(gameNumber);
6382 DisplayError("Game number out of range", 0);
6387 if (fseek(f, 0, 0) == -1) {
6388 if (f == lastLoadGameFP ?
6389 gameNumber == lastLoadGameNumber + 1 :
6393 DisplayError("Can't seek on game file", 0);
6399 lastLoadGameNumber = gameNumber;
6400 strcpy(lastLoadGameTitle, title);
6401 lastLoadGameUseList = useList;
6406 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
6407 sprintf(buf, "%s vs. %s", lg->gameInfo.white,
6408 lg->gameInfo.black);
6410 } else if (*title != NULLCHAR) {
6411 if (gameNumber > 1) {
6412 sprintf(buf, "%s %d", title, gameNumber);
6415 DisplayTitle(title);
6419 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
6420 gameMode = PlayFromGameFile;
6424 currentMove = forwardMostMove = backwardMostMove = 0;
6425 CopyBoard(boards[0], initialPosition);
6429 * Skip the first gn-1 games in the file.
6430 * Also skip over anything that precedes an identifiable
6431 * start of game marker, to avoid being confused by
6432 * garbage at the start of the file. Currently
6433 * recognized start of game markers are the move number "1",
6434 * the pattern "gnuchess .* game", the pattern
6435 * "^[#;%] [^ ]* game file", and a PGN tag block.
6436 * A game that starts with one of the latter two patterns
6437 * will also have a move number 1, possibly
6438 * following a position diagram.
6439 * 5-4-02: Let's try being more lenient and allowing a game to
6440 * start with an unnumbered move. Does that break anything?
6442 cm = lastLoadGameStart = (ChessMove) 0;
6444 yyboardindex = forwardMostMove;
6445 cm = (ChessMove) yylex();
6448 if (cmailMsgLoaded) {
6449 nCmailGames = CMAIL_MAX_GAMES - gn;
6452 DisplayError("Game not found in file", 0);
6459 lastLoadGameStart = cm;
6463 switch (lastLoadGameStart) {
6470 gn--; /* count this game */
6471 lastLoadGameStart = cm;
6480 switch (lastLoadGameStart) {
6485 gn--; /* count this game */
6486 lastLoadGameStart = cm;
6489 lastLoadGameStart = cm; /* game counted already */
6497 yyboardindex = forwardMostMove;
6498 cm = (ChessMove) yylex();
6499 } while (cm == PGNTag || cm == Comment);
6506 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
6507 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
6508 != CMAIL_OLD_RESULT) {
6510 cmailResult[ CMAIL_MAX_GAMES
6511 - gn - 1] = CMAIL_OLD_RESULT;
6517 /* Only a NormalMove can be at the start of a game
6518 * without a position diagram. */
6519 if (lastLoadGameStart == (ChessMove) 0) {
6521 lastLoadGameStart = MoveNumberOne;
6530 if (appData.debugMode)
6531 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
6533 if (cm == XBoardGame) {
6534 /* Skip any header junk before position diagram and/or move 1 */
6536 yyboardindex = forwardMostMove;
6537 cm = (ChessMove) yylex();
6539 if (cm == (ChessMove) 0 ||
6540 cm == GNUChessGame || cm == XBoardGame) {
6541 /* Empty game; pretend end-of-file and handle later */
6546 if (cm == MoveNumberOne || cm == PositionDiagram ||
6547 cm == PGNTag || cm == Comment)
6550 } else if (cm == GNUChessGame) {
6551 if (gameInfo.event != NULL) {
6552 free(gameInfo.event);
6554 gameInfo.event = StrSave(yy_text);
6557 startedFromSetupPosition = FALSE;
6558 while (cm == PGNTag) {
6559 if (appData.debugMode)
6560 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
6561 err = ParsePGNTag(yy_text, &gameInfo);
6562 if (!err) numPGNTags++;
6564 if (gameInfo.fen != NULL) {
6565 Board initial_position;
6566 startedFromSetupPosition = TRUE;
6567 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
6569 DisplayError("Bad FEN position in file", 0);
6572 CopyBoard(boards[0], initial_position);
6573 if (blackPlaysFirst) {
6574 currentMove = forwardMostMove = backwardMostMove = 1;
6575 CopyBoard(boards[1], initial_position);
6576 strcpy(moveList[0], "");
6577 strcpy(parseList[0], "");
6578 timeRemaining[0][1] = whiteTimeRemaining;
6579 timeRemaining[1][1] = blackTimeRemaining;
6580 if (commentList[0] != NULL) {
6581 commentList[1] = commentList[0];
6582 commentList[0] = NULL;
6585 currentMove = forwardMostMove = backwardMostMove = 0;
6587 yyboardindex = forwardMostMove;
6589 gameInfo.fen = NULL;
6592 yyboardindex = forwardMostMove;
6593 cm = (ChessMove) yylex();
6595 /* Handle comments interspersed among the tags */
6596 while (cm == Comment) {
6598 if (appData.debugMode)
6599 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
6601 if (*p == '{' || *p == '[' || *p == '(') {
6602 p[strlen(p) - 1] = NULLCHAR;
6605 while (*p == '\n') p++;
6606 AppendComment(currentMove, p);
6607 yyboardindex = forwardMostMove;
6608 cm = (ChessMove) yylex();
6612 /* don't rely on existence of Event tag since if game was
6613 * pasted from clipboard the Event tag may not exist
6615 if (numPGNTags > 0){
6617 if (gameInfo.variant == VariantNormal) {
6618 gameInfo.variant = StringToVariant(gameInfo.event);
6621 if( appData.autoDisplayTags ) {
6622 tags = PGNTags(&gameInfo);
6623 TagsPopUp(tags, CmailMsg());
6628 /* Make something up, but don't display it now */
6633 if (cm == PositionDiagram) {
6636 Board initial_position;
6638 if (appData.debugMode)
6639 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
6641 if (!startedFromSetupPosition) {
6643 for (i = BOARD_SIZE - 1; i >= 0; i--)
6644 for (j = 0; j < BOARD_SIZE; p++)
6654 initial_position[i][j++] = CharToPiece(*p);
6657 while (*p == ' ' || *p == '\t' ||
6658 *p == '\n' || *p == '\r') p++;
6660 if (strncmp(p, "black", strlen("black"))==0)
6661 blackPlaysFirst = TRUE;
6663 blackPlaysFirst = FALSE;
6664 startedFromSetupPosition = TRUE;
6666 CopyBoard(boards[0], initial_position);
6667 if (blackPlaysFirst) {
6668 currentMove = forwardMostMove = backwardMostMove = 1;
6669 CopyBoard(boards[1], initial_position);
6670 strcpy(moveList[0], "");
6671 strcpy(parseList[0], "");
6672 timeRemaining[0][1] = whiteTimeRemaining;
6673 timeRemaining[1][1] = blackTimeRemaining;
6674 if (commentList[0] != NULL) {
6675 commentList[1] = commentList[0];
6676 commentList[0] = NULL;
6679 currentMove = forwardMostMove = backwardMostMove = 0;
6682 yyboardindex = forwardMostMove;
6683 cm = (ChessMove) yylex();
6686 if (first.pr == NoProc) {
6687 StartChessProgram(&first);
6689 InitChessProgram(&first);
6690 SendToProgram("force\n", &first);
6691 if (startedFromSetupPosition) {
6692 SendBoard(&first, forwardMostMove);
6693 DisplayBothClocks();
6696 while (cm == Comment) {
6698 if (appData.debugMode)
6699 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
6701 if (*p == '{' || *p == '[' || *p == '(') {
6702 p[strlen(p) - 1] = NULLCHAR;
6705 while (*p == '\n') p++;
6706 AppendComment(currentMove, p);
6707 yyboardindex = forwardMostMove;
6708 cm = (ChessMove) yylex();
6711 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
6712 cm == WhiteWins || cm == BlackWins ||
6713 cm == GameIsDrawn || cm == GameUnfinished) {
6714 DisplayMessage("", "No moves in game");
6715 if (cmailMsgLoaded) {
6716 if (appData.debugMode)
6717 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
6721 DrawPosition(FALSE, boards[currentMove]);
6722 DisplayBothClocks();
6723 gameMode = EditGame;
6730 if (commentList[currentMove] != NULL) {
6731 if (!matchMode && (pausing || appData.timeDelay != 0)) {
6732 DisplayComment(currentMove - 1, commentList[currentMove]);
6735 if (!matchMode && appData.timeDelay != 0)
6736 DrawPosition(FALSE, boards[currentMove]);
6738 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
6739 programStats.ok_to_send = 1;
6742 /* if the first token after the PGN tags is a move
6743 * and not move number 1, retrieve it from the parser
6745 if (cm != MoveNumberOne)
6746 LoadGameOneMove(cm);
6748 /* load the remaining moves from the file */
6749 while (LoadGameOneMove((ChessMove)0)) {
6750 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
6751 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
6754 /* rewind to the start of the game */
6755 currentMove = backwardMostMove;
6757 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
6759 if (oldGameMode == AnalyzeFile ||
6760 oldGameMode == AnalyzeMode) {
6764 if (matchMode || appData.timeDelay == 0) {
6766 gameMode = EditGame;
6768 } else if (appData.timeDelay > 0) {
6772 if (appData.debugMode)
6773 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
6777 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
6779 ReloadPosition(offset)
6782 int positionNumber = lastLoadPositionNumber + offset;
6783 if (lastLoadPositionFP == NULL) {
6784 DisplayError("No position has been loaded yet", 0);
6787 if (positionNumber <= 0) {
6788 DisplayError("Can't back up any further", 0);
6791 return LoadPosition(lastLoadPositionFP, positionNumber,
6792 lastLoadPositionTitle);
6795 /* Load the nth position from the given file */
6797 LoadPositionFromFile(filename, n, title)
6805 if (strcmp(filename, "-") == 0) {
6806 return LoadPosition(stdin, n, "stdin");
6808 f = fopen(filename, "rb");
6810 sprintf(buf, "Can't open \"%s\"", filename);
6811 DisplayError(buf, errno);
6814 return LoadPosition(f, n, title);
6819 /* Load the nth position from the given open file, and close it */
6821 LoadPosition(f, positionNumber, title)
6826 char *p, line[MSG_SIZ];
6827 Board initial_position;
6828 int i, j, fenMode, pn;
6830 if (gameMode == Training )
6831 SetTrainingModeOff();
6833 if (gameMode != BeginningOfGame) {
6836 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
6837 fclose(lastLoadPositionFP);
6839 if (positionNumber == 0) positionNumber = 1;
6840 lastLoadPositionFP = f;
6841 lastLoadPositionNumber = positionNumber;
6842 strcpy(lastLoadPositionTitle, title);
6843 if (first.pr == NoProc) {
6844 StartChessProgram(&first);
6845 InitChessProgram(&first);
6847 pn = positionNumber;
6848 if (positionNumber < 0) {
6849 /* Negative position number means to seek to that byte offset */
6850 if (fseek(f, -positionNumber, 0) == -1) {
6851 DisplayError("Can't seek on position file", 0);
6856 if (fseek(f, 0, 0) == -1) {
6857 if (f == lastLoadPositionFP ?
6858 positionNumber == lastLoadPositionNumber + 1 :
6859 positionNumber == 1) {
6862 DisplayError("Can't seek on position file", 0);
6867 /* See if this file is FEN or old-style xboard */
6868 if (fgets(line, MSG_SIZ, f) == NULL) {
6869 DisplayError("Position not found in file", 0);
6877 case 'p': case 'n': case 'b': case 'r': case 'q': case 'k':
6878 case 'P': case 'N': case 'B': case 'R': case 'Q': case 'K':
6879 case '1': case '2': case '3': case '4': case '5': case '6':
6886 if (fenMode || line[0] == '#') pn--;
6888 /* skip postions before number pn */
6889 if (fgets(line, MSG_SIZ, f) == NULL) {
6891 DisplayError("Position not found in file", 0);
6894 if (fenMode || line[0] == '#') pn--;
6899 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
6900 DisplayError("Bad FEN position in file", 0);
6904 (void) fgets(line, MSG_SIZ, f);
6905 (void) fgets(line, MSG_SIZ, f);
6907 for (i = BOARD_SIZE - 1; i >= 0; i--) {
6908 (void) fgets(line, MSG_SIZ, f);
6909 for (p = line, j = 0; j < BOARD_SIZE; p++) {
6912 initial_position[i][j++] = CharToPiece(*p);
6916 blackPlaysFirst = FALSE;
6918 (void) fgets(line, MSG_SIZ, f);
6919 if (strncmp(line, "black", strlen("black"))==0)
6920 blackPlaysFirst = TRUE;
6923 startedFromSetupPosition = TRUE;
6925 SendToProgram("force\n", &first);
6926 CopyBoard(boards[0], initial_position);
6927 if (blackPlaysFirst) {
6928 currentMove = forwardMostMove = backwardMostMove = 1;
6929 strcpy(moveList[0], "");
6930 strcpy(parseList[0], "");
6931 CopyBoard(boards[1], initial_position);
6932 DisplayMessage("", "Black to play");
6934 currentMove = forwardMostMove = backwardMostMove = 0;
6935 DisplayMessage("", "White to play");
6937 SendBoard(&first, forwardMostMove);
6939 if (positionNumber > 1) {
6940 sprintf(line, "%s %d", title, positionNumber);
6943 DisplayTitle(title);
6945 gameMode = EditGame;
6948 timeRemaining[0][1] = whiteTimeRemaining;
6949 timeRemaining[1][1] = blackTimeRemaining;
6950 DrawPosition(FALSE, boards[currentMove]);
6957 CopyPlayerNameIntoFileName(dest, src)
6960 while (*src != NULLCHAR && *src != ',') {
6965 *(*dest)++ = *src++;
6970 char *DefaultFileName(ext)
6973 static char def[MSG_SIZ];
6976 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
6978 CopyPlayerNameIntoFileName(&p, gameInfo.white);
6980 CopyPlayerNameIntoFileName(&p, gameInfo.black);
6989 /* Save the current game to the given file */
6991 SaveGameToFile(filename, append)
6998 if (strcmp(filename, "-") == 0) {
6999 return SaveGame(stdout, 0, NULL);
7001 f = fopen(filename, append ? "a" : "w");
7003 sprintf(buf, "Can't open \"%s\"", filename);
7004 DisplayError(buf, errno);
7007 return SaveGame(f, 0, NULL);
7016 static char buf[MSG_SIZ];
7019 p = strchr(str, ' ');
7020 if (p == NULL) return str;
7021 strncpy(buf, str, p - str);
7022 buf[p - str] = NULLCHAR;
7026 #define PGN_MAX_LINE 75
7028 #define PGN_SIDE_WHITE 0
7029 #define PGN_SIDE_BLACK 1
7031 static int FindFirstMoveOutOfBook( int side )
7035 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
7036 int index = backwardMostMove;
7037 int has_book_hit = 0;
7039 if( (index % 2) != side ) {
7043 while( index < forwardMostMove ) {
7044 /* Check to see if engine is in book */
7045 int depth = pvInfoList[index].depth;
7046 int score = pvInfoList[index].score;
7050 in_book = 1; /* Yace */
7053 if( depth <= 1 || depth == 63 /* Zappa */ ) {
7058 has_book_hit += in_book;
7073 void GetOutOfBookInfo( char * buf )
7077 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
7079 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
7080 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
7084 if( oob[0] >= 0 || oob[1] >= 0 ) {
7085 for( i=0; i<2; i++ ) {
7089 if( i > 0 && oob[0] >= 0 ) {
7093 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
7094 sprintf( buf+strlen(buf), "%s%.2f",
7095 pvInfoList[idx].score >= 0 ? "+" : "",
7096 pvInfoList[idx].score / 100.0 );
7102 /* Save game in PGN style and close the file */
7107 int i, offset, linelen, newblock;
7111 int movelen, numlen, blank;
7112 char move_buffer[100]; /* [AS] Buffer for move+PV info */
7114 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
7116 tm = time((time_t *) NULL);
7118 PrintPGNTags(f, &gameInfo);
7120 if (backwardMostMove > 0 || startedFromSetupPosition) {
7121 char *fen = PositionToFEN(backwardMostMove, 1);
7122 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
7123 fprintf(f, "\n{--------------\n");
7124 PrintPosition(f, backwardMostMove);
7125 fprintf(f, "--------------}\n");
7129 /* [AS] Out of book annotation */
7130 if( appData.saveOutOfBookInfo ) {
7133 GetOutOfBookInfo( buf );
7135 if( buf[0] != '\0' ) {
7136 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
7143 i = backwardMostMove;
7147 while (i < forwardMostMove) {
7148 /* Print comments preceding this move */
7149 if (commentList[i] != NULL) {
7150 if (linelen > 0) fprintf(f, "\n");
7151 fprintf(f, "{\n%s}\n", commentList[i]);
7156 /* Format move number */
7158 sprintf(numtext, "%d.", (i - offset)/2 + 1);
7161 sprintf(numtext, "%d...", (i - offset)/2 + 1);
7163 numtext[0] = NULLCHAR;
7166 numlen = strlen(numtext);
7169 /* Print move number */
7170 blank = linelen > 0 && numlen > 0;
7171 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
7180 fprintf(f, numtext);
7184 movetext = SavePart(parseList[i]);
7186 /* [AS] Add PV info if present */
7187 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
7188 sprintf( move_buffer, "%s {%s%.2f/%d}",
7190 pvInfoList[i].score >= 0 ? "+" : "",
7191 pvInfoList[i].score / 100.0,
7192 pvInfoList[i].depth );
7193 movetext = move_buffer;
7196 movelen = strlen(movetext);
7199 blank = linelen > 0 && movelen > 0;
7200 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
7209 fprintf(f, movetext);
7215 /* Start a new line */
7216 if (linelen > 0) fprintf(f, "\n");
7218 /* Print comments after last move */
7219 if (commentList[i] != NULL) {
7220 fprintf(f, "{\n%s}\n", commentList[i]);
7224 if (gameInfo.resultDetails != NULL &&
7225 gameInfo.resultDetails[0] != NULLCHAR) {
7226 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
7227 PGNResult(gameInfo.result));
7229 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
7236 /* Save game in old style and close the file */
7244 tm = time((time_t *) NULL);
7246 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
7249 if (backwardMostMove > 0 || startedFromSetupPosition) {
7250 fprintf(f, "\n[--------------\n");
7251 PrintPosition(f, backwardMostMove);
7252 fprintf(f, "--------------]\n");
7257 i = backwardMostMove;
7258 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
7260 while (i < forwardMostMove) {
7261 if (commentList[i] != NULL) {
7262 fprintf(f, "[%s]\n", commentList[i]);
7266 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
7269 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
7271 if (commentList[i] != NULL) {
7275 if (i >= forwardMostMove) {
7279 fprintf(f, "%s\n", parseList[i]);
7284 if (commentList[i] != NULL) {
7285 fprintf(f, "[%s]\n", commentList[i]);
7288 /* This isn't really the old style, but it's close enough */
7289 if (gameInfo.resultDetails != NULL &&
7290 gameInfo.resultDetails[0] != NULLCHAR) {
7291 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
7292 gameInfo.resultDetails);
7294 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
7301 /* Save the current game to open file f and close the file */
7303 SaveGame(f, dummy, dummy2)
7308 if (gameMode == EditPosition) EditPositionDone();
7309 if (appData.oldSaveStyle)
7310 return SaveGameOldStyle(f);
7312 return SaveGamePGN(f);
7315 /* Save the current position to the given file */
7317 SavePositionToFile(filename)
7323 if (strcmp(filename, "-") == 0) {
7324 return SavePosition(stdout, 0, NULL);
7326 f = fopen(filename, "a");
7328 sprintf(buf, "Can't open \"%s\"", filename);
7329 DisplayError(buf, errno);
7332 SavePosition(f, 0, NULL);
7338 /* Save the current position to the given open file and close the file */
7340 SavePosition(f, dummy, dummy2)
7348 if (appData.oldSaveStyle) {
7349 tm = time((time_t *) NULL);
7351 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
7353 fprintf(f, "[--------------\n");
7354 PrintPosition(f, currentMove);
7355 fprintf(f, "--------------]\n");
7357 fen = PositionToFEN(currentMove, 1);
7358 fprintf(f, "%s\n", fen);
7366 ReloadCmailMsgEvent(unregister)
7370 static char *inFilename = NULL;
7371 static char *outFilename;
7373 struct stat inbuf, outbuf;
7376 /* Any registered moves are unregistered if unregister is set, */
7377 /* i.e. invoked by the signal handler */
7379 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
7380 cmailMoveRegistered[i] = FALSE;
7381 if (cmailCommentList[i] != NULL) {
7382 free(cmailCommentList[i]);
7383 cmailCommentList[i] = NULL;
7386 nCmailMovesRegistered = 0;
7389 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
7390 cmailResult[i] = CMAIL_NOT_RESULT;
7394 if (inFilename == NULL) {
7395 /* Because the filenames are static they only get malloced once */
7396 /* and they never get freed */
7397 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
7398 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
7400 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
7401 sprintf(outFilename, "%s.out", appData.cmailGameName);
7404 status = stat(outFilename, &outbuf);
7406 cmailMailedMove = FALSE;
7408 status = stat(inFilename, &inbuf);
7409 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
7412 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
7413 counts the games, notes how each one terminated, etc.
7415 It would be nice to remove this kludge and instead gather all
7416 the information while building the game list. (And to keep it
7417 in the game list nodes instead of having a bunch of fixed-size
7418 parallel arrays.) Note this will require getting each game's
7419 termination from the PGN tags, as the game list builder does
7420 not process the game moves. --mann
7422 cmailMsgLoaded = TRUE;
7423 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
7425 /* Load first game in the file or popup game menu */
7426 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
7436 char string[MSG_SIZ];
7438 if ( cmailMailedMove
7439 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
7440 return TRUE; /* Allow free viewing */
7443 /* Unregister move to ensure that we don't leave RegisterMove */
7444 /* with the move registered when the conditions for registering no */
7446 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
7447 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
7448 nCmailMovesRegistered --;
7450 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
7452 free(cmailCommentList[lastLoadGameNumber - 1]);
7453 cmailCommentList[lastLoadGameNumber - 1] = NULL;
7457 if (cmailOldMove == -1) {
7458 DisplayError("You have edited the game history.\nUse Reload Same Game and make your move again.", 0);
7462 if (currentMove > cmailOldMove + 1) {
7463 DisplayError("You have entered too many moves.\nBack up to the correct position and try again.", 0);
7467 if (currentMove < cmailOldMove) {
7468 DisplayError("Displayed position is not current.\nStep forward to the correct position and try again.", 0);
7472 if (forwardMostMove > currentMove) {
7473 /* Silently truncate extra moves */
7477 if ( (currentMove == cmailOldMove + 1)
7478 || ( (currentMove == cmailOldMove)
7479 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
7480 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
7481 if (gameInfo.result != GameUnfinished) {
7482 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
7485 if (commentList[currentMove] != NULL) {
7486 cmailCommentList[lastLoadGameNumber - 1]
7487 = StrSave(commentList[currentMove]);
7489 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
7491 if (appData.debugMode)
7492 fprintf(debugFP, "Saving %s for game %d\n",
7493 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
7496 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
7498 f = fopen(string, "w");
7499 if (appData.oldSaveStyle) {
7500 SaveGameOldStyle(f); /* also closes the file */
7502 sprintf(string, "%s.pos.out", appData.cmailGameName);
7503 f = fopen(string, "w");
7504 SavePosition(f, 0, NULL); /* also closes the file */
7506 fprintf(f, "{--------------\n");
7507 PrintPosition(f, currentMove);
7508 fprintf(f, "--------------}\n\n");
7510 SaveGame(f, 0, NULL); /* also closes the file*/
7513 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
7514 nCmailMovesRegistered ++;
7515 } else if (nCmailGames == 1) {
7516 DisplayError("You have not made a move yet", 0);
7527 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
7528 FILE *commandOutput;
7529 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
7530 int nBytes = 0; /* Suppress warnings on uninitialized variables */
7536 if (! cmailMsgLoaded) {
7537 DisplayError("The cmail message is not loaded.\nUse Reload CMail Message and make your move again.", 0);
7541 if (nCmailGames == nCmailResults) {
7542 DisplayError("No unfinished games", 0);
7546 #if CMAIL_PROHIBIT_REMAIL
7547 if (cmailMailedMove) {
7548 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);
7549 DisplayError(msg, 0);
7554 if (! (cmailMailedMove || RegisterMove())) return;
7556 if ( cmailMailedMove
7557 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
7558 sprintf(string, partCommandString,
7559 appData.debugMode ? " -v" : "", appData.cmailGameName);
7560 commandOutput = popen(string, "rb");
7562 if (commandOutput == NULL) {
7563 DisplayError("Failed to invoke cmail", 0);
7565 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
7566 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
7569 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
7570 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
7571 nBytes = MSG_SIZ - 1;
7573 (void) memcpy(msg, buffer, nBytes);
7575 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
7577 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
7578 cmailMailedMove = TRUE; /* Prevent >1 moves */
7581 for (i = 0; i < nCmailGames; i ++) {
7582 if (cmailResult[i] == CMAIL_NOT_RESULT) {
7587 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
7589 sprintf(buffer, "%s/%s.%s.archive",
7591 appData.cmailGameName,
7593 LoadGameFromFile(buffer, 1, buffer, FALSE);
7594 cmailMsgLoaded = FALSE;
7598 DisplayInformation(msg);
7599 pclose(commandOutput);
7602 if ((*cmailMsg) != '\0') {
7603 DisplayInformation(cmailMsg);
7617 int prependComma = 0;
7619 char string[MSG_SIZ]; /* Space for game-list */
7622 if (!cmailMsgLoaded) return "";
7624 if (cmailMailedMove) {
7625 sprintf(cmailMsg, "Waiting for reply from opponent\n");
7627 /* Create a list of games left */
7628 sprintf(string, "[");
7629 for (i = 0; i < nCmailGames; i ++) {
7630 if (! ( cmailMoveRegistered[i]
7631 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
7633 sprintf(number, ",%d", i + 1);
7635 sprintf(number, "%d", i + 1);
7639 strcat(string, number);
7642 strcat(string, "]");
7644 if (nCmailMovesRegistered + nCmailResults == 0) {
7645 switch (nCmailGames) {
7648 "Still need to make move for game\n");
7653 "Still need to make moves for both games\n");
7658 "Still need to make moves for all %d games\n",
7663 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
7666 "Still need to make a move for game %s\n",
7671 if (nCmailResults == nCmailGames) {
7672 sprintf(cmailMsg, "No unfinished games\n");
7674 sprintf(cmailMsg, "Ready to send mail\n");
7680 "Still need to make moves for games %s\n",
7692 if (gameMode == Training)
7693 SetTrainingModeOff();
7696 cmailMsgLoaded = FALSE;
7697 if (appData.icsActive) {
7698 SendToICS(ics_prefix);
7699 SendToICS("refresh\n");
7703 static int exiting = 0;
7711 /* Give up on clean exit */
7715 /* Keep trying for clean exit */
7719 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
7721 if (telnetISR != NULL) {
7722 RemoveInputSource(telnetISR);
7724 if (icsPR != NoProc) {
7725 DestroyChildProcess(icsPR, TRUE);
7727 /* Save game if resource set and not already saved by GameEnds() */
7728 if (gameInfo.resultDetails == NULL && forwardMostMove > 0) {
7729 if (*appData.saveGameFile != NULLCHAR) {
7730 SaveGameToFile(appData.saveGameFile, TRUE);
7731 } else if (appData.autoSaveGames) {
7734 if (*appData.savePositionFile != NULLCHAR) {
7735 SavePositionToFile(appData.savePositionFile);
7738 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
7740 /* Kill off chess programs */
7741 if (first.pr != NoProc) {
7744 DoSleep( appData.delayBeforeQuit );
7745 SendToProgram("quit\n", &first);
7746 DoSleep( appData.delayAfterQuit );
7747 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
7749 if (second.pr != NoProc) {
7750 DoSleep( appData.delayBeforeQuit );
7751 SendToProgram("quit\n", &second);
7752 DoSleep( appData.delayAfterQuit );
7753 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
7755 if (first.isr != NULL) {
7756 RemoveInputSource(first.isr);
7758 if (second.isr != NULL) {
7759 RemoveInputSource(second.isr);
7769 if (appData.debugMode)
7770 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
7774 if (gameMode == MachinePlaysWhite ||
7775 gameMode == MachinePlaysBlack) {
7778 DisplayBothClocks();
7780 if (gameMode == PlayFromGameFile) {
7781 if (appData.timeDelay >= 0)
7783 } else if (gameMode == IcsExamining && pauseExamInvalid) {
7785 SendToICS(ics_prefix);
7786 SendToICS("refresh\n");
7787 } else if (currentMove < forwardMostMove) {
7788 ForwardInner(forwardMostMove);
7790 pauseExamInvalid = FALSE;
7796 pauseExamForwardMostMove = forwardMostMove;
7797 pauseExamInvalid = FALSE;
7800 case IcsPlayingWhite:
7801 case IcsPlayingBlack:
7805 case PlayFromGameFile:
7806 (void) StopLoadGameTimer();
7810 case BeginningOfGame:
7811 if (appData.icsActive) return;
7812 /* else fall through */
7813 case MachinePlaysWhite:
7814 case MachinePlaysBlack:
7815 case TwoMachinesPlay:
7816 if (forwardMostMove == 0)
7817 return; /* don't pause if no one has moved */
7818 if ((gameMode == MachinePlaysWhite &&
7819 !WhiteOnMove(forwardMostMove)) ||
7820 (gameMode == MachinePlaysBlack &&
7821 WhiteOnMove(forwardMostMove))) {
7834 char title[MSG_SIZ];
7836 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
7837 strcpy(title, "Edit comment");
7839 sprintf(title, "Edit comment on %d.%s%s", (currentMove - 1) / 2 + 1,
7840 WhiteOnMove(currentMove - 1) ? " " : ".. ",
7841 parseList[currentMove - 1]);
7844 EditCommentPopUp(currentMove, title, commentList[currentMove]);
7851 char *tags = PGNTags(&gameInfo);
7852 EditTagsPopUp(tags);
7859 if (appData.noChessProgram || gameMode == AnalyzeMode)
7862 if (gameMode != AnalyzeFile) {
7864 if (gameMode != EditGame) return;
7865 ResurrectChessProgram();
7866 SendToProgram("analyze\n", &first);
7867 first.analyzing = TRUE;
7868 /*first.maybeThinking = TRUE;*/
7869 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
7870 AnalysisPopUp("Analysis",
7871 "Starting analysis mode...\nIf this message stays up, your chess program does not support analysis.");
7873 gameMode = AnalyzeMode;
7878 StartAnalysisClock();
7879 GetTimeMark(&lastNodeCountTime);
7886 if (appData.noChessProgram || gameMode == AnalyzeFile)
7889 if (gameMode != AnalyzeMode) {
7891 if (gameMode != EditGame) return;
7892 ResurrectChessProgram();
7893 SendToProgram("analyze\n", &first);
7894 first.analyzing = TRUE;
7895 /*first.maybeThinking = TRUE;*/
7896 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
7897 AnalysisPopUp("Analysis",
7898 "Starting analysis mode...\nIf this message stays up, your chess program does not support analysis.");
7900 gameMode = AnalyzeFile;
7905 StartAnalysisClock();
7906 GetTimeMark(&lastNodeCountTime);
7915 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
7919 if (gameMode == PlayFromGameFile ||
7920 gameMode == TwoMachinesPlay ||
7921 gameMode == Training ||
7922 gameMode == AnalyzeMode ||
7923 gameMode == EndOfGame)
7926 if (gameMode == EditPosition)
7929 if (!WhiteOnMove(currentMove)) {
7930 DisplayError("It is not White's turn", 0);
7934 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
7937 if (gameMode == EditGame || gameMode == AnalyzeMode ||
7938 gameMode == AnalyzeFile)
7941 ResurrectChessProgram(); /* in case it isn't running */
7942 gameMode = MachinePlaysWhite;
7946 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
7948 if (first.sendName) {
7949 sprintf(buf, "name %s\n", gameInfo.black);
7950 SendToProgram(buf, &first);
7952 if (first.sendTime) {
7953 if (first.useColors) {
7954 SendToProgram("black\n", &first); /*gnu kludge*/
7956 SendTimeRemaining(&first, TRUE);
7958 if (first.useColors) {
7959 SendToProgram("white\ngo\n", &first);
7961 SendToProgram("go\n", &first);
7963 SetMachineThinkingEnables();
7964 first.maybeThinking = TRUE;
7967 if (appData.autoFlipView && !flipView) {
7968 flipView = !flipView;
7969 DrawPosition(FALSE, NULL);
7978 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
7982 if (gameMode == PlayFromGameFile ||
7983 gameMode == TwoMachinesPlay ||
7984 gameMode == Training ||
7985 gameMode == AnalyzeMode ||
7986 gameMode == EndOfGame)
7989 if (gameMode == EditPosition)
7992 if (WhiteOnMove(currentMove)) {
7993 DisplayError("It is not Black's turn", 0);
7997 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
8000 if (gameMode == EditGame || gameMode == AnalyzeMode ||
8001 gameMode == AnalyzeFile)
8004 ResurrectChessProgram(); /* in case it isn't running */
8005 gameMode = MachinePlaysBlack;
8009 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
8011 if (first.sendName) {
8012 sprintf(buf, "name %s\n", gameInfo.white);
8013 SendToProgram(buf, &first);
8015 if (first.sendTime) {
8016 if (first.useColors) {
8017 SendToProgram("white\n", &first); /*gnu kludge*/
8019 SendTimeRemaining(&first, FALSE);
8021 if (first.useColors) {
8022 SendToProgram("black\ngo\n", &first);
8024 SendToProgram("go\n", &first);
8026 SetMachineThinkingEnables();
8027 first.maybeThinking = TRUE;
8030 if (appData.autoFlipView && flipView) {
8031 flipView = !flipView;
8032 DrawPosition(FALSE, NULL);
8038 DisplayTwoMachinesTitle()
8041 if (appData.matchGames > 0) {
8042 if (first.twoMachinesColor[0] == 'w') {
8043 sprintf(buf, "%s vs. %s (%d-%d-%d)",
8044 gameInfo.white, gameInfo.black,
8045 first.matchWins, second.matchWins,
8046 matchGame - 1 - (first.matchWins + second.matchWins));
8048 sprintf(buf, "%s vs. %s (%d-%d-%d)",
8049 gameInfo.white, gameInfo.black,
8050 second.matchWins, first.matchWins,
8051 matchGame - 1 - (first.matchWins + second.matchWins));
8054 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
8060 TwoMachinesEvent P((void))
8064 ChessProgramState *onmove;
8066 if (appData.noChessProgram) return;
8069 case TwoMachinesPlay:
8071 case MachinePlaysWhite:
8072 case MachinePlaysBlack:
8073 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
8074 DisplayError("Wait until your turn,\nor select Move Now", 0);
8078 case BeginningOfGame:
8079 case PlayFromGameFile:
8082 if (gameMode != EditGame) return;
8096 forwardMostMove = currentMove;
8097 ResurrectChessProgram(); /* in case first program isn't running */
8099 if (second.pr == NULL) {
8100 StartChessProgram(&second);
8101 if (second.protocolVersion == 1) {
8102 TwoMachinesEventIfReady();
8104 /* kludge: allow timeout for initial "feature" command */
8106 DisplayMessage("", "Starting second chess program");
8107 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
8111 DisplayMessage("", "");
8112 InitChessProgram(&second);
8113 SendToProgram("force\n", &second);
8114 if (startedFromSetupPosition) {
8115 SendBoard(&second, backwardMostMove);
8117 for (i = backwardMostMove; i < forwardMostMove; i++) {
8118 SendMoveToProgram(i, &second);
8121 gameMode = TwoMachinesPlay;
8125 DisplayTwoMachinesTitle();
8127 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
8133 SendToProgram(first.computerString, &first);
8134 if (first.sendName) {
8135 sprintf(buf, "name %s\n", second.tidy);
8136 SendToProgram(buf, &first);
8138 SendToProgram(second.computerString, &second);
8139 if (second.sendName) {
8140 sprintf(buf, "name %s\n", first.tidy);
8141 SendToProgram(buf, &second);
8144 if (!first.sendTime || !second.sendTime) {
8146 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8147 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8149 if (onmove->sendTime) {
8150 if (onmove->useColors) {
8151 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
8153 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
8155 if (onmove->useColors) {
8156 SendToProgram(onmove->twoMachinesColor, onmove);
8158 SendToProgram("go\n", onmove);
8159 onmove->maybeThinking = TRUE;
8160 SetMachineThinkingEnables();
8168 if (gameMode == Training) {
8169 SetTrainingModeOff();
8170 gameMode = PlayFromGameFile;
8171 DisplayMessage("", "Training mode off");
8173 gameMode = Training;
8174 animateTraining = appData.animate;
8176 /* make sure we are not already at the end of the game */
8177 if (currentMove < forwardMostMove) {
8178 SetTrainingModeOn();
8179 DisplayMessage("", "Training mode on");
8181 gameMode = PlayFromGameFile;
8182 DisplayError("Already at end of game", 0);
8191 if (!appData.icsActive) return;
8193 case IcsPlayingWhite:
8194 case IcsPlayingBlack:
8197 case BeginningOfGame:
8231 SetTrainingModeOff();
8233 case MachinePlaysWhite:
8234 case MachinePlaysBlack:
8235 case BeginningOfGame:
8236 SendToProgram("force\n", &first);
8237 SetUserThinkingEnables();
8239 case PlayFromGameFile:
8240 (void) StopLoadGameTimer();
8241 if (gameFileFP != NULL) {
8251 SendToProgram("force\n", &first);
8253 case TwoMachinesPlay:
8254 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8255 ResurrectChessProgram();
8256 SetUserThinkingEnables();
8259 ResurrectChessProgram();
8261 case IcsPlayingBlack:
8262 case IcsPlayingWhite:
8263 DisplayError("Warning: You are still playing a game", 0);
8266 DisplayError("Warning: You are still observing a game", 0);
8269 DisplayError("Warning: You are still examining a game", 0);
8280 first.offeredDraw = second.offeredDraw = 0;
8282 if (gameMode == PlayFromGameFile) {
8283 whiteTimeRemaining = timeRemaining[0][currentMove];
8284 blackTimeRemaining = timeRemaining[1][currentMove];
8288 if (gameMode == MachinePlaysWhite ||
8289 gameMode == MachinePlaysBlack ||
8290 gameMode == TwoMachinesPlay ||
8291 gameMode == EndOfGame) {
8292 i = forwardMostMove;
8293 while (i > currentMove) {
8294 SendToProgram("undo\n", &first);
8297 whiteTimeRemaining = timeRemaining[0][currentMove];
8298 blackTimeRemaining = timeRemaining[1][currentMove];
8299 DisplayBothClocks();
8300 if (whiteFlag || blackFlag) {
8301 whiteFlag = blackFlag = 0;
8306 gameMode = EditGame;
8315 if (gameMode == EditPosition) {
8321 if (gameMode != EditGame) return;
8323 gameMode = EditPosition;
8326 if (currentMove > 0)
8327 CopyBoard(boards[0], boards[currentMove]);
8329 blackPlaysFirst = !WhiteOnMove(currentMove);
8331 currentMove = forwardMostMove = backwardMostMove = 0;
8332 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8339 if (first.analysisSupport && first.analyzing) {
8340 SendToProgram("exit\n", &first);
8341 first.analyzing = FALSE;
8344 thinkOutput[0] = NULLCHAR;
8350 startedFromSetupPosition = TRUE;
8351 InitChessProgram(&first);
8352 SendToProgram("force\n", &first);
8353 if (blackPlaysFirst) {
8354 strcpy(moveList[0], "");
8355 strcpy(parseList[0], "");
8356 currentMove = forwardMostMove = backwardMostMove = 1;
8357 CopyBoard(boards[1], boards[0]);
8359 currentMove = forwardMostMove = backwardMostMove = 0;
8361 SendBoard(&first, forwardMostMove);
8363 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8364 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8365 gameMode = EditGame;
8367 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8368 ClearHighlights(); /* [AS] */
8371 /* Pause for `ms' milliseconds */
8372 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
8382 } while (SubtractTimeMarks(&m2, &m1) < ms);
8385 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
8387 SendMultiLineToICS(buf)
8390 char temp[MSG_SIZ+1], *p;
8397 strncpy(temp, buf, len);
8402 if (*p == '\n' || *p == '\r')
8409 SendToPlayer(temp, strlen(temp));
8413 SetWhiteToPlayEvent()
8415 if (gameMode == EditPosition) {
8416 blackPlaysFirst = FALSE;
8417 DisplayBothClocks(); /* works because currentMove is 0 */
8418 } else if (gameMode == IcsExamining) {
8419 SendToICS(ics_prefix);
8420 SendToICS("tomove white\n");
8425 SetBlackToPlayEvent()
8427 if (gameMode == EditPosition) {
8428 blackPlaysFirst = TRUE;
8429 currentMove = 1; /* kludge */
8430 DisplayBothClocks();
8432 } else if (gameMode == IcsExamining) {
8433 SendToICS(ics_prefix);
8434 SendToICS("tomove black\n");
8439 EditPositionMenuEvent(selection, x, y)
8440 ChessSquare selection;
8445 if (gameMode != EditPosition && gameMode != IcsExamining) return;
8447 switch (selection) {
8449 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
8450 SendToICS(ics_prefix);
8451 SendToICS("bsetup clear\n");
8452 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
8453 SendToICS(ics_prefix);
8454 SendToICS("clearboard\n");
8456 for (x = 0; x < BOARD_SIZE; x++) {
8457 for (y = 0; y < BOARD_SIZE; y++) {
8458 if (gameMode == IcsExamining) {
8459 if (boards[currentMove][y][x] != EmptySquare) {
8460 sprintf(buf, "%sx@%c%c\n", ics_prefix,
8465 boards[0][y][x] = EmptySquare;
8470 if (gameMode == EditPosition) {
8471 DrawPosition(FALSE, boards[0]);
8476 SetWhiteToPlayEvent();
8480 SetBlackToPlayEvent();
8484 if (gameMode == IcsExamining) {
8485 sprintf(buf, "%sx@%c%c\n", ics_prefix, 'a' + x, '1' + y);
8488 boards[0][y][x] = EmptySquare;
8489 DrawPosition(FALSE, boards[0]);
8494 if (gameMode == IcsExamining) {
8495 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
8496 PieceToChar(selection), 'a' + x, '1' + y);
8499 boards[0][y][x] = selection;
8500 DrawPosition(FALSE, boards[0]);
8508 DropMenuEvent(selection, x, y)
8509 ChessSquare selection;
8515 case IcsPlayingWhite:
8516 case MachinePlaysBlack:
8517 if (!WhiteOnMove(currentMove)) {
8518 DisplayMoveError("It is Black's turn");
8521 moveType = WhiteDrop;
8523 case IcsPlayingBlack:
8524 case MachinePlaysWhite:
8525 if (WhiteOnMove(currentMove)) {
8526 DisplayMoveError("It is White's turn");
8529 moveType = BlackDrop;
8532 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
8538 if (moveType == BlackDrop && selection < BlackPawn) {
8539 selection = (ChessSquare) ((int) selection
8540 + (int) BlackPawn - (int) WhitePawn);
8542 if (boards[currentMove][y][x] != EmptySquare) {
8543 DisplayMoveError("That square is occupied");
8547 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
8553 /* Accept a pending offer of any kind from opponent */
8555 if (appData.icsActive) {
8556 SendToICS(ics_prefix);
8557 SendToICS("accept\n");
8558 } else if (cmailMsgLoaded) {
8559 if (currentMove == cmailOldMove &&
8560 commentList[cmailOldMove] != NULL &&
8561 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
8562 "Black offers a draw" : "White offers a draw")) {
8564 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8565 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
8567 DisplayError("There is no pending offer on this move", 0);
8568 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8571 /* Not used for offers from chess program */
8578 /* Decline a pending offer of any kind from opponent */
8580 if (appData.icsActive) {
8581 SendToICS(ics_prefix);
8582 SendToICS("decline\n");
8583 } else if (cmailMsgLoaded) {
8584 if (currentMove == cmailOldMove &&
8585 commentList[cmailOldMove] != NULL &&
8586 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
8587 "Black offers a draw" : "White offers a draw")) {
8589 AppendComment(cmailOldMove, "Draw declined");
8590 DisplayComment(cmailOldMove - 1, "Draw declined");
8593 DisplayError("There is no pending offer on this move", 0);
8596 /* Not used for offers from chess program */
8603 /* Issue ICS rematch command */
8604 if (appData.icsActive) {
8605 SendToICS(ics_prefix);
8606 SendToICS("rematch\n");
8613 /* Call your opponent's flag (claim a win on time) */
8614 if (appData.icsActive) {
8615 SendToICS(ics_prefix);
8616 SendToICS("flag\n");
8621 case MachinePlaysWhite:
8624 GameEnds(GameIsDrawn, "Both players ran out of time",
8627 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
8629 DisplayError("Your opponent is not out of time", 0);
8632 case MachinePlaysBlack:
8635 GameEnds(GameIsDrawn, "Both players ran out of time",
8638 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
8640 DisplayError("Your opponent is not out of time", 0);
8650 /* Offer draw or accept pending draw offer from opponent */
8652 if (appData.icsActive) {
8653 /* Note: tournament rules require draw offers to be
8654 made after you make your move but before you punch
8655 your clock. Currently ICS doesn't let you do that;
8656 instead, you immediately punch your clock after making
8657 a move, but you can offer a draw at any time. */
8659 SendToICS(ics_prefix);
8660 SendToICS("draw\n");
8661 } else if (cmailMsgLoaded) {
8662 if (currentMove == cmailOldMove &&
8663 commentList[cmailOldMove] != NULL &&
8664 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
8665 "Black offers a draw" : "White offers a draw")) {
8666 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8667 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
8668 } else if (currentMove == cmailOldMove + 1) {
8669 char *offer = WhiteOnMove(cmailOldMove) ?
8670 "White offers a draw" : "Black offers a draw";
8671 AppendComment(currentMove, offer);
8672 DisplayComment(currentMove - 1, offer);
8673 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
8675 DisplayError("You must make your move before offering a draw", 0);
8676 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8678 } else if (first.offeredDraw) {
8679 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8681 if (first.sendDrawOffers) {
8682 SendToProgram("draw\n", &first);
8683 userOfferedDraw = TRUE;
8691 /* Offer Adjourn or accept pending Adjourn offer from opponent */
8693 if (appData.icsActive) {
8694 SendToICS(ics_prefix);
8695 SendToICS("adjourn\n");
8697 /* Currently GNU Chess doesn't offer or accept Adjourns */
8705 /* Offer Abort or accept pending Abort offer from opponent */
8707 if (appData.icsActive) {
8708 SendToICS(ics_prefix);
8709 SendToICS("abort\n");
8711 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
8718 /* Resign. You can do this even if it's not your turn. */
8720 if (appData.icsActive) {
8721 SendToICS(ics_prefix);
8722 SendToICS("resign\n");
8725 case MachinePlaysWhite:
8726 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8728 case MachinePlaysBlack:
8729 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8732 if (cmailMsgLoaded) {
8734 if (WhiteOnMove(cmailOldMove)) {
8735 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8737 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8739 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
8750 StopObservingEvent()
8752 /* Stop observing current games */
8753 SendToICS(ics_prefix);
8754 SendToICS("unobserve\n");
8758 StopExaminingEvent()
8760 /* Stop observing current game */
8761 SendToICS(ics_prefix);
8762 SendToICS("unexamine\n");
8766 ForwardInner(target)
8771 if (appData.debugMode)
8772 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
8773 target, currentMove, forwardMostMove);
8775 if (gameMode == EditPosition)
8778 if (gameMode == PlayFromGameFile && !pausing)
8781 if (gameMode == IcsExamining && pausing)
8782 limit = pauseExamForwardMostMove;
8784 limit = forwardMostMove;
8786 if (target > limit) target = limit;
8788 if (target > 0 && moveList[target - 1][0]) {
8789 int fromX, fromY, toX, toY;
8790 toX = moveList[target - 1][2] - 'a';
8791 toY = moveList[target - 1][3] - '1';
8792 if (moveList[target - 1][1] == '@') {
8793 if (appData.highlightLastMove) {
8794 SetHighlights(-1, -1, toX, toY);
8797 fromX = moveList[target - 1][0] - 'a';
8798 fromY = moveList[target - 1][1] - '1';
8799 if (target == currentMove + 1) {
8800 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8802 if (appData.highlightLastMove) {
8803 SetHighlights(fromX, fromY, toX, toY);
8807 if (gameMode == EditGame || gameMode == AnalyzeMode ||
8808 gameMode == Training || gameMode == PlayFromGameFile ||
8809 gameMode == AnalyzeFile) {
8810 while (currentMove < target) {
8811 SendMoveToProgram(currentMove++, &first);
8814 currentMove = target;
8817 if (gameMode == EditGame || gameMode == EndOfGame) {
8818 whiteTimeRemaining = timeRemaining[0][currentMove];
8819 blackTimeRemaining = timeRemaining[1][currentMove];
8821 DisplayBothClocks();
8822 DisplayMove(currentMove - 1);
8823 DrawPosition(FALSE, boards[currentMove]);
8824 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8825 if (commentList[currentMove] && !matchMode && gameMode != Training) {
8826 DisplayComment(currentMove - 1, commentList[currentMove]);
8834 if (gameMode == IcsExamining && !pausing) {
8835 SendToICS(ics_prefix);
8836 SendToICS("forward\n");
8838 ForwardInner(currentMove + 1);
8845 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
8846 /* to optimze, we temporarily turn off analysis mode while we feed
8847 * the remaining moves to the engine. Otherwise we get analysis output
8850 if (first.analysisSupport) {
8851 SendToProgram("exit\nforce\n", &first);
8852 first.analyzing = FALSE;
8856 if (gameMode == IcsExamining && !pausing) {
8857 SendToICS(ics_prefix);
8858 SendToICS("forward 999999\n");
8860 ForwardInner(forwardMostMove);
8863 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
8864 /* we have fed all the moves, so reactivate analysis mode */
8865 SendToProgram("analyze\n", &first);
8866 first.analyzing = TRUE;
8867 /*first.maybeThinking = TRUE;*/
8868 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
8873 BackwardInner(target)
8876 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
8878 if (appData.debugMode)
8879 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
8880 target, currentMove, forwardMostMove);
8882 if (gameMode == EditPosition) return;
8883 if (currentMove <= backwardMostMove) {
8885 DrawPosition(full_redraw, boards[currentMove]);
8888 if (gameMode == PlayFromGameFile && !pausing)
8891 if (moveList[target][0]) {
8892 int fromX, fromY, toX, toY;
8893 toX = moveList[target][2] - 'a';
8894 toY = moveList[target][3] - '1';
8895 if (moveList[target][1] == '@') {
8896 if (appData.highlightLastMove) {
8897 SetHighlights(-1, -1, toX, toY);
8900 fromX = moveList[target][0] - 'a';
8901 fromY = moveList[target][1] - '1';
8902 if (target == currentMove - 1) {
8903 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
8905 if (appData.highlightLastMove) {
8906 SetHighlights(fromX, fromY, toX, toY);
8910 if (gameMode == EditGame || gameMode==AnalyzeMode ||
8911 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8912 while (currentMove > target) {
8913 SendToProgram("undo\n", &first);
8917 currentMove = target;
8920 if (gameMode == EditGame || gameMode == EndOfGame) {
8921 whiteTimeRemaining = timeRemaining[0][currentMove];
8922 blackTimeRemaining = timeRemaining[1][currentMove];
8924 DisplayBothClocks();
8925 DisplayMove(currentMove - 1);
8926 DrawPosition(full_redraw, boards[currentMove]);
8927 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8928 if (commentList[currentMove] != NULL) {
8929 DisplayComment(currentMove - 1, commentList[currentMove]);
8936 if (gameMode == IcsExamining && !pausing) {
8937 SendToICS(ics_prefix);
8938 SendToICS("backward\n");
8940 BackwardInner(currentMove - 1);
8947 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
8948 /* to optimze, we temporarily turn off analysis mode while we undo
8949 * all the moves. Otherwise we get analysis output after each undo.
8951 if (first.analysisSupport) {
8952 SendToProgram("exit\nforce\n", &first);
8953 first.analyzing = FALSE;
8957 if (gameMode == IcsExamining && !pausing) {
8958 SendToICS(ics_prefix);
8959 SendToICS("backward 999999\n");
8961 BackwardInner(backwardMostMove);
8964 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
8965 /* we have fed all the moves, so reactivate analysis mode */
8966 SendToProgram("analyze\n", &first);
8967 first.analyzing = TRUE;
8968 /*first.maybeThinking = TRUE;*/
8969 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
8976 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
8977 if (to >= forwardMostMove) to = forwardMostMove;
8978 if (to <= backwardMostMove) to = backwardMostMove;
8979 if (to < currentMove) {
8989 if (gameMode != IcsExamining) {
8990 DisplayError("You are not examining a game", 0);
8994 DisplayError("You can't revert while pausing", 0);
8997 SendToICS(ics_prefix);
8998 SendToICS("revert\n");
9005 case MachinePlaysWhite:
9006 case MachinePlaysBlack:
9007 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
9008 DisplayError("Wait until your turn,\nor select Move Now", 0);
9011 if (forwardMostMove < 2) return;
9012 currentMove = forwardMostMove = forwardMostMove - 2;
9013 whiteTimeRemaining = timeRemaining[0][currentMove];
9014 blackTimeRemaining = timeRemaining[1][currentMove];
9015 DisplayBothClocks();
9016 DisplayMove(currentMove - 1);
9017 ClearHighlights();/*!! could figure this out*/
9018 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
9019 SendToProgram("remove\n", &first);
9020 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
9023 case BeginningOfGame:
9027 case IcsPlayingWhite:
9028 case IcsPlayingBlack:
9029 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
9030 SendToICS(ics_prefix);
9031 SendToICS("takeback 2\n");
9033 SendToICS(ics_prefix);
9034 SendToICS("takeback 1\n");
9043 ChessProgramState *cps;
9046 case MachinePlaysWhite:
9047 if (!WhiteOnMove(forwardMostMove)) {
9048 DisplayError("It is your turn", 0);
9053 case MachinePlaysBlack:
9054 if (WhiteOnMove(forwardMostMove)) {
9055 DisplayError("It is your turn", 0);
9060 case TwoMachinesPlay:
9061 if (WhiteOnMove(forwardMostMove) ==
9062 (first.twoMachinesColor[0] == 'w')) {
9068 case BeginningOfGame:
9072 SendToProgram("?\n", cps);
9079 if (gameMode != EditGame) return;
9086 if (forwardMostMove > currentMove) {
9087 if (gameInfo.resultDetails != NULL) {
9088 free(gameInfo.resultDetails);
9089 gameInfo.resultDetails = NULL;
9090 gameInfo.result = GameUnfinished;
9092 forwardMostMove = currentMove;
9093 HistorySet(parseList, backwardMostMove, forwardMostMove,
9101 if (appData.noChessProgram) return;
9103 case MachinePlaysWhite:
9104 if (WhiteOnMove(forwardMostMove)) {
9105 DisplayError("Wait until your turn", 0);
9109 case BeginningOfGame:
9110 case MachinePlaysBlack:
9111 if (!WhiteOnMove(forwardMostMove)) {
9112 DisplayError("Wait until your turn", 0);
9117 DisplayError("No hint available", 0);
9120 SendToProgram("hint\n", &first);
9121 hintRequested = TRUE;
9127 if (appData.noChessProgram) return;
9129 case MachinePlaysWhite:
9130 if (WhiteOnMove(forwardMostMove)) {
9131 DisplayError("Wait until your turn", 0);
9135 case BeginningOfGame:
9136 case MachinePlaysBlack:
9137 if (!WhiteOnMove(forwardMostMove)) {
9138 DisplayError("Wait until your turn", 0);
9145 case TwoMachinesPlay:
9150 SendToProgram("bk\n", &first);
9151 bookOutput[0] = NULLCHAR;
9152 bookRequested = TRUE;
9158 char *tags = PGNTags(&gameInfo);
9159 TagsPopUp(tags, CmailMsg());
9163 /* end button procedures */
9166 PrintPosition(fp, move)
9172 for (i = BOARD_SIZE - 1; i >= 0; i--) {
9173 for (j = 0; j < BOARD_SIZE; j++) {
9174 char c = PieceToChar(boards[move][i][j]);
9175 fputc(c == 'x' ? '.' : c, fp);
9176 fputc(j == BOARD_SIZE - 1 ? '\n' : ' ', fp);
9179 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
9180 fprintf(fp, "white to play\n");
9182 fprintf(fp, "black to play\n");
9189 if (gameInfo.white != NULL) {
9190 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
9196 /* Find last component of program's own name, using some heuristics */
9198 TidyProgramName(prog, host, buf)
9199 char *prog, *host, buf[MSG_SIZ];
9202 int local = (strcmp(host, "localhost") == 0);
9203 while (!local && (p = strchr(prog, ';')) != NULL) {
9205 while (*p == ' ') p++;
9208 if (*prog == '"' || *prog == '\'') {
9209 q = strchr(prog + 1, *prog);
9211 q = strchr(prog, ' ');
9213 if (q == NULL) q = prog + strlen(prog);
9215 while (p >= prog && *p != '/' && *p != '\\') p--;
9217 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
9218 memcpy(buf, p, q - p);
9219 buf[q - p] = NULLCHAR;
9227 TimeControlTagValue()
9230 if (!appData.clockMode) {
9232 } else if (movesPerSession > 0) {
9233 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
9234 } else if (timeIncrement == 0) {
9235 sprintf(buf, "%ld", timeControl/1000);
9237 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
9239 return StrSave(buf);
9245 /* This routine is used only for certain modes */
9246 VariantClass v = gameInfo.variant;
9247 ClearGameInfo(&gameInfo);
9248 gameInfo.variant = v;
9251 case MachinePlaysWhite:
9252 gameInfo.event = StrSave( appData.pgnEventHeader );
9253 gameInfo.site = StrSave(HostName());
9254 gameInfo.date = PGNDate();
9255 gameInfo.round = StrSave("-");
9256 gameInfo.white = StrSave(first.tidy);
9257 gameInfo.black = StrSave(UserName());
9258 gameInfo.timeControl = TimeControlTagValue();
9261 case MachinePlaysBlack:
9262 gameInfo.event = StrSave( appData.pgnEventHeader );
9263 gameInfo.site = StrSave(HostName());
9264 gameInfo.date = PGNDate();
9265 gameInfo.round = StrSave("-");
9266 gameInfo.white = StrSave(UserName());
9267 gameInfo.black = StrSave(first.tidy);
9268 gameInfo.timeControl = TimeControlTagValue();
9271 case TwoMachinesPlay:
9272 gameInfo.event = StrSave( appData.pgnEventHeader );
9273 gameInfo.site = StrSave(HostName());
9274 gameInfo.date = PGNDate();
9275 if (matchGame > 0) {
9277 sprintf(buf, "%d", matchGame);
9278 gameInfo.round = StrSave(buf);
9280 gameInfo.round = StrSave("-");
9282 if (first.twoMachinesColor[0] == 'w') {
9283 gameInfo.white = StrSave(first.tidy);
9284 gameInfo.black = StrSave(second.tidy);
9286 gameInfo.white = StrSave(second.tidy);
9287 gameInfo.black = StrSave(first.tidy);
9289 gameInfo.timeControl = TimeControlTagValue();
9293 gameInfo.event = StrSave("Edited game");
9294 gameInfo.site = StrSave(HostName());
9295 gameInfo.date = PGNDate();
9296 gameInfo.round = StrSave("-");
9297 gameInfo.white = StrSave("-");
9298 gameInfo.black = StrSave("-");
9302 gameInfo.event = StrSave("Edited position");
9303 gameInfo.site = StrSave(HostName());
9304 gameInfo.date = PGNDate();
9305 gameInfo.round = StrSave("-");
9306 gameInfo.white = StrSave("-");
9307 gameInfo.black = StrSave("-");
9310 case IcsPlayingWhite:
9311 case IcsPlayingBlack:
9316 case PlayFromGameFile:
9317 gameInfo.event = StrSave("Game from non-PGN file");
9318 gameInfo.site = StrSave(HostName());
9319 gameInfo.date = PGNDate();
9320 gameInfo.round = StrSave("-");
9321 gameInfo.white = StrSave("?");
9322 gameInfo.black = StrSave("?");
9331 ReplaceComment(index, text)
9337 while (*text == '\n') text++;
9339 while (len > 0 && text[len - 1] == '\n') len--;
9341 if (commentList[index] != NULL)
9342 free(commentList[index]);
9345 commentList[index] = NULL;
9348 commentList[index] = (char *) malloc(len + 2);
9349 strncpy(commentList[index], text, len);
9350 commentList[index][len] = '\n';
9351 commentList[index][len + 1] = NULLCHAR;
9364 if (ch == '\r') continue;
9366 } while (ch != '\0');
9370 AppendComment(index, text)
9377 GetInfoFromComment( index, text );
9380 while (*text == '\n') text++;
9382 while (len > 0 && text[len - 1] == '\n') len--;
9384 if (len == 0) return;
9386 if (commentList[index] != NULL) {
9387 old = commentList[index];
9388 oldlen = strlen(old);
9389 commentList[index] = (char *) malloc(oldlen + len + 2);
9390 strcpy(commentList[index], old);
9392 strncpy(&commentList[index][oldlen], text, len);
9393 commentList[index][oldlen + len] = '\n';
9394 commentList[index][oldlen + len + 1] = NULLCHAR;
9396 commentList[index] = (char *) malloc(len + 2);
9397 strncpy(commentList[index], text, len);
9398 commentList[index][len] = '\n';
9399 commentList[index][len + 1] = NULLCHAR;
9403 static char * FindStr( char * text, char * sub_text )
9405 char * result = strstr( text, sub_text );
9407 if( result != NULL ) {
9408 result += strlen( sub_text );
9414 /* [AS] Try to extract PV info from PGN comment */
9415 void GetInfoFromComment( int index, char * text )
9417 if( text != NULL && index > 0 ) {
9421 char * s_eval = FindStr( text, "[%eval " );
9422 char * s_emt = FindStr( text, "[%emt " );
9424 if( s_eval != NULL || s_emt != NULL ) {
9428 if( s_eval != NULL ) {
9429 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
9433 if( delim != ']' ) {
9438 if( s_emt != NULL ) {
9442 /* We expect something like: [+|-]nnn.nn/dd */
9443 char * sep = strchr( text, '/' );
9446 if( sep == NULL || sep < (text+4) ) {
9450 if( sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
9454 if( score_lo < 0 || score_lo >= 100 ) {
9458 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
9469 pvInfoList[index-1].depth = depth;
9470 pvInfoList[index-1].score = score;
9471 pvInfoList[index-1].time = time;
9476 SendToProgram(message, cps)
9478 ChessProgramState *cps;
9480 int count, outCount, error;
9483 if (cps->pr == NULL) return;
9486 if (appData.debugMode) {
9489 fprintf(debugFP, "%ld >%-6s: %s",
9490 SubtractTimeMarks(&now, &programStartTime),
9491 cps->which, message);
9494 count = strlen(message);
9495 outCount = OutputToProcess(cps->pr, message, count, &error);
9496 if (outCount < count && !exiting) {
9497 sprintf(buf, "Error writing to %s chess program", cps->which);
9498 DisplayFatalError(buf, error, 1);
9503 ReceiveFromProgram(isr, closure, message, count, error)
9512 ChessProgramState *cps = (ChessProgramState *)closure;
9514 if (isr != cps->isr) return; /* Killed intentionally */
9518 "Error: %s chess program (%s) exited unexpectedly",
9519 cps->which, cps->program);
9520 RemoveInputSource(cps->isr);
9521 DisplayFatalError(buf, 0, 1);
9524 "Error reading from %s chess program (%s)",
9525 cps->which, cps->program);
9526 RemoveInputSource(cps->isr);
9528 /* [AS] Program is misbehaving badly... kill it */
9530 DestroyChildProcess( cps->pr, 9 );
9534 DisplayFatalError(buf, error, 1);
9536 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9540 if ((end_str = strchr(message, '\r')) != NULL)
9541 *end_str = NULLCHAR;
9542 if ((end_str = strchr(message, '\n')) != NULL)
9543 *end_str = NULLCHAR;
9545 if (appData.debugMode) {
9548 fprintf(debugFP, "%ld <%-6s: %s\n",
9549 SubtractTimeMarks(&now, &programStartTime),
9550 cps->which, message);
9552 HandleMachineMove(message, cps);
9557 SendTimeControl(cps, mps, tc, inc, sd, st)
9558 ChessProgramState *cps;
9559 int mps, inc, sd, st;
9563 int seconds = (tc / 1000) % 60;
9565 if( timeControl_2 > 0 ) {
9566 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
9572 /* Set exact time per move, normally using st command */
9573 if (cps->stKludge) {
9574 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
9577 sprintf(buf, "level 1 %d\n", st/60);
9579 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
9582 sprintf(buf, "st %d\n", st);
9585 /* Set conventional or incremental time control, using level command */
9587 /* Note old gnuchess bug -- minutes:seconds used to not work.
9588 Fixed in later versions, but still avoid :seconds
9589 when seconds is 0. */
9590 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
9592 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
9596 SendToProgram(buf, cps);
9598 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
9599 /* Orthogonally, limit search to given depth */
9601 if (cps->sdKludge) {
9602 sprintf(buf, "depth\n%d\n", sd);
9604 sprintf(buf, "sd %d\n", sd);
9606 SendToProgram(buf, cps);
9611 SendTimeRemaining(cps, machineWhite)
9612 ChessProgramState *cps;
9613 int /*boolean*/ machineWhite;
9615 char message[MSG_SIZ];
9618 /* Note: this routine must be called when the clocks are stopped
9619 or when they have *just* been set or switched; otherwise
9620 it will be off by the time since the current tick started.
9623 time = whiteTimeRemaining / 10;
9624 otime = blackTimeRemaining / 10;
9626 time = blackTimeRemaining / 10;
9627 otime = whiteTimeRemaining / 10;
9629 if (time <= 0) time = 1;
9630 if (otime <= 0) otime = 1;
9632 sprintf(message, "time %ld\notim %ld\n", time, otime);
9633 SendToProgram(message, cps);
9637 BoolFeature(p, name, loc, cps)
9641 ChessProgramState *cps;
9644 int len = strlen(name);
9646 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
9648 sscanf(*p, "%d", &val);
9650 while (**p && **p != ' ') (*p)++;
9651 sprintf(buf, "accepted %s\n", name);
9652 SendToProgram(buf, cps);
9659 IntFeature(p, name, loc, cps)
9663 ChessProgramState *cps;
9666 int len = strlen(name);
9667 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
9669 sscanf(*p, "%d", loc);
9670 while (**p && **p != ' ') (*p)++;
9671 sprintf(buf, "accepted %s\n", name);
9672 SendToProgram(buf, cps);
9679 StringFeature(p, name, loc, cps)
9683 ChessProgramState *cps;
9686 int len = strlen(name);
9687 if (strncmp((*p), name, len) == 0
9688 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
9690 sscanf(*p, "%[^\"]", loc);
9691 while (**p && **p != '\"') (*p)++;
9692 if (**p == '\"') (*p)++;
9693 sprintf(buf, "accepted %s\n", name);
9694 SendToProgram(buf, cps);
9701 FeatureDone(cps, val)
9702 ChessProgramState* cps;
9705 DelayedEventCallback cb = GetDelayedEvent();
9706 if ((cb == InitBackEnd3 && cps == &first) ||
9707 (cb == TwoMachinesEventIfReady && cps == &second)) {
9708 CancelDelayedEvent();
9709 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
9711 cps->initDone = val;
9714 /* Parse feature command from engine */
9716 ParseFeatures(args, cps)
9718 ChessProgramState *cps;
9726 while (*p == ' ') p++;
9727 if (*p == NULLCHAR) return;
9729 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
9730 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
9731 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
9732 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
9733 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
9734 if (BoolFeature(&p, "reuse", &val, cps)) {
9735 /* Engine can disable reuse, but can't enable it if user said no */
9736 if (!val) cps->reuse = FALSE;
9739 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
9740 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
9741 if (gameMode == TwoMachinesPlay) {
9742 DisplayTwoMachinesTitle();
9748 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
9749 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
9750 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
9751 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
9752 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
9753 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
9754 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
9755 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
9756 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
9757 if (IntFeature(&p, "done", &val, cps)) {
9758 FeatureDone(cps, val);
9761 /* Added by Tord: */
9762 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
9763 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
9764 /* End of additions by Tord */
9766 /* unknown feature: complain and skip */
9768 while (*q && *q != '=') q++;
9769 sprintf(buf, "rejected %.*s\n", q-p, p);
9770 SendToProgram(buf, cps);
9776 while (*p && *p != '\"') p++;
9777 if (*p == '\"') p++;
9779 while (*p && *p != ' ') p++;
9787 PeriodicUpdatesEvent(newState)
9790 if (newState == appData.periodicUpdates)
9793 appData.periodicUpdates=newState;
9795 /* Display type changes, so update it now */
9798 /* Get the ball rolling again... */
9800 AnalysisPeriodicEvent(1);
9801 StartAnalysisClock();
9806 PonderNextMoveEvent(newState)
9809 if (newState == appData.ponderNextMove) return;
9810 if (gameMode == EditPosition) EditPositionDone();
9812 SendToProgram("hard\n", &first);
9813 if (gameMode == TwoMachinesPlay) {
9814 SendToProgram("hard\n", &second);
9817 SendToProgram("easy\n", &first);
9818 thinkOutput[0] = NULLCHAR;
9819 if (gameMode == TwoMachinesPlay) {
9820 SendToProgram("easy\n", &second);
9823 appData.ponderNextMove = newState;
9827 ShowThinkingEvent(newState)
9830 if (newState == appData.showThinking) return;
9831 if (gameMode == EditPosition) EditPositionDone();
9833 SendToProgram("post\n", &first);
9834 if (gameMode == TwoMachinesPlay) {
9835 SendToProgram("post\n", &second);
9838 SendToProgram("nopost\n", &first);
9839 thinkOutput[0] = NULLCHAR;
9840 if (gameMode == TwoMachinesPlay) {
9841 SendToProgram("nopost\n", &second);
9844 appData.showThinking = newState;
9848 AskQuestionEvent(title, question, replyPrefix, which)
9849 char *title; char *question; char *replyPrefix; char *which;
9851 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
9852 if (pr == NoProc) return;
9853 AskQuestion(title, question, replyPrefix, pr);
9857 DisplayMove(moveNumber)
9860 char message[MSG_SIZ];
9862 char cpThinkOutput[MSG_SIZ];
9864 if (moveNumber == forwardMostMove - 1 ||
9865 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
9867 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
9869 if (strchr(cpThinkOutput, '\n')) {
9870 *strchr(cpThinkOutput, '\n') = NULLCHAR;
9873 *cpThinkOutput = NULLCHAR;
9876 /* [AS] Hide thinking from human user */
9877 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
9878 *cpThinkOutput = NULLCHAR;
9879 if( thinkOutput[0] != NULLCHAR ) {
9882 for( i=0; i<=hiddenThinkOutputState; i++ ) {
9883 cpThinkOutput[i] = '.';
9885 cpThinkOutput[i] = NULLCHAR;
9886 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
9890 if (moveNumber == forwardMostMove - 1 &&
9891 gameInfo.resultDetails != NULL) {
9892 if (gameInfo.resultDetails[0] == NULLCHAR) {
9893 sprintf(res, " %s", PGNResult(gameInfo.result));
9895 sprintf(res, " {%s} %s",
9896 gameInfo.resultDetails, PGNResult(gameInfo.result));
9902 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
9903 DisplayMessage(res, cpThinkOutput);
9905 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
9906 WhiteOnMove(moveNumber) ? " " : ".. ",
9907 parseList[moveNumber], res);
9908 DisplayMessage(message, cpThinkOutput);
9913 DisplayAnalysisText(text)
9918 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
9919 sprintf(buf, "Analysis (%s)", first.tidy);
9920 AnalysisPopUp(buf, text);
9928 while (*str && isspace(*str)) ++str;
9929 while (*str && !isspace(*str)) ++str;
9930 if (!*str) return 1;
9931 while (*str && isspace(*str)) ++str;
9932 if (!*str) return 1;
9940 char lst[MSG_SIZ / 2];
9942 static char *xtra[] = { "", " (--)", " (++)" };
9945 if (programStats.time == 0) {
9946 programStats.time = 1;
9949 if (programStats.got_only_move) {
9950 safeStrCpy(buf, programStats.movelist, sizeof(buf));
9952 safeStrCpy( lst, programStats.movelist, sizeof(lst));
9954 nps = (((double)programStats.nodes) /
9955 (((double)programStats.time)/100.0));
9957 cs = programStats.time % 100;
9958 s = programStats.time / 100;
9964 if (programStats.moves_left > 0 && appData.periodicUpdates) {
9965 if (programStats.move_name[0] != NULLCHAR) {
9966 sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",
9968 programStats.nr_moves-programStats.moves_left,
9969 programStats.nr_moves, programStats.move_name,
9970 ((float)programStats.score)/100.0, lst,
9972 xtra[programStats.got_fail] : "",
9973 programStats.nodes, (int)nps, h, m, s, cs);
9975 sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",
9977 programStats.nr_moves-programStats.moves_left,
9978 programStats.nr_moves, ((float)programStats.score)/100.0,
9981 xtra[programStats.got_fail] : "",
9982 programStats.nodes, (int)nps, h, m, s, cs);
9985 sprintf(buf, "depth=%d %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",
9987 ((float)programStats.score)/100.0,
9990 xtra[programStats.got_fail] : "",
9991 programStats.nodes, (int)nps, h, m, s, cs);
9994 DisplayAnalysisText(buf);
9998 DisplayComment(moveNumber, text)
10002 char title[MSG_SIZ];
10004 if( appData.autoDisplayComment ) {
10005 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
10006 strcpy(title, "Comment");
10008 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
10009 WhiteOnMove(moveNumber) ? " " : ".. ",
10010 parseList[moveNumber]);
10013 CommentPopUp(title, text);
10017 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
10018 * might be busy thinking or pondering. It can be omitted if your
10019 * gnuchess is configured to stop thinking immediately on any user
10020 * input. However, that gnuchess feature depends on the FIONREAD
10021 * ioctl, which does not work properly on some flavors of Unix.
10025 ChessProgramState *cps;
10028 if (!cps->useSigint) return;
10029 if (appData.noChessProgram || (cps->pr == NoProc)) return;
10030 switch (gameMode) {
10031 case MachinePlaysWhite:
10032 case MachinePlaysBlack:
10033 case TwoMachinesPlay:
10034 case IcsPlayingWhite:
10035 case IcsPlayingBlack:
10038 /* Skip if we know it isn't thinking */
10039 if (!cps->maybeThinking) return;
10040 if (appData.debugMode)
10041 fprintf(debugFP, "Interrupting %s\n", cps->which);
10042 InterruptChildProcess(cps->pr);
10043 cps->maybeThinking = FALSE;
10048 #endif /*ATTENTION*/
10054 if (whiteTimeRemaining <= 0) {
10057 if (appData.icsActive) {
10058 if (appData.autoCallFlag &&
10059 gameMode == IcsPlayingBlack && !blackFlag) {
10060 SendToICS(ics_prefix);
10061 SendToICS("flag\n");
10065 DisplayTitle("Both flags fell");
10067 DisplayTitle("White's flag fell");
10068 if (appData.autoCallFlag) {
10069 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
10076 if (blackTimeRemaining <= 0) {
10079 if (appData.icsActive) {
10080 if (appData.autoCallFlag &&
10081 gameMode == IcsPlayingWhite && !whiteFlag) {
10082 SendToICS(ics_prefix);
10083 SendToICS("flag\n");
10087 DisplayTitle("Both flags fell");
10089 DisplayTitle("Black's flag fell");
10090 if (appData.autoCallFlag) {
10091 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
10104 if (!appData.clockMode || appData.icsActive ||
10105 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
10107 if (timeIncrement >= 0) {
10108 if (WhiteOnMove(forwardMostMove)) {
10109 blackTimeRemaining += timeIncrement;
10111 whiteTimeRemaining += timeIncrement;
10115 * add time to clocks when time control is achieved
10117 if (movesPerSession) {
10118 switch ((forwardMostMove + 1) % (movesPerSession * 2)) {
10120 /* White made time control */
10121 whiteTimeRemaining += GetTimeControlForWhite();
10124 /* Black made time control */
10125 blackTimeRemaining += GetTimeControlForBlack();
10134 DisplayBothClocks()
10136 int wom = gameMode == EditPosition ?
10137 !blackPlaysFirst : WhiteOnMove(currentMove);
10138 DisplayWhiteClock(whiteTimeRemaining, wom);
10139 DisplayBlackClock(blackTimeRemaining, !wom);
10143 /* Timekeeping seems to be a portability nightmare. I think everyone
10144 has ftime(), but I'm really not sure, so I'm including some ifdefs
10145 to use other calls if you don't. Clocks will be less accurate if
10146 you have neither ftime nor gettimeofday.
10149 /* Get the current time as a TimeMark */
10154 #if HAVE_GETTIMEOFDAY
10156 struct timeval timeVal;
10157 struct timezone timeZone;
10159 gettimeofday(&timeVal, &timeZone);
10160 tm->sec = (long) timeVal.tv_sec;
10161 tm->ms = (int) (timeVal.tv_usec / 1000L);
10163 #else /*!HAVE_GETTIMEOFDAY*/
10166 #include <sys/timeb.h>
10167 struct timeb timeB;
10170 tm->sec = (long) timeB.time;
10171 tm->ms = (int) timeB.millitm;
10173 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
10174 tm->sec = (long) time(NULL);
10180 /* Return the difference in milliseconds between two
10181 time marks. We assume the difference will fit in a long!
10184 SubtractTimeMarks(tm2, tm1)
10185 TimeMark *tm2, *tm1;
10187 return 1000L*(tm2->sec - tm1->sec) +
10188 (long) (tm2->ms - tm1->ms);
10193 * Code to manage the game clocks.
10195 * In tournament play, black starts the clock and then white makes a move.
10196 * We give the human user a slight advantage if he is playing white---the
10197 * clocks don't run until he makes his first move, so it takes zero time.
10198 * Also, we don't account for network lag, so we could get out of sync
10199 * with GNU Chess's clock -- but then, referees are always right.
10202 static TimeMark tickStartTM;
10203 static long intendedTickLength;
10206 NextTickLength(timeRemaining)
10207 long timeRemaining;
10209 long nominalTickLength, nextTickLength;
10211 if (timeRemaining > 0L && timeRemaining <= 10000L)
10212 nominalTickLength = 100L;
10214 nominalTickLength = 1000L;
10215 nextTickLength = timeRemaining % nominalTickLength;
10216 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
10218 return nextTickLength;
10221 /* Stop clocks and reset to a fresh time control */
10225 (void) StopClockTimer();
10226 if (appData.icsActive) {
10227 whiteTimeRemaining = blackTimeRemaining = 0;
10229 whiteTimeRemaining = GetTimeControlForWhite();
10230 blackTimeRemaining = GetTimeControlForBlack();
10232 if (whiteFlag || blackFlag) {
10234 whiteFlag = blackFlag = FALSE;
10236 DisplayBothClocks();
10239 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
10241 /* Decrement running clock by amount of time that has passed */
10245 long timeRemaining;
10246 long lastTickLength, fudge;
10249 if (!appData.clockMode) return;
10250 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
10254 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
10256 /* Fudge if we woke up a little too soon */
10257 fudge = intendedTickLength - lastTickLength;
10258 if (fudge < 0 || fudge > FUDGE) fudge = 0;
10260 if (WhiteOnMove(forwardMostMove)) {
10261 timeRemaining = whiteTimeRemaining -= lastTickLength;
10262 DisplayWhiteClock(whiteTimeRemaining - fudge,
10263 WhiteOnMove(currentMove));
10265 timeRemaining = blackTimeRemaining -= lastTickLength;
10266 DisplayBlackClock(blackTimeRemaining - fudge,
10267 !WhiteOnMove(currentMove));
10270 if (CheckFlags()) return;
10273 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
10274 StartClockTimer(intendedTickLength);
10276 /* if the time remaining has fallen below the alarm threshold, sound the
10277 * alarm. if the alarm has sounded and (due to a takeback or time control
10278 * with increment) the time remaining has increased to a level above the
10279 * threshold, reset the alarm so it can sound again.
10282 if (appData.icsActive && appData.icsAlarm) {
10284 /* make sure we are dealing with the user's clock */
10285 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
10286 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
10289 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
10290 alarmSounded = FALSE;
10291 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
10293 alarmSounded = TRUE;
10299 /* A player has just moved, so stop the previously running
10300 clock and (if in clock mode) start the other one.
10301 We redisplay both clocks in case we're in ICS mode, because
10302 ICS gives us an update to both clocks after every move.
10303 Note that this routine is called *after* forwardMostMove
10304 is updated, so the last fractional tick must be subtracted
10305 from the color that is *not* on move now.
10310 long lastTickLength;
10312 int flagged = FALSE;
10316 if (StopClockTimer() && appData.clockMode) {
10317 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
10318 if (WhiteOnMove(forwardMostMove)) {
10319 blackTimeRemaining -= lastTickLength;
10321 whiteTimeRemaining -= lastTickLength;
10323 flagged = CheckFlags();
10325 CheckTimeControl();
10327 if (flagged || !appData.clockMode) return;
10329 switch (gameMode) {
10330 case MachinePlaysBlack:
10331 case MachinePlaysWhite:
10332 case BeginningOfGame:
10333 if (pausing) return;
10337 case PlayFromGameFile:
10346 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
10347 whiteTimeRemaining : blackTimeRemaining);
10348 StartClockTimer(intendedTickLength);
10352 /* Stop both clocks */
10356 long lastTickLength;
10359 if (!StopClockTimer()) return;
10360 if (!appData.clockMode) return;
10364 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
10365 if (WhiteOnMove(forwardMostMove)) {
10366 whiteTimeRemaining -= lastTickLength;
10367 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
10369 blackTimeRemaining -= lastTickLength;
10370 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
10375 /* Start clock of player on move. Time may have been reset, so
10376 if clock is already running, stop and restart it. */
10380 (void) StopClockTimer(); /* in case it was running already */
10381 DisplayBothClocks();
10382 if (CheckFlags()) return;
10384 if (!appData.clockMode) return;
10385 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
10387 GetTimeMark(&tickStartTM);
10388 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
10389 whiteTimeRemaining : blackTimeRemaining);
10390 StartClockTimer(intendedTickLength);
10397 long second, minute, hour, day;
10399 static char buf[32];
10401 if (ms > 0 && ms <= 9900) {
10402 /* convert milliseconds to tenths, rounding up */
10403 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
10405 sprintf(buf, " %03.1f ", tenths/10.0);
10409 /* convert milliseconds to seconds, rounding up */
10410 /* use floating point to avoid strangeness of integer division
10411 with negative dividends on many machines */
10412 second = (long) floor(((double) (ms + 999L)) / 1000.0);
10419 day = second / (60 * 60 * 24);
10420 second = second % (60 * 60 * 24);
10421 hour = second / (60 * 60);
10422 second = second % (60 * 60);
10423 minute = second / 60;
10424 second = second % 60;
10427 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
10428 sign, day, hour, minute, second);
10430 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
10432 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
10439 * This is necessary because some C libraries aren't ANSI C compliant yet.
10442 StrStr(string, match)
10443 char *string, *match;
10447 length = strlen(match);
10449 for (i = strlen(string) - length; i >= 0; i--, string++)
10450 if (!strncmp(match, string, length))
10457 StrCaseStr(string, match)
10458 char *string, *match;
10462 length = strlen(match);
10464 for (i = strlen(string) - length; i >= 0; i--, string++) {
10465 for (j = 0; j < length; j++) {
10466 if (ToLower(match[j]) != ToLower(string[j]))
10469 if (j == length) return string;
10483 c1 = ToLower(*s1++);
10484 c2 = ToLower(*s2++);
10485 if (c1 > c2) return 1;
10486 if (c1 < c2) return -1;
10487 if (c1 == NULLCHAR) return 0;
10496 return isupper(c) ? tolower(c) : c;
10504 return islower(c) ? toupper(c) : c;
10506 #endif /* !_amigados */
10514 if ((ret = (char *) malloc(strlen(s) + 1))) {
10521 StrSavePtr(s, savePtr)
10522 char *s, **savePtr;
10527 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
10528 strcpy(*savePtr, s);
10540 clock = time((time_t *)NULL);
10541 tm = localtime(&clock);
10542 sprintf(buf, "%04d.%02d.%02d",
10543 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
10544 return StrSave(buf);
10549 PositionToFEN(move, useFEN960)
10553 int i, j, fromX, fromY, toX, toY;
10559 whiteToPlay = (gameMode == EditPosition) ?
10560 !blackPlaysFirst : (move % 2 == 0);
10563 /* Piece placement data */
10564 for (i = BOARD_SIZE - 1; i >= 0; i--) {
10566 for (j = 0; j < BOARD_SIZE; j++) {
10567 if (boards[move][i][j] == EmptySquare) {
10570 if (emptycount > 0) {
10571 *p++ = '0' + emptycount;
10574 *p++ = PieceToChar(boards[move][i][j]);
10577 if (emptycount > 0) {
10578 *p++ = '0' + emptycount;
10586 *p++ = whiteToPlay ? 'w' : 'b';
10589 /* HACK: we don't keep track of castling availability, so fake it! */
10591 /* PUSH Fabien & Tord */
10593 /* Declare all potential FRC castling rights (conservative) */
10594 /* outermost rook on each side of the king */
10596 if( gameInfo.variant == VariantFischeRandom ) {
10601 /* White castling rights */
10603 for (fk = 1; fk < 7; fk++) {
10605 if (boards[move][0][fk] == WhiteKing) {
10607 for (fr = 7; fr > fk; fr--) { /* H side */
10608 if (boards[move][0][fr] == WhiteRook) {
10609 *p++ = useFEN960 ? 'A' + fr : 'K';
10614 for (fr = 0; fr < fk; fr++) { /* A side */
10615 if (boards[move][0][fr] == WhiteRook) {
10616 *p++ = useFEN960 ? 'A' + fr : 'Q';
10623 /* Black castling rights */
10625 for (fk = 1; fk < 7; fk++) {
10627 if (boards[move][7][fk] == BlackKing) {
10629 for (fr = 7; fr > fk; fr--) { /* H side */
10630 if (boards[move][7][fr] == BlackRook) {
10631 *p++ = useFEN960 ? 'a' + fr : 'k';
10636 for (fr = 0; fr < fk; fr++) { /* A side */
10637 if (boards[move][7][fr] == BlackRook) {
10638 *p++ = useFEN960 ? 'a' + fr : 'q';
10645 if (q == p) *p++ = '-'; /* No castling rights */
10650 if (boards[move][0][4] == WhiteKing) {
10651 if (boards[move][0][7] == WhiteRook) *p++ = 'K';
10652 if (boards[move][0][0] == WhiteRook) *p++ = 'Q';
10654 if (boards[move][7][4] == BlackKing) {
10655 if (boards[move][7][7] == BlackRook) *p++ = 'k';
10656 if (boards[move][7][0] == BlackRook) *p++ = 'q';
10658 if (q == p) *p++ = '-';
10662 /* POP Fabien & Tord */
10664 /* En passant target square */
10665 if (move > backwardMostMove) {
10666 fromX = moveList[move - 1][0] - 'a';
10667 fromY = moveList[move - 1][1] - '1';
10668 toX = moveList[move - 1][2] - 'a';
10669 toY = moveList[move - 1][3] - '1';
10670 if (fromY == (whiteToPlay ? 6 : 1) &&
10671 toY == (whiteToPlay ? 4 : 3) &&
10672 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
10674 /* 2-square pawn move just happened */
10676 *p++ = whiteToPlay ? '6' : '3';
10684 /* We don't keep track of halfmove clock for 50-move rule */
10688 /* Fullmove number */
10689 sprintf(p, "%d", (move / 2) + 1);
10691 return StrSave(buf);
10695 ParseFEN(board, blackPlaysFirst, fen)
10697 int *blackPlaysFirst;
10706 /* Piece placement data */
10707 for (i = BOARD_SIZE - 1; i >= 0; i--) {
10710 if (*p == '/' || *p == ' ') {
10711 if (*p == '/') p++;
10712 emptycount = BOARD_SIZE - j;
10713 while (emptycount--) board[i][j++] = EmptySquare;
10715 } else if (isdigit(*p)) {
10716 emptycount = *p++ - '0';
10717 if (j + emptycount > BOARD_SIZE) return FALSE;
10718 while (emptycount--) board[i][j++] = EmptySquare;
10719 } else if (isalpha(*p)) {
10720 if (j >= BOARD_SIZE) return FALSE;
10721 board[i][j++] = CharToPiece(*p++);
10727 while (*p == '/' || *p == ' ') p++;
10732 *blackPlaysFirst = FALSE;
10735 *blackPlaysFirst = TRUE;
10741 /* !!We ignore the rest of the FEN notation */
10746 EditPositionPasteFEN(char *fen)
10749 Board initial_position;
10751 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
10752 DisplayError("Bad FEN position in clipboard", 0);
10755 int savedBlackPlaysFirst = blackPlaysFirst;
10756 EditPositionEvent();
10757 blackPlaysFirst = savedBlackPlaysFirst;
10758 CopyBoard(boards[0], initial_position);
10759 EditPositionDone();
10760 DisplayBothClocks();
10761 DrawPosition(FALSE, boards[currentMove]);