2 * backend.c -- Common back end for X and Windows NT versions of
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. */
55 #include <sys/types.h>
62 #else /* not STDC_HEADERS */
65 # else /* not HAVE_STRING_H */
67 # endif /* not HAVE_STRING_H */
68 #endif /* not STDC_HEADERS */
71 # include <sys/fcntl.h>
72 #else /* not HAVE_SYS_FCNTL_H */
75 # endif /* HAVE_FCNTL_H */
76 #endif /* not HAVE_SYS_FCNTL_H */
78 #if TIME_WITH_SYS_TIME
79 # include <sys/time.h>
83 # include <sys/time.h>
89 #if defined(_amigados) && !defined(__GNUC__)
94 extern int gettimeofday(struct timeval *, struct timezone *);
102 #include "frontend.h"
109 #include "backendz.h"
111 /* A point in time */
113 long sec; /* Assuming this is >= 32 bits */
114 int ms; /* Assuming this is >= 16 bits */
117 /* Search stats from chessprogram */
119 char movelist[MSG_SIZ]; /* Last PV we were sent */
120 int depth; /* Current search depth */
121 int nr_moves; /* Total nr of root moves */
122 int moves_left; /* Moves remaining to be searched */
123 char move_name[MOVE_LEN]; /* Current move being searched, if provided */
124 unsigned long nodes; /* # of nodes searched */
125 int time; /* Search time (centiseconds) */
126 int score; /* Score (centipawns) */
127 int got_only_move; /* If last msg was "(only move)" */
128 int got_fail; /* 0 - nothing, 1 - got "--", 2 - got "++" */
129 int ok_to_send; /* handshaking between send & recv */
130 int line_is_book; /* 1 if movelist is book moves */
131 int seen_stat; /* 1 if we've seen the stat01: line */
134 int establish P((void));
135 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
136 char *buf, int count, int error));
137 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
138 char *buf, int count, int error));
139 void SendToICS P((char *s));
140 void SendToICSDelayed P((char *s, long msdelay));
141 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
143 void InitPosition P((int redraw));
144 void HandleMachineMove P((char *message, ChessProgramState *cps));
145 int AutoPlayOneMove P((void));
146 int LoadGameOneMove P((ChessMove readAhead));
147 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
148 int LoadPositionFromFile P((char *filename, int n, char *title));
149 int SavePositionToFile P((char *filename));
150 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
152 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
153 void ShowMove P((int fromX, int fromY, int toX, int toY));
154 void FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
155 /*char*/int promoChar));
156 void BackwardInner P((int target));
157 void ForwardInner P((int target));
158 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
159 void EditPositionDone P((void));
160 void PrintOpponents P((FILE *fp));
161 void PrintPosition P((FILE *fp, int move));
162 void StartChessProgram P((ChessProgramState *cps));
163 void SendToProgram P((char *message, ChessProgramState *cps));
164 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
165 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
166 char *buf, int count, int error));
167 void SendTimeControl P((ChessProgramState *cps,
168 int mps, long tc, int inc, int sd, int st));
169 char *TimeControlTagValue P((void));
170 void Attention P((ChessProgramState *cps));
171 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
172 void ResurrectChessProgram P((void));
173 void DisplayComment P((int moveNumber, char *text));
174 void DisplayMove P((int moveNumber));
175 void DisplayAnalysis P((void));
177 void ParseGameHistory P((char *game));
178 void ParseBoard12 P((char *string));
179 void StartClocks P((void));
180 void SwitchClocks P((void));
181 void StopClocks P((void));
182 void ResetClocks P((void));
183 char *PGNDate P((void));
184 void SetGameInfo P((void));
185 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
186 int RegisterMove P((void));
187 void MakeRegisteredMove P((void));
188 void TruncateGame P((void));
189 int looking_at P((char *, int *, char *));
190 void CopyPlayerNameIntoFileName P((char **, char *));
191 char *SavePart P((char *));
192 int SaveGameOldStyle P((FILE *));
193 int SaveGamePGN P((FILE *));
194 void GetTimeMark P((TimeMark *));
195 long SubtractTimeMarks P((TimeMark *, TimeMark *));
196 int CheckFlags P((void));
197 long NextTickLength P((long));
198 void CheckTimeControl P((void));
199 void show_bytes P((FILE *, char *, int));
200 int string_to_rating P((char *str));
201 void ParseFeatures P((char* args, ChessProgramState *cps));
202 void InitBackEnd3 P((void));
203 void FeatureDone P((ChessProgramState* cps, int val));
204 void InitChessProgram P((ChessProgramState *cps));
206 extern int tinyLayout, smallLayout;
207 static ChessProgramStats programStats;
209 /* States for ics_getting_history */
211 #define H_REQUESTED 1
212 #define H_GOT_REQ_HEADER 2
213 #define H_GOT_UNREQ_HEADER 3
214 #define H_GETTING_MOVES 4
215 #define H_GOT_UNWANTED_HEADER 5
217 /* whosays values for GameEnds */
224 /* Maximum number of games in a cmail message */
225 #define CMAIL_MAX_GAMES 20
227 /* Different types of move when calling RegisterMove */
229 #define CMAIL_RESIGN 1
231 #define CMAIL_ACCEPT 3
233 /* Different types of result to remember for each game */
234 #define CMAIL_NOT_RESULT 0
235 #define CMAIL_OLD_RESULT 1
236 #define CMAIL_NEW_RESULT 2
238 /* Telnet protocol constants */
248 /* Fake up flags for now, as we aren't keeping track of castling
253 int flags = F_ALL_CASTLE_OK;
254 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
255 switch (gameInfo.variant) {
257 case VariantGiveaway:
258 flags |= F_IGNORE_CHECK;
259 flags &= ~F_ALL_CASTLE_OK;
262 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
264 case VariantKriegspiel:
265 flags |= F_KRIEGSPIEL_CAPTURE;
267 case VariantNoCastle:
268 flags &= ~F_ALL_CASTLE_OK;
276 FILE *gameFileFP, *debugFP;
278 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
279 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
280 char thinkOutput1[MSG_SIZ*10];
282 ChessProgramState first, second;
284 /* premove variables */
287 int premoveFromX = 0;
288 int premoveFromY = 0;
289 int premovePromoChar = 0;
291 Boolean alarmSounded;
292 /* end premove variables */
294 #define ICS_GENERIC 0
297 #define ICS_CHESSNET 3 /* not really supported */
298 int ics_type = ICS_GENERIC;
299 char *ics_prefix = "$";
301 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
302 int pauseExamForwardMostMove = 0;
303 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
304 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
305 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
306 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
307 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
308 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
309 int whiteFlag = FALSE, blackFlag = FALSE;
310 int userOfferedDraw = FALSE;
311 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
312 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
313 int cmailMoveType[CMAIL_MAX_GAMES];
314 long ics_clock_paused = 0;
315 ProcRef icsPR = NoProc, cmailPR = NoProc;
316 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
317 GameMode gameMode = BeginningOfGame;
318 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
319 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
320 char white_holding[64], black_holding[64];
321 TimeMark lastNodeCountTime;
322 long lastNodeCount=0;
323 int have_sent_ICS_logon = 0;
325 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
326 long timeRemaining[2][MAX_MOVES];
328 TimeMark programStartTime;
329 char ics_handle[MSG_SIZ];
330 int have_set_title = 0;
332 /* animateTraining preserves the state of appData.animate
333 * when Training mode is activated. This allows the
334 * response to be animated when appData.animate == TRUE and
335 * appData.animateDragging == TRUE.
337 Boolean animateTraining;
343 Board boards[MAX_MOVES];
344 Board initialPosition = {
345 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
346 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
347 { WhitePawn, WhitePawn, WhitePawn, WhitePawn,
348 WhitePawn, WhitePawn, WhitePawn, WhitePawn },
349 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
350 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
351 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
352 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
353 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
354 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
355 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
356 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
357 { BlackPawn, BlackPawn, BlackPawn, BlackPawn,
358 BlackPawn, BlackPawn, BlackPawn, BlackPawn },
359 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
360 BlackKing, BlackBishop, BlackKnight, BlackRook }
362 Board twoKingsPosition = {
363 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
364 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
365 { WhitePawn, WhitePawn, WhitePawn, WhitePawn,
366 WhitePawn, WhitePawn, WhitePawn, WhitePawn },
367 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
368 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
369 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
370 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
371 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
372 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
373 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
374 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
375 { BlackPawn, BlackPawn, BlackPawn, BlackPawn,
376 BlackPawn, BlackPawn, BlackPawn, BlackPawn },
377 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
378 BlackKing, BlackKing, BlackKnight, BlackRook }
382 /* Convert str to a rating. Checks for special cases of "----",
383 "++++", etc. Also strips ()'s */
385 string_to_rating(str)
388 while(*str && !isdigit(*str)) ++str;
390 return 0; /* One of the special "no rating" cases */
398 /* Init programStats */
399 programStats.movelist[0] = 0;
400 programStats.depth = 0;
401 programStats.nr_moves = 0;
402 programStats.moves_left = 0;
403 programStats.nodes = 0;
404 programStats.time = 100;
405 programStats.score = 0;
406 programStats.got_only_move = 0;
407 programStats.got_fail = 0;
408 programStats.line_is_book = 0;
414 int matched, min, sec;
416 GetTimeMark(&programStartTime);
419 programStats.ok_to_send = 1;
420 programStats.seen_stat = 0;
423 * Initialize game list
429 * Internet chess server status
431 if (appData.icsActive) {
432 appData.matchMode = FALSE;
433 appData.matchGames = 0;
435 appData.noChessProgram = !appData.zippyPlay;
437 appData.zippyPlay = FALSE;
438 appData.zippyTalk = FALSE;
439 appData.noChessProgram = TRUE;
441 if (*appData.icsHelper != NULLCHAR) {
442 appData.useTelnet = TRUE;
443 appData.telnetProgram = appData.icsHelper;
446 appData.zippyTalk = appData.zippyPlay = FALSE;
450 * Parse timeControl resource
452 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
453 appData.movesPerSession)) {
455 sprintf(buf, "bad timeControl option %s", appData.timeControl);
456 DisplayFatalError(buf, 0, 2);
460 * Parse searchTime resource
462 if (*appData.searchTime != NULLCHAR) {
463 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
465 searchTime = min * 60;
466 } else if (matched == 2) {
467 searchTime = min * 60 + sec;
470 sprintf(buf, "bad searchTime option %s", appData.searchTime);
471 DisplayFatalError(buf, 0, 2);
475 first.which = "first";
476 second.which = "second";
477 first.maybeThinking = second.maybeThinking = FALSE;
478 first.pr = second.pr = NoProc;
479 first.isr = second.isr = NULL;
480 first.sendTime = second.sendTime = 2;
481 first.sendDrawOffers = 1;
482 if (appData.firstPlaysBlack) {
483 first.twoMachinesColor = "black\n";
484 second.twoMachinesColor = "white\n";
486 first.twoMachinesColor = "white\n";
487 second.twoMachinesColor = "black\n";
489 first.program = appData.firstChessProgram;
490 second.program = appData.secondChessProgram;
491 first.host = appData.firstHost;
492 second.host = appData.secondHost;
493 first.dir = appData.firstDirectory;
494 second.dir = appData.secondDirectory;
495 first.other = &second;
496 second.other = &first;
497 first.initString = appData.initString;
498 second.initString = appData.secondInitString;
499 first.computerString = appData.firstComputerString;
500 second.computerString = appData.secondComputerString;
501 first.useSigint = second.useSigint = TRUE;
502 first.useSigterm = second.useSigterm = TRUE;
503 first.reuse = appData.reuseFirst;
504 second.reuse = appData.reuseSecond;
505 first.useSetboard = second.useSetboard = FALSE;
506 first.useSAN = second.useSAN = FALSE;
507 first.usePing = second.usePing = FALSE;
508 first.lastPing = second.lastPing = 0;
509 first.lastPong = second.lastPong = 0;
510 first.usePlayother = second.usePlayother = FALSE;
511 first.useColors = second.useColors = TRUE;
512 first.useUsermove = second.useUsermove = FALSE;
513 first.sendICS = second.sendICS = FALSE;
514 first.sendName = second.sendName = appData.icsActive;
515 first.sdKludge = second.sdKludge = FALSE;
516 first.stKludge = second.stKludge = FALSE;
517 TidyProgramName(first.program, first.host, first.tidy);
518 TidyProgramName(second.program, second.host, second.tidy);
519 first.matchWins = second.matchWins = 0;
520 strcpy(first.variants, appData.variant);
521 strcpy(second.variants, appData.variant);
522 first.analysisSupport = second.analysisSupport = 2; /* detect */
523 first.analyzing = second.analyzing = FALSE;
524 first.initDone = second.initDone = FALSE;
526 if (appData.firstProtocolVersion > PROTOVER ||
527 appData.firstProtocolVersion < 1) {
529 sprintf(buf, "protocol version %d not supported",
530 appData.firstProtocolVersion);
531 DisplayFatalError(buf, 0, 2);
533 first.protocolVersion = appData.firstProtocolVersion;
536 if (appData.secondProtocolVersion > PROTOVER ||
537 appData.secondProtocolVersion < 1) {
539 sprintf(buf, "protocol version %d not supported",
540 appData.secondProtocolVersion);
541 DisplayFatalError(buf, 0, 2);
543 second.protocolVersion = appData.secondProtocolVersion;
546 if (appData.icsActive) {
547 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
548 } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
549 appData.clockMode = FALSE;
550 first.sendTime = second.sendTime = 0;
554 /* Override some settings from environment variables, for backward
555 compatibility. Unfortunately it's not feasible to have the env
556 vars just set defaults, at least in xboard. Ugh.
558 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
563 if (appData.noChessProgram) {
564 programVersion = (char*) malloc(5 + strlen(PRODUCT) + strlen(VERSION)
565 + strlen(PATCHLEVEL));
566 sprintf(programVersion, "%s %s.%s", PRODUCT, VERSION, PATCHLEVEL);
570 while (*q != ' ' && *q != NULLCHAR) q++;
572 while (p > first.program && *(p-1) != '/') p--;
573 programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)
574 + strlen(PATCHLEVEL) + (q - p));
575 sprintf(programVersion, "%s %s.%s + ", PRODUCT, VERSION, PATCHLEVEL);
576 strncat(programVersion, p, q - p);
579 if (!appData.icsActive) {
581 /* Check for variants that are supported only in ICS mode,
582 or not at all. Some that are accepted here nevertheless
583 have bugs; see comments below.
585 VariantClass variant = StringToVariant(appData.variant);
587 case VariantBughouse: /* need four players and two boards */
588 case VariantKriegspiel: /* need to hide pieces and move details */
589 case VariantFischeRandom: /* castling doesn't work, shuffle not done */
590 sprintf(buf, "Variant %s supported only in ICS mode", appData.variant);
591 DisplayFatalError(buf, 0, 2);
595 case VariantLoadable:
605 sprintf(buf, "Unknown variant name %s", appData.variant);
606 DisplayFatalError(buf, 0, 2);
609 case VariantNormal: /* definitely works! */
610 case VariantWildCastle: /* pieces not automatically shuffled */
611 case VariantNoCastle: /* pieces not automatically shuffled */
612 case VariantCrazyhouse: /* holdings not shown,
613 offboard interposition not understood */
614 case VariantLosers: /* should work except for win condition,
615 and doesn't know captures are mandatory */
616 case VariantSuicide: /* should work except for win condition,
617 and doesn't know captures are mandatory */
618 case VariantGiveaway: /* should work except for win condition,
619 and doesn't know captures are mandatory */
620 case VariantTwoKings: /* should work */
621 case VariantAtomic: /* should work except for win condition */
622 case Variant3Check: /* should work except for win condition */
623 case VariantShatranj: /* might work if TestLegality is off */
630 ParseTimeControl(tc, ti, mps)
635 int matched, min, sec;
637 matched = sscanf(tc, "%d:%d", &min, &sec);
639 timeControl = min * 60 * 1000;
640 } else if (matched == 2) {
641 timeControl = (min * 60 + sec) * 1000;
647 timeIncrement = ti * 1000; /* convert to ms */
651 movesPerSession = mps;
659 if (appData.debugMode) {
660 fprintf(debugFP, "%s\n", programVersion);
663 if (appData.matchGames > 0) {
664 appData.matchMode = TRUE;
665 } else if (appData.matchMode) {
666 appData.matchGames = 1;
669 if (appData.noChessProgram || first.protocolVersion == 1) {
672 /* kludge: allow timeout for initial "feature" commands */
674 DisplayMessage("", "Starting chess program");
675 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
680 InitBackEnd3 P((void))
682 GameMode initialMode;
686 InitChessProgram(&first);
688 if (appData.icsActive) {
691 if (*appData.icsCommPort != NULLCHAR) {
692 sprintf(buf, "Could not open comm port %s",
693 appData.icsCommPort);
695 sprintf(buf, "Could not connect to host %s, port %s",
696 appData.icsHost, appData.icsPort);
698 DisplayFatalError(buf, err, 1);
703 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
705 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
706 } else if (appData.noChessProgram) {
712 if (*appData.cmailGameName != NULLCHAR) {
714 OpenLoopback(&cmailPR);
716 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
720 DisplayMessage("", "");
721 if (StrCaseCmp(appData.initialMode, "") == 0) {
722 initialMode = BeginningOfGame;
723 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
724 initialMode = TwoMachinesPlay;
725 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
726 initialMode = AnalyzeFile;
727 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
728 initialMode = AnalyzeMode;
729 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
730 initialMode = MachinePlaysWhite;
731 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
732 initialMode = MachinePlaysBlack;
733 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
734 initialMode = EditGame;
735 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
736 initialMode = EditPosition;
737 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
738 initialMode = Training;
740 sprintf(buf, "Unknown initialMode %s", appData.initialMode);
741 DisplayFatalError(buf, 0, 2);
745 if (appData.matchMode) {
746 /* Set up machine vs. machine match */
747 if (appData.noChessProgram) {
748 DisplayFatalError("Can't have a match with no chess programs",
754 if (*appData.loadGameFile != NULLCHAR) {
755 if (!LoadGameFromFile(appData.loadGameFile,
756 appData.loadGameIndex,
757 appData.loadGameFile, FALSE)) {
758 DisplayFatalError("Bad game file", 0, 1);
761 } else if (*appData.loadPositionFile != NULLCHAR) {
762 if (!LoadPositionFromFile(appData.loadPositionFile,
763 appData.loadPositionIndex,
764 appData.loadPositionFile)) {
765 DisplayFatalError("Bad position file", 0, 1);
770 } else if (*appData.cmailGameName != NULLCHAR) {
771 /* Set up cmail mode */
772 ReloadCmailMsgEvent(TRUE);
774 /* Set up other modes */
775 if (initialMode == AnalyzeFile) {
776 if (*appData.loadGameFile == NULLCHAR) {
777 DisplayFatalError("AnalyzeFile mode requires a game file", 0, 1);
781 if (*appData.loadGameFile != NULLCHAR) {
782 (void) LoadGameFromFile(appData.loadGameFile,
783 appData.loadGameIndex,
784 appData.loadGameFile, TRUE);
785 } else if (*appData.loadPositionFile != NULLCHAR) {
786 (void) LoadPositionFromFile(appData.loadPositionFile,
787 appData.loadPositionIndex,
788 appData.loadPositionFile);
790 if (initialMode == AnalyzeMode) {
791 if (appData.noChessProgram) {
792 DisplayFatalError("Analysis mode requires a chess engine", 0, 2);
795 if (appData.icsActive) {
796 DisplayFatalError("Analysis mode does not work with ICS mode",0,2);
800 } else if (initialMode == AnalyzeFile) {
801 ShowThinkingEvent(TRUE);
803 AnalysisPeriodicEvent(1);
804 } else if (initialMode == MachinePlaysWhite) {
805 if (appData.noChessProgram) {
806 DisplayFatalError("MachineWhite mode requires a chess engine",
810 if (appData.icsActive) {
811 DisplayFatalError("MachineWhite mode does not work with ICS mode",
816 } else if (initialMode == MachinePlaysBlack) {
817 if (appData.noChessProgram) {
818 DisplayFatalError("MachineBlack mode requires a chess engine",
822 if (appData.icsActive) {
823 DisplayFatalError("MachineBlack mode does not work with ICS mode",
828 } else if (initialMode == TwoMachinesPlay) {
829 if (appData.noChessProgram) {
830 DisplayFatalError("TwoMachines mode requires a chess engine",
834 if (appData.icsActive) {
835 DisplayFatalError("TwoMachines mode does not work with ICS mode",
840 } else if (initialMode == EditGame) {
842 } else if (initialMode == EditPosition) {
844 } else if (initialMode == Training) {
845 if (*appData.loadGameFile == NULLCHAR) {
846 DisplayFatalError("Training mode requires a game file", 0, 2);
855 * Establish will establish a contact to a remote host.port.
856 * Sets icsPR to a ProcRef for a process (or pseudo-process)
857 * used to talk to the host.
858 * Returns 0 if okay, error code if not.
865 if (*appData.icsCommPort != NULLCHAR) {
866 /* Talk to the host through a serial comm port */
867 return OpenCommPort(appData.icsCommPort, &icsPR);
869 } else if (*appData.gateway != NULLCHAR) {
870 if (*appData.remoteShell == NULLCHAR) {
871 /* Use the rcmd protocol to run telnet program on a gateway host */
872 sprintf(buf, "%s %s %s",
873 appData.telnetProgram, appData.icsHost, appData.icsPort);
874 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
877 /* Use the rsh program to run telnet program on a gateway host */
878 if (*appData.remoteUser == NULLCHAR) {
879 sprintf(buf, "%s %s %s %s %s", appData.remoteShell,
880 appData.gateway, appData.telnetProgram,
881 appData.icsHost, appData.icsPort);
883 sprintf(buf, "%s %s -l %s %s %s %s",
884 appData.remoteShell, appData.gateway,
885 appData.remoteUser, appData.telnetProgram,
886 appData.icsHost, appData.icsPort);
888 return StartChildProcess(buf, "", &icsPR);
891 } else if (appData.useTelnet) {
892 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
895 /* TCP socket interface differs somewhat between
896 Unix and NT; handle details in the front end.
898 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
903 show_bytes(fp, buf, count)
909 if (*buf < 040 || *(unsigned char *) buf > 0177) {
910 fprintf(fp, "\\%03o", *buf & 0xff);
919 /* Returns an errno value */
921 OutputMaybeTelnet(pr, message, count, outError)
927 char buf[8192], *p, *q, *buflim;
928 int left, newcount, outcount;
930 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
931 *appData.gateway != NULLCHAR) {
932 if (appData.debugMode) {
933 fprintf(debugFP, ">ICS: ");
934 show_bytes(debugFP, message, count);
935 fprintf(debugFP, "\n");
937 return OutputToProcess(pr, message, count, outError);
940 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
947 if (appData.debugMode) {
948 fprintf(debugFP, ">ICS: ");
949 show_bytes(debugFP, buf, newcount);
950 fprintf(debugFP, "\n");
952 outcount = OutputToProcess(pr, buf, newcount, outError);
953 if (outcount < newcount) return -1; /* to be sure */
960 } else if (((unsigned char) *p) == TN_IAC) {
961 *q++ = (char) TN_IAC;
968 if (appData.debugMode) {
969 fprintf(debugFP, ">ICS: ");
970 show_bytes(debugFP, buf, newcount);
971 fprintf(debugFP, "\n");
973 outcount = OutputToProcess(pr, buf, newcount, outError);
974 if (outcount < newcount) return -1; /* to be sure */
979 read_from_player(isr, closure, message, count, error)
986 int outError, outCount;
987 static int gotEof = 0;
989 /* Pass data read from player on to ICS */
992 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
993 if (outCount < count) {
994 DisplayFatalError("Error writing to ICS", outError, 1);
996 } else if (count < 0) {
997 RemoveInputSource(isr);
998 DisplayFatalError("Error reading from keyboard", error, 1);
999 } else if (gotEof++ > 0) {
1000 RemoveInputSource(isr);
1001 DisplayFatalError("Got end of file from keyboard", 0, 0);
1009 int count, outCount, outError;
1011 if (icsPR == NULL) return;
1014 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1015 if (outCount < count) {
1016 DisplayFatalError("Error writing to ICS", outError, 1);
1020 /* This is used for sending logon scripts to the ICS. Sending
1021 without a delay causes problems when using timestamp on ICC
1022 (at least on my machine). */
1024 SendToICSDelayed(s,msdelay)
1028 int count, outCount, outError;
1030 if (icsPR == NULL) return;
1033 if (appData.debugMode) {
1034 fprintf(debugFP, ">ICS: ");
1035 show_bytes(debugFP, s, count);
1036 fprintf(debugFP, "\n");
1038 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1040 if (outCount < count) {
1041 DisplayFatalError("Error writing to ICS", outError, 1);
1046 /* Remove all highlighting escape sequences in s
1047 Also deletes any suffix starting with '('
1050 StripHighlightAndTitle(s)
1053 static char retbuf[MSG_SIZ];
1056 while (*s != NULLCHAR) {
1057 while (*s == '\033') {
1058 while (*s != NULLCHAR && !isalpha(*s)) s++;
1059 if (*s != NULLCHAR) s++;
1061 while (*s != NULLCHAR && *s != '\033') {
1062 if (*s == '(' || *s == '[') {
1073 /* Remove all highlighting escape sequences in s */
1078 static char retbuf[MSG_SIZ];
1081 while (*s != NULLCHAR) {
1082 while (*s == '\033') {
1083 while (*s != NULLCHAR && !isalpha(*s)) s++;
1084 if (*s != NULLCHAR) s++;
1086 while (*s != NULLCHAR && *s != '\033') {
1094 char *variantNames[] = VARIANT_NAMES;
1099 return variantNames[v];
1103 /* Identify a variant from the strings the chess servers use or the
1104 PGN Variant tag names we use. */
1111 VariantClass v = VariantNormal;
1112 int i, found = FALSE;
1117 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1118 if (StrCaseStr(e, variantNames[i])) {
1119 v = (VariantClass) i;
1126 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1127 || StrCaseStr(e, "wild/fr")) {
1128 v = VariantFischeRandom;
1129 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1130 (i = 1, p = StrCaseStr(e, "w"))) {
1132 while (*p && (isspace(*p) || *p == '(')) p++;
1139 case 0: /* FICS only, actually */
1141 /* Castling legal even if K starts on d-file */
1142 v = VariantWildCastle;
1147 /* Castling illegal even if K & R happen to start in
1148 normal positions. */
1149 v = VariantNoCastle;
1162 /* Castling legal iff K & R start in normal positions */
1168 /* Special wilds for position setup; unclear what to do here */
1169 v = VariantLoadable;
1172 /* Bizarre ICC game */
1173 v = VariantTwoKings;
1176 v = VariantKriegspiel;
1182 v = VariantFischeRandom;
1185 v = VariantCrazyhouse;
1188 v = VariantBughouse;
1194 /* Not quite the same as FICS suicide! */
1195 v = VariantGiveaway;
1201 v = VariantShatranj;
1204 /* Temporary names for future ICC types. The name *will* change in
1205 the next xboard/WinBoard release after ICC defines it. */
1232 /* Found "wild" or "w" in the string but no number;
1233 must assume it's normal chess. */
1237 sprintf(buf, "Unknown wild type %d", wnum);
1238 DisplayError(buf, 0);
1244 if (appData.debugMode) {
1245 fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
1246 e, wnum, VariantName(v));
1251 static int leftover_start = 0, leftover_len = 0;
1252 char star_match[STAR_MATCH_N][MSG_SIZ];
1254 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1255 advance *index beyond it, and set leftover_start to the new value of
1256 *index; else return FALSE. If pattern contains the character '*', it
1257 matches any sequence of characters not containing '\r', '\n', or the
1258 character following the '*' (if any), and the matched sequence(s) are
1259 copied into star_match.
1262 looking_at(buf, index, pattern)
1267 char *bufp = &buf[*index], *patternp = pattern;
1269 char *matchp = star_match[0];
1272 if (*patternp == NULLCHAR) {
1273 *index = leftover_start = bufp - buf;
1277 if (*bufp == NULLCHAR) return FALSE;
1278 if (*patternp == '*') {
1279 if (*bufp == *(patternp + 1)) {
1281 matchp = star_match[++star_count];
1285 } else if (*bufp == '\n' || *bufp == '\r') {
1287 if (*patternp == NULLCHAR)
1292 *matchp++ = *bufp++;
1296 if (*patternp != *bufp) return FALSE;
1303 SendToPlayer(data, length)
1307 int error, outCount;
1308 outCount = OutputToProcess(NoProc, data, length, &error);
1309 if (outCount < length) {
1310 DisplayFatalError("Error writing to display", error, 1);
1315 PackHolding(packed, holding)
1327 switch (runlength) {
1338 sprintf(q, "%d", runlength);
1350 /* Telnet protocol requests from the front end */
1352 TelnetRequest(ddww, option)
1353 unsigned char ddww, option;
1355 unsigned char msg[3];
1356 int outCount, outError;
1358 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1360 if (appData.debugMode) {
1361 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1377 sprintf(buf1, "%d", ddww);
1386 sprintf(buf2, "%d", option);
1389 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1394 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1396 DisplayFatalError("Error writing to ICS", outError, 1);
1403 if (!appData.icsActive) return;
1404 TelnetRequest(TN_DO, TN_ECHO);
1410 if (!appData.icsActive) return;
1411 TelnetRequest(TN_DONT, TN_ECHO);
1414 static int loggedOn = FALSE;
1416 /*-- Game start info cache: --*/
1418 char gs_kind[MSG_SIZ];
1419 static char player1Name[128] = "";
1420 static char player2Name[128] = "";
1421 static int player1Rating = -1;
1422 static int player2Rating = -1;
1423 /*----------------------------*/
1426 read_from_ics(isr, closure, data, count, error)
1433 #define BUF_SIZE 8192
1434 #define STARTED_NONE 0
1435 #define STARTED_MOVES 1
1436 #define STARTED_BOARD 2
1437 #define STARTED_OBSERVE 3
1438 #define STARTED_HOLDINGS 4
1439 #define STARTED_CHATTER 5
1440 #define STARTED_COMMENT 6
1441 #define STARTED_MOVES_NOHIDE 7
1443 static int started = STARTED_NONE;
1444 static char parse[20000];
1445 static int parse_pos = 0;
1446 static char buf[BUF_SIZE + 1];
1447 static int firstTime = TRUE, intfSet = FALSE;
1448 static ColorClass curColor = ColorNormal;
1449 static ColorClass prevColor = ColorNormal;
1450 static int savingComment = FALSE;
1459 /* If last read ended with a partial line that we couldn't parse,
1460 prepend it to the new read and try again. */
1461 if (leftover_len > 0) {
1462 for (i=0; i<leftover_len; i++)
1463 buf[i] = buf[leftover_start + i];
1466 /* Copy in new characters, removing nulls and \r's */
1467 buf_len = leftover_len;
1468 for (i = 0; i < count; i++) {
1469 if (data[i] != NULLCHAR && data[i] != '\r')
1470 buf[buf_len++] = data[i];
1473 buf[buf_len] = NULLCHAR;
1474 next_out = leftover_len;
1478 while (i < buf_len) {
1479 /* Deal with part of the TELNET option negotiation
1480 protocol. We refuse to do anything beyond the
1481 defaults, except that we allow the WILL ECHO option,
1482 which ICS uses to turn off password echoing when we are
1483 directly connected to it. We reject this option
1484 if localLineEditing mode is on (always on in xboard)
1485 and we are talking to port 23, which might be a real
1486 telnet server that will try to keep WILL ECHO on permanently.
1488 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
1489 static int remoteEchoOption = FALSE; /* telnet ECHO option */
1490 unsigned char option;
1492 switch ((unsigned char) buf[++i]) {
1494 if (appData.debugMode)
1495 fprintf(debugFP, "\n<WILL ");
1496 switch (option = (unsigned char) buf[++i]) {
1498 if (appData.debugMode)
1499 fprintf(debugFP, "ECHO ");
1500 /* Reply only if this is a change, according
1501 to the protocol rules. */
1502 if (remoteEchoOption) break;
1503 if (appData.localLineEditing &&
1504 atoi(appData.icsPort) == TN_PORT) {
1505 TelnetRequest(TN_DONT, TN_ECHO);
1508 TelnetRequest(TN_DO, TN_ECHO);
1509 remoteEchoOption = TRUE;
1513 if (appData.debugMode)
1514 fprintf(debugFP, "%d ", option);
1515 /* Whatever this is, we don't want it. */
1516 TelnetRequest(TN_DONT, option);
1521 if (appData.debugMode)
1522 fprintf(debugFP, "\n<WONT ");
1523 switch (option = (unsigned char) buf[++i]) {
1525 if (appData.debugMode)
1526 fprintf(debugFP, "ECHO ");
1527 /* Reply only if this is a change, according
1528 to the protocol rules. */
1529 if (!remoteEchoOption) break;
1531 TelnetRequest(TN_DONT, TN_ECHO);
1532 remoteEchoOption = FALSE;
1535 if (appData.debugMode)
1536 fprintf(debugFP, "%d ", (unsigned char) option);
1537 /* Whatever this is, it must already be turned
1538 off, because we never agree to turn on
1539 anything non-default, so according to the
1540 protocol rules, we don't reply. */
1545 if (appData.debugMode)
1546 fprintf(debugFP, "\n<DO ");
1547 switch (option = (unsigned char) buf[++i]) {
1549 /* Whatever this is, we refuse to do it. */
1550 if (appData.debugMode)
1551 fprintf(debugFP, "%d ", option);
1552 TelnetRequest(TN_WONT, option);
1557 if (appData.debugMode)
1558 fprintf(debugFP, "\n<DONT ");
1559 switch (option = (unsigned char) buf[++i]) {
1561 if (appData.debugMode)
1562 fprintf(debugFP, "%d ", option);
1563 /* Whatever this is, we are already not doing
1564 it, because we never agree to do anything
1565 non-default, so according to the protocol
1566 rules, we don't reply. */
1571 if (appData.debugMode)
1572 fprintf(debugFP, "\n<IAC ");
1573 /* Doubled IAC; pass it through */
1577 if (appData.debugMode)
1578 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
1579 /* Drop all other telnet commands on the floor */
1582 if (oldi > next_out)
1583 SendToPlayer(&buf[next_out], oldi - next_out);
1589 /* OK, this at least will *usually* work */
1590 if (!loggedOn && looking_at(buf, &i, "ics%")) {
1594 if (loggedOn && !intfSet) {
1595 if (ics_type == ICS_ICC) {
1597 "/set-quietly interface %s\n/set-quietly style 12\n",
1600 } else if (ics_type == ICS_CHESSNET) {
1601 sprintf(str, "/style 12\n");
1603 strcpy(str, "alias $ @\n$set interface ");
1604 strcat(str, programVersion);
1605 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
1607 strcat(str, "$iset nohighlight 1\n");
1609 strcat(str, "$iset lock 1\n$style 12\n");
1615 if (started == STARTED_COMMENT) {
1616 /* Accumulate characters in comment */
1617 parse[parse_pos++] = buf[i];
1618 if (buf[i] == '\n') {
1619 parse[parse_pos] = NULLCHAR;
1620 AppendComment(forwardMostMove, StripHighlight(parse));
1621 started = STARTED_NONE;
1623 /* Don't match patterns against characters in chatter */
1628 if (started == STARTED_CHATTER) {
1629 if (buf[i] != '\n') {
1630 /* Don't match patterns against characters in chatter */
1634 started = STARTED_NONE;
1637 /* Kludge to deal with rcmd protocol */
1638 if (firstTime && looking_at(buf, &i, "\001*")) {
1639 DisplayFatalError(&buf[1], 0, 1);
1645 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
1648 if (appData.debugMode)
1649 fprintf(debugFP, "ics_type %d\n", ics_type);
1652 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
1653 ics_type = ICS_FICS;
1655 if (appData.debugMode)
1656 fprintf(debugFP, "ics_type %d\n", ics_type);
1659 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
1660 ics_type = ICS_CHESSNET;
1662 if (appData.debugMode)
1663 fprintf(debugFP, "ics_type %d\n", ics_type);
1668 (looking_at(buf, &i, "\"*\" is *a registered name") ||
1669 looking_at(buf, &i, "Logging you in as \"*\"") ||
1670 looking_at(buf, &i, "will be \"*\""))) {
1671 strcpy(ics_handle, star_match[0]);
1675 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
1677 sprintf(buf, "%s@%s", ics_handle, appData.icsHost);
1678 DisplayIcsInteractionTitle(buf);
1679 have_set_title = TRUE;
1682 /* skip finger notes */
1683 if (started == STARTED_NONE &&
1684 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
1685 (buf[i] == '1' && buf[i+1] == '0')) &&
1686 buf[i+2] == ':' && buf[i+3] == ' ') {
1687 started = STARTED_CHATTER;
1692 /* skip formula vars */
1693 if (started == STARTED_NONE &&
1694 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
1695 started = STARTED_CHATTER;
1701 if (appData.zippyTalk || appData.zippyPlay) {
1703 if (ZippyControl(buf, &i) ||
1704 ZippyConverse(buf, &i) ||
1705 (appData.zippyPlay && ZippyMatch(buf, &i))) {
1711 if (/* Don't color "message" or "messages" output */
1712 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
1713 looking_at(buf, &i, "*. * at *:*: ") ||
1714 looking_at(buf, &i, "--* (*:*): ") ||
1715 /* Regular tells and says */
1716 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
1717 looking_at(buf, &i, "* (your partner) tells you: ") ||
1718 looking_at(buf, &i, "* says: ") ||
1719 /* Message notifications (same color as tells) */
1720 looking_at(buf, &i, "* has left a message ") ||
1721 looking_at(buf, &i, "* just sent you a message:\n") ||
1722 /* Whispers and kibitzes */
1723 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
1724 looking_at(buf, &i, "* kibitzes: ") ||
1726 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
1728 if (tkind == 1 && strchr(star_match[0], ':')) {
1729 /* Avoid "tells you:" spoofs in channels */
1732 if (star_match[0][0] == NULLCHAR ||
1733 strchr(star_match[0], ' ') ||
1734 (tkind == 3 && strchr(star_match[1], ' '))) {
1735 /* Reject bogus matches */
1738 if (appData.colorize) {
1739 if (oldi > next_out) {
1740 SendToPlayer(&buf[next_out], oldi - next_out);
1745 Colorize(ColorTell, FALSE);
1746 curColor = ColorTell;
1749 Colorize(ColorKibitz, FALSE);
1750 curColor = ColorKibitz;
1753 p = strrchr(star_match[1], '(');
1760 Colorize(ColorChannel1, FALSE);
1761 curColor = ColorChannel1;
1763 Colorize(ColorChannel, FALSE);
1764 curColor = ColorChannel;
1768 curColor = ColorNormal;
1772 if (started == STARTED_NONE && appData.autoComment &&
1773 (gameMode == IcsObserving ||
1774 gameMode == IcsPlayingWhite ||
1775 gameMode == IcsPlayingBlack)) {
1776 parse_pos = i - oldi;
1777 memcpy(parse, &buf[oldi], parse_pos);
1778 parse[parse_pos] = NULLCHAR;
1779 started = STARTED_COMMENT;
1780 savingComment = TRUE;
1782 started = STARTED_CHATTER;
1783 savingComment = FALSE;
1790 if (looking_at(buf, &i, "* s-shouts: ") ||
1791 looking_at(buf, &i, "* c-shouts: ")) {
1792 if (appData.colorize) {
1793 if (oldi > next_out) {
1794 SendToPlayer(&buf[next_out], oldi - next_out);
1797 Colorize(ColorSShout, FALSE);
1798 curColor = ColorSShout;
1801 started = STARTED_CHATTER;
1805 if (looking_at(buf, &i, "--->")) {
1810 if (looking_at(buf, &i, "* shouts: ") ||
1811 looking_at(buf, &i, "--> ")) {
1812 if (appData.colorize) {
1813 if (oldi > next_out) {
1814 SendToPlayer(&buf[next_out], oldi - next_out);
1817 Colorize(ColorShout, FALSE);
1818 curColor = ColorShout;
1821 started = STARTED_CHATTER;
1825 if (looking_at( buf, &i, "Challenge:")) {
1826 if (appData.colorize) {
1827 if (oldi > next_out) {
1828 SendToPlayer(&buf[next_out], oldi - next_out);
1831 Colorize(ColorChallenge, FALSE);
1832 curColor = ColorChallenge;
1838 if (looking_at(buf, &i, "* offers you") ||
1839 looking_at(buf, &i, "* offers to be") ||
1840 looking_at(buf, &i, "* would like to") ||
1841 looking_at(buf, &i, "* requests to") ||
1842 looking_at(buf, &i, "Your opponent offers") ||
1843 looking_at(buf, &i, "Your opponent requests")) {
1845 if (appData.colorize) {
1846 if (oldi > next_out) {
1847 SendToPlayer(&buf[next_out], oldi - next_out);
1850 Colorize(ColorRequest, FALSE);
1851 curColor = ColorRequest;
1856 if (looking_at(buf, &i, "* (*) seeking")) {
1857 if (appData.colorize) {
1858 if (oldi > next_out) {
1859 SendToPlayer(&buf[next_out], oldi - next_out);
1862 Colorize(ColorSeek, FALSE);
1863 curColor = ColorSeek;
1869 if (looking_at(buf, &i, "\\ ")) {
1870 if (prevColor != ColorNormal) {
1871 if (oldi > next_out) {
1872 SendToPlayer(&buf[next_out], oldi - next_out);
1875 Colorize(prevColor, TRUE);
1876 curColor = prevColor;
1878 if (savingComment) {
1879 parse_pos = i - oldi;
1880 memcpy(parse, &buf[oldi], parse_pos);
1881 parse[parse_pos] = NULLCHAR;
1882 started = STARTED_COMMENT;
1884 started = STARTED_CHATTER;
1889 if (looking_at(buf, &i, "Black Strength :") ||
1890 looking_at(buf, &i, "<<< style 10 board >>>") ||
1891 looking_at(buf, &i, "<10>") ||
1892 looking_at(buf, &i, "#@#")) {
1893 /* Wrong board style */
1895 SendToICS(ics_prefix);
1896 SendToICS("set style 12\n");
1897 SendToICS(ics_prefix);
1898 SendToICS("refresh\n");
1902 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
1904 have_sent_ICS_logon = 1;
1908 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
1909 (looking_at(buf, &i, "\n<12> ") ||
1910 looking_at(buf, &i, "<12> "))) {
1912 if (oldi > next_out) {
1913 SendToPlayer(&buf[next_out], oldi - next_out);
1916 started = STARTED_BOARD;
1921 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
1922 looking_at(buf, &i, "<b1> ")) {
1923 if (oldi > next_out) {
1924 SendToPlayer(&buf[next_out], oldi - next_out);
1927 started = STARTED_HOLDINGS;
1932 if (looking_at(buf, &i, "* *vs. * *--- *")) {
1934 /* Header for a move list -- first line */
1936 switch (ics_getting_history) {
1940 case BeginningOfGame:
1941 /* User typed "moves" or "oldmoves" while we
1942 were idle. Pretend we asked for these
1943 moves and soak them up so user can step
1944 through them and/or save them.
1947 gameMode = IcsObserving;
1950 ics_getting_history = H_GOT_UNREQ_HEADER;
1952 case EditGame: /*?*/
1953 case EditPosition: /*?*/
1954 /* Should above feature work in these modes too? */
1955 /* For now it doesn't */
1956 ics_getting_history = H_GOT_UNWANTED_HEADER;
1959 ics_getting_history = H_GOT_UNWANTED_HEADER;
1964 /* Is this the right one? */
1965 if (gameInfo.white && gameInfo.black &&
1966 strcmp(gameInfo.white, star_match[0]) == 0 &&
1967 strcmp(gameInfo.black, star_match[2]) == 0) {
1969 ics_getting_history = H_GOT_REQ_HEADER;
1972 case H_GOT_REQ_HEADER:
1973 case H_GOT_UNREQ_HEADER:
1974 case H_GOT_UNWANTED_HEADER:
1975 case H_GETTING_MOVES:
1976 /* Should not happen */
1977 DisplayError("Error gathering move list: two headers", 0);
1978 ics_getting_history = H_FALSE;
1982 /* Save player ratings into gameInfo if needed */
1983 if ((ics_getting_history == H_GOT_REQ_HEADER ||
1984 ics_getting_history == H_GOT_UNREQ_HEADER) &&
1985 (gameInfo.whiteRating == -1 ||
1986 gameInfo.blackRating == -1)) {
1988 gameInfo.whiteRating = string_to_rating(star_match[1]);
1989 gameInfo.blackRating = string_to_rating(star_match[3]);
1990 if (appData.debugMode)
1991 fprintf(debugFP, "Ratings from header: W %d, B %d\n",
1992 gameInfo.whiteRating, gameInfo.blackRating);
1997 if (looking_at(buf, &i,
1998 "* * match, initial time: * minute*, increment: * second")) {
1999 /* Header for a move list -- second line */
2000 /* Initial board will follow if this is a wild game */
2002 if (gameInfo.event != NULL) free(gameInfo.event);
2003 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2004 gameInfo.event = StrSave(str);
2005 gameInfo.variant = StringToVariant(gameInfo.event);
2009 if (looking_at(buf, &i, "Move ")) {
2010 /* Beginning of a move list */
2011 switch (ics_getting_history) {
2013 /* Normally should not happen */
2014 /* Maybe user hit reset while we were parsing */
2017 /* Happens if we are ignoring a move list that is not
2018 * the one we just requested. Common if the user
2019 * tries to observe two games without turning off
2022 case H_GETTING_MOVES:
2023 /* Should not happen */
2024 DisplayError("Error gathering move list: nested", 0);
2025 ics_getting_history = H_FALSE;
2027 case H_GOT_REQ_HEADER:
2028 ics_getting_history = H_GETTING_MOVES;
2029 started = STARTED_MOVES;
2031 if (oldi > next_out) {
2032 SendToPlayer(&buf[next_out], oldi - next_out);
2035 case H_GOT_UNREQ_HEADER:
2036 ics_getting_history = H_GETTING_MOVES;
2037 started = STARTED_MOVES_NOHIDE;
2040 case H_GOT_UNWANTED_HEADER:
2041 ics_getting_history = H_FALSE;
2047 if (looking_at(buf, &i, "% ") ||
2048 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2049 && looking_at(buf, &i, "}*"))) {
2050 savingComment = FALSE;
2053 case STARTED_MOVES_NOHIDE:
2054 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2055 parse[parse_pos + i - oldi] = NULLCHAR;
2056 ParseGameHistory(parse);
2058 if (appData.zippyPlay && first.initDone) {
2059 FeedMovesToProgram(&first, forwardMostMove);
2060 if (gameMode == IcsPlayingWhite) {
2061 if (WhiteOnMove(forwardMostMove)) {
2062 if (first.sendTime) {
2063 if (first.useColors) {
2064 SendToProgram("black\n", &first);
2066 SendTimeRemaining(&first, TRUE);
2068 if (first.useColors) {
2069 SendToProgram("white\ngo\n", &first);
2071 SendToProgram("go\n", &first);
2073 first.maybeThinking = TRUE;
2075 if (first.usePlayother) {
2076 if (first.sendTime) {
2077 SendTimeRemaining(&first, TRUE);
2079 SendToProgram("playother\n", &first);
2085 } else if (gameMode == IcsPlayingBlack) {
2086 if (!WhiteOnMove(forwardMostMove)) {
2087 if (first.sendTime) {
2088 if (first.useColors) {
2089 SendToProgram("white\n", &first);
2091 SendTimeRemaining(&first, FALSE);
2093 if (first.useColors) {
2094 SendToProgram("black\ngo\n", &first);
2096 SendToProgram("go\n", &first);
2098 first.maybeThinking = TRUE;
2100 if (first.usePlayother) {
2101 if (first.sendTime) {
2102 SendTimeRemaining(&first, FALSE);
2104 SendToProgram("playother\n", &first);
2113 if (gameMode == IcsObserving && ics_gamenum == -1) {
2114 /* Moves came from oldmoves or moves command
2115 while we weren't doing anything else.
2117 currentMove = forwardMostMove;
2118 ClearHighlights();/*!!could figure this out*/
2119 flipView = appData.flipView;
2120 DrawPosition(FALSE, boards[currentMove]);
2121 DisplayBothClocks();
2122 sprintf(str, "%s vs. %s",
2123 gameInfo.white, gameInfo.black);
2127 /* Moves were history of an active game */
2128 if (gameInfo.resultDetails != NULL) {
2129 free(gameInfo.resultDetails);
2130 gameInfo.resultDetails = NULL;
2133 HistorySet(parseList, backwardMostMove,
2134 forwardMostMove, currentMove-1);
2135 DisplayMove(currentMove - 1);
2136 if (started == STARTED_MOVES) next_out = i;
2137 started = STARTED_NONE;
2138 ics_getting_history = H_FALSE;
2141 case STARTED_OBSERVE:
2142 started = STARTED_NONE;
2143 SendToICS(ics_prefix);
2144 SendToICS("refresh\n");
2153 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2154 started == STARTED_HOLDINGS ||
2155 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2156 /* Accumulate characters in move list or board */
2157 parse[parse_pos++] = buf[i];
2160 /* Start of game messages. Mostly we detect start of game
2161 when the first board image arrives. On some versions
2162 of the ICS, though, we need to do a "refresh" after starting
2163 to observe in order to get the current board right away. */
2164 if (looking_at(buf, &i, "Adding game * to observation list")) {
2165 started = STARTED_OBSERVE;
2169 /* Handle auto-observe */
2170 if (appData.autoObserve &&
2171 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2172 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2174 /* Choose the player that was highlighted, if any. */
2175 if (star_match[0][0] == '\033' ||
2176 star_match[1][0] != '\033') {
2177 player = star_match[0];
2179 player = star_match[2];
2181 sprintf(str, "%sobserve %s\n",
2182 ics_prefix, StripHighlightAndTitle(player));
2185 /* Save ratings from notify string */
2186 strcpy(player1Name, star_match[0]);
2187 player1Rating = string_to_rating(star_match[1]);
2188 strcpy(player2Name, star_match[2]);
2189 player2Rating = string_to_rating(star_match[3]);
2191 if (appData.debugMode)
2193 "Ratings from 'Game notification:' %s %d, %s %d\n",
2194 player1Name, player1Rating,
2195 player2Name, player2Rating);
2200 /* Deal with automatic examine mode after a game,
2201 and with IcsObserving -> IcsExamining transition */
2202 if (looking_at(buf, &i, "Entering examine mode for game *") ||
2203 looking_at(buf, &i, "has made you an examiner of game *")) {
2205 int gamenum = atoi(star_match[0]);
2206 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2207 gamenum == ics_gamenum) {
2208 /* We were already playing or observing this game;
2209 no need to refetch history */
2210 gameMode = IcsExamining;
2212 pauseExamForwardMostMove = forwardMostMove;
2213 } else if (currentMove < forwardMostMove) {
2214 ForwardInner(forwardMostMove);
2217 /* I don't think this case really can happen */
2218 SendToICS(ics_prefix);
2219 SendToICS("refresh\n");
2224 /* Error messages */
2225 if (ics_user_moved) {
2226 if (looking_at(buf, &i, "Illegal move") ||
2227 looking_at(buf, &i, "Not a legal move") ||
2228 looking_at(buf, &i, "Your king is in check") ||
2229 looking_at(buf, &i, "It isn't your turn") ||
2230 looking_at(buf, &i, "It is not your move")) {
2233 if (forwardMostMove > backwardMostMove) {
2234 currentMove = --forwardMostMove;
2235 DisplayMove(currentMove - 1); /* before DMError */
2236 DisplayMoveError("Illegal move (rejected by ICS)");
2237 DrawPosition(FALSE, boards[currentMove]);
2239 DisplayBothClocks();
2245 if (looking_at(buf, &i, "still have time") ||
2246 looking_at(buf, &i, "not out of time") ||
2247 looking_at(buf, &i, "either player is out of time") ||
2248 looking_at(buf, &i, "has timeseal; checking")) {
2249 /* We must have called his flag a little too soon */
2250 whiteFlag = blackFlag = FALSE;
2254 if (looking_at(buf, &i, "added * seconds to") ||
2255 looking_at(buf, &i, "seconds were added to")) {
2256 /* Update the clocks */
2257 SendToICS(ics_prefix);
2258 SendToICS("refresh\n");
2262 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
2263 ics_clock_paused = TRUE;
2268 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
2269 ics_clock_paused = FALSE;
2274 /* Grab player ratings from the Creating: message.
2275 Note we have to check for the special case when
2276 the ICS inserts things like [white] or [black]. */
2277 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
2278 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
2280 0 player 1 name (not necessarily white)
2282 2 empty, white, or black (IGNORED)
2283 3 player 2 name (not necessarily black)
2286 The names/ratings are sorted out when the game
2287 actually starts (below).
2289 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
2290 player1Rating = string_to_rating(star_match[1]);
2291 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
2292 player2Rating = string_to_rating(star_match[4]);
2294 if (appData.debugMode)
2296 "Ratings from 'Creating:' %s %d, %s %d\n",
2297 player1Name, player1Rating,
2298 player2Name, player2Rating);
2303 /* Improved generic start/end-of-game messages */
2304 if (looking_at(buf, &i, "{Game * (* vs. *) *}*")) {
2305 /* star_match[0] is the game number */
2306 /* [1] is the white player's name */
2307 /* [2] is the black player's name */
2308 /* For end-of-game: */
2309 /* [3] is the reason for the game end */
2310 /* [4] is a PGN end game-token, preceded by " " */
2311 /* For start-of-game: */
2312 /* [3] begins with "Creating" or "Continuing" */
2313 /* [4] is " *" or empty (don't care). */
2314 int gamenum = atoi(star_match[0]);
2315 char *why = star_match[3];
2316 char *endtoken = star_match[4];
2317 ChessMove endtype = (ChessMove) 0;
2319 /* Game start messages */
2320 if (strncmp(why, "Creating ", 9) == 0 ||
2321 strncmp(why, "Continuing ", 11) == 0) {
2322 gs_gamenum = gamenum;
2323 strcpy(gs_kind, strchr(why, ' ') + 1);
2325 if (appData.zippyPlay) {
2326 ZippyGameStart(star_match[1], star_match[2]);
2332 /* Game end messages */
2333 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
2334 ics_gamenum != gamenum) {
2337 while (endtoken[0] == ' ') endtoken++;
2338 switch (endtoken[0]) {
2341 endtype = GameUnfinished;
2344 endtype = BlackWins;
2347 if (endtoken[1] == '/')
2348 endtype = GameIsDrawn;
2350 endtype = WhiteWins;
2353 GameEnds(endtype, why, GE_ICS);
2355 if (appData.zippyPlay && first.initDone) {
2356 ZippyGameEnd(endtype, why);
2357 if (first.pr == NULL) {
2358 /* Start the next process early so that we'll
2359 be ready for the next challenge */
2360 StartChessProgram(&first);
2362 /* Send "new" early, in case this command takes
2363 a long time to finish, so that we'll be ready
2364 for the next challenge. */
2371 if (looking_at(buf, &i, "Removing game * from observation") ||
2372 looking_at(buf, &i, "no longer observing game *") ||
2373 looking_at(buf, &i, "Game * (*) has no examiners")) {
2374 if (gameMode == IcsObserving &&
2375 atoi(star_match[0]) == ics_gamenum)
2380 ics_user_moved = FALSE;
2385 if (looking_at(buf, &i, "no longer examining game *")) {
2386 if (gameMode == IcsExamining &&
2387 atoi(star_match[0]) == ics_gamenum)
2391 ics_user_moved = FALSE;
2396 /* Advance leftover_start past any newlines we find,
2397 so only partial lines can get reparsed */
2398 if (looking_at(buf, &i, "\n")) {
2399 prevColor = curColor;
2400 if (curColor != ColorNormal) {
2401 if (oldi > next_out) {
2402 SendToPlayer(&buf[next_out], oldi - next_out);
2405 Colorize(ColorNormal, FALSE);
2406 curColor = ColorNormal;
2408 if (started == STARTED_BOARD) {
2409 started = STARTED_NONE;
2410 parse[parse_pos] = NULLCHAR;
2411 ParseBoard12(parse);
2414 /* Send premove here */
2415 if (appData.premove) {
2417 if (currentMove == 0 &&
2418 gameMode == IcsPlayingWhite &&
2419 appData.premoveWhite) {
2420 sprintf(str, "%s%s\n", ics_prefix,
2421 appData.premoveWhiteText);
2422 if (appData.debugMode)
2423 fprintf(debugFP, "Sending premove:\n");
2425 } else if (currentMove == 1 &&
2426 gameMode == IcsPlayingBlack &&
2427 appData.premoveBlack) {
2428 sprintf(str, "%s%s\n", ics_prefix,
2429 appData.premoveBlackText);
2430 if (appData.debugMode)
2431 fprintf(debugFP, "Sending premove:\n");
2433 } else if (gotPremove) {
2435 ClearPremoveHighlights();
2436 if (appData.debugMode)
2437 fprintf(debugFP, "Sending premove:\n");
2438 UserMoveEvent(premoveFromX, premoveFromY,
2439 premoveToX, premoveToY,
2444 /* Usually suppress following prompt */
2445 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
2446 if (looking_at(buf, &i, "*% ")) {
2447 savingComment = FALSE;
2451 } else if (started == STARTED_HOLDINGS) {
2453 char new_piece[MSG_SIZ];
2454 started = STARTED_NONE;
2455 parse[parse_pos] = NULLCHAR;
2456 if (appData.debugMode)
2457 fprintf(debugFP, "Parsing holdings: %s\n", parse);
2458 if (sscanf(parse, " game %d", &gamenum) == 1 &&
2459 gamenum == ics_gamenum) {
2460 if (gameInfo.variant == VariantNormal) {
2461 gameInfo.variant = VariantCrazyhouse; /*temp guess*/
2462 /* Get a move list just to see the header, which
2463 will tell us whether this is really bug or zh */
2464 if (ics_getting_history == H_FALSE) {
2465 ics_getting_history = H_REQUESTED;
2466 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
2470 new_piece[0] = NULLCHAR;
2471 sscanf(parse, "game %d white [%s black [%s <- %s",
2472 &gamenum, white_holding, black_holding,
2474 white_holding[strlen(white_holding)-1] = NULLCHAR;
2475 black_holding[strlen(black_holding)-1] = NULLCHAR;
2477 if (appData.zippyPlay && first.initDone) {
2478 ZippyHoldings(white_holding, black_holding,
2482 if (tinyLayout || smallLayout) {
2483 char wh[16], bh[16];
2484 PackHolding(wh, white_holding);
2485 PackHolding(bh, black_holding);
2486 sprintf(str, "[%s-%s] %s-%s", wh, bh,
2487 gameInfo.white, gameInfo.black);
2489 sprintf(str, "%s [%s] vs. %s [%s]",
2490 gameInfo.white, white_holding,
2491 gameInfo.black, black_holding);
2493 DrawPosition(FALSE, NULL);
2496 /* Suppress following prompt */
2497 if (looking_at(buf, &i, "*% ")) {
2498 savingComment = FALSE;
2505 i++; /* skip unparsed character and loop back */
2508 if (started != STARTED_MOVES && started != STARTED_BOARD &&
2509 started != STARTED_HOLDINGS && i > next_out) {
2510 SendToPlayer(&buf[next_out], i - next_out);
2514 leftover_len = buf_len - leftover_start;
2515 /* if buffer ends with something we couldn't parse,
2516 reparse it after appending the next read */
2518 } else if (count == 0) {
2519 RemoveInputSource(isr);
2520 DisplayFatalError("Connection closed by ICS", 0, 0);
2522 DisplayFatalError("Error reading from ICS", error, 1);
2527 /* Board style 12 looks like this:
2529 <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
2531 * The "<12> " is stripped before it gets to this routine. The two
2532 * trailing 0's (flip state and clock ticking) are later addition, and
2533 * some chess servers may not have them, or may have only the first.
2534 * Additional trailing fields may be added in the future.
2537 #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"
2539 #define RELATION_OBSERVING_PLAYED 0
2540 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
2541 #define RELATION_PLAYING_MYMOVE 1
2542 #define RELATION_PLAYING_NOTMYMOVE -1
2543 #define RELATION_EXAMINING 2
2544 #define RELATION_ISOLATED_BOARD -3
2545 #define RELATION_STARTING_POSITION -4 /* FICS only */
2548 ParseBoard12(string)
2551 GameMode newGameMode;
2552 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
2553 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
2554 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
2555 char to_play, board_chars[72];
2556 char move_str[500], str[500], elapsed_time[500];
2557 char black[32], white[32];
2559 int prevMove = currentMove;
2562 int fromX, fromY, toX, toY;
2565 fromX = fromY = toX = toY = -1;
2569 if (appData.debugMode)
2570 fprintf(debugFP, "Parsing board: %s\n", string);
2572 move_str[0] = NULLCHAR;
2573 elapsed_time[0] = NULLCHAR;
2574 n = sscanf(string, PATTERN, board_chars, &to_play, &double_push,
2575 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
2576 &gamenum, white, black, &relation, &basetime, &increment,
2577 &white_stren, &black_stren, &white_time, &black_time,
2578 &moveNum, str, elapsed_time, move_str, &ics_flip,
2582 sprintf(str, "Failed to parse board string:\n\"%s\"", string);
2583 DisplayError(str, 0);
2587 /* Convert the move number to internal form */
2588 moveNum = (moveNum - 1) * 2;
2589 if (to_play == 'B') moveNum++;
2590 if (moveNum >= MAX_MOVES) {
2591 DisplayFatalError("Game too long; increase MAX_MOVES and recompile",
2597 case RELATION_OBSERVING_PLAYED:
2598 case RELATION_OBSERVING_STATIC:
2599 if (gamenum == -1) {
2600 /* Old ICC buglet */
2601 relation = RELATION_OBSERVING_STATIC;
2603 newGameMode = IcsObserving;
2605 case RELATION_PLAYING_MYMOVE:
2606 case RELATION_PLAYING_NOTMYMOVE:
2608 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
2609 IcsPlayingWhite : IcsPlayingBlack;
2611 case RELATION_EXAMINING:
2612 newGameMode = IcsExamining;
2614 case RELATION_ISOLATED_BOARD:
2616 /* Just display this board. If user was doing something else,
2617 we will forget about it until the next board comes. */
2618 newGameMode = IcsIdle;
2620 case RELATION_STARTING_POSITION:
2621 newGameMode = gameMode;
2625 /* Modify behavior for initial board display on move listing
2628 switch (ics_getting_history) {
2632 case H_GOT_REQ_HEADER:
2633 case H_GOT_UNREQ_HEADER:
2634 /* This is the initial position of the current game */
2635 gamenum = ics_gamenum;
2636 moveNum = 0; /* old ICS bug workaround */
2637 if (to_play == 'B') {
2638 startedFromSetupPosition = TRUE;
2639 blackPlaysFirst = TRUE;
2641 if (forwardMostMove == 0) forwardMostMove = 1;
2642 if (backwardMostMove == 0) backwardMostMove = 1;
2643 if (currentMove == 0) currentMove = 1;
2645 newGameMode = gameMode;
2646 relation = RELATION_STARTING_POSITION; /* ICC needs this */
2648 case H_GOT_UNWANTED_HEADER:
2649 /* This is an initial board that we don't want */
2651 case H_GETTING_MOVES:
2652 /* Should not happen */
2653 DisplayError("Error gathering move list: extra board", 0);
2654 ics_getting_history = H_FALSE;
2658 /* Take action if this is the first board of a new game, or of a
2659 different game than is currently being displayed. */
2660 if (gamenum != ics_gamenum || newGameMode != gameMode ||
2661 relation == RELATION_ISOLATED_BOARD) {
2663 /* Forget the old game and get the history (if any) of the new one */
2664 if (gameMode != BeginningOfGame) {
2668 if (appData.autoRaiseBoard) BoardToTop();
2670 if (gamenum == -1) {
2671 newGameMode = IcsIdle;
2672 } else if (moveNum > 0 && newGameMode != IcsIdle &&
2673 appData.getMoveList) {
2674 /* Need to get game history */
2675 ics_getting_history = H_REQUESTED;
2676 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
2680 /* Initially flip the board to have black on the bottom if playing
2681 black or if the ICS flip flag is set, but let the user change
2682 it with the Flip View button. */
2683 flipView = appData.autoFlipView ?
2684 (newGameMode == IcsPlayingBlack) || ics_flip :
2687 /* Done with values from previous mode; copy in new ones */
2688 gameMode = newGameMode;
2690 ics_gamenum = gamenum;
2691 if (gamenum == gs_gamenum) {
2692 int klen = strlen(gs_kind);
2693 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
2694 sprintf(str, "ICS %s", gs_kind);
2695 gameInfo.event = StrSave(str);
2697 gameInfo.event = StrSave("ICS game");
2699 gameInfo.site = StrSave(appData.icsHost);
2700 gameInfo.date = PGNDate();
2701 gameInfo.round = StrSave("-");
2702 gameInfo.white = StrSave(white);
2703 gameInfo.black = StrSave(black);
2704 timeControl = basetime * 60 * 1000;
2705 timeIncrement = increment * 1000;
2706 movesPerSession = 0;
2707 gameInfo.timeControl = TimeControlTagValue();
2708 gameInfo.variant = StringToVariant(gameInfo.event);
2710 /* Do we have the ratings? */
2711 if (strcmp(player1Name, white) == 0 &&
2712 strcmp(player2Name, black) == 0) {
2713 if (appData.debugMode)
2714 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
2715 player1Rating, player2Rating);
2716 gameInfo.whiteRating = player1Rating;
2717 gameInfo.blackRating = player2Rating;
2718 } else if (strcmp(player2Name, white) == 0 &&
2719 strcmp(player1Name, black) == 0) {
2720 if (appData.debugMode)
2721 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
2722 player2Rating, player1Rating);
2723 gameInfo.whiteRating = player2Rating;
2724 gameInfo.blackRating = player1Rating;
2726 player1Name[0] = player2Name[0] = NULLCHAR;
2728 /* Silence shouts if requested */
2729 if (appData.quietPlay &&
2730 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
2731 SendToICS(ics_prefix);
2732 SendToICS("set shout 0\n");
2736 /* Deal with midgame name changes */
2738 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
2739 if (gameInfo.white) free(gameInfo.white);
2740 gameInfo.white = StrSave(white);
2742 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
2743 if (gameInfo.black) free(gameInfo.black);
2744 gameInfo.black = StrSave(black);
2748 /* Throw away game result if anything actually changes in examine mode */
2749 if (gameMode == IcsExamining && !newGame) {
2750 gameInfo.result = GameUnfinished;
2751 if (gameInfo.resultDetails != NULL) {
2752 free(gameInfo.resultDetails);
2753 gameInfo.resultDetails = NULL;
2757 /* In pausing && IcsExamining mode, we ignore boards coming
2758 in if they are in a different variation than we are. */
2759 if (pauseExamInvalid) return;
2760 if (pausing && gameMode == IcsExamining) {
2761 if (moveNum <= pauseExamForwardMostMove) {
2762 pauseExamInvalid = TRUE;
2763 forwardMostMove = pauseExamForwardMostMove;
2768 /* Parse the board */
2769 for (k = 0; k < 8; k++)
2770 for (j = 0; j < 8; j++)
2771 board[k][j] = CharToPiece(board_chars[(7-k)*9 + j]);
2772 CopyBoard(boards[moveNum], board);
2774 startedFromSetupPosition =
2775 !CompareBoards(board, initialPosition);
2778 if (ics_getting_history == H_GOT_REQ_HEADER ||
2779 ics_getting_history == H_GOT_UNREQ_HEADER) {
2780 /* This was an initial position from a move list, not
2781 the current position */
2785 /* Update currentMove and known move number limits */
2786 newMove = newGame || moveNum > forwardMostMove;
2788 forwardMostMove = backwardMostMove = currentMove = moveNum;
2789 if (gameMode == IcsExamining && moveNum == 0) {
2790 /* Workaround for ICS limitation: we are not told the wild
2791 type when starting to examine a game. But if we ask for
2792 the move list, the move list header will tell us */
2793 ics_getting_history = H_REQUESTED;
2794 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
2797 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
2798 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
2799 forwardMostMove = moveNum;
2800 if (!pausing || currentMove > forwardMostMove)
2801 currentMove = forwardMostMove;
2803 /* New part of history that is not contiguous with old part */
2804 if (pausing && gameMode == IcsExamining) {
2805 pauseExamInvalid = TRUE;
2806 forwardMostMove = pauseExamForwardMostMove;
2809 forwardMostMove = backwardMostMove = currentMove = moveNum;
2810 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
2811 ics_getting_history = H_REQUESTED;
2812 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
2817 /* Update the clocks */
2818 if (strchr(elapsed_time, '.')) {
2820 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
2821 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
2823 /* Time is in seconds */
2824 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
2825 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
2830 if (appData.zippyPlay && newGame &&
2831 gameMode != IcsObserving && gameMode != IcsIdle &&
2832 gameMode != IcsExamining)
2833 ZippyFirstBoard(moveNum, basetime, increment);
2836 /* Put the move on the move list, first converting
2837 to canonical algebraic form. */
2839 if (moveNum <= backwardMostMove) {
2840 /* We don't know what the board looked like before
2842 strcpy(parseList[moveNum - 1], move_str);
2843 strcat(parseList[moveNum - 1], " ");
2844 strcat(parseList[moveNum - 1], elapsed_time);
2845 moveList[moveNum - 1][0] = NULLCHAR;
2846 } else if (ParseOneMove(move_str, moveNum - 1, &moveType,
2847 &fromX, &fromY, &toX, &toY, &promoChar)) {
2848 (void) CoordsToAlgebraic(boards[moveNum - 1],
2849 PosFlags(moveNum - 1), EP_UNKNOWN,
2850 fromY, fromX, toY, toX, promoChar,
2851 parseList[moveNum-1]);
2852 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN)){
2858 strcat(parseList[moveNum - 1], "+");
2861 strcat(parseList[moveNum - 1], "#");
2864 strcat(parseList[moveNum - 1], " ");
2865 strcat(parseList[moveNum - 1], elapsed_time);
2866 /* currentMoveString is set as a side-effect of ParseOneMove */
2867 strcpy(moveList[moveNum - 1], currentMoveString);
2868 strcat(moveList[moveNum - 1], "\n");
2869 } else if (strcmp(move_str, "none") == 0) {
2870 /* Again, we don't know what the board looked like;
2871 this is really the start of the game. */
2872 parseList[moveNum - 1][0] = NULLCHAR;
2873 moveList[moveNum - 1][0] = NULLCHAR;
2874 backwardMostMove = moveNum;
2875 startedFromSetupPosition = TRUE;
2876 fromX = fromY = toX = toY = -1;
2878 /* Move from ICS was illegal!? Punt. */
2880 if (appData.testLegality && appData.debugMode) {
2881 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
2882 DisplayError(str, 0);
2885 strcpy(parseList[moveNum - 1], move_str);
2886 strcat(parseList[moveNum - 1], " ");
2887 strcat(parseList[moveNum - 1], elapsed_time);
2888 moveList[moveNum - 1][0] = NULLCHAR;
2889 fromX = fromY = toX = toY = -1;
2893 /* Send move to chess program (BEFORE animating it). */
2894 if (appData.zippyPlay && !newGame && newMove &&
2895 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
2897 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
2898 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
2899 if (moveList[moveNum - 1][0] == NULLCHAR) {
2900 sprintf(str, "Couldn't parse move \"%s\" from ICS",
2902 DisplayError(str, 0);
2904 if (first.sendTime) {
2905 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
2907 SendMoveToProgram(moveNum - 1, &first);
2910 if (first.useColors) {
2911 SendToProgram(gameMode == IcsPlayingWhite ?
2913 "black\ngo\n", &first);
2915 SendToProgram("go\n", &first);
2917 first.maybeThinking = TRUE;
2920 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
2921 if (moveList[moveNum - 1][0] == NULLCHAR) {
2922 sprintf(str, "Couldn't parse move \"%s\" from ICS", move_str);
2923 DisplayError(str, 0);
2925 SendMoveToProgram(moveNum - 1, &first);
2932 if (moveNum > 0 && !gotPremove) {
2933 /* If move comes from a remote source, animate it. If it
2934 isn't remote, it will have already been animated. */
2935 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
2936 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
2938 if (!pausing && appData.highlightLastMove) {
2939 SetHighlights(fromX, fromY, toX, toY);
2943 /* Start the clocks */
2944 whiteFlag = blackFlag = FALSE;
2945 appData.clockMode = !(basetime == 0 && increment == 0);
2947 ics_clock_paused = TRUE;
2949 } else if (ticking == 1) {
2950 ics_clock_paused = FALSE;
2952 if (gameMode == IcsIdle ||
2953 relation == RELATION_OBSERVING_STATIC ||
2954 relation == RELATION_EXAMINING ||
2956 DisplayBothClocks();
2960 /* Display opponents and material strengths */
2961 if (gameInfo.variant != VariantBughouse &&
2962 gameInfo.variant != VariantCrazyhouse) {
2963 if (tinyLayout || smallLayout) {
2964 sprintf(str, "%s(%d) %s(%d) {%d %d}",
2965 gameInfo.white, white_stren, gameInfo.black, black_stren,
2966 basetime, increment);
2968 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
2969 gameInfo.white, white_stren, gameInfo.black, black_stren,
2970 basetime, increment);
2976 /* Display the board */
2979 if (appData.premove)
2981 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
2982 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
2983 ClearPremoveHighlights();
2985 DrawPosition(FALSE, boards[currentMove]);
2986 DisplayMove(moveNum - 1);
2987 if (appData.ringBellAfterMoves && !ics_user_moved)
2991 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
2998 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
2999 ics_getting_history = H_REQUESTED;
3000 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3006 AnalysisPeriodicEvent(force)
3009 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3010 && !force) || !appData.periodicUpdates)
3013 /* Send . command to Crafty to collect stats */
3014 SendToProgram(".\n", &first);
3016 /* Don't send another until we get a response (this makes
3017 us stop sending to old Crafty's which don't understand
3018 the "." command (sending illegal cmds resets node count & time,
3019 which looks bad)) */
3020 programStats.ok_to_send = 0;
3024 SendMoveToProgram(moveNum, cps)
3026 ChessProgramState *cps;
3029 if (cps->useUsermove) {
3030 SendToProgram("usermove ", cps);
3034 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
3035 int len = space - parseList[moveNum];
3036 memcpy(buf, parseList[moveNum], len);
3038 buf[len] = NULLCHAR;
3040 sprintf(buf, "%s\n", parseList[moveNum]);
3042 SendToProgram(buf, cps);
3044 SendToProgram(moveList[moveNum], cps);
3049 SendMoveToICS(moveType, fromX, fromY, toX, toY)
3051 int fromX, fromY, toX, toY;
3053 char user_move[MSG_SIZ];
3057 sprintf(user_move, "say Internal error; bad moveType %d (%d,%d-%d,%d)",
3058 (int)moveType, fromX, fromY, toX, toY);
3059 DisplayError(user_move + strlen("say "), 0);
3061 case WhiteKingSideCastle:
3062 case BlackKingSideCastle:
3063 case WhiteQueenSideCastleWild:
3064 case BlackQueenSideCastleWild:
3065 sprintf(user_move, "o-o\n");
3067 case WhiteQueenSideCastle:
3068 case BlackQueenSideCastle:
3069 case WhiteKingSideCastleWild:
3070 case BlackKingSideCastleWild:
3071 sprintf(user_move, "o-o-o\n");
3073 case WhitePromotionQueen:
3074 case BlackPromotionQueen:
3075 case WhitePromotionRook:
3076 case BlackPromotionRook:
3077 case WhitePromotionBishop:
3078 case BlackPromotionBishop:
3079 case WhitePromotionKnight:
3080 case BlackPromotionKnight:
3081 case WhitePromotionKing:
3082 case BlackPromotionKing:
3083 sprintf(user_move, "%c%c%c%c=%c\n",
3084 'a' + fromX, '1' + fromY, 'a' + toX, '1' + toY,
3085 PieceToChar(PromoPiece(moveType)));
3089 sprintf(user_move, "%c@%c%c\n",
3090 ToUpper(PieceToChar((ChessSquare) fromX)),
3091 'a' + toX, '1' + toY);
3094 case WhiteCapturesEnPassant:
3095 case BlackCapturesEnPassant:
3096 case IllegalMove: /* could be a variant we don't quite understand */
3097 sprintf(user_move, "%c%c%c%c\n",
3098 'a' + fromX, '1' + fromY, 'a' + toX, '1' + toY);
3101 SendToICS(user_move);
3105 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
3110 if (rf == DROP_RANK) {
3111 sprintf(move, "%c@%c%c\n",
3112 ToUpper(PieceToChar((ChessSquare) ff)), 'a' + ft, '1' + rt);
3114 if (promoChar == 'x' || promoChar == NULLCHAR) {
3115 sprintf(move, "%c%c%c%c\n",
3116 'a' + ff, '1' + rf, 'a' + ft, '1' + rt);
3118 sprintf(move, "%c%c%c%c%c\n",
3119 'a' + ff, '1' + rf, 'a' + ft, '1' + rt, promoChar);
3125 ProcessICSInitScript(f)
3130 while (fgets(buf, MSG_SIZ, f)) {
3131 SendToICSDelayed(buf,(long)appData.msLoginDelay);
3138 /* Parser for moves from gnuchess, ICS, or user typein box */
3140 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
3143 ChessMove *moveType;
3144 int *fromX, *fromY, *toX, *toY;
3147 *moveType = yylexstr(moveNum, move);
3148 switch (*moveType) {
3149 case WhitePromotionQueen:
3150 case BlackPromotionQueen:
3151 case WhitePromotionRook:
3152 case BlackPromotionRook:
3153 case WhitePromotionBishop:
3154 case BlackPromotionBishop:
3155 case WhitePromotionKnight:
3156 case BlackPromotionKnight:
3157 case WhitePromotionKing:
3158 case BlackPromotionKing:
3160 case WhiteCapturesEnPassant:
3161 case BlackCapturesEnPassant:
3162 case WhiteKingSideCastle:
3163 case WhiteQueenSideCastle:
3164 case BlackKingSideCastle:
3165 case BlackQueenSideCastle:
3166 case WhiteKingSideCastleWild:
3167 case WhiteQueenSideCastleWild:
3168 case BlackKingSideCastleWild:
3169 case BlackQueenSideCastleWild:
3170 case IllegalMove: /* bug or odd chess variant */
3171 *fromX = currentMoveString[0] - 'a';
3172 *fromY = currentMoveString[1] - '1';
3173 *toX = currentMoveString[2] - 'a';
3174 *toY = currentMoveString[3] - '1';
3175 *promoChar = currentMoveString[4];
3176 if (*fromX < 0 || *fromX > 7 || *fromY < 0 || *fromY > 7 ||
3177 *toX < 0 || *toX > 7 || *toY < 0 || *toY > 7) {
3178 *fromX = *fromY = *toX = *toY = 0;
3181 if (appData.testLegality) {
3182 return (*moveType != IllegalMove);
3184 return !(fromX == fromY && toX == toY);
3189 *fromX = *moveType == WhiteDrop ?
3190 (int) CharToPiece(ToUpper(currentMoveString[0])) :
3191 (int) CharToPiece(ToLower(currentMoveString[0]));
3193 *toX = currentMoveString[2] - 'a';
3194 *toY = currentMoveString[3] - '1';
3195 *promoChar = NULLCHAR;
3199 case ImpossibleMove:
3200 case (ChessMove) 0: /* end of file */
3210 *fromX = *fromY = *toX = *toY = 0;
3211 *promoChar = NULLCHAR;
3218 InitPosition(redraw)
3221 currentMove = forwardMostMove = backwardMostMove = 0;
3222 switch (gameInfo.variant) {
3224 CopyBoard(boards[0], initialPosition);
3226 case VariantTwoKings:
3227 CopyBoard(boards[0], twoKingsPosition);
3228 startedFromSetupPosition = TRUE;
3230 case VariantWildCastle:
3231 CopyBoard(boards[0], initialPosition);
3232 /* !!?shuffle with kings guaranteed to be on d or e file */
3234 case VariantNoCastle:
3235 CopyBoard(boards[0], initialPosition);
3236 /* !!?unconstrained back-rank shuffle */
3238 case VariantFischeRandom:
3239 CopyBoard(boards[0], initialPosition);
3240 /* !!shuffle according to FR rules */
3244 DrawPosition(FALSE, boards[currentMove]);
3248 SendBoard(cps, moveNum)
3249 ChessProgramState *cps;
3252 char message[MSG_SIZ];
3254 if (cps->useSetboard) {
3255 char* fen = PositionToFEN(moveNum);
3256 sprintf(message, "setboard %s\n", fen);
3257 SendToProgram(message, cps);
3263 /* Kludge to set black to move, avoiding the troublesome and now
3264 * deprecated "black" command.
3266 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
3268 SendToProgram("edit\n", cps);
3269 SendToProgram("#\n", cps);
3270 for (i = BOARD_SIZE - 1; i >= 0; i--) {
3271 bp = &boards[moveNum][i][0];
3272 for (j = 0; j < BOARD_SIZE; j++, bp++) {
3273 if ((int) *bp < (int) BlackPawn) {
3274 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
3276 SendToProgram(message, cps);
3281 SendToProgram("c\n", cps);
3282 for (i = BOARD_SIZE - 1; i >= 0; i--) {
3283 bp = &boards[moveNum][i][0];
3284 for (j = 0; j < BOARD_SIZE; j++, bp++) {
3285 if (((int) *bp != (int) EmptySquare)
3286 && ((int) *bp >= (int) BlackPawn)) {
3287 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
3289 SendToProgram(message, cps);
3294 SendToProgram(".\n", cps);
3299 IsPromotion(fromX, fromY, toX, toY)
3300 int fromX, fromY, toX, toY;
3302 return gameMode != EditPosition &&
3303 fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0 &&
3304 ((boards[currentMove][fromY][fromX] == WhitePawn && toY == 7) ||
3305 (boards[currentMove][fromY][fromX] == BlackPawn && toY == 0));
3310 PieceForSquare (x, y)
3314 if (x < 0 || x >= BOARD_SIZE || y < 0 || y >= BOARD_SIZE)
3317 return boards[currentMove][y][x];
3321 OKToStartUserMove(x, y)
3324 ChessSquare from_piece;
3327 if (matchMode) return FALSE;
3328 if (gameMode == EditPosition) return TRUE;
3330 if (x >= 0 && y >= 0)
3331 from_piece = boards[currentMove][y][x];
3333 from_piece = EmptySquare;
3335 if (from_piece == EmptySquare) return FALSE;
3337 white_piece = (int)from_piece >= (int)WhitePawn &&
3338 (int)from_piece <= (int)WhiteKing;
3341 case PlayFromGameFile:
3343 case TwoMachinesPlay:
3351 case MachinePlaysWhite:
3352 case IcsPlayingBlack:
3353 if (appData.zippyPlay) return FALSE;
3355 DisplayMoveError("You are playing Black");
3360 case MachinePlaysBlack:
3361 case IcsPlayingWhite:
3362 if (appData.zippyPlay) return FALSE;
3364 DisplayMoveError("You are playing White");
3370 if (!white_piece && WhiteOnMove(currentMove)) {
3371 DisplayMoveError("It is White's turn");
3374 if (white_piece && !WhiteOnMove(currentMove)) {
3375 DisplayMoveError("It is Black's turn");
3378 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
3379 /* Editing correspondence game history */
3380 /* Could disallow this or prompt for confirmation */
3383 if (currentMove < forwardMostMove) {
3384 /* Discarding moves */
3385 /* Could prompt for confirmation here,
3386 but I don't think that's such a good idea */
3387 forwardMostMove = currentMove;
3391 case BeginningOfGame:
3392 if (appData.icsActive) return FALSE;
3393 if (!appData.noChessProgram) {
3395 DisplayMoveError("You are playing White");
3402 if (!white_piece && WhiteOnMove(currentMove)) {
3403 DisplayMoveError("It is White's turn");
3406 if (white_piece && !WhiteOnMove(currentMove)) {
3407 DisplayMoveError("It is Black's turn");
3416 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
3417 && gameMode != AnalyzeFile && gameMode != Training) {
3418 DisplayMoveError("Displayed position is not current");
3424 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
3425 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
3426 int lastLoadGameUseList = FALSE;
3427 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
3428 ChessMove lastLoadGameStart = (ChessMove) 0;
3432 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
3433 int fromX, fromY, toX, toY;
3438 if (fromX < 0 || fromY < 0) return;
3439 if ((fromX == toX) && (fromY == toY)) {
3443 /* Check if the user is playing in turn. This is complicated because we
3444 let the user "pick up" a piece before it is his turn. So the piece he
3445 tried to pick up may have been captured by the time he puts it down!
3446 Therefore we use the color the user is supposed to be playing in this
3447 test, not the color of the piece that is currently on the starting
3448 square---except in EditGame mode, where the user is playing both
3449 sides; fortunately there the capture race can't happen. (It can
3450 now happen in IcsExamining mode, but that's just too bad. The user
3451 will get a somewhat confusing message in that case.)
3455 case PlayFromGameFile:
3457 case TwoMachinesPlay:
3461 /* We switched into a game mode where moves are not accepted,
3462 perhaps while the mouse button was down. */
3465 case MachinePlaysWhite:
3466 /* User is moving for Black */
3467 if (WhiteOnMove(currentMove)) {
3468 DisplayMoveError("It is White's turn");
3473 case MachinePlaysBlack:
3474 /* User is moving for White */
3475 if (!WhiteOnMove(currentMove)) {
3476 DisplayMoveError("It is Black's turn");
3483 case BeginningOfGame:
3486 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
3487 (int) boards[currentMove][fromY][fromX] <= (int) BlackKing) {
3488 /* User is moving for Black */
3489 if (WhiteOnMove(currentMove)) {
3490 DisplayMoveError("It is White's turn");
3494 /* User is moving for White */
3495 if (!WhiteOnMove(currentMove)) {
3496 DisplayMoveError("It is Black's turn");
3502 case IcsPlayingBlack:
3503 /* User is moving for Black */
3504 if (WhiteOnMove(currentMove)) {
3505 if (!appData.premove) {
3506 DisplayMoveError("It is White's turn");
3507 } else if (toX >= 0 && toY >= 0) {
3510 premoveFromX = fromX;
3511 premoveFromY = fromY;
3512 premovePromoChar = promoChar;
3514 if (appData.debugMode)
3515 fprintf(debugFP, "Got premove: fromX %d,"
3516 "fromY %d, toX %d, toY %d\n",
3517 fromX, fromY, toX, toY);
3523 case IcsPlayingWhite:
3524 /* User is moving for White */
3525 if (!WhiteOnMove(currentMove)) {
3526 if (!appData.premove) {
3527 DisplayMoveError("It is Black's turn");
3528 } else if (toX >= 0 && toY >= 0) {
3531 premoveFromX = fromX;
3532 premoveFromY = fromY;
3533 premovePromoChar = promoChar;
3535 if (appData.debugMode)
3536 fprintf(debugFP, "Got premove: fromX %d,"
3537 "fromY %d, toX %d, toY %d\n",
3538 fromX, fromY, toX, toY);
3548 if (toX == -2 || toY == -2) {
3549 boards[0][fromY][fromX] = EmptySquare;
3550 DrawPosition(FALSE, boards[currentMove]);
3551 } else if (toX >= 0 && toY >= 0) {
3552 boards[0][toY][toX] = boards[0][fromY][fromX];
3553 boards[0][fromY][fromX] = EmptySquare;
3554 DrawPosition(FALSE, boards[currentMove]);
3559 if (toX < 0 || toY < 0) return;
3560 userOfferedDraw = FALSE;
3562 if (appData.testLegality) {
3563 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
3564 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar);
3565 if (moveType == IllegalMove || moveType == ImpossibleMove) {
3566 DisplayMoveError("Illegal move");
3570 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
3573 if (gameMode == Training) {
3574 /* compare the move played on the board to the next move in the
3575 * game. If they match, display the move and the opponent's response.
3576 * If they don't match, display an error message.
3580 CopyBoard(testBoard, boards[currentMove]);
3581 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
3583 if (CompareBoards(testBoard, boards[currentMove+1])) {
3584 ForwardInner(currentMove+1);
3586 /* Autoplay the opponent's response.
3587 * if appData.animate was TRUE when Training mode was entered,
3588 * the response will be animated.
3590 saveAnimate = appData.animate;
3591 appData.animate = animateTraining;
3592 ForwardInner(currentMove+1);
3593 appData.animate = saveAnimate;
3595 /* check for the end of the game */
3596 if (currentMove >= forwardMostMove) {
3597 gameMode = PlayFromGameFile;
3599 SetTrainingModeOff();
3600 DisplayInformation("End of game");
3603 DisplayError("Incorrect move", 0);
3608 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
3611 /* Common tail of UserMoveEvent and DropMenuEvent */
3613 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
3615 int fromX, fromY, toX, toY;
3616 /*char*/int promoChar;
3618 /* Ok, now we know that the move is good, so we can kill
3619 the previous line in Analysis Mode */
3620 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
3621 forwardMostMove = currentMove;
3624 /* If we need the chess program but it's dead, restart it */
3625 ResurrectChessProgram();
3627 /* A user move restarts a paused game*/
3631 thinkOutput[0] = NULLCHAR;
3633 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
3635 if (gameMode == BeginningOfGame) {
3636 if (appData.noChessProgram) {
3637 gameMode = EditGame;
3641 gameMode = MachinePlaysBlack;
3643 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
3645 if (first.sendName) {
3646 sprintf(buf, "name %s\n", gameInfo.white);
3647 SendToProgram(buf, &first);
3653 /* Relay move to ICS or chess engine */
3654 if (appData.icsActive) {
3655 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
3656 gameMode == IcsExamining) {
3657 SendMoveToICS(moveType, fromX, fromY, toX, toY);
3661 if (first.sendTime && (gameMode == BeginningOfGame ||
3662 gameMode == MachinePlaysWhite ||
3663 gameMode == MachinePlaysBlack)) {
3664 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
3666 SendMoveToProgram(forwardMostMove-1, &first);
3667 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
3668 first.maybeThinking = TRUE;
3670 if (currentMove == cmailOldMove + 1) {
3671 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
3675 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
3679 switch (MateTest(boards[currentMove], PosFlags(currentMove),
3685 if (WhiteOnMove(currentMove)) {
3686 GameEnds(BlackWins, "Black mates", GE_PLAYER);
3688 GameEnds(WhiteWins, "White mates", GE_PLAYER);
3692 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
3697 case MachinePlaysBlack:
3698 case MachinePlaysWhite:
3699 /* disable certain menu options while machine is thinking */
3700 SetMachineThinkingEnables();
3709 HandleMachineMove(message, cps)
3711 ChessProgramState *cps;
3713 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
3714 char realname[MSG_SIZ];
3715 int fromX, fromY, toX, toY;
3722 * Kludge to ignore BEL characters
3724 while (*message == '\007') message++;
3727 * Look for book output
3729 if (cps == &first && bookRequested) {
3730 if (message[0] == '\t' || message[0] == ' ') {
3731 /* Part of the book output is here; append it */
3732 strcat(bookOutput, message);
3733 strcat(bookOutput, " \n");
3735 } else if (bookOutput[0] != NULLCHAR) {
3736 /* All of book output has arrived; display it */
3737 char *p = bookOutput;
3738 while (*p != NULLCHAR) {
3739 if (*p == '\t') *p = ' ';
3742 DisplayInformation(bookOutput);
3743 bookRequested = FALSE;
3744 /* Fall through to parse the current output */
3749 * Look for machine move.
3751 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 &&
3752 strcmp(buf2, "...") == 0) ||
3753 (sscanf(message, "%s %s", buf1, machineMove) == 2 &&
3754 strcmp(buf1, "move") == 0)) {
3756 /* This method is only useful on engines that support ping */
3757 if (cps->lastPing != cps->lastPong) {
3758 if (gameMode == BeginningOfGame) {
3759 /* Extra move from before last new; ignore */
3760 if (appData.debugMode) {
3761 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
3764 if (appData.debugMode) {
3765 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
3766 cps->which, gameMode);
3768 SendToProgram("undo\n", cps);
3774 case BeginningOfGame:
3775 /* Extra move from before last reset; ignore */
3776 if (appData.debugMode) {
3777 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
3784 /* Extra move after we tried to stop. The mode test is
3785 not a reliable way of detecting this problem, but it's
3786 the best we can do on engines that don't support ping.
3788 if (appData.debugMode) {
3789 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
3790 cps->which, gameMode);
3792 SendToProgram("undo\n", cps);
3795 case MachinePlaysWhite:
3796 case IcsPlayingWhite:
3797 machineWhite = TRUE;
3800 case MachinePlaysBlack:
3801 case IcsPlayingBlack:
3802 machineWhite = FALSE;
3805 case TwoMachinesPlay:
3806 machineWhite = (cps->twoMachinesColor[0] == 'w');
3809 if (WhiteOnMove(forwardMostMove) != machineWhite) {
3810 if (appData.debugMode) {
3812 "Ignoring move out of turn by %s, gameMode %d"
3813 ", forwardMost %d\n",
3814 cps->which, gameMode, forwardMostMove);
3819 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
3820 &fromX, &fromY, &toX, &toY, &promoChar)) {
3821 /* Machine move could not be parsed; ignore it. */
3822 sprintf(buf1, "Illegal move \"%s\" from %s machine",
3823 machineMove, cps->which);
3824 /*!!if (appData.debugMode)*/ DisplayError(buf1, 0);
3828 hintRequested = FALSE;
3829 lastHint[0] = NULLCHAR;
3830 bookRequested = FALSE;
3831 /* Program may be pondering now */
3832 cps->maybeThinking = TRUE;
3833 if (cps->sendTime == 2) cps->sendTime = 1;
3834 if (cps->offeredDraw) cps->offeredDraw--;
3837 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
3839 SendMoveToICS(moveType, fromX, fromY, toX, toY);
3843 /* currentMoveString is set as a side-effect of ParseOneMove */
3844 strcpy(machineMove, currentMoveString);
3845 strcat(machineMove, "\n");
3846 strcpy(moveList[forwardMostMove], machineMove);
3848 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
3850 if (gameMode == TwoMachinesPlay) {
3851 if (cps->other->sendTime) {
3852 SendTimeRemaining(cps->other,
3853 cps->other->twoMachinesColor[0] == 'w');
3855 SendMoveToProgram(forwardMostMove-1, cps->other);
3858 if (cps->other->useColors) {
3859 SendToProgram(cps->other->twoMachinesColor, cps->other);
3861 SendToProgram("go\n", cps->other);
3863 cps->other->maybeThinking = TRUE;
3866 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
3867 if (!pausing && appData.ringBellAfterMoves) {
3871 * Reenable menu items that were disabled while
3872 * machine was thinking
3874 if (gameMode != TwoMachinesPlay)
3875 SetUserThinkingEnables();
3879 /* Set special modes for chess engines. Later something general
3880 * could be added here; for now there is just one kludge feature,
3881 * needed because Crafty 15.10 and earlier don't ignore SIGINT
3882 * when "xboard" is given as an interactive command.
3884 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
3885 cps->useSigint = FALSE;
3886 cps->useSigterm = FALSE;
3890 * Look for communication commands
3892 if (!strncmp(message, "telluser ", 9)) {
3893 DisplayInformation(message + 9);
3896 if (!strncmp(message, "tellusererror ", 14)) {
3897 DisplayError(message + 14, 0);
3900 if (!strncmp(message, "tellopponent ", 13)) {
3901 if (appData.icsActive) {
3903 sprintf(buf1, "%ssay %s\n", ics_prefix, message + 13);
3907 DisplayInformation(message + 13);
3911 if (!strncmp(message, "tellothers ", 11)) {
3912 if (appData.icsActive) {
3914 sprintf(buf1, "%swhisper %s\n", ics_prefix, message + 11);
3920 if (!strncmp(message, "tellall ", 8)) {
3921 if (appData.icsActive) {
3923 sprintf(buf1, "%skibitz %s\n", ics_prefix, message + 8);
3927 DisplayInformation(message + 8);
3931 if (strncmp(message, "warning", 7) == 0) {
3932 /* Undocumented feature, use tellusererror in new code */
3933 DisplayError(message, 0);
3936 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
3937 strcpy(realname, cps->tidy);
3938 strcat(realname, " query");
3939 AskQuestion(realname, buf2, buf1, cps->pr);
3942 /* Commands from the engine directly to ICS. We don't allow these to be
3943 * sent until we are logged on. Crafty kibitzes have been known to
3944 * interfere with the login process.
3947 if (!strncmp(message, "tellics ", 8)) {
3948 SendToICS(message + 8);
3952 if (!strncmp(message, "tellicsnoalias ", 15)) {
3953 SendToICS(ics_prefix);
3954 SendToICS(message + 15);
3958 /* The following are for backward compatibility only */
3959 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
3960 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
3961 SendToICS(ics_prefix);
3967 if (strncmp(message, "feature ", 8) == 0) {
3968 ParseFeatures(message+8, cps);
3970 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
3974 * If the move is illegal, cancel it and redraw the board.
3975 * Also deal with other error cases. Matching is rather loose
3976 * here to accommodate engines written before the spec.
3978 if (strncmp(message + 1, "llegal move", 11) == 0 ||
3979 strncmp(message, "Error", 5) == 0) {
3980 if (StrStr(message, "name") ||
3981 StrStr(message, "rating") || StrStr(message, "?") ||
3982 StrStr(message, "result") || StrStr(message, "board") ||
3983 StrStr(message, "bk") || StrStr(message, "computer") ||
3984 StrStr(message, "variant") || StrStr(message, "hint") ||
3985 StrStr(message, "random") || StrStr(message, "depth") ||
3986 StrStr(message, "accepted")) {
3989 if (StrStr(message, "protover")) {
3990 /* Program is responding to input, so it's apparently done
3991 initializing, and this error message indicates it is
3992 protocol version 1. So we don't need to wait any longer
3993 for it to initialize and send feature commands. */
3994 FeatureDone(cps, 1);
3995 cps->protocolVersion = 1;
3998 cps->maybeThinking = FALSE;
4000 if (StrStr(message, "draw")) {
4001 /* Program doesn't have "draw" command */
4002 cps->sendDrawOffers = 0;
4005 if (cps->sendTime != 1 &&
4006 (StrStr(message, "time") || StrStr(message, "otim"))) {
4007 /* Program apparently doesn't have "time" or "otim" command */
4011 if (StrStr(message, "analyze")) {
4012 cps->analysisSupport = FALSE;
4013 cps->analyzing = FALSE;
4015 sprintf(buf2, "%s does not support analysis", cps->tidy);
4016 DisplayError(buf2, 0);
4019 if (StrStr(message, "st")) {
4020 cps->stKludge = TRUE;
4021 SendTimeControl(cps, movesPerSession, timeControl,
4022 timeIncrement, appData.searchDepth,
4026 if (StrStr(message, "sd")) {
4027 cps->sdKludge = TRUE;
4028 SendTimeControl(cps, movesPerSession, timeControl,
4029 timeIncrement, appData.searchDepth,
4033 if (!StrStr(message, "llegal")) return;
4034 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
4035 gameMode == IcsIdle) return;
4036 if (forwardMostMove <= backwardMostMove) return;
4037 if (cps == &first && programStats.ok_to_send == 0) {
4038 /* Bogus message from Crafty responding to "." This filtering
4039 can miss some of the bad messages, but fortunately the bug
4040 is fixed in current Crafty versions, so it doesn't matter. */
4043 if (pausing) PauseEvent();
4044 if (gameMode == PlayFromGameFile) {
4045 /* Stop reading this game file */
4046 gameMode = EditGame;
4049 currentMove = --forwardMostMove;
4050 DisplayMove(currentMove-1); /* before DisplayMoveError */
4052 DisplayBothClocks();
4053 sprintf(buf1, "Illegal move \"%s\" (rejected by %s chess program)",
4054 parseList[currentMove], cps->which);
4055 DisplayMoveError(buf1);
4056 DrawPosition(FALSE, boards[currentMove]);
4059 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
4060 /* Program has a broken "time" command that
4061 outputs a string not ending in newline.
4067 * If chess program startup fails, exit with an error message.
4068 * Attempts to recover here are futile.
4070 if ((StrStr(message, "unknown host") != NULL)
4071 || (StrStr(message, "No remote directory") != NULL)
4072 || (StrStr(message, "not found") != NULL)
4073 || (StrStr(message, "No such file") != NULL)
4074 || (StrStr(message, "can't alloc") != NULL)
4075 || (StrStr(message, "Permission denied") != NULL)) {
4077 cps->maybeThinking = FALSE;
4078 sprintf(buf1, "Failed to start %s chess program %s on %s: %s\n",
4079 cps->which, cps->program, cps->host, message);
4080 RemoveInputSource(cps->isr);
4081 DisplayFatalError(buf1, 0, 1);
4086 * Look for hint output
4088 if (sscanf(message, "Hint: %s", buf1) == 1) {
4089 if (cps == &first && hintRequested) {
4090 hintRequested = FALSE;
4091 if (ParseOneMove(buf1, forwardMostMove, &moveType,
4092 &fromX, &fromY, &toX, &toY, &promoChar)) {
4093 (void) CoordsToAlgebraic(boards[forwardMostMove],
4094 PosFlags(forwardMostMove), EP_UNKNOWN,
4095 fromY, fromX, toY, toX, promoChar, buf1);
4096 sprintf(buf2, "Hint: %s", buf1);
4097 DisplayInformation(buf2);
4099 /* Hint move could not be parsed!? */
4101 "Illegal hint move \"%s\"\nfrom %s chess program",
4103 DisplayError(buf2, 0);
4106 strcpy(lastHint, buf1);
4112 * Ignore other messages if game is not in progress
4114 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
4115 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
4118 * look for win, lose, draw, or draw offer
4120 if (strncmp(message, "1-0", 3) == 0) {
4121 char *p, *q, *r = "";
4122 p = strchr(message, '{');
4130 GameEnds(WhiteWins, r, GE_ENGINE);
4132 } else if (strncmp(message, "0-1", 3) == 0) {
4133 char *p, *q, *r = "";
4134 p = strchr(message, '{');
4142 /* Kludge for Arasan 4.1 bug */
4143 if (strcmp(r, "Black resigns") == 0) {
4144 GameEnds(WhiteWins, r, GE_ENGINE);
4147 GameEnds(BlackWins, r, GE_ENGINE);
4149 } else if (strncmp(message, "1/2", 3) == 0) {
4150 char *p, *q, *r = "";
4151 p = strchr(message, '{');
4159 GameEnds(GameIsDrawn, r, GE_ENGINE);
4162 } else if (strncmp(message, "White resign", 12) == 0) {
4163 GameEnds(BlackWins, "White resigns", GE_ENGINE);
4165 } else if (strncmp(message, "Black resign", 12) == 0) {
4166 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
4168 } else if (strncmp(message, "White", 5) == 0 &&
4169 message[5] != '(' &&
4170 StrStr(message, "Black") == NULL) {
4171 GameEnds(WhiteWins, "White mates", GE_ENGINE);
4173 } else if (strncmp(message, "Black", 5) == 0 &&
4174 message[5] != '(') {
4175 GameEnds(BlackWins, "Black mates", GE_ENGINE);
4177 } else if (strcmp(message, "resign") == 0 ||
4178 strcmp(message, "computer resigns") == 0) {
4180 case MachinePlaysBlack:
4181 case IcsPlayingBlack:
4182 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
4184 case MachinePlaysWhite:
4185 case IcsPlayingWhite:
4186 GameEnds(BlackWins, "White resigns", GE_ENGINE);
4188 case TwoMachinesPlay:
4189 if (cps->twoMachinesColor[0] == 'w')
4190 GameEnds(BlackWins, "White resigns", GE_ENGINE);
4192 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
4199 } else if (strncmp(message, "opponent mates", 14) == 0) {
4201 case MachinePlaysBlack:
4202 case IcsPlayingBlack:
4203 GameEnds(WhiteWins, "White mates", GE_ENGINE);
4205 case MachinePlaysWhite:
4206 case IcsPlayingWhite:
4207 GameEnds(BlackWins, "Black mates", GE_ENGINE);
4209 case TwoMachinesPlay:
4210 if (cps->twoMachinesColor[0] == 'w')
4211 GameEnds(BlackWins, "Black mates", GE_ENGINE);
4213 GameEnds(WhiteWins, "White mates", GE_ENGINE);
4220 } else if (strncmp(message, "computer mates", 14) == 0) {
4222 case MachinePlaysBlack:
4223 case IcsPlayingBlack:
4224 GameEnds(BlackWins, "Black mates", GE_ENGINE);
4226 case MachinePlaysWhite:
4227 case IcsPlayingWhite:
4228 GameEnds(WhiteWins, "White mates", GE_ENGINE);
4230 case TwoMachinesPlay:
4231 if (cps->twoMachinesColor[0] == 'w')
4232 GameEnds(WhiteWins, "White mates", GE_ENGINE);
4234 GameEnds(BlackWins, "Black mates", GE_ENGINE);
4241 } else if (strncmp(message, "checkmate", 9) == 0) {
4242 if (WhiteOnMove(forwardMostMove)) {
4243 GameEnds(BlackWins, "Black mates", GE_ENGINE);
4245 GameEnds(WhiteWins, "White mates", GE_ENGINE);
4248 } else if (strstr(message, "Draw") != NULL ||
4249 strstr(message, "game is a draw") != NULL) {
4250 GameEnds(GameIsDrawn, "Draw", GE_ENGINE);
4252 } else if (strstr(message, "offer") != NULL &&
4253 strstr(message, "draw") != NULL) {
4255 if (appData.zippyPlay && first.initDone) {
4256 /* Relay offer to ICS */
4257 SendToICS(ics_prefix);
4258 SendToICS("draw\n");
4261 cps->offeredDraw = 2; /* valid until this engine moves twice */
4262 if (gameMode == TwoMachinesPlay) {
4263 if (cps->other->offeredDraw) {
4264 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
4266 if (cps->other->sendDrawOffers) {
4267 SendToProgram("draw\n", cps->other);
4270 } else if (gameMode == MachinePlaysWhite ||
4271 gameMode == MachinePlaysBlack) {
4272 if (userOfferedDraw) {
4273 DisplayInformation("Machine accepts your draw offer");
4274 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
4276 DisplayInformation("Machine offers a draw\nSelect Action / Draw to agree");
4283 * Look for thinking output
4285 if (appData.showThinking) {
4286 int plylev, mvleft, mvtot, curscore, time;
4287 char mvname[MOVE_LEN];
4288 unsigned long nodes;
4291 int prefixHint = FALSE;
4292 mvname[0] = NULLCHAR;
4295 case MachinePlaysBlack:
4296 case IcsPlayingBlack:
4297 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
4299 case MachinePlaysWhite:
4300 case IcsPlayingWhite:
4301 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
4306 case TwoMachinesPlay:
4307 if ((cps->twoMachinesColor[0] == 'w') !=
4308 WhiteOnMove(forwardMostMove)) {
4319 if (sscanf(message, "%d%c %d %d %lu %[^\n]\n",
4320 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
4322 if (plyext != ' ' && plyext != '\t') {
4325 programStats.depth = plylev;
4326 programStats.nodes = nodes;
4327 programStats.time = time;
4328 programStats.score = curscore;
4329 strcpy(programStats.movelist, buf1);
4330 programStats.got_only_move = 0;
4332 if (programStats.seen_stat) {
4333 programStats.ok_to_send = 1;
4336 if (strchr(programStats.movelist, '(') != NULL) {
4337 programStats.line_is_book = 1;
4338 programStats.nr_moves = 0;
4339 programStats.moves_left = 0;
4341 programStats.line_is_book = 0;
4344 sprintf(thinkOutput, "[%d]%c%+.2f %s%s%s",
4346 (gameMode == TwoMachinesPlay ?
4347 ToUpper(cps->twoMachinesColor[0]) : ' '),
4348 ((double) curscore) / 100.0,
4349 prefixHint ? lastHint : "",
4350 prefixHint ? " " : "", buf1);
4352 if (currentMove == forwardMostMove ||
4353 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
4354 DisplayMove(currentMove - 1);
4359 } else if ((p=StrStr(message, "(only move)")) != NULL) {
4360 /* crafty (9.25+) says "(only move) <move>"
4361 * if there is only 1 legal move
4363 sscanf(p, "(only move) %s", buf1);
4364 sprintf(thinkOutput, "%s (only move)", buf1);
4365 sprintf(programStats.movelist, "%s (only move)", buf1);
4366 programStats.depth = 1;
4367 programStats.nr_moves = 1;
4368 programStats.moves_left = 1;
4369 programStats.nodes = 1;
4370 programStats.time = 1;
4371 programStats.got_only_move = 1;
4373 /* Not really, but we also use this member to
4374 mean "line isn't going to change" (Crafty
4375 isn't searching, so stats won't change) */
4376 programStats.line_is_book = 1;
4378 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
4379 gameMode == AnalyzeFile) {
4380 DisplayMove(currentMove - 1);
4384 } else if (sscanf(message,"stat01: %d %lu %d %d %d %s",
4385 &time, &nodes, &plylev, &mvleft,
4386 &mvtot, mvname) >= 5) {
4387 /* The stat01: line is from Crafty (9.29+) in response
4388 to the "." command */
4389 programStats.seen_stat = 1;
4390 cps->maybeThinking = TRUE;
4392 if (programStats.got_only_move || !appData.periodicUpdates)
4395 programStats.depth = plylev;
4396 programStats.time = time;
4397 programStats.nodes = nodes;
4398 programStats.moves_left = mvleft;
4399 programStats.nr_moves = mvtot;
4400 strcpy(programStats.move_name, mvname);
4401 programStats.ok_to_send = 1;
4405 } else if (strncmp(message,"++",2) == 0) {
4406 /* Crafty 9.29+ outputs this */
4407 programStats.got_fail = 2;
4410 } else if (strncmp(message,"--",2) == 0) {
4411 /* Crafty 9.29+ outputs this */
4412 programStats.got_fail = 1;
4415 } else if (thinkOutput[0] != NULLCHAR &&
4416 strncmp(message, " ", 4) == 0) {
4418 while (*p && *p == ' ') p++;
4419 strcat(thinkOutput, " ");
4420 strcat(thinkOutput, p);
4421 strcat(programStats.movelist, " ");
4422 strcat(programStats.movelist, p);
4423 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
4424 gameMode == AnalyzeFile) {
4425 DisplayMove(currentMove - 1);
4435 /* Parse a game score from the character string "game", and
4436 record it as the history of the current game. The game
4437 score is NOT assumed to start from the standard position.
4438 The display is not updated in any way.
4441 ParseGameHistory(game)
4445 int fromX, fromY, toX, toY, boardIndex;
4450 if (appData.debugMode)
4451 fprintf(debugFP, "Parsing game history: %s\n", game);
4453 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
4454 gameInfo.site = StrSave(appData.icsHost);
4455 gameInfo.date = PGNDate();
4456 gameInfo.round = StrSave("-");
4458 /* Parse out names of players */
4459 while (*game == ' ') game++;
4461 while (*game != ' ') *p++ = *game++;
4463 gameInfo.white = StrSave(buf);
4464 while (*game == ' ') game++;
4466 while (*game != ' ' && *game != '\n') *p++ = *game++;
4468 gameInfo.black = StrSave(buf);
4471 boardIndex = blackPlaysFirst ? 1 : 0;
4474 yyboardindex = boardIndex;
4475 moveType = (ChessMove) yylex();
4477 case WhitePromotionQueen:
4478 case BlackPromotionQueen:
4479 case WhitePromotionRook:
4480 case BlackPromotionRook:
4481 case WhitePromotionBishop:
4482 case BlackPromotionBishop:
4483 case WhitePromotionKnight:
4484 case BlackPromotionKnight:
4485 case WhitePromotionKing:
4486 case BlackPromotionKing:
4488 case WhiteCapturesEnPassant:
4489 case BlackCapturesEnPassant:
4490 case WhiteKingSideCastle:
4491 case WhiteQueenSideCastle:
4492 case BlackKingSideCastle:
4493 case BlackQueenSideCastle:
4494 case WhiteKingSideCastleWild:
4495 case WhiteQueenSideCastleWild:
4496 case BlackKingSideCastleWild:
4497 case BlackQueenSideCastleWild:
4498 case IllegalMove: /* maybe suicide chess, etc. */
4499 fromX = currentMoveString[0] - 'a';
4500 fromY = currentMoveString[1] - '1';
4501 toX = currentMoveString[2] - 'a';
4502 toY = currentMoveString[3] - '1';
4503 promoChar = currentMoveString[4];
4507 fromX = moveType == WhiteDrop ?
4508 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4509 (int) CharToPiece(ToLower(currentMoveString[0]));
4511 toX = currentMoveString[2] - 'a';
4512 toY = currentMoveString[3] - '1';
4513 promoChar = NULLCHAR;
4517 sprintf(buf, "Ambiguous move in ICS output: \"%s\"", yy_text);
4518 DisplayError(buf, 0);
4520 case ImpossibleMove:
4522 sprintf(buf, "Illegal move in ICS output: \"%s\"", yy_text);
4523 DisplayError(buf, 0);
4525 case (ChessMove) 0: /* end of file */
4526 if (boardIndex < backwardMostMove) {
4527 /* Oops, gap. How did that happen? */
4528 DisplayError("Gap in move list", 0);
4531 backwardMostMove = blackPlaysFirst ? 1 : 0;
4532 if (boardIndex > forwardMostMove) {
4533 forwardMostMove = boardIndex;
4537 if (boardIndex > 0) {
4538 strcat(parseList[boardIndex-1], " ");
4539 strcat(parseList[boardIndex-1], yy_text);
4551 case GameUnfinished:
4552 if (gameMode == IcsExamining) {
4553 if (boardIndex < backwardMostMove) {
4554 /* Oops, gap. How did that happen? */
4557 backwardMostMove = blackPlaysFirst ? 1 : 0;
4560 gameInfo.result = moveType;
4561 p = strchr(yy_text, '{');
4562 if (p == NULL) p = strchr(yy_text, '(');
4565 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
4567 q = strchr(p, *p == '{' ? '}' : ')');
4568 if (q != NULL) *q = NULLCHAR;
4571 gameInfo.resultDetails = StrSave(p);
4574 if (boardIndex >= forwardMostMove &&
4575 !(gameMode == IcsObserving && ics_gamenum == -1)) {
4576 backwardMostMove = blackPlaysFirst ? 1 : 0;
4579 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
4580 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
4581 parseList[boardIndex]);
4582 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
4583 /* currentMoveString is set as a side-effect of yylex */
4584 strcpy(moveList[boardIndex], currentMoveString);
4585 strcat(moveList[boardIndex], "\n");
4587 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
4588 switch (MateTest(boards[boardIndex],
4589 PosFlags(boardIndex), EP_UNKNOWN)) {
4595 strcat(parseList[boardIndex - 1], "+");
4598 strcat(parseList[boardIndex - 1], "#");
4605 /* Apply a move to the given board */
4607 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
4608 int fromX, fromY, toX, toY;
4612 ChessSquare captured = board[toY][toX];
4613 if (fromY == DROP_RANK) {
4615 board[toY][toX] = (ChessSquare) fromX;
4616 } else if (fromX == toX && fromY == toY) {
4618 } else if (fromY == 0 && fromX == 4
4619 && board[fromY][fromX] == WhiteKing
4620 && toY == 0 && toX == 6) {
4621 board[fromY][fromX] = EmptySquare;
4622 board[toY][toX] = WhiteKing;
4623 board[fromY][7] = EmptySquare;
4624 board[toY][5] = WhiteRook;
4625 } else if (fromY == 0 && fromX == 4
4626 && board[fromY][fromX] == WhiteKing
4627 && toY == 0 && toX == 2) {
4628 board[fromY][fromX] = EmptySquare;
4629 board[toY][toX] = WhiteKing;
4630 board[fromY][0] = EmptySquare;
4631 board[toY][3] = WhiteRook;
4632 } else if (fromY == 0 && fromX == 3
4633 && board[fromY][fromX] == WhiteKing
4634 && toY == 0 && toX == 5) {
4635 board[fromY][fromX] = EmptySquare;
4636 board[toY][toX] = WhiteKing;
4637 board[fromY][7] = EmptySquare;
4638 board[toY][4] = WhiteRook;
4639 } else if (fromY == 0 && fromX == 3
4640 && board[fromY][fromX] == WhiteKing
4641 && toY == 0 && toX == 1) {
4642 board[fromY][fromX] = EmptySquare;
4643 board[toY][toX] = WhiteKing;
4644 board[fromY][0] = EmptySquare;
4645 board[toY][2] = WhiteRook;
4646 } else if (board[fromY][fromX] == WhitePawn
4648 /* white pawn promotion */
4649 board[7][toX] = CharToPiece(ToUpper(promoChar));
4650 if (board[7][toX] == EmptySquare) {
4651 board[7][toX] = WhiteQueen;
4653 board[fromY][fromX] = EmptySquare;
4654 } else if ((fromY == 4)
4656 && (board[fromY][fromX] == WhitePawn)
4657 && (board[toY][toX] == EmptySquare)) {
4658 board[fromY][fromX] = EmptySquare;
4659 board[toY][toX] = WhitePawn;
4660 captured = board[toY - 1][toX];
4661 board[toY - 1][toX] = EmptySquare;
4662 } else if (fromY == 7 && fromX == 4
4663 && board[fromY][fromX] == BlackKing
4664 && toY == 7 && toX == 6) {
4665 board[fromY][fromX] = EmptySquare;
4666 board[toY][toX] = BlackKing;
4667 board[fromY][7] = EmptySquare;
4668 board[toY][5] = BlackRook;
4669 } else if (fromY == 7 && fromX == 4
4670 && board[fromY][fromX] == BlackKing
4671 && toY == 7 && toX == 2) {
4672 board[fromY][fromX] = EmptySquare;
4673 board[toY][toX] = BlackKing;
4674 board[fromY][0] = EmptySquare;
4675 board[toY][3] = BlackRook;
4676 } else if (fromY == 7 && fromX == 3
4677 && board[fromY][fromX] == BlackKing
4678 && toY == 7 && toX == 5) {
4679 board[fromY][fromX] = EmptySquare;
4680 board[toY][toX] = BlackKing;
4681 board[fromY][7] = EmptySquare;
4682 board[toY][4] = BlackRook;
4683 } else if (fromY == 7 && fromX == 3
4684 && board[fromY][fromX] == BlackKing
4685 && toY == 7 && toX == 1) {
4686 board[fromY][fromX] = EmptySquare;
4687 board[toY][toX] = BlackKing;
4688 board[fromY][0] = EmptySquare;
4689 board[toY][2] = BlackRook;
4690 } else if (board[fromY][fromX] == BlackPawn
4692 /* black pawn promotion */
4693 board[0][toX] = CharToPiece(ToLower(promoChar));
4694 if (board[0][toX] == EmptySquare) {
4695 board[0][toX] = BlackQueen;
4697 board[fromY][fromX] = EmptySquare;
4698 } else if ((fromY == 3)
4700 && (board[fromY][fromX] == BlackPawn)
4701 && (board[toY][toX] == EmptySquare)) {
4702 board[fromY][fromX] = EmptySquare;
4703 board[toY][toX] = BlackPawn;
4704 captured = board[toY + 1][toX];
4705 board[toY + 1][toX] = EmptySquare;
4707 board[toY][toX] = board[fromY][fromX];
4708 board[fromY][fromX] = EmptySquare;
4710 if (gameInfo.variant == VariantCrazyhouse) {
4712 /* !!A lot more code needs to be written to support holdings */
4713 if (fromY == DROP_RANK) {
4714 /* Delete from holdings */
4715 if (holdings[(int) fromX] > 0) holdings[(int) fromX]--;
4717 if (captured != EmptySquare) {
4718 /* Add to holdings */
4719 if (captured < BlackPawn) {
4720 holdings[(int)captured - (int)BlackPawn + (int)WhitePawn]++;
4722 holdings[(int)captured - (int)WhitePawn + (int)BlackPawn]++;
4726 } else if (gameInfo.variant == VariantAtomic) {
4727 if (captured != EmptySquare) {
4729 for (y = toY-1; y <= toY+1; y++) {
4730 for (x = toX-1; x <= toX+1; x++) {
4731 if (y >= 0 && y <= 7 && x >= 0 && x <= 7 &&
4732 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
4733 board[y][x] = EmptySquare;
4737 board[toY][toX] = EmptySquare;
4742 /* Updates forwardMostMove */
4744 MakeMove(fromX, fromY, toX, toY, promoChar)
4745 int fromX, fromY, toX, toY;
4749 if (forwardMostMove >= MAX_MOVES) {
4750 DisplayFatalError("Game too long; increase MAX_MOVES and recompile",
4755 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
4756 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
4757 if (commentList[forwardMostMove] != NULL) {
4758 free(commentList[forwardMostMove]);
4759 commentList[forwardMostMove] = NULL;
4761 CopyBoard(boards[forwardMostMove], boards[forwardMostMove - 1]);
4762 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove]);
4763 gameInfo.result = GameUnfinished;
4764 if (gameInfo.resultDetails != NULL) {
4765 free(gameInfo.resultDetails);
4766 gameInfo.resultDetails = NULL;
4768 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
4769 moveList[forwardMostMove - 1]);
4770 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
4771 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
4772 fromY, fromX, toY, toX, promoChar,
4773 parseList[forwardMostMove - 1]);
4774 switch (MateTest(boards[forwardMostMove],
4775 PosFlags(forwardMostMove), EP_UNKNOWN)){
4781 strcat(parseList[forwardMostMove - 1], "+");
4784 strcat(parseList[forwardMostMove - 1], "#");
4789 /* Updates currentMove if not pausing */
4791 ShowMove(fromX, fromY, toX, toY)
4793 int instant = (gameMode == PlayFromGameFile) ?
4794 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
4795 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
4797 if (forwardMostMove == currentMove + 1) {
4798 AnimateMove(boards[forwardMostMove - 1],
4799 fromX, fromY, toX, toY);
4801 if (appData.highlightLastMove) {
4802 SetHighlights(fromX, fromY, toX, toY);
4805 currentMove = forwardMostMove;
4808 if (instant) return;
4809 DisplayMove(currentMove - 1);
4810 DrawPosition(FALSE, boards[currentMove]);
4811 DisplayBothClocks();
4812 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
4817 InitChessProgram(cps)
4818 ChessProgramState *cps;
4821 if (appData.noChessProgram) return;
4822 hintRequested = FALSE;
4823 bookRequested = FALSE;
4824 SendToProgram(cps->initString, cps);
4825 if (gameInfo.variant != VariantNormal &&
4826 gameInfo.variant != VariantLoadable) {
4827 char *v = VariantName(gameInfo.variant);
4828 if (StrStr(cps->variants, v) == NULL) {
4829 sprintf(buf, "Variant %s not supported by %s", v, cps->tidy);
4830 DisplayFatalError(buf, 0, 1);
4833 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
4834 SendToProgram(buf, cps);
4837 sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-");
4838 SendToProgram(buf, cps);
4840 cps->maybeThinking = FALSE;
4841 cps->offeredDraw = 0;
4842 if (!appData.icsActive) {
4843 SendTimeControl(cps, movesPerSession, timeControl,
4844 timeIncrement, appData.searchDepth,
4847 if (appData.showThinking) {
4848 SendToProgram("post\n", cps);
4850 SendToProgram("hard\n", cps);
4851 if (!appData.ponderNextMove) {
4852 /* Warning: "easy" is a toggle in GNU Chess, so don't send
4853 it without being sure what state we are in first. "hard"
4854 is not a toggle, so that one is OK.
4856 SendToProgram("easy\n", cps);
4859 sprintf(buf, "ping %d\n", ++cps->lastPing);
4860 SendToProgram(buf, cps);
4862 cps->initDone = TRUE;
4867 StartChessProgram(cps)
4868 ChessProgramState *cps;
4873 if (appData.noChessProgram) return;
4874 cps->initDone = FALSE;
4876 if (strcmp(cps->host, "localhost") == 0) {
4877 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
4878 } else if (*appData.remoteShell == NULLCHAR) {
4879 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
4881 if (*appData.remoteUser == NULLCHAR) {
4882 sprintf(buf, "%s %s %s", appData.remoteShell, cps->host,
4885 sprintf(buf, "%s %s -l %s %s", appData.remoteShell,
4886 cps->host, appData.remoteUser, cps->program);
4888 err = StartChildProcess(buf, "", &cps->pr);
4892 sprintf(buf, "Startup failure on '%s'", cps->program);
4893 DisplayFatalError(buf, err, 1);
4899 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
4900 if (cps->protocolVersion > 1) {
4901 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
4902 SendToProgram(buf, cps);
4904 SendToProgram("xboard\n", cps);
4910 TwoMachinesEventIfReady P((void))
4912 if (first.lastPing != first.lastPong) {
4913 DisplayMessage("", "Waiting for first chess program");
4914 ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000);
4917 if (second.lastPing != second.lastPong) {
4918 DisplayMessage("", "Waiting for second chess program");
4919 ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000);
4927 NextMatchGame P((void))
4930 if (*appData.loadGameFile != NULLCHAR) {
4931 LoadGameFromFile(appData.loadGameFile,
4932 appData.loadGameIndex,
4933 appData.loadGameFile, FALSE);
4934 } else if (*appData.loadPositionFile != NULLCHAR) {
4935 LoadPositionFromFile(appData.loadPositionFile,
4936 appData.loadPositionIndex,
4937 appData.loadPositionFile);
4939 TwoMachinesEventIfReady();
4943 GameEnds(result, resultDetails, whosays)
4945 char *resultDetails;
4948 GameMode nextGameMode;
4951 if (appData.debugMode) {
4952 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
4953 result, resultDetails ? resultDetails : "(null)", whosays);
4956 if (appData.icsActive && whosays == GE_ENGINE) {
4957 /* If we are playing on ICS, the server decides when the
4958 game is over, but the engine can offer to draw, claim
4962 if (appData.zippyPlay && first.initDone) {
4963 if (result == GameIsDrawn) {
4964 /* In case draw still needs to be claimed */
4965 SendToICS(ics_prefix);
4966 SendToICS("draw\n");
4967 } else if (StrCaseStr(resultDetails, "resign")) {
4968 SendToICS(ics_prefix);
4969 SendToICS("resign\n");
4976 /* If we're loading the game from a file, stop */
4977 if (whosays == GE_FILE) {
4978 (void) StopLoadGameTimer();
4982 /* Cancel draw offers */
4983 first.offeredDraw = second.offeredDraw = 0;
4985 /* If this is an ICS game, only ICS can really say it's done;
4986 if not, anyone can. */
4987 isIcsGame = (gameMode == IcsPlayingWhite ||
4988 gameMode == IcsPlayingBlack ||
4989 gameMode == IcsObserving ||
4990 gameMode == IcsExamining);
4992 if (!isIcsGame || whosays == GE_ICS) {
4993 /* OK -- not an ICS game, or ICS said it was done */
4995 if (!isIcsGame && !appData.noChessProgram)
4996 SetUserThinkingEnables();
4998 if (resultDetails != NULL) {
4999 gameInfo.result = result;
5000 gameInfo.resultDetails = StrSave(resultDetails);
5002 /* Tell program how game ended in case it is learning */
5003 if (gameMode == MachinePlaysWhite ||
5004 gameMode == MachinePlaysBlack ||
5005 gameMode == TwoMachinesPlay ||
5006 gameMode == IcsPlayingWhite ||
5007 gameMode == IcsPlayingBlack ||
5008 gameMode == BeginningOfGame) {
5010 sprintf(buf, "result %s {%s}\n", PGNResult(result),
5012 if (first.pr != NoProc) {
5013 SendToProgram(buf, &first);
5015 if (second.pr != NoProc &&
5016 gameMode == TwoMachinesPlay) {
5017 SendToProgram(buf, &second);
5021 /* display last move only if game was not loaded from file */
5022 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
5023 DisplayMove(currentMove - 1);
5025 if (forwardMostMove != 0) {
5026 if (gameMode != PlayFromGameFile && gameMode != EditGame) {
5027 if (*appData.saveGameFile != NULLCHAR) {
5028 SaveGameToFile(appData.saveGameFile, TRUE);
5029 } else if (appData.autoSaveGames) {
5032 if (*appData.savePositionFile != NULLCHAR) {
5033 SavePositionToFile(appData.savePositionFile);
5039 if (appData.icsActive) {
5040 if (appData.quietPlay &&
5041 (gameMode == IcsPlayingWhite ||
5042 gameMode == IcsPlayingBlack)) {
5043 SendToICS(ics_prefix);
5044 SendToICS("set shout 1\n");
5046 nextGameMode = IcsIdle;
5047 ics_user_moved = FALSE;
5048 /* clean up premove. It's ugly when the game has ended and the
5049 * premove highlights are still on the board.
5053 ClearPremoveHighlights();
5054 DrawPosition(FALSE, boards[currentMove]);
5056 if (whosays == GE_ICS) {
5059 if (gameMode == IcsPlayingWhite)
5061 else if(gameMode == IcsPlayingBlack)
5065 if (gameMode == IcsPlayingBlack)
5067 else if(gameMode == IcsPlayingWhite)
5074 PlayIcsUnfinishedSound();
5077 } else if (gameMode == EditGame ||
5078 gameMode == PlayFromGameFile ||
5079 gameMode == AnalyzeMode ||
5080 gameMode == AnalyzeFile) {
5081 nextGameMode = gameMode;
5083 nextGameMode = EndOfGame;
5088 nextGameMode = gameMode;
5091 if (appData.noChessProgram) {
5092 gameMode = nextGameMode;
5098 /* Put first chess program into idle state */
5099 if (first.pr != NoProc &&
5100 (gameMode == MachinePlaysWhite ||
5101 gameMode == MachinePlaysBlack ||
5102 gameMode == TwoMachinesPlay ||
5103 gameMode == IcsPlayingWhite ||
5104 gameMode == IcsPlayingBlack ||
5105 gameMode == BeginningOfGame)) {
5106 SendToProgram("force\n", &first);
5107 if (first.usePing) {
5109 sprintf(buf, "ping %d\n", ++first.lastPing);
5110 SendToProgram(buf, &first);
5113 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
5114 /* Kill off first chess program */
5115 if (first.isr != NULL)
5116 RemoveInputSource(first.isr);
5119 if (first.pr != NoProc) {
5121 SendToProgram("quit\n", &first);
5122 DestroyChildProcess(first.pr, first.useSigterm);
5127 /* Put second chess program into idle state */
5128 if (second.pr != NoProc &&
5129 gameMode == TwoMachinesPlay) {
5130 SendToProgram("force\n", &second);
5131 if (second.usePing) {
5133 sprintf(buf, "ping %d\n", ++second.lastPing);
5134 SendToProgram(buf, &second);
5137 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
5138 /* Kill off second chess program */
5139 if (second.isr != NULL)
5140 RemoveInputSource(second.isr);
5143 if (second.pr != NoProc) {
5144 SendToProgram("quit\n", &second);
5145 DestroyChildProcess(second.pr, second.useSigterm);
5150 if (matchMode && gameMode == TwoMachinesPlay) {
5153 if (first.twoMachinesColor[0] == 'w') {
5160 if (first.twoMachinesColor[0] == 'b') {
5169 if (matchGame < appData.matchGames) {
5171 tmp = first.twoMachinesColor;
5172 first.twoMachinesColor = second.twoMachinesColor;
5173 second.twoMachinesColor = tmp;
5174 gameMode = nextGameMode;
5176 ScheduleDelayedEvent(NextMatchGame, 10000);
5180 gameMode = nextGameMode;
5181 sprintf(buf, "Match %s vs. %s: final score %d-%d-%d",
5182 first.tidy, second.tidy,
5183 first.matchWins, second.matchWins,
5184 appData.matchGames - (first.matchWins + second.matchWins));
5185 DisplayFatalError(buf, 0, 0);
5188 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
5189 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
5191 gameMode = nextGameMode;
5195 /* Assumes program was just initialized (initString sent).
5196 Leaves program in force mode. */
5198 FeedMovesToProgram(cps, upto)
5199 ChessProgramState *cps;
5204 if (appData.debugMode)
5205 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
5206 startedFromSetupPosition ? "position and " : "",
5207 backwardMostMove, upto, cps->which);
5208 SendToProgram("force\n", cps);
5209 if (startedFromSetupPosition) {
5210 SendBoard(cps, backwardMostMove);
5212 for (i = backwardMostMove; i < upto; i++) {
5213 SendMoveToProgram(i, cps);
5219 ResurrectChessProgram()
5221 /* The chess program may have exited.
5222 If so, restart it and feed it all the moves made so far. */
5224 if (appData.noChessProgram || first.pr != NoProc) return;
5226 StartChessProgram(&first);
5227 InitChessProgram(&first);
5228 FeedMovesToProgram(&first, currentMove);
5230 if (!first.sendTime) {
5231 /* can't tell gnuchess what its clock should read,
5232 so we bow to its notion. */
5234 timeRemaining[0][currentMove] = whiteTimeRemaining;
5235 timeRemaining[1][currentMove] = blackTimeRemaining;
5238 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
5239 first.analysisSupport) {
5240 SendToProgram("analyze\n", &first);
5241 first.analyzing = TRUE;
5254 if (appData.debugMode) {
5255 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
5256 redraw, init, gameMode);
5259 pausing = pauseExamInvalid = FALSE;
5260 startedFromSetupPosition = blackPlaysFirst = FALSE;
5262 whiteFlag = blackFlag = FALSE;
5263 userOfferedDraw = FALSE;
5264 hintRequested = bookRequested = FALSE;
5265 first.maybeThinking = FALSE;
5266 second.maybeThinking = FALSE;
5267 thinkOutput[0] = NULLCHAR;
5268 lastHint[0] = NULLCHAR;
5269 ClearGameInfo(&gameInfo);
5270 gameInfo.variant = StringToVariant(appData.variant);
5271 ics_user_moved = ics_clock_paused = FALSE;
5272 ics_getting_history = H_FALSE;
5274 white_holding[0] = black_holding[0] = NULLCHAR;
5275 ClearProgramStats();
5279 flipView = appData.flipView;
5280 ClearPremoveHighlights();
5282 alarmSounded = FALSE;
5284 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
5286 gameMode = BeginningOfGame;
5288 InitPosition(redraw);
5289 for (i = 0; i < MAX_MOVES; i++) {
5290 if (commentList[i] != NULL) {
5291 free(commentList[i]);
5292 commentList[i] = NULL;
5296 timeRemaining[0][0] = whiteTimeRemaining;
5297 timeRemaining[1][0] = blackTimeRemaining;
5298 if (first.pr == NULL) {
5299 StartChessProgram(&first);
5301 if (init) InitChessProgram(&first);
5303 DisplayMessage("", "");
5304 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5311 if (!AutoPlayOneMove())
5313 if (matchMode || appData.timeDelay == 0)
5315 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
5317 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
5326 int fromX, fromY, toX, toY;
5328 if (appData.debugMode) {
5329 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
5332 if (gameMode != PlayFromGameFile)
5335 if (currentMove >= forwardMostMove) {
5336 gameMode = EditGame;
5341 toX = moveList[currentMove][2] - 'a';
5342 toY = moveList[currentMove][3] - '1';
5344 if (moveList[currentMove][1] == '@') {
5345 if (appData.highlightLastMove) {
5346 SetHighlights(-1, -1, toX, toY);
5349 fromX = moveList[currentMove][0] - 'a';
5350 fromY = moveList[currentMove][1] - '1';
5351 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
5353 if (appData.highlightLastMove) {
5354 SetHighlights(fromX, fromY, toX, toY);
5357 DisplayMove(currentMove);
5358 SendMoveToProgram(currentMove++, &first);
5359 DisplayBothClocks();
5360 DrawPosition(FALSE, boards[currentMove]);
5361 if (commentList[currentMove] != NULL) {
5362 DisplayComment(currentMove - 1, commentList[currentMove]);
5369 LoadGameOneMove(readAhead)
5370 ChessMove readAhead;
5372 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
5373 char promoChar = NULLCHAR;
5378 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
5379 gameMode != AnalyzeMode && gameMode != Training) {
5384 yyboardindex = forwardMostMove;
5385 if (readAhead != (ChessMove)0) {
5386 moveType = readAhead;
5388 if (gameFileFP == NULL)
5390 moveType = (ChessMove) yylex();
5396 if (appData.debugMode)
5397 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
5399 if (*p == '{' || *p == '[' || *p == '(') {
5400 p[strlen(p) - 1] = NULLCHAR;
5404 /* append the comment but don't display it */
5405 while (*p == '\n') p++;
5406 AppendComment(currentMove, p);
5409 case WhiteCapturesEnPassant:
5410 case BlackCapturesEnPassant:
5411 case WhitePromotionQueen:
5412 case BlackPromotionQueen:
5413 case WhitePromotionRook:
5414 case BlackPromotionRook:
5415 case WhitePromotionBishop:
5416 case BlackPromotionBishop:
5417 case WhitePromotionKnight:
5418 case BlackPromotionKnight:
5419 case WhitePromotionKing:
5420 case BlackPromotionKing:
5422 case WhiteKingSideCastle:
5423 case WhiteQueenSideCastle:
5424 case BlackKingSideCastle:
5425 case BlackQueenSideCastle:
5426 case WhiteKingSideCastleWild:
5427 case WhiteQueenSideCastleWild:
5428 case BlackKingSideCastleWild:
5429 case BlackQueenSideCastleWild:
5430 if (appData.debugMode)
5431 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
5432 fromX = currentMoveString[0] - 'a';
5433 fromY = currentMoveString[1] - '1';
5434 toX = currentMoveString[2] - 'a';
5435 toY = currentMoveString[3] - '1';
5436 promoChar = currentMoveString[4];
5441 if (appData.debugMode)
5442 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
5443 fromX = moveType == WhiteDrop ?
5444 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5445 (int) CharToPiece(ToLower(currentMoveString[0]));
5447 toX = currentMoveString[2] - 'a';
5448 toY = currentMoveString[3] - '1';
5454 case GameUnfinished:
5455 if (appData.debugMode)
5456 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
5457 p = strchr(yy_text, '{');
5458 if (p == NULL) p = strchr(yy_text, '(');
5461 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
5463 q = strchr(p, *p == '{' ? '}' : ')');
5464 if (q != NULL) *q = NULLCHAR;
5467 GameEnds(moveType, p, GE_FILE);
5469 if (cmailMsgLoaded) {
5471 flipView = WhiteOnMove(currentMove);
5472 if (moveType == GameUnfinished) flipView = !flipView;
5473 if (appData.debugMode)
5474 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
5478 case (ChessMove) 0: /* end of file */
5479 if (appData.debugMode)
5480 fprintf(debugFP, "Parser hit end of file\n");
5481 switch (MateTest(boards[currentMove], PosFlags(currentMove),
5487 if (WhiteOnMove(currentMove)) {
5488 GameEnds(BlackWins, "Black mates", GE_FILE);
5490 GameEnds(WhiteWins, "White mates", GE_FILE);
5494 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
5501 if (lastLoadGameStart == GNUChessGame) {
5502 /* GNUChessGames have numbers, but they aren't move numbers */
5503 if (appData.debugMode)
5504 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
5505 yy_text, (int) moveType);
5506 return LoadGameOneMove((ChessMove)0); /* tail recursion */
5508 /* else fall thru */
5513 /* Reached start of next game in file */
5514 if (appData.debugMode)
5515 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
5516 switch (MateTest(boards[currentMove], PosFlags(currentMove),
5522 if (WhiteOnMove(currentMove)) {
5523 GameEnds(BlackWins, "Black mates", GE_FILE);
5525 GameEnds(WhiteWins, "White mates", GE_FILE);
5529 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
5535 case PositionDiagram: /* should not happen; ignore */
5536 case ElapsedTime: /* ignore */
5537 case NAG: /* ignore */
5538 if (appData.debugMode)
5539 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
5540 yy_text, (int) moveType);
5541 return LoadGameOneMove((ChessMove)0); /* tail recursion */
5544 if (appData.testLegality) {
5545 if (appData.debugMode)
5546 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
5547 sprintf(move, "Illegal move: %d.%s%s",
5548 (forwardMostMove / 2) + 1,
5549 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
5550 DisplayError(move, 0);
5553 if (appData.debugMode)
5554 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
5555 yy_text, currentMoveString);
5556 fromX = currentMoveString[0] - 'a';
5557 fromY = currentMoveString[1] - '1';
5558 toX = currentMoveString[2] - 'a';
5559 toY = currentMoveString[3] - '1';
5560 promoChar = currentMoveString[4];
5565 if (appData.debugMode)
5566 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
5567 sprintf(move, "Ambiguous move: %d.%s%s",
5568 (forwardMostMove / 2) + 1,
5569 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
5570 DisplayError(move, 0);
5575 case ImpossibleMove:
5576 if (appData.debugMode)
5577 fprintf(debugFP, "Parsed ImpossibleMove: %s\n", yy_text);
5578 sprintf(move, "Illegal move: %d.%s%s",
5579 (forwardMostMove / 2) + 1,
5580 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
5581 DisplayError(move, 0);
5587 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
5588 DrawPosition(FALSE, boards[currentMove]);
5589 DisplayBothClocks();
5590 if (!appData.matchMode && commentList[currentMove] != NULL)
5591 DisplayComment(currentMove - 1, commentList[currentMove]);
5593 (void) StopLoadGameTimer();
5595 cmailOldMove = forwardMostMove;
5598 /* currentMoveString is set as a side-effect of yylex */
5599 strcat(currentMoveString, "\n");
5600 strcpy(moveList[forwardMostMove], currentMoveString);
5602 thinkOutput[0] = NULLCHAR;
5603 MakeMove(fromX, fromY, toX, toY, promoChar);
5604 currentMove = forwardMostMove;
5609 /* Load the nth game from the given file */
5611 LoadGameFromFile(filename, n, title, useList)
5615 /*Boolean*/ int useList;
5620 if (strcmp(filename, "-") == 0) {
5624 f = fopen(filename, "rb");
5626 sprintf(buf, "Can't open \"%s\"", filename);
5627 DisplayError(buf, errno);
5631 if (fseek(f, 0, 0) == -1) {
5632 /* f is not seekable; probably a pipe */
5635 if (useList && n == 0) {
5636 int error = GameListBuild(f);
5638 DisplayError("Cannot build game list", error);
5639 } else if (!ListEmpty(&gameList) &&
5640 ((ListGame *) gameList.tailPred)->number > 1) {
5641 GameListPopUp(f, title);
5648 return LoadGame(f, n, title, FALSE);
5653 MakeRegisteredMove()
5655 int fromX, fromY, toX, toY;
5657 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
5658 switch (cmailMoveType[lastLoadGameNumber - 1]) {
5661 if (appData.debugMode)
5662 fprintf(debugFP, "Restoring %s for game %d\n",
5663 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
5665 thinkOutput[0] = NULLCHAR;
5666 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
5667 fromX = cmailMove[lastLoadGameNumber - 1][0] - 'a';
5668 fromY = cmailMove[lastLoadGameNumber - 1][1] - '1';
5669 toX = cmailMove[lastLoadGameNumber - 1][2] - 'a';
5670 toY = cmailMove[lastLoadGameNumber - 1][3] - '1';
5671 promoChar = cmailMove[lastLoadGameNumber - 1][4];
5672 MakeMove(fromX, fromY, toX, toY, promoChar);
5673 ShowMove(fromX, fromY, toX, toY);
5675 switch (MateTest(boards[currentMove], PosFlags(currentMove),
5682 if (WhiteOnMove(currentMove)) {
5683 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5685 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5690 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5697 if (WhiteOnMove(currentMove)) {
5698 GameEnds(BlackWins, "White resigns", GE_PLAYER);
5700 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
5705 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
5716 /* Wrapper around LoadGame for use when a Cmail message is loaded */
5718 CmailLoadGame(f, gameNumber, title, useList)
5726 if (gameNumber > nCmailGames) {
5727 DisplayError("No more games in this message", 0);
5730 if (f == lastLoadGameFP) {
5731 int offset = gameNumber - lastLoadGameNumber;
5733 cmailMsg[0] = NULLCHAR;
5734 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
5735 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
5736 nCmailMovesRegistered--;
5738 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5739 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
5740 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
5743 if (! RegisterMove()) return FALSE;
5747 retVal = LoadGame(f, gameNumber, title, useList);
5749 /* Make move registered during previous look at this game, if any */
5750 MakeRegisteredMove();
5752 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
5753 commentList[currentMove]
5754 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
5755 DisplayComment(currentMove - 1, commentList[currentMove]);
5761 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
5766 int gameNumber = lastLoadGameNumber + offset;
5767 if (lastLoadGameFP == NULL) {
5768 DisplayError("No game has been loaded yet", 0);
5771 if (gameNumber <= 0) {
5772 DisplayError("Can't back up any further", 0);
5775 if (cmailMsgLoaded) {
5776 return CmailLoadGame(lastLoadGameFP, gameNumber,
5777 lastLoadGameTitle, lastLoadGameUseList);
5779 return LoadGame(lastLoadGameFP, gameNumber,
5780 lastLoadGameTitle, lastLoadGameUseList);
5786 /* Load the nth game from open file f */
5788 LoadGame(f, gameNumber, title, useList)
5796 int gn = gameNumber;
5797 ListGame *lg = NULL;
5800 GameMode oldGameMode;
5802 if (appData.debugMode)
5803 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
5805 if (gameMode == Training )
5806 SetTrainingModeOff();
5808 oldGameMode = gameMode;
5809 if (gameMode != BeginningOfGame) {
5814 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
5815 fclose(lastLoadGameFP);
5819 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
5822 fseek(f, lg->offset, 0);
5823 GameListHighlight(gameNumber);
5827 DisplayError("Game number out of range", 0);
5832 if (fseek(f, 0, 0) == -1) {
5833 if (f == lastLoadGameFP ?
5834 gameNumber == lastLoadGameNumber + 1 :
5838 DisplayError("Can't seek on game file", 0);
5844 lastLoadGameNumber = gameNumber;
5845 strcpy(lastLoadGameTitle, title);
5846 lastLoadGameUseList = useList;
5851 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
5852 sprintf(buf, "%s vs. %s", lg->gameInfo.white,
5853 lg->gameInfo.black);
5855 } else if (*title != NULLCHAR) {
5856 if (gameNumber > 1) {
5857 sprintf(buf, "%s %d", title, gameNumber);
5860 DisplayTitle(title);
5864 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
5865 gameMode = PlayFromGameFile;
5869 currentMove = forwardMostMove = backwardMostMove = 0;
5870 CopyBoard(boards[0], initialPosition);
5874 * Skip the first gn-1 games in the file.
5875 * Also skip over anything that precedes an identifiable
5876 * start of game marker, to avoid being confused by
5877 * garbage at the start of the file. Currently
5878 * recognized start of game markers are the move number "1",
5879 * the pattern "gnuchess .* game", the pattern
5880 * "^[#;%] [^ ]* game file", and a PGN tag block.
5881 * A game that starts with one of the latter two patterns
5882 * will also have a move number 1, possibly
5883 * following a position diagram.
5885 cm = lastLoadGameStart = (ChessMove) 0;
5888 yyboardindex = forwardMostMove;
5889 cm = (ChessMove) yylex();
5890 yyskipmoves = FALSE;
5893 if (cmailMsgLoaded) {
5894 nCmailGames = CMAIL_MAX_GAMES - gn;
5897 DisplayError("Game not found in file", 0);
5899 yyskipmoves = FALSE;
5905 lastLoadGameStart = cm;
5909 switch (lastLoadGameStart) {
5916 gn--; /* count this game */
5917 lastLoadGameStart = cm;
5926 switch (lastLoadGameStart) {
5931 gn--; /* count this game */
5932 lastLoadGameStart = cm;
5935 lastLoadGameStart = cm; /* game counted already */
5943 yyboardindex = forwardMostMove;
5944 cm = (ChessMove) yylex();
5945 } while (cm == PGNTag || cm == Comment);
5952 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
5953 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
5954 != CMAIL_OLD_RESULT) {
5956 cmailResult[ CMAIL_MAX_GAMES
5957 - gn - 1] = CMAIL_OLD_RESULT;
5966 yyskipmoves = FALSE;
5968 if (appData.debugMode)
5969 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
5971 if (cm == XBoardGame) {
5972 /* Skip any header junk before position diagram and/or move 1 */
5974 yyboardindex = forwardMostMove;
5975 cm = (ChessMove) yylex();
5977 if (cm == (ChessMove) 0 ||
5978 cm == GNUChessGame || cm == XBoardGame) {
5979 /* Empty game; pretend end-of-file and handle later */
5984 if (cm == MoveNumberOne || cm == PositionDiagram ||
5985 cm == PGNTag || cm == Comment)
5988 } else if (cm == GNUChessGame) {
5989 if (gameInfo.event != NULL) {
5990 free(gameInfo.event);
5992 gameInfo.event = StrSave(yy_text);
5995 startedFromSetupPosition = FALSE;
5996 while (cm == PGNTag) {
5997 if (appData.debugMode)
5998 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
5999 err = ParsePGNTag(yy_text, &gameInfo);
6000 if (!err) numPGNTags++;
6002 if (gameInfo.fen != NULL) {
6003 Board initial_position;
6004 startedFromSetupPosition = TRUE;
6005 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
6007 DisplayError("Bad FEN position in file", 0);
6010 CopyBoard(boards[0], initial_position);
6011 if (blackPlaysFirst) {
6012 currentMove = forwardMostMove = backwardMostMove = 1;
6013 CopyBoard(boards[1], initial_position);
6014 strcpy(moveList[0], "");
6015 strcpy(parseList[0], "");
6016 timeRemaining[0][1] = whiteTimeRemaining;
6017 timeRemaining[1][1] = blackTimeRemaining;
6018 if (commentList[0] != NULL) {
6019 commentList[1] = commentList[0];
6020 commentList[0] = NULL;
6023 currentMove = forwardMostMove = backwardMostMove = 0;
6025 yyboardindex = forwardMostMove;
6027 gameInfo.fen = NULL;
6030 yyboardindex = forwardMostMove;
6031 cm = (ChessMove) yylex();
6033 /* Handle comments interspersed among the tags */
6034 while (cm == Comment) {
6036 if (appData.debugMode)
6037 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
6039 if (*p == '{' || *p == '[' || *p == '(') {
6040 p[strlen(p) - 1] = NULLCHAR;
6043 while (*p == '\n') p++;
6044 AppendComment(currentMove, p);
6045 yyboardindex = forwardMostMove;
6046 cm = (ChessMove) yylex();
6050 /* don't rely on existence of Event tag since if game was
6051 * pasted from clipboard the Event tag may not exist
6053 if (numPGNTags > 0){
6055 if (gameInfo.variant == VariantNormal) {
6056 gameInfo.variant = StringToVariant(gameInfo.event);
6059 tags = PGNTags(&gameInfo);
6060 TagsPopUp(tags, CmailMsg());
6064 /* Make something up, but don't display it now */
6069 if (cm == PositionDiagram) {
6072 Board initial_position;
6074 if (appData.debugMode)
6075 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
6077 if (!startedFromSetupPosition) {
6079 for (i = BOARD_SIZE - 1; i >= 0; i--)
6080 for (j = 0; j < BOARD_SIZE; p++)
6090 initial_position[i][j++] = CharToPiece(*p);
6093 while (*p == ' ' || *p == '\t' ||
6094 *p == '\n' || *p == '\r') p++;
6096 if (strncmp(p, "black", strlen("black"))==0)
6097 blackPlaysFirst = TRUE;
6099 blackPlaysFirst = FALSE;
6100 startedFromSetupPosition = TRUE;
6102 CopyBoard(boards[0], initial_position);
6103 if (blackPlaysFirst) {
6104 currentMove = forwardMostMove = backwardMostMove = 1;
6105 CopyBoard(boards[1], initial_position);
6106 strcpy(moveList[0], "");
6107 strcpy(parseList[0], "");
6108 timeRemaining[0][1] = whiteTimeRemaining;
6109 timeRemaining[1][1] = blackTimeRemaining;
6110 if (commentList[0] != NULL) {
6111 commentList[1] = commentList[0];
6112 commentList[0] = NULL;
6115 currentMove = forwardMostMove = backwardMostMove = 0;
6118 yyboardindex = forwardMostMove;
6119 cm = (ChessMove) yylex();
6122 if (first.pr == NoProc) {
6123 StartChessProgram(&first);
6125 InitChessProgram(&first);
6126 SendToProgram("force\n", &first);
6127 if (startedFromSetupPosition) {
6128 SendBoard(&first, forwardMostMove);
6129 DisplayBothClocks();
6132 while (cm == Comment) {
6134 if (appData.debugMode)
6135 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
6137 if (*p == '{' || *p == '[' || *p == '(') {
6138 p[strlen(p) - 1] = NULLCHAR;
6141 while (*p == '\n') p++;
6142 AppendComment(currentMove, p);
6143 yyboardindex = forwardMostMove;
6144 cm = (ChessMove) yylex();
6147 if (cm == (ChessMove) 0 || cm == WhiteWins || cm == BlackWins ||
6148 cm == GameIsDrawn || cm == GameUnfinished) {
6149 DisplayMessage("", "No moves in game");
6150 if (cmailMsgLoaded) {
6151 if (appData.debugMode)
6152 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
6156 DrawPosition(FALSE, boards[currentMove]);
6157 DisplayBothClocks();
6158 gameMode = EditGame;
6165 if (commentList[currentMove] != NULL) {
6166 if (!matchMode && (pausing || appData.timeDelay != 0)) {
6167 DisplayComment(currentMove - 1, commentList[currentMove]);
6170 if (!matchMode && appData.timeDelay != 0)
6171 DrawPosition(FALSE, boards[currentMove]);
6173 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
6174 programStats.ok_to_send = 1;
6177 /* if the first token after the PGN tags is a move
6178 * and not move number 1, retrieve it from the parser
6180 if (cm != MoveNumberOne)
6181 LoadGameOneMove(cm);
6183 /* load the remaining moves from the file */
6184 while (LoadGameOneMove((ChessMove)0)) {
6185 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
6186 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
6189 /* rewind to the start of the game */
6190 currentMove = backwardMostMove;
6192 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
6194 if (oldGameMode == AnalyzeFile ||
6195 oldGameMode == AnalyzeMode) {
6199 if (matchMode || appData.timeDelay == 0) {
6201 gameMode = EditGame;
6203 } else if (appData.timeDelay > 0) {
6207 if (appData.debugMode)
6208 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
6212 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
6214 ReloadPosition(offset)
6217 int positionNumber = lastLoadPositionNumber + offset;
6218 if (lastLoadPositionFP == NULL) {
6219 DisplayError("No position has been loaded yet", 0);
6222 if (positionNumber <= 0) {
6223 DisplayError("Can't back up any further", 0);
6226 return LoadPosition(lastLoadPositionFP, positionNumber,
6227 lastLoadPositionTitle);
6230 /* Load the nth position from the given file */
6232 LoadPositionFromFile(filename, n, title)
6240 if (strcmp(filename, "-") == 0) {
6241 return LoadPosition(stdin, n, "stdin");
6243 f = fopen(filename, "rb");
6245 sprintf(buf, "Can't open \"%s\"", filename);
6246 DisplayError(buf, errno);
6249 return LoadPosition(f, n, title);
6254 /* Load the nth position from the given open file, and close it */
6256 LoadPosition(f, positionNumber, title)
6261 char *p, line[MSG_SIZ];
6262 Board initial_position;
6263 int i, j, fenMode, pn;
6265 if (gameMode == Training )
6266 SetTrainingModeOff();
6268 if (gameMode != BeginningOfGame) {
6271 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
6272 fclose(lastLoadPositionFP);
6274 if (positionNumber == 0) positionNumber = 1;
6275 lastLoadPositionFP = f;
6276 lastLoadPositionNumber = positionNumber;
6277 strcpy(lastLoadPositionTitle, title);
6278 if (first.pr == NoProc) {
6279 StartChessProgram(&first);
6280 InitChessProgram(&first);
6282 pn = positionNumber;
6283 if (positionNumber < 0) {
6284 /* Negative position number means to seek to that byte offset */
6285 if (fseek(f, -positionNumber, 0) == -1) {
6286 DisplayError("Can't seek on position file", 0);
6291 if (fseek(f, 0, 0) == -1) {
6292 if (f == lastLoadPositionFP ?
6293 positionNumber == lastLoadPositionNumber + 1 :
6294 positionNumber == 1) {
6297 DisplayError("Can't seek on position file", 0);
6302 /* See if this file is FEN or old-style xboard */
6303 if (fgets(line, MSG_SIZ, f) == NULL) {
6304 DisplayError("Position not found in file", 0);
6312 case 'p': case 'n': case 'b': case 'r': case 'q': case 'k':
6313 case 'P': case 'N': case 'B': case 'R': case 'Q': case 'K':
6314 case '1': case '2': case '3': case '4': case '5': case '6':
6321 if (fenMode || line[0] == '#') pn--;
6323 /* skip postions before number pn */
6324 if (fgets(line, MSG_SIZ, f) == NULL) {
6325 DisplayError("Position not found in file", 0);
6328 if (fenMode || line[0] == '#') pn--;
6333 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
6334 DisplayError("Bad FEN position in file", 0);
6338 (void) fgets(line, MSG_SIZ, f);
6339 (void) fgets(line, MSG_SIZ, f);
6341 for (i = BOARD_SIZE - 1; i >= 0; i--) {
6342 (void) fgets(line, MSG_SIZ, f);
6343 for (p = line, j = 0; j < BOARD_SIZE; p++) {
6346 initial_position[i][j++] = CharToPiece(*p);
6350 blackPlaysFirst = FALSE;
6352 (void) fgets(line, MSG_SIZ, f);
6353 if (strncmp(line, "black", strlen("black"))==0)
6354 blackPlaysFirst = TRUE;
6357 startedFromSetupPosition = TRUE;
6359 SendToProgram("force\n", &first);
6360 CopyBoard(boards[0], initial_position);
6361 if (blackPlaysFirst) {
6362 currentMove = forwardMostMove = backwardMostMove = 1;
6363 strcpy(moveList[0], "");
6364 strcpy(parseList[0], "");
6365 CopyBoard(boards[1], initial_position);
6366 DisplayMessage("", "Black to play");
6368 currentMove = forwardMostMove = backwardMostMove = 0;
6369 DisplayMessage("", "White to play");
6371 SendBoard(&first, forwardMostMove);
6373 if (positionNumber > 1) {
6374 sprintf(line, "%s %d", title, positionNumber);
6377 DisplayTitle(title);
6379 gameMode = EditGame;
6382 timeRemaining[0][1] = whiteTimeRemaining;
6383 timeRemaining[1][1] = blackTimeRemaining;
6384 DrawPosition(FALSE, boards[currentMove]);
6391 CopyPlayerNameIntoFileName(dest, src)
6394 while (*src != NULLCHAR && *src != ',') {
6399 *(*dest)++ = *src++;
6404 char *DefaultFileName(ext)
6407 static char def[MSG_SIZ];
6410 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
6412 CopyPlayerNameIntoFileName(&p, gameInfo.white);
6414 CopyPlayerNameIntoFileName(&p, gameInfo.black);
6423 /* Save the current game to the given file */
6425 SaveGameToFile(filename, append)
6432 if (strcmp(filename, "-") == 0) {
6433 return SaveGame(stdout, 0, NULL);
6435 f = fopen(filename, append ? "a" : "w");
6437 sprintf(buf, "Can't open \"%s\"", filename);
6438 DisplayError(buf, errno);
6441 return SaveGame(f, 0, NULL);
6450 static char buf[MSG_SIZ];
6453 p = strchr(str, ' ');
6454 if (p == NULL) return str;
6455 strncpy(buf, str, p - str);
6456 buf[p - str] = NULLCHAR;
6460 #define PGN_MAX_LINE 75
6462 /* Save game in PGN style and close the file */
6467 int i, offset, linelen, newblock;
6471 int movelen, numlen, blank;
6473 tm = time((time_t *) NULL);
6475 PrintPGNTags(f, &gameInfo);
6477 if (backwardMostMove > 0 || startedFromSetupPosition) {
6478 char *fen = PositionToFEN(backwardMostMove);
6479 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
6480 fprintf(f, "\n{--------------\n");
6481 PrintPosition(f, backwardMostMove);
6482 fprintf(f, "--------------}\n");
6488 i = backwardMostMove;
6489 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
6493 while (i < forwardMostMove) {
6494 /* Print comments preceding this move */
6495 if (commentList[i] != NULL) {
6496 if (linelen > 0) fprintf(f, "\n");
6497 fprintf(f, "{\n%s}\n", commentList[i]);
6502 /* Format move number */
6504 sprintf(numtext, "%d.", (i - offset)/2 + 1);
6507 sprintf(numtext, "%d...", (i - offset)/2 + 1);
6509 numtext[0] = NULLCHAR;
6512 numlen = strlen(numtext);
6515 /* Print move number */
6516 blank = linelen > 0 && numlen > 0;
6517 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
6526 fprintf(f, numtext);
6530 movetext = SavePart(parseList[i]);
6531 movelen = strlen(movetext);
6534 blank = linelen > 0 && movelen > 0;
6535 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
6544 fprintf(f, movetext);
6550 /* Start a new line */
6551 if (linelen > 0) fprintf(f, "\n");
6553 /* Print comments after last move */
6554 if (commentList[i] != NULL) {
6555 fprintf(f, "{\n%s}\n", commentList[i]);
6559 if (gameInfo.resultDetails != NULL &&
6560 gameInfo.resultDetails[0] != NULLCHAR) {
6561 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
6562 PGNResult(gameInfo.result));
6564 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
6571 /* Save game in old style and close the file */
6579 tm = time((time_t *) NULL);
6581 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
6584 if (backwardMostMove > 0 || startedFromSetupPosition) {
6585 fprintf(f, "\n[--------------\n");
6586 PrintPosition(f, backwardMostMove);
6587 fprintf(f, "--------------]\n");
6592 i = backwardMostMove;
6593 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
6595 while (i < forwardMostMove) {
6596 if (commentList[i] != NULL) {
6597 fprintf(f, "[%s]\n", commentList[i]);
6601 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
6604 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
6606 if (commentList[i] != NULL) {
6610 if (i >= forwardMostMove) {
6614 fprintf(f, "%s\n", parseList[i]);
6619 if (commentList[i] != NULL) {
6620 fprintf(f, "[%s]\n", commentList[i]);
6623 /* This isn't really the old style, but it's close enough */
6624 if (gameInfo.resultDetails != NULL &&
6625 gameInfo.resultDetails[0] != NULLCHAR) {
6626 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
6627 gameInfo.resultDetails);
6629 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
6636 /* Save the current game to open file f and close the file */
6638 SaveGame(f, dummy, dummy2)
6643 if (gameMode == EditPosition) EditPositionDone();
6644 if (appData.oldSaveStyle)
6645 return SaveGameOldStyle(f);
6647 return SaveGamePGN(f);
6650 /* Save the current position to the given file */
6652 SavePositionToFile(filename)
6658 if (strcmp(filename, "-") == 0) {
6659 return SavePosition(stdout, 0, NULL);
6661 f = fopen(filename, "a");
6663 sprintf(buf, "Can't open \"%s\"", filename);
6664 DisplayError(buf, errno);
6667 SavePosition(f, 0, NULL);
6673 /* Save the current position to the given open file and close the file */
6675 SavePosition(f, dummy, dummy2)
6683 if (appData.oldSaveStyle) {
6684 tm = time((time_t *) NULL);
6686 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
6688 fprintf(f, "[--------------\n");
6689 PrintPosition(f, currentMove);
6690 fprintf(f, "--------------]\n");
6692 fen = PositionToFEN(currentMove);
6693 fprintf(f, "%s\n", fen);
6701 ReloadCmailMsgEvent(unregister)
6704 static char *inFilename = NULL;
6705 static char *outFilename;
6707 struct stat inbuf, outbuf;
6710 /* Any registered moves are unregistered if unregister is set, */
6711 /* i.e. invoked by the signal handler */
6713 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
6714 cmailMoveRegistered[i] = FALSE;
6715 if (cmailCommentList[i] != NULL) {
6716 free(cmailCommentList[i]);
6717 cmailCommentList[i] = NULL;
6720 nCmailMovesRegistered = 0;
6723 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
6724 cmailResult[i] = CMAIL_NOT_RESULT;
6728 if (inFilename == NULL) {
6729 /* Because the filenames are static they only get malloced once */
6730 /* and they never get freed */
6731 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
6732 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
6734 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
6735 sprintf(outFilename, "%s.out", appData.cmailGameName);
6738 status = stat(outFilename, &outbuf);
6740 cmailMailedMove = FALSE;
6742 status = stat(inFilename, &inbuf);
6743 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
6746 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
6747 counts the games, notes how each one terminated, etc.
6749 It would be nice to remove this kludge and instead gather all
6750 the information while building the game list. (And to keep it
6751 in the game list nodes instead of having a bunch of fixed-size
6752 parallel arrays.) Note this will require getting each game's
6753 termination from the PGN tags, as the game list builder does
6754 not process the game moves. --mann
6756 cmailMsgLoaded = TRUE;
6757 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
6759 /* Load first game in the file or popup game menu */
6760 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
6769 char string[MSG_SIZ];
6771 if ( cmailMailedMove
6772 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
6773 return TRUE; /* Allow free viewing */
6776 /* Unregister move to ensure that we don't leave RegisterMove */
6777 /* with the move registered when the conditions for registering no */
6779 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
6780 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
6781 nCmailMovesRegistered --;
6783 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
6785 free(cmailCommentList[lastLoadGameNumber - 1]);
6786 cmailCommentList[lastLoadGameNumber - 1] = NULL;
6790 if (cmailOldMove == -1) {
6791 DisplayError("You have edited the game history.\nUse Reload Same Game and make your move again.", 0);
6795 if (currentMove > cmailOldMove + 1) {
6796 DisplayError("You have entered too many moves.\nBack up to the correct position and try again.", 0);
6800 if (currentMove < cmailOldMove) {
6801 DisplayError("Displayed position is not current.\nStep forward to the correct position and try again.", 0);
6805 if (forwardMostMove > currentMove) {
6806 /* Silently truncate extra moves */
6810 if ( (currentMove == cmailOldMove + 1)
6811 || ( (currentMove == cmailOldMove)
6812 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
6813 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
6814 if (gameInfo.result != GameUnfinished) {
6815 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
6818 if (commentList[currentMove] != NULL) {
6819 cmailCommentList[lastLoadGameNumber - 1]
6820 = StrSave(commentList[currentMove]);
6822 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
6824 if (appData.debugMode)
6825 fprintf(debugFP, "Saving %s for game %d\n",
6826 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
6829 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
6831 f = fopen(string, "w");
6832 if (appData.oldSaveStyle) {
6833 SaveGameOldStyle(f); /* also closes the file */
6835 sprintf(string, "%s.pos.out", appData.cmailGameName);
6836 f = fopen(string, "w");
6837 SavePosition(f, 0, NULL); /* also closes the file */
6839 fprintf(f, "{--------------\n");
6840 PrintPosition(f, currentMove);
6841 fprintf(f, "--------------}\n\n");
6843 SaveGame(f, 0, NULL); /* also closes the file*/
6846 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
6847 nCmailMovesRegistered ++;
6848 } else if (nCmailGames == 1) {
6849 DisplayError("You have not made a move yet", 0);
6859 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
6860 FILE *commandOutput;
6861 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
6862 int nBytes = 0; /* Suppress warnings on uninitialized variables */
6868 if (! cmailMsgLoaded) {
6869 DisplayError("The cmail message is not loaded.\nUse Reload CMail Message and make your move again.", 0);
6873 if (nCmailGames == nCmailResults) {
6874 DisplayError("No unfinished games", 0);
6878 #if CMAIL_PROHIBIT_REMAIL
6879 if (cmailMailedMove) {
6880 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);
6881 DisplayError(msg, 0);
6886 if (! (cmailMailedMove || RegisterMove())) return;
6888 if ( cmailMailedMove
6889 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
6890 sprintf(string, partCommandString,
6891 appData.debugMode ? " -v" : "", appData.cmailGameName);
6892 commandOutput = popen(string, "r");
6894 if (commandOutput == NULL) {
6895 DisplayError("Failed to invoke cmail", 0);
6897 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
6898 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
6901 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
6902 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
6903 nBytes = MSG_SIZ - 1;
6905 (void) memcpy(msg, buffer, nBytes);
6907 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
6909 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
6910 cmailMailedMove = TRUE; /* Prevent >1 moves */
6913 for (i = 0; i < nCmailGames; i ++) {
6914 if (cmailResult[i] == CMAIL_NOT_RESULT) {
6919 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
6921 sprintf(buffer, "%s/%s.%s.archive",
6923 appData.cmailGameName,
6925 LoadGameFromFile(buffer, 1, buffer, FALSE);
6926 cmailMsgLoaded = FALSE;
6930 DisplayInformation(msg);
6931 pclose(commandOutput);
6934 if ((*cmailMsg) != '\0') {
6935 DisplayInformation(cmailMsg);
6945 int prependComma = 0;
6947 char string[MSG_SIZ]; /* Space for game-list */
6950 if (!cmailMsgLoaded) return "";
6952 if (cmailMailedMove) {
6953 sprintf(cmailMsg, "Waiting for reply from opponent\n");
6955 /* Create a list of games left */
6956 sprintf(string, "[");
6957 for (i = 0; i < nCmailGames; i ++) {
6958 if (! ( cmailMoveRegistered[i]
6959 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
6961 sprintf(number, ",%d", i + 1);
6963 sprintf(number, "%d", i + 1);
6967 strcat(string, number);
6970 strcat(string, "]");
6972 if (nCmailMovesRegistered + nCmailResults == 0) {
6973 switch (nCmailGames) {
6976 "Still need to make move for game\n");
6981 "Still need to make moves for both games\n");
6986 "Still need to make moves for all %d games\n",
6991 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
6994 "Still need to make a move for game %s\n",
6999 if (nCmailResults == nCmailGames) {
7000 sprintf(cmailMsg, "No unfinished games\n");
7002 sprintf(cmailMsg, "Ready to send mail\n");
7008 "Still need to make moves for games %s\n",
7020 if (gameMode == Training)
7021 SetTrainingModeOff();
7024 cmailMsgLoaded = FALSE;
7025 if (appData.icsActive) {
7026 SendToICS(ics_prefix);
7027 SendToICS("refresh\n");
7031 static int exiting = 0;
7039 /* Give up on clean exit */
7043 /* Keep trying for clean exit */
7047 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
7049 if (telnetISR != NULL) {
7050 RemoveInputSource(telnetISR);
7052 if (icsPR != NoProc) {
7053 DestroyChildProcess(icsPR, TRUE);
7055 /* Save game if resource set and not already saved by GameEnds() */
7056 if (gameInfo.resultDetails == NULL && forwardMostMove > 0) {
7057 if (*appData.saveGameFile != NULLCHAR) {
7058 SaveGameToFile(appData.saveGameFile, TRUE);
7059 } else if (appData.autoSaveGames) {
7062 if (*appData.savePositionFile != NULLCHAR) {
7063 SavePositionToFile(appData.savePositionFile);
7066 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
7068 /* Kill off chess programs */
7069 if (first.pr != NoProc) {
7071 SendToProgram("quit\n", &first);
7072 DestroyChildProcess(first.pr, first.useSigterm);
7074 if (second.pr != NoProc) {
7075 SendToProgram("quit\n", &second);
7076 DestroyChildProcess(second.pr, second.useSigterm);
7078 if (first.isr != NULL) {
7079 RemoveInputSource(first.isr);
7081 if (second.isr != NULL) {
7082 RemoveInputSource(second.isr);
7092 if (appData.debugMode)
7093 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
7097 if (gameMode == MachinePlaysWhite ||
7098 gameMode == MachinePlaysBlack) {
7101 DisplayBothClocks();
7103 if (gameMode == PlayFromGameFile) {
7104 if (appData.timeDelay >= 0)
7106 } else if (gameMode == IcsExamining && pauseExamInvalid) {
7108 SendToICS(ics_prefix);
7109 SendToICS("refresh\n");
7110 } else if (currentMove < forwardMostMove) {
7111 ForwardInner(forwardMostMove);
7113 pauseExamInvalid = FALSE;
7119 pauseExamForwardMostMove = forwardMostMove;
7120 pauseExamInvalid = FALSE;
7123 case IcsPlayingWhite:
7124 case IcsPlayingBlack:
7128 case PlayFromGameFile:
7129 (void) StopLoadGameTimer();
7133 case BeginningOfGame:
7134 if (appData.icsActive) return;
7135 /* else fall through */
7136 case MachinePlaysWhite:
7137 case MachinePlaysBlack:
7138 case TwoMachinesPlay:
7139 if (forwardMostMove == 0)
7140 return; /* don't pause if no one has moved */
7141 if ((gameMode == MachinePlaysWhite &&
7142 !WhiteOnMove(forwardMostMove)) ||
7143 (gameMode == MachinePlaysBlack &&
7144 WhiteOnMove(forwardMostMove))) {
7157 char title[MSG_SIZ];
7159 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
7160 strcpy(title, "Edit comment");
7162 sprintf(title, "Edit comment on %d.%s%s", (currentMove - 1) / 2 + 1,
7163 WhiteOnMove(currentMove - 1) ? " " : ".. ",
7164 parseList[currentMove - 1]);
7167 EditCommentPopUp(currentMove, title, commentList[currentMove]);
7174 char *tags = PGNTags(&gameInfo);
7175 EditTagsPopUp(tags);
7182 if (appData.noChessProgram || gameMode == AnalyzeMode)
7185 if (gameMode != AnalyzeFile) {
7187 if (gameMode != EditGame) return;
7188 ResurrectChessProgram();
7189 SendToProgram("analyze\n", &first);
7190 first.analyzing = TRUE;
7191 /*first.maybeThinking = TRUE;*/
7192 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
7193 AnalysisPopUp("Analysis",
7194 "Starting analysis mode...\nIf this message stays up, your chess program does not support analysis.");
7196 gameMode = AnalyzeMode;
7201 StartAnalysisClock();
7202 GetTimeMark(&lastNodeCountTime);
7209 if (appData.noChessProgram || gameMode == AnalyzeFile)
7212 if (gameMode != AnalyzeMode) {
7214 if (gameMode != EditGame) return;
7215 ResurrectChessProgram();
7216 SendToProgram("analyze\n", &first);
7217 first.analyzing = TRUE;
7218 /*first.maybeThinking = TRUE;*/
7219 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
7220 AnalysisPopUp("Analysis",
7221 "Starting analysis mode...\nIf this message stays up, your chess program does not support analysis.");
7223 gameMode = AnalyzeFile;
7228 StartAnalysisClock();
7229 GetTimeMark(&lastNodeCountTime);
7238 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
7242 if (gameMode == PlayFromGameFile ||
7243 gameMode == TwoMachinesPlay ||
7244 gameMode == Training ||
7245 gameMode == AnalyzeMode ||
7246 gameMode == EndOfGame)
7249 if (gameMode == EditPosition)
7252 if (!WhiteOnMove(currentMove)) {
7253 DisplayError("It is not White's turn", 0);
7257 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
7260 if (gameMode == EditGame || gameMode == AnalyzeMode ||
7261 gameMode == AnalyzeFile)
7264 ResurrectChessProgram(); /* in case it isn't running */
7265 gameMode = MachinePlaysWhite;
7269 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
7271 if (first.sendName) {
7272 sprintf(buf, "name %s\n", gameInfo.black);
7273 SendToProgram(buf, &first);
7275 if (first.sendTime) {
7276 if (first.useColors) {
7277 SendToProgram("black\n", &first); /*gnu kludge*/
7279 SendTimeRemaining(&first, TRUE);
7281 if (first.useColors) {
7282 SendToProgram("white\ngo\n", &first);
7284 SendToProgram("go\n", &first);
7286 SetMachineThinkingEnables();
7287 first.maybeThinking = TRUE;
7290 if (appData.autoFlipView && !flipView) {
7291 flipView = !flipView;
7292 DrawPosition(FALSE, NULL);
7301 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
7305 if (gameMode == PlayFromGameFile ||
7306 gameMode == TwoMachinesPlay ||
7307 gameMode == Training ||
7308 gameMode == AnalyzeMode ||
7309 gameMode == EndOfGame)
7312 if (gameMode == EditPosition)
7315 if (WhiteOnMove(currentMove)) {
7316 DisplayError("It is not Black's turn", 0);
7320 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
7323 if (gameMode == EditGame || gameMode == AnalyzeMode ||
7324 gameMode == AnalyzeFile)
7327 ResurrectChessProgram(); /* in case it isn't running */
7328 gameMode = MachinePlaysBlack;
7332 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
7334 if (first.sendName) {
7335 sprintf(buf, "name %s\n", gameInfo.white);
7336 SendToProgram(buf, &first);
7338 if (first.sendTime) {
7339 if (first.useColors) {
7340 SendToProgram("white\n", &first); /*gnu kludge*/
7342 SendTimeRemaining(&first, FALSE);
7344 if (first.useColors) {
7345 SendToProgram("black\ngo\n", &first);
7347 SendToProgram("go\n", &first);
7349 SetMachineThinkingEnables();
7350 first.maybeThinking = TRUE;
7353 if (appData.autoFlipView && flipView) {
7354 flipView = !flipView;
7355 DrawPosition(FALSE, NULL);
7361 DisplayTwoMachinesTitle()
7364 if (appData.matchGames > 0) {
7365 if (first.twoMachinesColor[0] == 'w') {
7366 sprintf(buf, "%s vs. %s (%d-%d-%d)",
7367 gameInfo.white, gameInfo.black,
7368 first.matchWins, second.matchWins,
7369 matchGame - 1 - (first.matchWins + second.matchWins));
7371 sprintf(buf, "%s vs. %s (%d-%d-%d)",
7372 gameInfo.white, gameInfo.black,
7373 second.matchWins, first.matchWins,
7374 matchGame - 1 - (first.matchWins + second.matchWins));
7377 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
7383 TwoMachinesEvent P((void))
7387 ChessProgramState *onmove;
7389 if (appData.noChessProgram) return;
7392 case TwoMachinesPlay:
7394 case MachinePlaysWhite:
7395 case MachinePlaysBlack:
7396 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
7397 DisplayError("Wait until your turn,\nor select Move Now", 0);
7401 case BeginningOfGame:
7402 case PlayFromGameFile:
7405 if (gameMode != EditGame) return;
7419 forwardMostMove = currentMove;
7420 ResurrectChessProgram(); /* in case first program isn't running */
7422 if (second.pr == NULL) {
7423 StartChessProgram(&second);
7424 if (second.protocolVersion == 1) {
7425 TwoMachinesEventIfReady();
7427 /* kludge: allow timeout for initial "feature" command */
7429 DisplayMessage("", "Starting second chess program");
7430 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
7434 DisplayMessage("", "");
7435 InitChessProgram(&second);
7436 SendToProgram("force\n", &second);
7437 if (startedFromSetupPosition) {
7438 SendBoard(&second, backwardMostMove);
7440 for (i = backwardMostMove; i < forwardMostMove; i++) {
7441 SendMoveToProgram(i, &second);
7444 gameMode = TwoMachinesPlay;
7448 DisplayTwoMachinesTitle();
7450 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
7456 SendToProgram(first.computerString, &first);
7457 if (first.sendName) {
7458 sprintf(buf, "name %s\n", second.tidy);
7459 SendToProgram(buf, &first);
7461 SendToProgram(second.computerString, &second);
7462 if (second.sendName) {
7463 sprintf(buf, "name %s\n", first.tidy);
7464 SendToProgram(buf, &second);
7467 if (!first.sendTime || !second.sendTime) {
7469 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7470 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7472 if (onmove->sendTime) {
7473 if (onmove->useColors) {
7474 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
7476 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
7478 if (onmove->useColors) {
7479 SendToProgram(onmove->twoMachinesColor, onmove);
7481 SendToProgram("go\n", onmove);
7482 onmove->maybeThinking = TRUE;
7483 SetMachineThinkingEnables();
7491 if (gameMode == Training) {
7492 SetTrainingModeOff();
7493 gameMode = PlayFromGameFile;
7494 DisplayMessage("", "Training mode off");
7496 gameMode = Training;
7497 animateTraining = appData.animate;
7499 /* make sure we are not already at the end of the game */
7500 if (currentMove < forwardMostMove) {
7501 SetTrainingModeOn();
7502 DisplayMessage("", "Training mode on");
7504 gameMode = PlayFromGameFile;
7505 DisplayError("Already at end of game", 0);
7514 if (!appData.icsActive) return;
7516 case IcsPlayingWhite:
7517 case IcsPlayingBlack:
7520 case BeginningOfGame:
7554 SetTrainingModeOff();
7556 case MachinePlaysWhite:
7557 case MachinePlaysBlack:
7558 case BeginningOfGame:
7559 SendToProgram("force\n", &first);
7560 SetUserThinkingEnables();
7562 case PlayFromGameFile:
7563 (void) StopLoadGameTimer();
7564 if (gameFileFP != NULL) {
7574 SendToProgram("force\n", &first);
7576 case TwoMachinesPlay:
7577 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
7578 ResurrectChessProgram();
7579 SetUserThinkingEnables();
7582 ResurrectChessProgram();
7584 case IcsPlayingBlack:
7585 case IcsPlayingWhite:
7586 DisplayError("Warning: You are still playing a game", 0);
7589 DisplayError("Warning: You are still observing a game", 0);
7592 DisplayError("Warning: You are still examining a game", 0);
7603 first.offeredDraw = second.offeredDraw = 0;
7605 if (gameMode == PlayFromGameFile) {
7606 whiteTimeRemaining = timeRemaining[0][currentMove];
7607 blackTimeRemaining = timeRemaining[1][currentMove];
7611 if (gameMode == MachinePlaysWhite ||
7612 gameMode == MachinePlaysBlack ||
7613 gameMode == TwoMachinesPlay ||
7614 gameMode == EndOfGame) {
7615 i = forwardMostMove;
7616 while (i > currentMove) {
7617 SendToProgram("undo\n", &first);
7620 whiteTimeRemaining = timeRemaining[0][currentMove];
7621 blackTimeRemaining = timeRemaining[1][currentMove];
7622 DisplayBothClocks();
7623 if (whiteFlag || blackFlag) {
7624 whiteFlag = blackFlag = 0;
7629 gameMode = EditGame;
7638 if (gameMode == EditPosition) {
7644 if (gameMode != EditGame) return;
7646 gameMode = EditPosition;
7649 if (currentMove > 0)
7650 CopyBoard(boards[0], boards[currentMove]);
7652 blackPlaysFirst = !WhiteOnMove(currentMove);
7654 currentMove = forwardMostMove = backwardMostMove = 0;
7655 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
7662 if (first.analysisSupport && first.analyzing) {
7663 SendToProgram("exit\n", &first);
7664 first.analyzing = FALSE;
7667 thinkOutput[0] = NULLCHAR;
7673 startedFromSetupPosition = TRUE;
7674 InitChessProgram(&first);
7675 SendToProgram("force\n", &first);
7676 if (blackPlaysFirst) {
7677 strcpy(moveList[0], "");
7678 strcpy(parseList[0], "");
7679 currentMove = forwardMostMove = backwardMostMove = 1;
7680 CopyBoard(boards[1], boards[0]);
7682 currentMove = forwardMostMove = backwardMostMove = 0;
7684 SendBoard(&first, forwardMostMove);
7686 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7687 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7688 gameMode = EditGame;
7690 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
7693 /* Pause for `ms' milliseconds */
7694 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
7704 } while (SubtractTimeMarks(&m2, &m1) < ms);
7707 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
7709 SendMultiLineToICS(buf)
7712 char temp[MSG_SIZ+1], *p;
7719 strncpy(temp, buf, len);
7724 if (*p == '\n' || *p == '\r')
7731 SendToPlayer(temp, strlen(temp));
7735 SetWhiteToPlayEvent()
7737 if (gameMode == EditPosition) {
7738 blackPlaysFirst = FALSE;
7739 DisplayBothClocks(); /* works because currentMove is 0 */
7740 } else if (gameMode == IcsExamining) {
7741 SendToICS(ics_prefix);
7742 SendToICS("tomove white\n");
7747 SetBlackToPlayEvent()
7749 if (gameMode == EditPosition) {
7750 blackPlaysFirst = TRUE;
7751 currentMove = 1; /* kludge */
7752 DisplayBothClocks();
7754 } else if (gameMode == IcsExamining) {
7755 SendToICS(ics_prefix);
7756 SendToICS("tomove black\n");
7761 EditPositionMenuEvent(selection, x, y)
7762 ChessSquare selection;
7767 if (gameMode != EditPosition && gameMode != IcsExamining) return;
7769 switch (selection) {
7771 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
7772 SendToICS(ics_prefix);
7773 SendToICS("bsetup clear\n");
7774 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
7775 SendToICS(ics_prefix);
7776 SendToICS("clearboard\n");
7778 for (x = 0; x < BOARD_SIZE; x++) {
7779 for (y = 0; y < BOARD_SIZE; y++) {
7780 if (gameMode == IcsExamining) {
7781 if (boards[currentMove][y][x] != EmptySquare) {
7782 sprintf(buf, "%sx@%c%c\n", ics_prefix,
7787 boards[0][y][x] = EmptySquare;
7792 if (gameMode == EditPosition) {
7793 DrawPosition(FALSE, boards[0]);
7798 SetWhiteToPlayEvent();
7802 SetBlackToPlayEvent();
7806 if (gameMode == IcsExamining) {
7807 sprintf(buf, "%sx@%c%c\n", ics_prefix, 'a' + x, '1' + y);
7810 boards[0][y][x] = EmptySquare;
7811 DrawPosition(FALSE, boards[0]);
7816 if (gameMode == IcsExamining) {
7817 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
7818 PieceToChar(selection), 'a' + x, '1' + y);
7821 boards[0][y][x] = selection;
7822 DrawPosition(FALSE, boards[0]);
7830 DropMenuEvent(selection, x, y)
7831 ChessSquare selection;
7837 case IcsPlayingWhite:
7838 case MachinePlaysBlack:
7839 if (!WhiteOnMove(currentMove)) {
7840 DisplayMoveError("It is Black's turn");
7843 moveType = WhiteDrop;
7845 case IcsPlayingBlack:
7846 case MachinePlaysWhite:
7847 if (WhiteOnMove(currentMove)) {
7848 DisplayMoveError("It is White's turn");
7851 moveType = BlackDrop;
7854 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7860 if (moveType == BlackDrop && selection < BlackPawn) {
7861 selection = (ChessSquare) ((int) selection
7862 + (int) BlackPawn - (int) WhitePawn);
7864 if (boards[currentMove][y][x] != EmptySquare) {
7865 DisplayMoveError("That square is occupied");
7869 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
7875 /* Accept a pending offer of any kind from opponent */
7877 if (appData.icsActive) {
7878 SendToICS(ics_prefix);
7879 SendToICS("accept\n");
7880 } else if (cmailMsgLoaded) {
7881 if (currentMove == cmailOldMove &&
7882 commentList[cmailOldMove] != NULL &&
7883 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
7884 "Black offers a draw" : "White offers a draw")) {
7886 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
7887 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
7889 DisplayError("There is no pending offer on this move", 0);
7890 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7893 /* Not used for offers from chess program */
7900 /* Decline a pending offer of any kind from opponent */
7902 if (appData.icsActive) {
7903 SendToICS(ics_prefix);
7904 SendToICS("decline\n");
7905 } else if (cmailMsgLoaded) {
7906 if (currentMove == cmailOldMove &&
7907 commentList[cmailOldMove] != NULL &&
7908 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
7909 "Black offers a draw" : "White offers a draw")) {
7911 AppendComment(cmailOldMove, "Draw declined");
7912 DisplayComment(cmailOldMove - 1, "Draw declined");
7915 DisplayError("There is no pending offer on this move", 0);
7918 /* Not used for offers from chess program */
7925 /* Issue ICS rematch command */
7926 if (appData.icsActive) {
7927 SendToICS(ics_prefix);
7928 SendToICS("rematch\n");
7935 /* Call your opponent's flag (claim a win on time) */
7936 if (appData.icsActive) {
7937 SendToICS(ics_prefix);
7938 SendToICS("flag\n");
7943 case MachinePlaysWhite:
7946 GameEnds(GameIsDrawn, "Both players ran out of time",
7949 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
7951 DisplayError("Your opponent is not out of time", 0);
7954 case MachinePlaysBlack:
7957 GameEnds(GameIsDrawn, "Both players ran out of time",
7960 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
7962 DisplayError("Your opponent is not out of time", 0);
7972 /* Offer draw or accept pending draw offer from opponent */
7974 if (appData.icsActive) {
7975 /* Note: tournament rules require draw offers to be
7976 made after you make your move but before you punch
7977 your clock. Currently ICS doesn't let you do that;
7978 instead, you immediately punch your clock after making
7979 a move, but you can offer a draw at any time. */
7981 SendToICS(ics_prefix);
7982 SendToICS("draw\n");
7983 } else if (cmailMsgLoaded) {
7984 if (currentMove == cmailOldMove &&
7985 commentList[cmailOldMove] != NULL &&
7986 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
7987 "Black offers a draw" : "White offers a draw")) {
7988 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
7989 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
7990 } else if (currentMove == cmailOldMove + 1) {
7991 char *offer = WhiteOnMove(cmailOldMove) ?
7992 "White offers a draw" : "Black offers a draw";
7993 AppendComment(currentMove, offer);
7994 DisplayComment(currentMove - 1, offer);
7995 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
7997 DisplayError("You must make your move before offering a draw", 0);
7998 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8000 } else if (first.offeredDraw) {
8001 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8003 if (first.sendDrawOffers) {
8004 SendToProgram("draw\n", &first);
8005 userOfferedDraw = TRUE;
8013 /* Offer Adjourn or accept pending Adjourn offer from opponent */
8015 if (appData.icsActive) {
8016 SendToICS(ics_prefix);
8017 SendToICS("adjourn\n");
8019 /* Currently GNU Chess doesn't offer or accept Adjourns */
8027 /* Offer Abort or accept pending Abort offer from opponent */
8029 if (appData.icsActive) {
8030 SendToICS(ics_prefix);
8031 SendToICS("abort\n");
8033 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
8040 /* Resign. You can do this even if it's not your turn. */
8042 if (appData.icsActive) {
8043 SendToICS(ics_prefix);
8044 SendToICS("resign\n");
8047 case MachinePlaysWhite:
8048 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8050 case MachinePlaysBlack:
8051 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8054 if (cmailMsgLoaded) {
8056 if (WhiteOnMove(cmailOldMove)) {
8057 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8059 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8061 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
8072 StopObservingEvent()
8074 /* Stop observing current games */
8075 SendToICS(ics_prefix);
8076 SendToICS("unobserve\n");
8080 StopExaminingEvent()
8082 /* Stop observing current game */
8083 SendToICS(ics_prefix);
8084 SendToICS("unexamine\n");
8088 ForwardInner(target)
8093 if (appData.debugMode)
8094 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
8095 target, currentMove, forwardMostMove);
8097 if (gameMode == EditPosition)
8100 if (gameMode == PlayFromGameFile && !pausing)
8103 if (gameMode == IcsExamining && pausing)
8104 limit = pauseExamForwardMostMove;
8106 limit = forwardMostMove;
8108 if (target > limit) target = limit;
8110 if (target > 0 && moveList[target - 1][0]) {
8111 int fromX, fromY, toX, toY;
8112 toX = moveList[target - 1][2] - 'a';
8113 toY = moveList[target - 1][3] - '1';
8114 if (moveList[target - 1][1] == '@') {
8115 if (appData.highlightLastMove) {
8116 SetHighlights(-1, -1, toX, toY);
8119 fromX = moveList[target - 1][0] - 'a';
8120 fromY = moveList[target - 1][1] - '1';
8121 if (target == currentMove + 1) {
8122 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8124 if (appData.highlightLastMove) {
8125 SetHighlights(fromX, fromY, toX, toY);
8129 if (gameMode == EditGame || gameMode == AnalyzeMode ||
8130 gameMode == Training || gameMode == PlayFromGameFile ||
8131 gameMode == AnalyzeFile) {
8132 while (currentMove < target) {
8133 SendMoveToProgram(currentMove++, &first);
8136 currentMove = target;
8139 if (gameMode == EditGame || gameMode == EndOfGame) {
8140 whiteTimeRemaining = timeRemaining[0][currentMove];
8141 blackTimeRemaining = timeRemaining[1][currentMove];
8143 DisplayBothClocks();
8144 DisplayMove(currentMove - 1);
8145 DrawPosition(FALSE, boards[currentMove]);
8146 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8147 if (commentList[currentMove] && !matchMode && gameMode != Training) {
8148 DisplayComment(currentMove - 1, commentList[currentMove]);
8156 if (gameMode == IcsExamining && !pausing) {
8157 SendToICS(ics_prefix);
8158 SendToICS("forward\n");
8160 ForwardInner(currentMove + 1);
8167 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
8168 /* to optimze, we temporarily turn off analysis mode while we feed
8169 * the remaining moves to the engine. Otherwise we get analysis output
8172 if (first.analysisSupport) {
8173 SendToProgram("exit\nforce\n", &first);
8174 first.analyzing = FALSE;
8178 if (gameMode == IcsExamining && !pausing) {
8179 SendToICS(ics_prefix);
8180 SendToICS("forward 999999\n");
8182 ForwardInner(forwardMostMove);
8185 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
8186 /* we have fed all the moves, so reactivate analysis mode */
8187 SendToProgram("analyze\n", &first);
8188 first.analyzing = TRUE;
8189 /*first.maybeThinking = TRUE;*/
8190 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
8195 BackwardInner(target)
8198 if (appData.debugMode)
8199 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
8200 target, currentMove, forwardMostMove);
8202 if (gameMode == EditPosition) return;
8203 if (currentMove <= backwardMostMove) {
8205 DrawPosition(FALSE, boards[currentMove]);
8208 if (gameMode == PlayFromGameFile && !pausing)
8211 if (moveList[target][0]) {
8212 int fromX, fromY, toX, toY;
8213 toX = moveList[target][2] - 'a';
8214 toY = moveList[target][3] - '1';
8215 if (moveList[target][1] == '@') {
8216 if (appData.highlightLastMove) {
8217 SetHighlights(-1, -1, toX, toY);
8220 fromX = moveList[target][0] - 'a';
8221 fromY = moveList[target][1] - '1';
8222 if (target == currentMove - 1) {
8223 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
8225 if (appData.highlightLastMove) {
8226 SetHighlights(fromX, fromY, toX, toY);
8230 if (gameMode == EditGame || gameMode==AnalyzeMode ||
8231 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8232 while (currentMove > target) {
8233 SendToProgram("undo\n", &first);
8237 currentMove = target;
8240 if (gameMode == EditGame || gameMode == EndOfGame) {
8241 whiteTimeRemaining = timeRemaining[0][currentMove];
8242 blackTimeRemaining = timeRemaining[1][currentMove];
8244 DisplayBothClocks();
8245 DisplayMove(currentMove - 1);
8246 DrawPosition(FALSE, boards[currentMove]);
8247 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8248 if (commentList[currentMove] != NULL) {
8249 DisplayComment(currentMove - 1, commentList[currentMove]);
8256 if (gameMode == IcsExamining && !pausing) {
8257 SendToICS(ics_prefix);
8258 SendToICS("backward\n");
8260 BackwardInner(currentMove - 1);
8267 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
8268 /* to optimze, we temporarily turn off analysis mode while we undo
8269 * all the moves. Otherwise we get analysis output after each undo.
8271 if (first.analysisSupport) {
8272 SendToProgram("exit\nforce\n", &first);
8273 first.analyzing = FALSE;
8277 if (gameMode == IcsExamining && !pausing) {
8278 SendToICS(ics_prefix);
8279 SendToICS("backward 999999\n");
8281 BackwardInner(backwardMostMove);
8284 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
8285 /* we have fed all the moves, so reactivate analysis mode */
8286 SendToProgram("analyze\n", &first);
8287 first.analyzing = TRUE;
8288 /*first.maybeThinking = TRUE;*/
8289 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
8296 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
8297 if (to >= forwardMostMove) to = forwardMostMove;
8298 if (to <= backwardMostMove) to = backwardMostMove;
8299 if (to < currentMove) {
8309 if (gameMode != IcsExamining) {
8310 DisplayError("You are not examining a game", 0);
8314 DisplayError("You can't revert while pausing", 0);
8317 SendToICS(ics_prefix);
8318 SendToICS("revert\n");
8325 case MachinePlaysWhite:
8326 case MachinePlaysBlack:
8327 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
8328 DisplayError("Wait until your turn,\nor select Move Now", 0);
8331 if (forwardMostMove < 2) return;
8332 currentMove = forwardMostMove = forwardMostMove - 2;
8333 whiteTimeRemaining = timeRemaining[0][currentMove];
8334 blackTimeRemaining = timeRemaining[1][currentMove];
8335 DisplayBothClocks();
8336 DisplayMove(currentMove - 1);
8337 ClearHighlights();/*!! could figure this out*/
8338 DrawPosition(FALSE, boards[currentMove]);
8339 SendToProgram("remove\n", &first);
8340 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
8343 case BeginningOfGame:
8347 case IcsPlayingWhite:
8348 case IcsPlayingBlack:
8349 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
8350 SendToICS(ics_prefix);
8351 SendToICS("takeback 2\n");
8353 SendToICS(ics_prefix);
8354 SendToICS("takeback 1\n");
8363 ChessProgramState *cps;
8366 case MachinePlaysWhite:
8367 if (!WhiteOnMove(forwardMostMove)) {
8368 DisplayError("It is your turn", 0);
8373 case MachinePlaysBlack:
8374 if (WhiteOnMove(forwardMostMove)) {
8375 DisplayError("It is your turn", 0);
8380 case TwoMachinesPlay:
8381 if (WhiteOnMove(forwardMostMove) ==
8382 (first.twoMachinesColor[0] == 'w')) {
8388 case BeginningOfGame:
8392 SendToProgram("?\n", cps);
8399 if (gameMode != EditGame) return;
8406 if (forwardMostMove > currentMove) {
8407 if (gameInfo.resultDetails != NULL) {
8408 free(gameInfo.resultDetails);
8409 gameInfo.resultDetails = NULL;
8410 gameInfo.result = GameUnfinished;
8412 forwardMostMove = currentMove;
8413 HistorySet(parseList, backwardMostMove, forwardMostMove,
8421 if (appData.noChessProgram) return;
8423 case MachinePlaysWhite:
8424 if (WhiteOnMove(forwardMostMove)) {
8425 DisplayError("Wait until your turn", 0);
8429 case BeginningOfGame:
8430 case MachinePlaysBlack:
8431 if (!WhiteOnMove(forwardMostMove)) {
8432 DisplayError("Wait until your turn", 0);
8437 DisplayError("No hint available", 0);
8440 SendToProgram("hint\n", &first);
8441 hintRequested = TRUE;
8447 if (appData.noChessProgram) return;
8449 case MachinePlaysWhite:
8450 if (WhiteOnMove(forwardMostMove)) {
8451 DisplayError("Wait until your turn", 0);
8455 case BeginningOfGame:
8456 case MachinePlaysBlack:
8457 if (!WhiteOnMove(forwardMostMove)) {
8458 DisplayError("Wait until your turn", 0);
8465 case TwoMachinesPlay:
8470 SendToProgram("bk\n", &first);
8471 bookOutput[0] = NULLCHAR;
8472 bookRequested = TRUE;
8478 char *tags = PGNTags(&gameInfo);
8479 TagsPopUp(tags, CmailMsg());
8483 /* end button procedures */
8486 PrintPosition(fp, move)
8492 for (i = BOARD_SIZE - 1; i >= 0; i--) {
8493 for (j = 0; j < BOARD_SIZE; j++) {
8494 char c = PieceToChar(boards[move][i][j]);
8495 fputc(c == 'x' ? '.' : c, fp);
8496 fputc(j == BOARD_SIZE - 1 ? '\n' : ' ', fp);
8499 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
8500 fprintf(fp, "white to play\n");
8502 fprintf(fp, "black to play\n");
8509 if (gameInfo.white != NULL) {
8510 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
8516 /* Find last component of program's own name, using some heuristics */
8518 TidyProgramName(prog, host, buf)
8519 char *prog, *host, buf[MSG_SIZ];
8522 int local = (strcmp(host, "localhost") == 0);
8523 while (!local && (p = strchr(prog, ';')) != NULL) {
8525 while (*p == ' ') p++;
8528 if (*prog == '"' || *prog == '\'') {
8529 q = strchr(prog + 1, *prog);
8531 q = strchr(prog, ' ');
8533 if (q == NULL) q = prog + strlen(prog);
8535 while (p >= prog && *p != '/' && *p != '\\') p--;
8537 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
8538 memcpy(buf, p, q - p);
8539 buf[q - p] = NULLCHAR;
8547 TimeControlTagValue()
8550 if (!appData.clockMode) {
8552 } else if (movesPerSession > 0) {
8553 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
8554 } else if (timeIncrement == 0) {
8555 sprintf(buf, "%ld", timeControl/1000);
8557 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
8559 return StrSave(buf);
8565 /* This routine is used only for certain modes */
8566 VariantClass v = gameInfo.variant;
8567 ClearGameInfo(&gameInfo);
8568 gameInfo.variant = v;
8571 case MachinePlaysWhite:
8572 gameInfo.event = StrSave("Computer chess game");
8573 gameInfo.site = StrSave(HostName());
8574 gameInfo.date = PGNDate();
8575 gameInfo.round = StrSave("-");
8576 gameInfo.white = StrSave(first.tidy);
8577 gameInfo.black = StrSave(UserName());
8578 gameInfo.timeControl = TimeControlTagValue();
8581 case MachinePlaysBlack:
8582 gameInfo.event = StrSave("Computer chess game");
8583 gameInfo.site = StrSave(HostName());
8584 gameInfo.date = PGNDate();
8585 gameInfo.round = StrSave("-");
8586 gameInfo.white = StrSave(UserName());
8587 gameInfo.black = StrSave(first.tidy);
8588 gameInfo.timeControl = TimeControlTagValue();
8591 case TwoMachinesPlay:
8592 gameInfo.event = StrSave("Computer chess game");
8593 gameInfo.site = StrSave(HostName());
8594 gameInfo.date = PGNDate();
8595 if (matchGame > 0) {
8597 sprintf(buf, "%d", matchGame);
8598 gameInfo.round = StrSave(buf);
8600 gameInfo.round = StrSave("-");
8602 if (first.twoMachinesColor[0] == 'w') {
8603 gameInfo.white = StrSave(first.tidy);
8604 gameInfo.black = StrSave(second.tidy);
8606 gameInfo.white = StrSave(second.tidy);
8607 gameInfo.black = StrSave(first.tidy);
8609 gameInfo.timeControl = TimeControlTagValue();
8613 gameInfo.event = StrSave("Edited game");
8614 gameInfo.site = StrSave(HostName());
8615 gameInfo.date = PGNDate();
8616 gameInfo.round = StrSave("-");
8617 gameInfo.white = StrSave("-");
8618 gameInfo.black = StrSave("-");
8622 gameInfo.event = StrSave("Edited position");
8623 gameInfo.site = StrSave(HostName());
8624 gameInfo.date = PGNDate();
8625 gameInfo.round = StrSave("-");
8626 gameInfo.white = StrSave("-");
8627 gameInfo.black = StrSave("-");
8630 case IcsPlayingWhite:
8631 case IcsPlayingBlack:
8636 case PlayFromGameFile:
8637 gameInfo.event = StrSave("Game from non-PGN file");
8638 gameInfo.site = StrSave(HostName());
8639 gameInfo.date = PGNDate();
8640 gameInfo.round = StrSave("-");
8641 gameInfo.white = StrSave("?");
8642 gameInfo.black = StrSave("?");
8651 ReplaceComment(index, text)
8657 while (*text == '\n') text++;
8659 while (len > 0 && text[len - 1] == '\n') len--;
8661 if (commentList[index] != NULL)
8662 free(commentList[index]);
8665 commentList[index] = NULL;
8668 commentList[index] = (char *) malloc(len + 2);
8669 strncpy(commentList[index], text, len);
8670 commentList[index][len] = '\n';
8671 commentList[index][len + 1] = NULLCHAR;
8675 AppendComment(index, text)
8682 while (*text == '\n') text++;
8684 while (len > 0 && text[len - 1] == '\n') len--;
8686 if (len == 0) return;
8688 if (commentList[index] != NULL) {
8689 old = commentList[index];
8690 oldlen = strlen(old);
8691 commentList[index] = (char *) malloc(oldlen + len + 2);
8692 strcpy(commentList[index], old);
8694 strncpy(&commentList[index][oldlen], text, len);
8695 commentList[index][oldlen + len] = '\n';
8696 commentList[index][oldlen + len + 1] = NULLCHAR;
8698 commentList[index] = (char *) malloc(len + 2);
8699 strncpy(commentList[index], text, len);
8700 commentList[index][len] = '\n';
8701 commentList[index][len + 1] = NULLCHAR;
8706 SendToProgram(message, cps)
8708 ChessProgramState *cps;
8710 int count, outCount, error;
8713 if (cps->pr == NULL) return;
8716 if (appData.debugMode) {
8719 fprintf(debugFP, "%ld >%-6s: %s",
8720 SubtractTimeMarks(&now, &programStartTime),
8721 cps->which, message);
8724 count = strlen(message);
8725 outCount = OutputToProcess(cps->pr, message, count, &error);
8726 if (outCount < count && !exiting) {
8727 sprintf(buf, "Error writing to %s chess program", cps->which);
8728 DisplayFatalError(buf, error, 1);
8733 ReceiveFromProgram(isr, closure, message, count, error)
8742 ChessProgramState *cps = (ChessProgramState *)closure;
8744 if (isr != cps->isr) return; /* Killed intentionally */
8748 "Error: %s chess program (%s) exited unexpectedly",
8749 cps->which, cps->program);
8750 RemoveInputSource(cps->isr);
8751 DisplayFatalError(buf, 0, 1);
8754 "Error reading from %s chess program (%s)",
8755 cps->which, cps->program);
8756 RemoveInputSource(cps->isr);
8757 DisplayFatalError(buf, error, 1);
8759 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8763 if ((end_str = strchr(message, '\r')) != NULL)
8764 *end_str = NULLCHAR;
8765 if ((end_str = strchr(message, '\n')) != NULL)
8766 *end_str = NULLCHAR;
8768 if (appData.debugMode) {
8771 fprintf(debugFP, "%ld <%-6s: %s\n",
8772 SubtractTimeMarks(&now, &programStartTime),
8773 cps->which, message);
8775 HandleMachineMove(message, cps);
8780 SendTimeControl(cps, mps, tc, inc, sd, st)
8781 ChessProgramState *cps;
8782 int mps, inc, sd, st;
8786 int seconds = (tc / 1000) % 60;
8789 /* Set exact time per move, normally using st command */
8790 if (cps->stKludge) {
8791 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
8794 sprintf(buf, "level 1 %d\n", st/60);
8796 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
8799 sprintf(buf, "st %d\n", st);
8802 /* Set conventional or incremental time control, using level command */
8804 /* Note old gnuchess bug -- minutes:seconds used to not work.
8805 Fixed in later versions, but still avoid :seconds
8806 when seconds is 0. */
8807 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
8809 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
8813 SendToProgram(buf, cps);
8815 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
8816 /* Orthogonally, limit search to given depth */
8818 if (cps->sdKludge) {
8819 sprintf(buf, "depth\n%d\n", sd);
8821 sprintf(buf, "sd %d\n", sd);
8823 SendToProgram(buf, cps);
8828 SendTimeRemaining(cps, machineWhite)
8829 ChessProgramState *cps;
8830 int /*boolean*/ machineWhite;
8832 char message[MSG_SIZ];
8835 /* Note: this routine must be called when the clocks are stopped
8836 or when they have *just* been set or switched; otherwise
8837 it will be off by the time since the current tick started.
8840 time = whiteTimeRemaining / 10;
8841 otime = blackTimeRemaining / 10;
8843 time = blackTimeRemaining / 10;
8844 otime = whiteTimeRemaining / 10;
8846 if (time <= 0) time = 1;
8847 if (otime <= 0) otime = 1;
8849 sprintf(message, "time %ld\notim %ld\n", time, otime);
8850 SendToProgram(message, cps);
8854 BoolFeature(p, name, loc, cps)
8858 ChessProgramState *cps;
8861 int len = strlen(name);
8863 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
8865 sscanf(*p, "%d", &val);
8867 while (**p && **p != ' ') (*p)++;
8868 sprintf(buf, "accepted %s\n", name);
8869 SendToProgram(buf, cps);
8876 IntFeature(p, name, loc, cps)
8880 ChessProgramState *cps;
8883 int len = strlen(name);
8884 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
8886 sscanf(*p, "%d", loc);
8887 while (**p && **p != ' ') (*p)++;
8888 sprintf(buf, "accepted %s\n", name);
8889 SendToProgram(buf, cps);
8896 StringFeature(p, name, loc, cps)
8900 ChessProgramState *cps;
8903 int len = strlen(name);
8904 if (strncmp((*p), name, len) == 0
8905 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
8907 sscanf(*p, "%[^\"]", loc);
8908 while (**p && **p != '\"') (*p)++;
8909 if (**p == '\"') (*p)++;
8910 sprintf(buf, "accepted %s\n", name);
8911 SendToProgram(buf, cps);
8918 FeatureDone(cps, val)
8919 ChessProgramState* cps;
8922 DelayedEventCallback cb = GetDelayedEvent();
8923 if ((cb == InitBackEnd3 && cps == &first) ||
8924 (cb == TwoMachinesEventIfReady && cps == &second)) {
8925 CancelDelayedEvent();
8926 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
8928 cps->initDone = val;
8931 /* Parse feature command from engine */
8933 ParseFeatures(args, cps)
8935 ChessProgramState *cps;
8943 while (*p == ' ') p++;
8944 if (*p == NULLCHAR) return;
8946 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
8947 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
8948 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
8949 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
8950 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
8951 if (BoolFeature(&p, "reuse", &val, cps)) {
8952 /* Engine can disable reuse, but can't enable it if user said no */
8953 if (!val) cps->reuse = FALSE;
8956 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
8957 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
8958 if (gameMode == TwoMachinesPlay) {
8959 DisplayTwoMachinesTitle();
8965 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
8966 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
8967 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
8968 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
8969 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
8970 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
8971 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
8972 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
8973 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
8974 if (IntFeature(&p, "done", &val, cps)) {
8975 FeatureDone(cps, val);
8979 /* unknown feature: complain and skip */
8981 while (*q && *q != '=') q++;
8982 sprintf(buf, "rejected %.*s\n", q-p, p);
8983 SendToProgram(buf, cps);
8989 while (*p && *p != '\"') p++;
8990 if (*p == '\"') p++;
8992 while (*p && *p != ' ') p++;
9000 PeriodicUpdatesEvent(newState)
9003 if (newState == appData.periodicUpdates)
9006 appData.periodicUpdates=newState;
9008 /* Display type changes, so update it now */
9011 /* Get the ball rolling again... */
9013 AnalysisPeriodicEvent(1);
9014 StartAnalysisClock();
9019 PonderNextMoveEvent(newState)
9022 if (newState == appData.ponderNextMove) return;
9023 if (gameMode == EditPosition) EditPositionDone();
9025 SendToProgram("hard\n", &first);
9026 if (gameMode == TwoMachinesPlay) {
9027 SendToProgram("hard\n", &second);
9030 SendToProgram("easy\n", &first);
9031 thinkOutput[0] = NULLCHAR;
9032 if (gameMode == TwoMachinesPlay) {
9033 SendToProgram("easy\n", &second);
9036 appData.ponderNextMove = newState;
9040 ShowThinkingEvent(newState)
9043 if (newState == appData.showThinking) return;
9044 if (gameMode == EditPosition) EditPositionDone();
9046 SendToProgram("post\n", &first);
9047 if (gameMode == TwoMachinesPlay) {
9048 SendToProgram("post\n", &second);
9051 SendToProgram("nopost\n", &first);
9052 thinkOutput[0] = NULLCHAR;
9053 if (gameMode == TwoMachinesPlay) {
9054 SendToProgram("nopost\n", &second);
9057 appData.showThinking = newState;
9061 AskQuestionEvent(title, question, replyPrefix, which)
9062 char *title; char *question; char *replyPrefix; char *which;
9064 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
9065 if (pr == NoProc) return;
9066 AskQuestion(title, question, replyPrefix, pr);
9070 DisplayMove(moveNumber)
9073 char message[MSG_SIZ];
9075 char cpThinkOutput[MSG_SIZ];
9077 if (moveNumber == forwardMostMove - 1 ||
9078 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
9080 strcpy(cpThinkOutput, thinkOutput);
9081 if (strchr(cpThinkOutput, '\n'))
9082 *strchr(cpThinkOutput, '\n') = NULLCHAR;
9084 *cpThinkOutput = NULLCHAR;
9087 if (moveNumber == forwardMostMove - 1 &&
9088 gameInfo.resultDetails != NULL) {
9089 if (gameInfo.resultDetails[0] == NULLCHAR) {
9090 sprintf(res, " %s", PGNResult(gameInfo.result));
9092 sprintf(res, " {%s} %s",
9093 gameInfo.resultDetails, PGNResult(gameInfo.result));
9099 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
9100 DisplayMessage(res, cpThinkOutput);
9102 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
9103 WhiteOnMove(moveNumber) ? " " : ".. ",
9104 parseList[moveNumber], res);
9105 DisplayMessage(message, cpThinkOutput);
9110 DisplayAnalysisText(text)
9115 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
9116 sprintf(buf, "Analysis (%s)", first.tidy);
9117 AnalysisPopUp(buf, text);
9125 while (*str && isspace(*str)) ++str;
9126 while (*str && !isspace(*str)) ++str;
9127 if (!*str) return 1;
9128 while (*str && isspace(*str)) ++str;
9129 if (!*str) return 1;
9138 static char *xtra[] = { "", " (--)", " (++)" };
9141 if (programStats.time == 0) {
9142 programStats.time = 1;
9145 if (programStats.got_only_move) {
9146 strcpy(buf, programStats.movelist);
9148 nps = (((double)programStats.nodes) /
9149 (((double)programStats.time)/100.0));
9151 cs = programStats.time % 100;
9152 s = programStats.time / 100;
9158 if (programStats.moves_left > 0 && appData.periodicUpdates) {
9159 if (programStats.move_name[0] != NULLCHAR) {
9160 sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",
9162 programStats.nr_moves-programStats.moves_left,
9163 programStats.nr_moves, programStats.move_name,
9164 ((float)programStats.score)/100.0, programStats.movelist,
9165 only_one_move(programStats.movelist)?
9166 xtra[programStats.got_fail] : "",
9167 programStats.nodes, (int)nps, h, m, s, cs);
9169 sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",
9171 programStats.nr_moves-programStats.moves_left,
9172 programStats.nr_moves, ((float)programStats.score)/100.0,
9173 programStats.movelist,
9174 only_one_move(programStats.movelist)?
9175 xtra[programStats.got_fail] : "",
9176 programStats.nodes, (int)nps, h, m, s, cs);
9179 sprintf(buf, "depth=%d %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",
9181 ((float)programStats.score)/100.0,
9182 programStats.movelist,
9183 only_one_move(programStats.movelist)?
9184 xtra[programStats.got_fail] : "",
9185 programStats.nodes, (int)nps, h, m, s, cs);
9188 DisplayAnalysisText(buf);
9192 DisplayComment(moveNumber, text)
9196 char title[MSG_SIZ];
9198 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
9199 strcpy(title, "Comment");
9201 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
9202 WhiteOnMove(moveNumber) ? " " : ".. ",
9203 parseList[moveNumber]);
9206 CommentPopUp(title, text);
9209 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
9210 * might be busy thinking or pondering. It can be omitted if your
9211 * gnuchess is configured to stop thinking immediately on any user
9212 * input. However, that gnuchess feature depends on the FIONREAD
9213 * ioctl, which does not work properly on some flavors of Unix.
9217 ChessProgramState *cps;
9220 if (!cps->useSigint) return;
9221 if (appData.noChessProgram || (cps->pr == NoProc)) return;
9223 case MachinePlaysWhite:
9224 case MachinePlaysBlack:
9225 case TwoMachinesPlay:
9226 case IcsPlayingWhite:
9227 case IcsPlayingBlack:
9230 /* Skip if we know it isn't thinking */
9231 if (!cps->maybeThinking) return;
9232 if (appData.debugMode)
9233 fprintf(debugFP, "Interrupting %s\n", cps->which);
9234 InterruptChildProcess(cps->pr);
9235 cps->maybeThinking = FALSE;
9240 #endif /*ATTENTION*/
9246 if (whiteTimeRemaining <= 0) {
9249 if (appData.icsActive) {
9250 if (appData.autoCallFlag &&
9251 gameMode == IcsPlayingBlack && !blackFlag) {
9252 SendToICS(ics_prefix);
9253 SendToICS("flag\n");
9257 DisplayTitle("Both flags fell");
9259 DisplayTitle("White's flag fell");
9260 if (appData.autoCallFlag) {
9261 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
9268 if (blackTimeRemaining <= 0) {
9271 if (appData.icsActive) {
9272 if (appData.autoCallFlag &&
9273 gameMode == IcsPlayingWhite && !whiteFlag) {
9274 SendToICS(ics_prefix);
9275 SendToICS("flag\n");
9279 DisplayTitle("Both flags fell");
9281 DisplayTitle("Black's flag fell");
9282 if (appData.autoCallFlag) {
9283 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
9296 if (!appData.clockMode || appData.icsActive ||
9297 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
9299 if (timeIncrement >= 0) {
9300 if (WhiteOnMove(forwardMostMove)) {
9301 blackTimeRemaining += timeIncrement;
9303 whiteTimeRemaining += timeIncrement;
9307 * add time to clocks when time control is achieved
9309 if (movesPerSession) {
9310 switch ((forwardMostMove + 1) % (movesPerSession * 2)) {
9312 /* White made time control */
9313 whiteTimeRemaining += timeControl;
9316 /* Black made time control */
9317 blackTimeRemaining += timeControl;
9328 int wom = gameMode == EditPosition ?
9329 !blackPlaysFirst : WhiteOnMove(currentMove);
9330 DisplayWhiteClock(whiteTimeRemaining, wom);
9331 DisplayBlackClock(blackTimeRemaining, !wom);
9335 /* Timekeeping seems to be a portability nightmare. I think everyone
9336 has ftime(), but I'm really not sure, so I'm including some ifdefs
9337 to use other calls if you don't. Clocks will be less accurate if
9338 you have neither ftime nor gettimeofday.
9341 /* Get the current time as a TimeMark */
9346 #if HAVE_GETTIMEOFDAY
9348 struct timeval timeVal;
9349 struct timezone timeZone;
9351 gettimeofday(&timeVal, &timeZone);
9352 tm->sec = (long) timeVal.tv_sec;
9353 tm->ms = (int) (timeVal.tv_usec / 1000L);
9355 #else /*!HAVE_GETTIMEOFDAY*/
9358 #include <sys/timeb.h>
9362 tm->sec = (long) timeB.time;
9363 tm->ms = (int) timeB.millitm;
9365 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
9366 tm->sec = (long) time(NULL);
9372 /* Return the difference in milliseconds between two
9373 time marks. We assume the difference will fit in a long!
9376 SubtractTimeMarks(tm2, tm1)
9377 TimeMark *tm2, *tm1;
9379 return 1000L*(tm2->sec - tm1->sec) +
9380 (long) (tm2->ms - tm1->ms);
9385 * Code to manage the game clocks.
9387 * In tournament play, black starts the clock and then white makes a move.
9388 * We give the human user a slight advantage if he is playing white---the
9389 * clocks don't run until he makes his first move, so it takes zero time.
9390 * Also, we don't account for network lag, so we could get out of sync
9391 * with GNU Chess's clock -- but then, referees are always right.
9394 static TimeMark tickStartTM;
9395 static long intendedTickLength;
9398 NextTickLength(timeRemaining)
9401 long nominalTickLength, nextTickLength;
9403 if (timeRemaining > 0L && timeRemaining <= 10000L)
9404 nominalTickLength = 100L;
9406 nominalTickLength = 1000L;
9407 nextTickLength = timeRemaining % nominalTickLength;
9408 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
9410 return nextTickLength;
9413 /* Stop clocks and reset to a fresh time control */
9417 (void) StopClockTimer();
9418 if (appData.icsActive) {
9419 whiteTimeRemaining = blackTimeRemaining = 0;
9421 whiteTimeRemaining = blackTimeRemaining = timeControl;
9423 if (whiteFlag || blackFlag) {
9425 whiteFlag = blackFlag = FALSE;
9427 DisplayBothClocks();
9430 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
9432 /* Decrement running clock by amount of time that has passed */
9437 long lastTickLength, fudge;
9440 if (!appData.clockMode) return;
9441 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
9445 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
9447 /* Fudge if we woke up a little too soon */
9448 fudge = intendedTickLength - lastTickLength;
9449 if (fudge < 0 || fudge > FUDGE) fudge = 0;
9451 if (WhiteOnMove(forwardMostMove)) {
9452 timeRemaining = whiteTimeRemaining -= lastTickLength;
9453 DisplayWhiteClock(whiteTimeRemaining - fudge,
9454 WhiteOnMove(currentMove));
9456 timeRemaining = blackTimeRemaining -= lastTickLength;
9457 DisplayBlackClock(blackTimeRemaining - fudge,
9458 !WhiteOnMove(currentMove));
9461 if (CheckFlags()) return;
9464 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
9465 StartClockTimer(intendedTickLength);
9467 /* if the time remaining has fallen below the alarm threshold, sound the
9468 * alarm. if the alarm has sounded and (due to a takeback or time control
9469 * with increment) the time remaining has increased to a level above the
9470 * threshold, reset the alarm so it can sound again.
9473 if (appData.icsActive && appData.icsAlarm) {
9475 /* make sure we are dealing with the user's clock */
9476 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
9477 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
9480 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
9481 alarmSounded = FALSE;
9482 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
9484 alarmSounded = TRUE;
9490 /* A player has just moved, so stop the previously running
9491 clock and (if in clock mode) start the other one.
9492 We redisplay both clocks in case we're in ICS mode, because
9493 ICS gives us an update to both clocks after every move.
9494 Note that this routine is called *after* forwardMostMove
9495 is updated, so the last fractional tick must be subtracted
9496 from the color that is *not* on move now.
9501 long lastTickLength;
9503 int flagged = FALSE;
9507 if (StopClockTimer() && appData.clockMode) {
9508 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
9509 if (WhiteOnMove(forwardMostMove)) {
9510 blackTimeRemaining -= lastTickLength;
9512 whiteTimeRemaining -= lastTickLength;
9514 flagged = CheckFlags();
9518 if (flagged || !appData.clockMode) return;
9521 case MachinePlaysBlack:
9522 case MachinePlaysWhite:
9523 case BeginningOfGame:
9524 if (pausing) return;
9528 case PlayFromGameFile:
9537 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
9538 whiteTimeRemaining : blackTimeRemaining);
9539 StartClockTimer(intendedTickLength);
9543 /* Stop both clocks */
9547 long lastTickLength;
9550 if (!StopClockTimer()) return;
9551 if (!appData.clockMode) return;
9555 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
9556 if (WhiteOnMove(forwardMostMove)) {
9557 whiteTimeRemaining -= lastTickLength;
9558 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
9560 blackTimeRemaining -= lastTickLength;
9561 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
9566 /* Start clock of player on move. Time may have been reset, so
9567 if clock is already running, stop and restart it. */
9571 (void) StopClockTimer(); /* in case it was running already */
9572 DisplayBothClocks();
9573 if (CheckFlags()) return;
9575 if (!appData.clockMode) return;
9576 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
9578 GetTimeMark(&tickStartTM);
9579 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
9580 whiteTimeRemaining : blackTimeRemaining);
9581 StartClockTimer(intendedTickLength);
9588 long second, minute, hour, day;
9590 static char buf[32];
9592 if (ms > 0 && ms <= 9900) {
9593 /* convert milliseconds to tenths, rounding up */
9594 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
9596 sprintf(buf, " %03.1f ", tenths/10.0);
9600 /* convert milliseconds to seconds, rounding up */
9601 /* use floating point to avoid strangeness of integer division
9602 with negative dividends on many machines */
9603 second = (long) floor(((double) (ms + 999L)) / 1000.0);
9610 day = second / (60 * 60 * 24);
9611 second = second % (60 * 60 * 24);
9612 hour = second / (60 * 60);
9613 second = second % (60 * 60);
9614 minute = second / 60;
9615 second = second % 60;
9618 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
9619 sign, day, hour, minute, second);
9621 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
9623 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
9630 * This is necessary because some C libraries aren't ANSI C compliant yet.
9633 StrStr(string, match)
9634 char *string, *match;
9638 length = strlen(match);
9640 for (i = strlen(string) - length; i >= 0; i--, string++)
9641 if (!strncmp(match, string, length))
9648 StrCaseStr(string, match)
9649 char *string, *match;
9653 length = strlen(match);
9655 for (i = strlen(string) - length; i >= 0; i--, string++) {
9656 for (j = 0; j < length; j++) {
9657 if (ToLower(match[j]) != ToLower(string[j]))
9660 if (j == length) return string;
9674 c1 = ToLower(*s1++);
9675 c2 = ToLower(*s2++);
9676 if (c1 > c2) return 1;
9677 if (c1 < c2) return -1;
9678 if (c1 == NULLCHAR) return 0;
9687 return isupper(c) ? tolower(c) : c;
9695 return islower(c) ? toupper(c) : c;
9697 #endif /* !_amigados */
9705 if ((ret = (char *) malloc(strlen(s) + 1))) {
9712 StrSavePtr(s, savePtr)
9718 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
9719 strcpy(*savePtr, s);
9731 clock = time((time_t *)NULL);
9732 tm = localtime(&clock);
9733 sprintf(buf, "%04d.%02d.%02d",
9734 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
9735 return StrSave(buf);
9743 int i, j, fromX, fromY, toX, toY;
9749 whiteToPlay = (gameMode == EditPosition) ?
9750 !blackPlaysFirst : (move % 2 == 0);
9753 /* Piece placement data */
9754 for (i = BOARD_SIZE - 1; i >= 0; i--) {
9756 for (j = 0; j < BOARD_SIZE; j++) {
9757 if (boards[move][i][j] == EmptySquare) {
9760 if (emptycount > 0) {
9761 *p++ = '0' + emptycount;
9764 *p++ = PieceToChar(boards[move][i][j]);
9767 if (emptycount > 0) {
9768 *p++ = '0' + emptycount;
9776 *p++ = whiteToPlay ? 'w' : 'b';
9779 /* !!We don't keep track of castling availability, so fake it */
9781 if (boards[move][0][4] == WhiteKing) {
9782 if (boards[move][0][7] == WhiteRook) *p++ = 'K';
9783 if (boards[move][0][0] == WhiteRook) *p++ = 'Q';
9785 if (boards[move][7][4] == BlackKing) {
9786 if (boards[move][7][7] == BlackRook) *p++ = 'k';
9787 if (boards[move][7][0] == BlackRook) *p++ = 'q';
9789 if (q == p) *p++ = '-';
9792 /* En passant target square */
9793 if (move > backwardMostMove) {
9794 fromX = moveList[move - 1][0] - 'a';
9795 fromY = moveList[move - 1][1] - '1';
9796 toX = moveList[move - 1][2] - 'a';
9797 toY = moveList[move - 1][3] - '1';
9798 if (fromY == (whiteToPlay ? 6 : 1) &&
9799 toY == (whiteToPlay ? 4 : 3) &&
9800 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
9802 /* 2-square pawn move just happened */
9804 *p++ = whiteToPlay ? '6' : '3';
9812 /* !!We don't keep track of halfmove clock for 50-move rule */
9816 /* Fullmove number */
9817 sprintf(p, "%d", (move / 2) + 1);
9819 return StrSave(buf);
9823 ParseFEN(board, blackPlaysFirst, fen)
9825 int *blackPlaysFirst;
9834 /* Piece placement data */
9835 for (i = BOARD_SIZE - 1; i >= 0; i--) {
9838 if (*p == '/' || *p == ' ') {
9840 emptycount = BOARD_SIZE - j;
9841 while (emptycount--) board[i][j++] = EmptySquare;
9843 } else if (isdigit(*p)) {
9844 emptycount = *p++ - '0';
9845 if (j + emptycount > BOARD_SIZE) return FALSE;
9846 while (emptycount--) board[i][j++] = EmptySquare;
9847 } else if (isalpha(*p)) {
9848 if (j >= BOARD_SIZE) return FALSE;
9849 board[i][j++] = CharToPiece(*p++);
9855 while (*p == '/' || *p == ' ') p++;
9860 *blackPlaysFirst = FALSE;
9863 *blackPlaysFirst = TRUE;
9868 /* !!We ignore the rest of the FEN notation */
9873 EditPositionPasteFEN(char *fen)
9876 Board initial_position;
9878 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
9879 DisplayError("Bad FEN position in clipboard", 0);
9882 int savedBlackPlaysFirst = blackPlaysFirst;
9883 EditPositionEvent();
9884 blackPlaysFirst = savedBlackPlaysFirst;
9885 CopyBoard(boards[0], initial_position);
9887 DisplayBothClocks();
9888 DrawPosition(FALSE, boards[currentMove]);