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"
113 # define _(s) gettext (s)
114 # define N_(s) gettext_noop (s)
121 /* A point in time */
123 long sec; /* Assuming this is >= 32 bits */
124 int ms; /* Assuming this is >= 16 bits */
127 /* Search stats from chessprogram */
129 char movelist[2*MSG_SIZ]; /* Last PV we were sent */
130 int depth; /* Current search depth */
131 int nr_moves; /* Total nr of root moves */
132 int moves_left; /* Moves remaining to be searched */
133 char move_name[MOVE_LEN]; /* Current move being searched, if provided */
134 unsigned long nodes; /* # of nodes searched */
135 int time; /* Search time (centiseconds) */
136 int score; /* Score (centipawns) */
137 int got_only_move; /* If last msg was "(only move)" */
138 int got_fail; /* 0 - nothing, 1 - got "--", 2 - got "++" */
139 int ok_to_send; /* handshaking between send & recv */
140 int line_is_book; /* 1 if movelist is book moves */
141 int seen_stat; /* 1 if we've seen the stat01: line */
144 int establish P((void));
145 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
146 char *buf, int count, int error));
147 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
148 char *buf, int count, int error));
149 void SendToICS P((char *s));
150 void SendToICSDelayed P((char *s, long msdelay));
151 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
153 void InitPosition P((int redraw));
154 void HandleMachineMove P((char *message, ChessProgramState *cps));
155 int AutoPlayOneMove P((void));
156 int LoadGameOneMove P((ChessMove readAhead));
157 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
158 int LoadPositionFromFile P((char *filename, int n, char *title));
159 int SavePositionToFile P((char *filename));
160 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
162 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
163 void ShowMove P((int fromX, int fromY, int toX, int toY));
164 void FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
165 /*char*/int promoChar));
166 void BackwardInner P((int target));
167 void ForwardInner P((int target));
168 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
169 void EditPositionDone P((void));
170 void PrintOpponents P((FILE *fp));
171 void PrintPosition P((FILE *fp, int move));
172 void StartChessProgram P((ChessProgramState *cps));
173 void SendToProgram P((char *message, ChessProgramState *cps));
174 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
175 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
176 char *buf, int count, int error));
177 void SendTimeControl P((ChessProgramState *cps,
178 int mps, long tc, int inc, int sd, int st));
179 char *TimeControlTagValue P((void));
180 void Attention P((ChessProgramState *cps));
181 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
182 void ResurrectChessProgram P((void));
183 void DisplayComment P((int moveNumber, char *text));
184 void DisplayMove P((int moveNumber));
185 void DisplayAnalysis P((void));
187 void ParseGameHistory P((char *game));
188 void ParseBoard12 P((char *string));
189 void StartClocks P((void));
190 void SwitchClocks P((void));
191 void StopClocks P((void));
192 void ResetClocks P((void));
193 char *PGNDate P((void));
194 void SetGameInfo P((void));
195 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
196 int RegisterMove P((void));
197 void MakeRegisteredMove P((void));
198 void TruncateGame P((void));
199 int looking_at P((char *, int *, char *));
200 void CopyPlayerNameIntoFileName P((char **, char *));
201 char *SavePart P((char *));
202 int SaveGameOldStyle P((FILE *));
203 int SaveGamePGN P((FILE *));
204 void GetTimeMark P((TimeMark *));
205 long SubtractTimeMarks P((TimeMark *, TimeMark *));
206 int CheckFlags P((void));
207 long NextTickLength P((long));
208 void CheckTimeControl P((void));
209 void show_bytes P((FILE *, char *, int));
210 int string_to_rating P((char *str));
211 void ParseFeatures P((char* args, ChessProgramState *cps));
212 void InitBackEnd3 P((void));
213 void FeatureDone P((ChessProgramState* cps, int val));
214 void InitChessProgram P((ChessProgramState *cps));
216 extern int tinyLayout, smallLayout;
217 static ChessProgramStats programStats;
219 /* States for ics_getting_history */
221 #define H_REQUESTED 1
222 #define H_GOT_REQ_HEADER 2
223 #define H_GOT_UNREQ_HEADER 3
224 #define H_GETTING_MOVES 4
225 #define H_GOT_UNWANTED_HEADER 5
227 /* whosays values for GameEnds */
234 /* Maximum number of games in a cmail message */
235 #define CMAIL_MAX_GAMES 20
237 /* Different types of move when calling RegisterMove */
239 #define CMAIL_RESIGN 1
241 #define CMAIL_ACCEPT 3
243 /* Different types of result to remember for each game */
244 #define CMAIL_NOT_RESULT 0
245 #define CMAIL_OLD_RESULT 1
246 #define CMAIL_NEW_RESULT 2
248 /* Telnet protocol constants */
258 /* Fake up flags for now, as we aren't keeping track of castling
263 int flags = F_ALL_CASTLE_OK;
264 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
265 switch (gameInfo.variant) {
267 case VariantGiveaway:
268 flags |= F_IGNORE_CHECK;
269 flags &= ~F_ALL_CASTLE_OK;
272 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
274 case VariantKriegspiel:
275 flags |= F_KRIEGSPIEL_CAPTURE;
277 case VariantNoCastle:
278 flags &= ~F_ALL_CASTLE_OK;
286 FILE *gameFileFP, *debugFP;
288 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
289 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
290 char thinkOutput1[MSG_SIZ*10];
292 ChessProgramState first, second;
294 /* premove variables */
297 int premoveFromX = 0;
298 int premoveFromY = 0;
299 int premovePromoChar = 0;
301 Boolean alarmSounded;
302 /* end premove variables */
304 #define ICS_GENERIC 0
307 #define ICS_CHESSNET 3 /* not really supported */
308 int ics_type = ICS_GENERIC;
309 char *ics_prefix = "$";
311 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
312 int pauseExamForwardMostMove = 0;
313 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
314 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
315 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
316 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
317 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
318 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
319 int whiteFlag = FALSE, blackFlag = FALSE;
320 int userOfferedDraw = FALSE;
321 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
322 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
323 int cmailMoveType[CMAIL_MAX_GAMES];
324 long ics_clock_paused = 0;
325 ProcRef icsPR = NoProc, cmailPR = NoProc;
326 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
327 GameMode gameMode = BeginningOfGame;
328 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
329 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
330 char white_holding[64], black_holding[64];
331 TimeMark lastNodeCountTime;
332 long lastNodeCount=0;
333 int have_sent_ICS_logon = 0;
335 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
336 long timeRemaining[2][MAX_MOVES];
338 TimeMark programStartTime;
339 char ics_handle[MSG_SIZ];
340 int have_set_title = 0;
342 /* animateTraining preserves the state of appData.animate
343 * when Training mode is activated. This allows the
344 * response to be animated when appData.animate == TRUE and
345 * appData.animateDragging == TRUE.
347 Boolean animateTraining;
353 Board boards[MAX_MOVES];
354 Board initialPosition = {
355 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
356 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
357 { WhitePawn, WhitePawn, WhitePawn, WhitePawn,
358 WhitePawn, WhitePawn, WhitePawn, WhitePawn },
359 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
360 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
361 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
362 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
363 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
364 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
365 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
366 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
367 { BlackPawn, BlackPawn, BlackPawn, BlackPawn,
368 BlackPawn, BlackPawn, BlackPawn, BlackPawn },
369 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
370 BlackKing, BlackBishop, BlackKnight, BlackRook }
372 Board twoKingsPosition = {
373 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
374 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
375 { WhitePawn, WhitePawn, WhitePawn, WhitePawn,
376 WhitePawn, WhitePawn, WhitePawn, WhitePawn },
377 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
378 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
379 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
380 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
381 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
382 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
383 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
384 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
385 { BlackPawn, BlackPawn, BlackPawn, BlackPawn,
386 BlackPawn, BlackPawn, BlackPawn, BlackPawn },
387 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
388 BlackKing, BlackKing, BlackKnight, BlackRook }
392 /* Convert str to a rating. Checks for special cases of "----",
393 "++++", etc. Also strips ()'s */
395 string_to_rating(str)
398 while(*str && !isdigit(*str)) ++str;
400 return 0; /* One of the special "no rating" cases */
408 /* Init programStats */
409 programStats.movelist[0] = 0;
410 programStats.depth = 0;
411 programStats.nr_moves = 0;
412 programStats.moves_left = 0;
413 programStats.nodes = 0;
414 programStats.time = 100;
415 programStats.score = 0;
416 programStats.got_only_move = 0;
417 programStats.got_fail = 0;
418 programStats.line_is_book = 0;
424 int matched, min, sec;
426 GetTimeMark(&programStartTime);
429 programStats.ok_to_send = 1;
430 programStats.seen_stat = 0;
433 * Initialize game list
439 * Internet chess server status
441 if (appData.icsActive) {
442 appData.matchMode = FALSE;
443 appData.matchGames = 0;
445 appData.noChessProgram = !appData.zippyPlay;
447 appData.zippyPlay = FALSE;
448 appData.zippyTalk = FALSE;
449 appData.noChessProgram = TRUE;
451 if (*appData.icsHelper != NULLCHAR) {
452 appData.useTelnet = TRUE;
453 appData.telnetProgram = appData.icsHelper;
456 appData.zippyTalk = appData.zippyPlay = FALSE;
460 * Parse timeControl resource
462 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
463 appData.movesPerSession)) {
465 sprintf(buf, _("bad timeControl option %s"), appData.timeControl);
466 DisplayFatalError(buf, 0, 2);
470 * Parse searchTime resource
472 if (*appData.searchTime != NULLCHAR) {
473 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
475 searchTime = min * 60;
476 } else if (matched == 2) {
477 searchTime = min * 60 + sec;
480 sprintf(buf, _("bad searchTime option %s"), appData.searchTime);
481 DisplayFatalError(buf, 0, 2);
485 first.which = "first";
486 second.which = "second";
487 first.maybeThinking = second.maybeThinking = FALSE;
488 first.pr = second.pr = NoProc;
489 first.isr = second.isr = NULL;
490 first.sendTime = second.sendTime = 2;
491 first.sendDrawOffers = 1;
492 if (appData.firstPlaysBlack) {
493 first.twoMachinesColor = "black\n";
494 second.twoMachinesColor = "white\n";
496 first.twoMachinesColor = "white\n";
497 second.twoMachinesColor = "black\n";
499 first.program = appData.firstChessProgram;
500 second.program = appData.secondChessProgram;
501 first.host = appData.firstHost;
502 second.host = appData.secondHost;
503 first.dir = appData.firstDirectory;
504 second.dir = appData.secondDirectory;
505 first.other = &second;
506 second.other = &first;
507 first.initString = appData.initString;
508 second.initString = appData.secondInitString;
509 first.computerString = appData.firstComputerString;
510 second.computerString = appData.secondComputerString;
511 first.useSigint = second.useSigint = TRUE;
512 first.useSigterm = second.useSigterm = TRUE;
513 first.reuse = appData.reuseFirst;
514 second.reuse = appData.reuseSecond;
515 first.useSetboard = second.useSetboard = FALSE;
516 first.useSAN = second.useSAN = FALSE;
517 first.usePing = second.usePing = FALSE;
518 first.lastPing = second.lastPing = 0;
519 first.lastPong = second.lastPong = 0;
520 first.usePlayother = second.usePlayother = FALSE;
521 first.useColors = second.useColors = TRUE;
522 first.useUsermove = second.useUsermove = FALSE;
523 first.sendICS = second.sendICS = FALSE;
524 first.sendName = second.sendName = appData.icsActive;
525 first.sdKludge = second.sdKludge = FALSE;
526 first.stKludge = second.stKludge = FALSE;
527 TidyProgramName(first.program, first.host, first.tidy);
528 TidyProgramName(second.program, second.host, second.tidy);
529 first.matchWins = second.matchWins = 0;
530 strcpy(first.variants, appData.variant);
531 strcpy(second.variants, appData.variant);
532 first.analysisSupport = second.analysisSupport = 2; /* detect */
533 first.analyzing = second.analyzing = FALSE;
534 first.initDone = second.initDone = FALSE;
536 if (appData.firstProtocolVersion > PROTOVER ||
537 appData.firstProtocolVersion < 1) {
539 sprintf(buf, _("protocol version %d not supported"),
540 appData.firstProtocolVersion);
541 DisplayFatalError(buf, 0, 2);
543 first.protocolVersion = appData.firstProtocolVersion;
546 if (appData.secondProtocolVersion > PROTOVER ||
547 appData.secondProtocolVersion < 1) {
549 sprintf(buf, _("protocol version %d not supported"),
550 appData.secondProtocolVersion);
551 DisplayFatalError(buf, 0, 2);
553 second.protocolVersion = appData.secondProtocolVersion;
556 if (appData.icsActive) {
557 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
558 } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
559 appData.clockMode = FALSE;
560 first.sendTime = second.sendTime = 0;
564 /* Override some settings from environment variables, for backward
565 compatibility. Unfortunately it's not feasible to have the env
566 vars just set defaults, at least in xboard. Ugh.
568 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
573 if (appData.noChessProgram) {
574 programVersion = (char*) malloc(5 + strlen(PRODUCT) + strlen(VERSION)
575 + strlen(PATCHLEVEL));
576 sprintf(programVersion, "%s %s.%s", PRODUCT, VERSION, PATCHLEVEL);
580 while (*q != ' ' && *q != NULLCHAR) q++;
582 while (p > first.program && *(p-1) != '/') p--;
583 programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)
584 + strlen(PATCHLEVEL) + (q - p));
585 sprintf(programVersion, "%s %s.%s + ", PRODUCT, VERSION, PATCHLEVEL);
586 strncat(programVersion, p, q - p);
589 if (!appData.icsActive) {
591 /* Check for variants that are supported only in ICS mode,
592 or not at all. Some that are accepted here nevertheless
593 have bugs; see comments below.
595 VariantClass variant = StringToVariant(appData.variant);
597 case VariantBughouse: /* need four players and two boards */
598 case VariantKriegspiel: /* need to hide pieces and move details */
599 case VariantFischeRandom: /* castling doesn't work, shuffle not done */
600 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
601 DisplayFatalError(buf, 0, 2);
605 case VariantLoadable:
615 sprintf(buf, _("Unknown variant name %s"), appData.variant);
616 DisplayFatalError(buf, 0, 2);
619 case VariantNormal: /* definitely works! */
620 case VariantWildCastle: /* pieces not automatically shuffled */
621 case VariantNoCastle: /* pieces not automatically shuffled */
622 case VariantCrazyhouse: /* holdings not shown,
623 offboard interposition not understood */
624 case VariantLosers: /* should work except for win condition,
625 and doesn't know captures are mandatory */
626 case VariantSuicide: /* should work except for win condition,
627 and doesn't know captures are mandatory */
628 case VariantGiveaway: /* should work except for win condition,
629 and doesn't know captures are mandatory */
630 case VariantTwoKings: /* should work */
631 case VariantAtomic: /* should work except for win condition */
632 case Variant3Check: /* should work except for win condition */
633 case VariantShatranj: /* might work if TestLegality is off */
640 ParseTimeControl(tc, ti, mps)
645 int matched, min, sec;
647 matched = sscanf(tc, "%d:%d", &min, &sec);
649 timeControl = min * 60 * 1000;
650 } else if (matched == 2) {
651 timeControl = (min * 60 + sec) * 1000;
657 timeIncrement = ti * 1000; /* convert to ms */
661 movesPerSession = mps;
669 if (appData.debugMode) {
670 fprintf(debugFP, "%s\n", programVersion);
673 if (appData.matchGames > 0) {
674 appData.matchMode = TRUE;
675 } else if (appData.matchMode) {
676 appData.matchGames = 1;
679 if (appData.noChessProgram || first.protocolVersion == 1) {
682 /* kludge: allow timeout for initial "feature" commands */
684 DisplayMessage("", "Starting chess program");
685 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
690 InitBackEnd3 P((void))
692 GameMode initialMode;
696 InitChessProgram(&first);
698 if (appData.icsActive) {
701 if (*appData.icsCommPort != NULLCHAR) {
702 sprintf(buf, _("Could not open comm port %s"),
703 appData.icsCommPort);
705 sprintf(buf, _("Could not connect to host %s, port %s"),
706 appData.icsHost, appData.icsPort);
708 DisplayFatalError(buf, err, 1);
713 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
715 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
716 } else if (appData.noChessProgram) {
722 if (*appData.cmailGameName != NULLCHAR) {
724 OpenLoopback(&cmailPR);
726 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
730 DisplayMessage("", "");
731 if (StrCaseCmp(appData.initialMode, "") == 0) {
732 initialMode = BeginningOfGame;
733 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
734 initialMode = TwoMachinesPlay;
735 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
736 initialMode = AnalyzeFile;
737 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
738 initialMode = AnalyzeMode;
739 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
740 initialMode = MachinePlaysWhite;
741 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
742 initialMode = MachinePlaysBlack;
743 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
744 initialMode = EditGame;
745 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
746 initialMode = EditPosition;
747 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
748 initialMode = Training;
750 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
751 DisplayFatalError(buf, 0, 2);
755 if (appData.matchMode) {
756 /* Set up machine vs. machine match */
757 if (appData.noChessProgram) {
758 DisplayFatalError(_("Can't have a match with no chess programs"),
764 if (*appData.loadGameFile != NULLCHAR) {
765 if (!LoadGameFromFile(appData.loadGameFile,
766 appData.loadGameIndex,
767 appData.loadGameFile, FALSE)) {
768 DisplayFatalError(_("Bad game file"), 0, 1);
771 } else if (*appData.loadPositionFile != NULLCHAR) {
772 if (!LoadPositionFromFile(appData.loadPositionFile,
773 appData.loadPositionIndex,
774 appData.loadPositionFile)) {
775 DisplayFatalError(_("Bad position file"), 0, 1);
780 } else if (*appData.cmailGameName != NULLCHAR) {
781 /* Set up cmail mode */
782 ReloadCmailMsgEvent(TRUE);
784 /* Set up other modes */
785 if (initialMode == AnalyzeFile) {
786 if (*appData.loadGameFile == NULLCHAR) {
787 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
791 if (*appData.loadGameFile != NULLCHAR) {
792 (void) LoadGameFromFile(appData.loadGameFile,
793 appData.loadGameIndex,
794 appData.loadGameFile, TRUE);
795 } else if (*appData.loadPositionFile != NULLCHAR) {
796 (void) LoadPositionFromFile(appData.loadPositionFile,
797 appData.loadPositionIndex,
798 appData.loadPositionFile);
800 if (initialMode == AnalyzeMode) {
801 if (appData.noChessProgram) {
802 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
805 if (appData.icsActive) {
806 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
810 } else if (initialMode == AnalyzeFile) {
811 ShowThinkingEvent(TRUE);
813 AnalysisPeriodicEvent(1);
814 } else if (initialMode == MachinePlaysWhite) {
815 if (appData.noChessProgram) {
816 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
820 if (appData.icsActive) {
821 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
826 } else if (initialMode == MachinePlaysBlack) {
827 if (appData.noChessProgram) {
828 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
832 if (appData.icsActive) {
833 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
838 } else if (initialMode == TwoMachinesPlay) {
839 if (appData.noChessProgram) {
840 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
844 if (appData.icsActive) {
845 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
850 } else if (initialMode == EditGame) {
852 } else if (initialMode == EditPosition) {
854 } else if (initialMode == Training) {
855 if (*appData.loadGameFile == NULLCHAR) {
856 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
865 * Establish will establish a contact to a remote host.port.
866 * Sets icsPR to a ProcRef for a process (or pseudo-process)
867 * used to talk to the host.
868 * Returns 0 if okay, error code if not.
875 if (*appData.icsCommPort != NULLCHAR) {
876 /* Talk to the host through a serial comm port */
877 return OpenCommPort(appData.icsCommPort, &icsPR);
879 } else if (*appData.gateway != NULLCHAR) {
880 if (*appData.remoteShell == NULLCHAR) {
881 /* Use the rcmd protocol to run telnet program on a gateway host */
882 sprintf(buf, "%s %s %s",
883 appData.telnetProgram, appData.icsHost, appData.icsPort);
884 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
887 /* Use the rsh program to run telnet program on a gateway host */
888 if (*appData.remoteUser == NULLCHAR) {
889 sprintf(buf, "%s %s %s %s %s", appData.remoteShell,
890 appData.gateway, appData.telnetProgram,
891 appData.icsHost, appData.icsPort);
893 sprintf(buf, "%s %s -l %s %s %s %s",
894 appData.remoteShell, appData.gateway,
895 appData.remoteUser, appData.telnetProgram,
896 appData.icsHost, appData.icsPort);
898 return StartChildProcess(buf, "", &icsPR);
901 } else if (appData.useTelnet) {
902 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
905 /* TCP socket interface differs somewhat between
906 Unix and NT; handle details in the front end.
908 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
913 show_bytes(fp, buf, count)
919 if (*buf < 040 || *(unsigned char *) buf > 0177) {
920 fprintf(fp, "\\%03o", *buf & 0xff);
929 /* Returns an errno value */
931 OutputMaybeTelnet(pr, message, count, outError)
937 char buf[8192], *p, *q, *buflim;
938 int left, newcount, outcount;
940 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
941 *appData.gateway != NULLCHAR) {
942 if (appData.debugMode) {
943 fprintf(debugFP, ">ICS: ");
944 show_bytes(debugFP, message, count);
945 fprintf(debugFP, "\n");
947 return OutputToProcess(pr, message, count, outError);
950 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
957 if (appData.debugMode) {
958 fprintf(debugFP, ">ICS: ");
959 show_bytes(debugFP, buf, newcount);
960 fprintf(debugFP, "\n");
962 outcount = OutputToProcess(pr, buf, newcount, outError);
963 if (outcount < newcount) return -1; /* to be sure */
970 } else if (((unsigned char) *p) == TN_IAC) {
971 *q++ = (char) TN_IAC;
978 if (appData.debugMode) {
979 fprintf(debugFP, ">ICS: ");
980 show_bytes(debugFP, buf, newcount);
981 fprintf(debugFP, "\n");
983 outcount = OutputToProcess(pr, buf, newcount, outError);
984 if (outcount < newcount) return -1; /* to be sure */
989 read_from_player(isr, closure, message, count, error)
996 int outError, outCount;
997 static int gotEof = 0;
999 /* Pass data read from player on to ICS */
1002 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1003 if (outCount < count) {
1004 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1006 } else if (count < 0) {
1007 RemoveInputSource(isr);
1008 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1009 } else if (gotEof++ > 0) {
1010 RemoveInputSource(isr);
1011 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1019 int count, outCount, outError;
1021 if (icsPR == NULL) return;
1024 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1025 if (outCount < count) {
1026 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1030 /* This is used for sending logon scripts to the ICS. Sending
1031 without a delay causes problems when using timestamp on ICC
1032 (at least on my machine). */
1034 SendToICSDelayed(s,msdelay)
1038 int count, outCount, outError;
1040 if (icsPR == NULL) return;
1043 if (appData.debugMode) {
1044 fprintf(debugFP, ">ICS: ");
1045 show_bytes(debugFP, s, count);
1046 fprintf(debugFP, "\n");
1048 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1050 if (outCount < count) {
1051 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1056 /* Remove all highlighting escape sequences in s
1057 Also deletes any suffix starting with '('
1060 StripHighlightAndTitle(s)
1063 static char retbuf[MSG_SIZ];
1066 while (*s != NULLCHAR) {
1067 while (*s == '\033') {
1068 while (*s != NULLCHAR && !isalpha(*s)) s++;
1069 if (*s != NULLCHAR) s++;
1071 while (*s != NULLCHAR && *s != '\033') {
1072 if (*s == '(' || *s == '[') {
1083 /* Remove all highlighting escape sequences in s */
1088 static char retbuf[MSG_SIZ];
1091 while (*s != NULLCHAR) {
1092 while (*s == '\033') {
1093 while (*s != NULLCHAR && !isalpha(*s)) s++;
1094 if (*s != NULLCHAR) s++;
1096 while (*s != NULLCHAR && *s != '\033') {
1104 char *variantNames[] = VARIANT_NAMES;
1109 return variantNames[v];
1113 /* Identify a variant from the strings the chess servers use or the
1114 PGN Variant tag names we use. */
1121 VariantClass v = VariantNormal;
1122 int i, found = FALSE;
1127 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1128 if (StrCaseStr(e, variantNames[i])) {
1129 v = (VariantClass) i;
1136 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1137 || StrCaseStr(e, "wild/fr")) {
1138 v = VariantFischeRandom;
1139 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1140 (i = 1, p = StrCaseStr(e, "w"))) {
1142 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1149 case 0: /* FICS only, actually */
1151 /* Castling legal even if K starts on d-file */
1152 v = VariantWildCastle;
1157 /* Castling illegal even if K & R happen to start in
1158 normal positions. */
1159 v = VariantNoCastle;
1172 /* Castling legal iff K & R start in normal positions */
1178 /* Special wilds for position setup; unclear what to do here */
1179 v = VariantLoadable;
1182 /* Bizarre ICC game */
1183 v = VariantTwoKings;
1186 v = VariantKriegspiel;
1192 v = VariantFischeRandom;
1195 v = VariantCrazyhouse;
1198 v = VariantBughouse;
1204 /* Not quite the same as FICS suicide! */
1205 v = VariantGiveaway;
1211 v = VariantShatranj;
1214 /* Temporary names for future ICC types. The name *will* change in
1215 the next xboard/WinBoard release after ICC defines it. */
1242 /* Found "wild" or "w" in the string but no number;
1243 must assume it's normal chess. */
1247 sprintf(buf, "Unknown wild type %d", wnum);
1248 DisplayError(buf, 0);
1254 if (appData.debugMode) {
1255 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1256 e, wnum, VariantName(v));
1261 static int leftover_start = 0, leftover_len = 0;
1262 char star_match[STAR_MATCH_N][MSG_SIZ];
1264 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1265 advance *index beyond it, and set leftover_start to the new value of
1266 *index; else return FALSE. If pattern contains the character '*', it
1267 matches any sequence of characters not containing '\r', '\n', or the
1268 character following the '*' (if any), and the matched sequence(s) are
1269 copied into star_match.
1272 looking_at(buf, index, pattern)
1277 char *bufp = &buf[*index], *patternp = pattern;
1279 char *matchp = star_match[0];
1282 if (*patternp == NULLCHAR) {
1283 *index = leftover_start = bufp - buf;
1287 if (*bufp == NULLCHAR) return FALSE;
1288 if (*patternp == '*') {
1289 if (*bufp == *(patternp + 1)) {
1291 matchp = star_match[++star_count];
1295 } else if (*bufp == '\n' || *bufp == '\r') {
1297 if (*patternp == NULLCHAR)
1302 *matchp++ = *bufp++;
1306 if (*patternp != *bufp) return FALSE;
1313 SendToPlayer(data, length)
1317 int error, outCount;
1318 outCount = OutputToProcess(NoProc, data, length, &error);
1319 if (outCount < length) {
1320 DisplayFatalError(_("Error writing to display"), error, 1);
1325 PackHolding(packed, holding)
1337 switch (runlength) {
1348 sprintf(q, "%d", runlength);
1360 /* Telnet protocol requests from the front end */
1362 TelnetRequest(ddww, option)
1363 unsigned char ddww, option;
1365 unsigned char msg[3];
1366 int outCount, outError;
1368 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1370 if (appData.debugMode) {
1371 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1387 sprintf(buf1, "%d", ddww);
1396 sprintf(buf2, "%d", option);
1399 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1404 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1406 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1413 if (!appData.icsActive) return;
1414 TelnetRequest(TN_DO, TN_ECHO);
1420 if (!appData.icsActive) return;
1421 TelnetRequest(TN_DONT, TN_ECHO);
1424 static int loggedOn = FALSE;
1426 /*-- Game start info cache: --*/
1428 char gs_kind[MSG_SIZ];
1429 static char player1Name[128] = "";
1430 static char player2Name[128] = "";
1431 static int player1Rating = -1;
1432 static int player2Rating = -1;
1433 /*----------------------------*/
1436 read_from_ics(isr, closure, data, count, error)
1443 #define BUF_SIZE 8192
1444 #define STARTED_NONE 0
1445 #define STARTED_MOVES 1
1446 #define STARTED_BOARD 2
1447 #define STARTED_OBSERVE 3
1448 #define STARTED_HOLDINGS 4
1449 #define STARTED_CHATTER 5
1450 #define STARTED_COMMENT 6
1451 #define STARTED_MOVES_NOHIDE 7
1453 static int started = STARTED_NONE;
1454 static char parse[20000];
1455 static int parse_pos = 0;
1456 static char buf[BUF_SIZE + 1];
1457 static int firstTime = TRUE, intfSet = FALSE;
1458 static ColorClass curColor = ColorNormal;
1459 static ColorClass prevColor = ColorNormal;
1460 static int savingComment = FALSE;
1467 /* For zippy color lines of winboard
\r
1468 * cleanup for gcc compiler */
\r
1474 if (appData.debugMode) {
1476 fprintf(debugFP, "<ICS: ");
1477 show_bytes(debugFP, data, count);
1478 fprintf(debugFP, "\n");
1484 /* If last read ended with a partial line that we couldn't parse,
1485 prepend it to the new read and try again. */
1486 if (leftover_len > 0) {
1487 for (i=0; i<leftover_len; i++)
1488 buf[i] = buf[leftover_start + i];
1491 /* Copy in new characters, removing nulls and \r's */
1492 buf_len = leftover_len;
1493 for (i = 0; i < count; i++) {
1494 if (data[i] != NULLCHAR && data[i] != '\r')
1495 buf[buf_len++] = data[i];
1498 buf[buf_len] = NULLCHAR;
1499 next_out = leftover_len;
1503 while (i < buf_len) {
1504 /* Deal with part of the TELNET option negotiation
1505 protocol. We refuse to do anything beyond the
1506 defaults, except that we allow the WILL ECHO option,
1507 which ICS uses to turn off password echoing when we are
1508 directly connected to it. We reject this option
1509 if localLineEditing mode is on (always on in xboard)
1510 and we are talking to port 23, which might be a real
1511 telnet server that will try to keep WILL ECHO on permanently.
1513 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
1514 static int remoteEchoOption = FALSE; /* telnet ECHO option */
1515 unsigned char option;
1517 switch ((unsigned char) buf[++i]) {
1519 if (appData.debugMode)
1520 fprintf(debugFP, "\n<WILL ");
1521 switch (option = (unsigned char) buf[++i]) {
1523 if (appData.debugMode)
1524 fprintf(debugFP, "ECHO ");
1525 /* Reply only if this is a change, according
1526 to the protocol rules. */
1527 if (remoteEchoOption) break;
1528 if (appData.localLineEditing &&
1529 atoi(appData.icsPort) == TN_PORT) {
1530 TelnetRequest(TN_DONT, TN_ECHO);
1533 TelnetRequest(TN_DO, TN_ECHO);
1534 remoteEchoOption = TRUE;
1538 if (appData.debugMode)
1539 fprintf(debugFP, "%d ", option);
1540 /* Whatever this is, we don't want it. */
1541 TelnetRequest(TN_DONT, option);
1546 if (appData.debugMode)
1547 fprintf(debugFP, "\n<WONT ");
1548 switch (option = (unsigned char) buf[++i]) {
1550 if (appData.debugMode)
1551 fprintf(debugFP, "ECHO ");
1552 /* Reply only if this is a change, according
1553 to the protocol rules. */
1554 if (!remoteEchoOption) break;
1556 TelnetRequest(TN_DONT, TN_ECHO);
1557 remoteEchoOption = FALSE;
1560 if (appData.debugMode)
1561 fprintf(debugFP, "%d ", (unsigned char) option);
1562 /* Whatever this is, it must already be turned
1563 off, because we never agree to turn on
1564 anything non-default, so according to the
1565 protocol rules, we don't reply. */
1570 if (appData.debugMode)
1571 fprintf(debugFP, "\n<DO ");
1572 switch (option = (unsigned char) buf[++i]) {
1574 /* Whatever this is, we refuse to do it. */
1575 if (appData.debugMode)
1576 fprintf(debugFP, "%d ", option);
1577 TelnetRequest(TN_WONT, option);
1582 if (appData.debugMode)
1583 fprintf(debugFP, "\n<DONT ");
1584 switch (option = (unsigned char) buf[++i]) {
1586 if (appData.debugMode)
1587 fprintf(debugFP, "%d ", option);
1588 /* Whatever this is, we are already not doing
1589 it, because we never agree to do anything
1590 non-default, so according to the protocol
1591 rules, we don't reply. */
1596 if (appData.debugMode)
1597 fprintf(debugFP, "\n<IAC ");
1598 /* Doubled IAC; pass it through */
1602 if (appData.debugMode)
1603 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
1604 /* Drop all other telnet commands on the floor */
1607 if (oldi > next_out)
1608 SendToPlayer(&buf[next_out], oldi - next_out);
1614 /* OK, this at least will *usually* work */
1615 if (!loggedOn && looking_at(buf, &i, "ics%")) {
1619 if (loggedOn && !intfSet) {
1620 if (ics_type == ICS_ICC) {
1622 "/set-quietly interface %s\n/set-quietly style 12\n",
1625 } else if (ics_type == ICS_CHESSNET) {
1626 sprintf(str, "/style 12\n");
1628 strcpy(str, "alias $ @\n$set interface ");
1629 strcat(str, programVersion);
1630 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
1632 strcat(str, "$iset nohighlight 1\n");
1634 strcat(str, "$iset lock 1\n$style 12\n");
1640 if (started == STARTED_COMMENT) {
1641 /* Accumulate characters in comment */
1642 parse[parse_pos++] = buf[i];
1643 if (buf[i] == '\n') {
1644 parse[parse_pos] = NULLCHAR;
1645 AppendComment(forwardMostMove, StripHighlight(parse));
1646 started = STARTED_NONE;
1648 /* Don't match patterns against characters in chatter */
1653 if (started == STARTED_CHATTER) {
1654 if (buf[i] != '\n') {
1655 /* Don't match patterns against characters in chatter */
1659 started = STARTED_NONE;
1662 /* Kludge to deal with rcmd protocol */
1663 if (firstTime && looking_at(buf, &i, "\001*")) {
1664 DisplayFatalError(&buf[1], 0, 1);
1670 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
1673 if (appData.debugMode)
1674 fprintf(debugFP, "ics_type %d\n", ics_type);
1677 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
1678 ics_type = ICS_FICS;
1680 if (appData.debugMode)
1681 fprintf(debugFP, "ics_type %d\n", ics_type);
1684 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
1685 ics_type = ICS_CHESSNET;
1687 if (appData.debugMode)
1688 fprintf(debugFP, "ics_type %d\n", ics_type);
1693 (looking_at(buf, &i, "\"*\" is *a registered name") ||
1694 looking_at(buf, &i, "Logging you in as \"*\"") ||
1695 looking_at(buf, &i, "will be \"*\""))) {
1696 strcpy(ics_handle, star_match[0]);
1700 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
1702 sprintf(buf, "%s@%s", ics_handle, appData.icsHost);
1703 DisplayIcsInteractionTitle(buf);
1704 have_set_title = TRUE;
1707 /* skip finger notes */
1708 if (started == STARTED_NONE &&
1709 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
1710 (buf[i] == '1' && buf[i+1] == '0')) &&
1711 buf[i+2] == ':' && buf[i+3] == ' ') {
1712 started = STARTED_CHATTER;
1717 /* skip formula vars */
1718 if (started == STARTED_NONE &&
1719 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
1720 started = STARTED_CHATTER;
1726 if (appData.zippyTalk || appData.zippyPlay) {
1729 /* Backup adress for color zippy lines */
\r
1731 if (first.initDone && loggedOn == TRUE)
\r
1732 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
\r
1733 (appData.zippyPlay && ZippyMatch(buf, &backup)));
\r
1735 if (ZippyControl(buf, &i) ||
1736 ZippyConverse(buf, &i) ||
1737 (appData.zippyPlay && ZippyMatch(buf, &i))) {
1744 if (/* Don't color "message" or "messages" output */
1745 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
1746 looking_at(buf, &i, "*. * at *:*: ") ||
1747 looking_at(buf, &i, "--* (*:*): ") ||
1748 /* Regular tells and says */
1749 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
1750 looking_at(buf, &i, "* (your partner) tells you: ") ||
1751 looking_at(buf, &i, "* says: ") ||
1752 /* Message notifications (same color as tells) */
1753 looking_at(buf, &i, "* has left a message ") ||
1754 looking_at(buf, &i, "* just sent you a message:\n") ||
1755 /* Whispers and kibitzes */
1756 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
1757 looking_at(buf, &i, "* kibitzes: ") ||
1759 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
1761 if (tkind == 1 && strchr(star_match[0], ':')) {
1762 /* Avoid "tells you:" spoofs in channels */
1765 if (star_match[0][0] == NULLCHAR ||
1766 strchr(star_match[0], ' ') ||
1767 (tkind == 3 && strchr(star_match[1], ' '))) {
1768 /* Reject bogus matches */
1771 if (appData.colorize) {
1772 if (oldi > next_out) {
1773 SendToPlayer(&buf[next_out], oldi - next_out);
1778 Colorize(ColorTell, FALSE);
1779 curColor = ColorTell;
1782 Colorize(ColorKibitz, FALSE);
1783 curColor = ColorKibitz;
1786 p = strrchr(star_match[1], '(');
1793 Colorize(ColorChannel1, FALSE);
1794 curColor = ColorChannel1;
1796 Colorize(ColorChannel, FALSE);
1797 curColor = ColorChannel;
1801 curColor = ColorNormal;
1805 if (started == STARTED_NONE && appData.autoComment &&
1806 (gameMode == IcsObserving ||
1807 gameMode == IcsPlayingWhite ||
1808 gameMode == IcsPlayingBlack)) {
1809 parse_pos = i - oldi;
1810 memcpy(parse, &buf[oldi], parse_pos);
1811 parse[parse_pos] = NULLCHAR;
1812 started = STARTED_COMMENT;
1813 savingComment = TRUE;
1815 started = STARTED_CHATTER;
1816 savingComment = FALSE;
1823 if (looking_at(buf, &i, "* s-shouts: ") ||
1824 looking_at(buf, &i, "* c-shouts: ")) {
1825 if (appData.colorize) {
1826 if (oldi > next_out) {
1827 SendToPlayer(&buf[next_out], oldi - next_out);
1830 Colorize(ColorSShout, FALSE);
1831 curColor = ColorSShout;
1834 started = STARTED_CHATTER;
1838 if (looking_at(buf, &i, "--->")) {
1843 if (looking_at(buf, &i, "* shouts: ") ||
1844 looking_at(buf, &i, "--> ")) {
1845 if (appData.colorize) {
1846 if (oldi > next_out) {
1847 SendToPlayer(&buf[next_out], oldi - next_out);
1850 Colorize(ColorShout, FALSE);
1851 curColor = ColorShout;
1854 started = STARTED_CHATTER;
1858 if (looking_at( buf, &i, "Challenge:")) {
1859 if (appData.colorize) {
1860 if (oldi > next_out) {
1861 SendToPlayer(&buf[next_out], oldi - next_out);
1864 Colorize(ColorChallenge, FALSE);
1865 curColor = ColorChallenge;
1871 if (looking_at(buf, &i, "* offers you") ||
1872 looking_at(buf, &i, "* offers to be") ||
1873 looking_at(buf, &i, "* would like to") ||
1874 looking_at(buf, &i, "* requests to") ||
1875 looking_at(buf, &i, "Your opponent offers") ||
1876 looking_at(buf, &i, "Your opponent requests")) {
1878 if (appData.colorize) {
1879 if (oldi > next_out) {
1880 SendToPlayer(&buf[next_out], oldi - next_out);
1883 Colorize(ColorRequest, FALSE);
1884 curColor = ColorRequest;
1889 if (looking_at(buf, &i, "* (*) seeking")) {
1890 if (appData.colorize) {
1891 if (oldi > next_out) {
1892 SendToPlayer(&buf[next_out], oldi - next_out);
1895 Colorize(ColorSeek, FALSE);
1896 curColor = ColorSeek;
1901 if (looking_at(buf, &i, "\\ ")) {
1902 if (prevColor != ColorNormal) {
1903 if (oldi > next_out) {
1904 SendToPlayer(&buf[next_out], oldi - next_out);
1907 Colorize(prevColor, TRUE);
1908 curColor = prevColor;
1910 if (savingComment) {
1911 parse_pos = i - oldi;
1912 memcpy(parse, &buf[oldi], parse_pos);
1913 parse[parse_pos] = NULLCHAR;
1914 started = STARTED_COMMENT;
1916 started = STARTED_CHATTER;
1921 if (looking_at(buf, &i, "Black Strength :") ||
1922 looking_at(buf, &i, "<<< style 10 board >>>") ||
1923 looking_at(buf, &i, "<10>") ||
1924 looking_at(buf, &i, "#@#")) {
1925 /* Wrong board style */
1927 SendToICS(ics_prefix);
1928 SendToICS("set style 12\n");
1929 SendToICS(ics_prefix);
1930 SendToICS("refresh\n");
1934 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
1936 have_sent_ICS_logon = 1;
1940 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
1941 (looking_at(buf, &i, "\n<12> ") ||
1942 looking_at(buf, &i, "<12> "))) {
1944 if (oldi > next_out) {
1945 SendToPlayer(&buf[next_out], oldi - next_out);
1948 started = STARTED_BOARD;
1953 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
1954 looking_at(buf, &i, "<b1> ")) {
1955 if (oldi > next_out) {
1956 SendToPlayer(&buf[next_out], oldi - next_out);
1959 started = STARTED_HOLDINGS;
1964 if (looking_at(buf, &i, "* *vs. * *--- *")) {
1966 /* Header for a move list -- first line */
1968 switch (ics_getting_history) {
1972 case BeginningOfGame:
1973 /* User typed "moves" or "oldmoves" while we
1974 were idle. Pretend we asked for these
1975 moves and soak them up so user can step
1976 through them and/or save them.
1979 gameMode = IcsObserving;
1982 ics_getting_history = H_GOT_UNREQ_HEADER;
1984 case EditGame: /*?*/
1985 case EditPosition: /*?*/
1986 /* Should above feature work in these modes too? */
1987 /* For now it doesn't */
1988 ics_getting_history = H_GOT_UNWANTED_HEADER;
1991 ics_getting_history = H_GOT_UNWANTED_HEADER;
1996 /* Is this the right one? */
1997 if (gameInfo.white && gameInfo.black &&
1998 strcmp(gameInfo.white, star_match[0]) == 0 &&
1999 strcmp(gameInfo.black, star_match[2]) == 0) {
2001 ics_getting_history = H_GOT_REQ_HEADER;
2004 case H_GOT_REQ_HEADER:
2005 case H_GOT_UNREQ_HEADER:
2006 case H_GOT_UNWANTED_HEADER:
2007 case H_GETTING_MOVES:
2008 /* Should not happen */
2009 DisplayError(_("Error gathering move list: two headers"), 0);
2010 ics_getting_history = H_FALSE;
2014 /* Save player ratings into gameInfo if needed */
2015 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2016 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2017 (gameInfo.whiteRating == -1 ||
2018 gameInfo.blackRating == -1)) {
2020 gameInfo.whiteRating = string_to_rating(star_match[1]);
2021 gameInfo.blackRating = string_to_rating(star_match[3]);
2022 if (appData.debugMode)
2023 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2024 gameInfo.whiteRating, gameInfo.blackRating);
2029 if (looking_at(buf, &i,
2030 "* * match, initial time: * minute*, increment: * second")) {
2031 /* Header for a move list -- second line */
2032 /* Initial board will follow if this is a wild game */
2034 if (gameInfo.event != NULL) free(gameInfo.event);
2035 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2036 gameInfo.event = StrSave(str);
2037 gameInfo.variant = StringToVariant(gameInfo.event);
2041 if (looking_at(buf, &i, "Move ")) {
2042 /* Beginning of a move list */
2043 switch (ics_getting_history) {
2045 /* Normally should not happen */
2046 /* Maybe user hit reset while we were parsing */
2049 /* Happens if we are ignoring a move list that is not
2050 * the one we just requested. Common if the user
2051 * tries to observe two games without turning off
2054 case H_GETTING_MOVES:
2055 /* Should not happen */
2056 DisplayError(_("Error gathering move list: nested"), 0);
2057 ics_getting_history = H_FALSE;
2059 case H_GOT_REQ_HEADER:
2060 ics_getting_history = H_GETTING_MOVES;
2061 started = STARTED_MOVES;
2063 if (oldi > next_out) {
2064 SendToPlayer(&buf[next_out], oldi - next_out);
2067 case H_GOT_UNREQ_HEADER:
2068 ics_getting_history = H_GETTING_MOVES;
2069 started = STARTED_MOVES_NOHIDE;
2072 case H_GOT_UNWANTED_HEADER:
2073 ics_getting_history = H_FALSE;
2079 if (looking_at(buf, &i, "% ") ||
2080 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2081 && looking_at(buf, &i, "}*"))) {
2082 savingComment = FALSE;
2085 case STARTED_MOVES_NOHIDE:
2086 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2087 parse[parse_pos + i - oldi] = NULLCHAR;
2088 ParseGameHistory(parse);
2090 if (appData.zippyPlay && first.initDone) {
2091 FeedMovesToProgram(&first, forwardMostMove);
2092 if (gameMode == IcsPlayingWhite) {
2093 if (WhiteOnMove(forwardMostMove)) {
2094 if (first.sendTime) {
2095 if (first.useColors) {
2096 SendToProgram("black\n", &first);
2098 SendTimeRemaining(&first, TRUE);
2100 if (first.useColors) {
2101 SendToProgram("white\ngo\n", &first);
2103 SendToProgram("go\n", &first);
2105 first.maybeThinking = TRUE;
2107 if (first.usePlayother) {
2108 if (first.sendTime) {
2109 SendTimeRemaining(&first, TRUE);
2111 SendToProgram("playother\n", &first);
2117 } else if (gameMode == IcsPlayingBlack) {
2118 if (!WhiteOnMove(forwardMostMove)) {
2119 if (first.sendTime) {
2120 if (first.useColors) {
2121 SendToProgram("white\n", &first);
2123 SendTimeRemaining(&first, FALSE);
2125 if (first.useColors) {
2126 SendToProgram("black\ngo\n", &first);
2128 SendToProgram("go\n", &first);
2130 first.maybeThinking = TRUE;
2132 if (first.usePlayother) {
2133 if (first.sendTime) {
2134 SendTimeRemaining(&first, FALSE);
2136 SendToProgram("playother\n", &first);
2145 if (gameMode == IcsObserving && ics_gamenum == -1) {
2146 /* Moves came from oldmoves or moves command
2147 while we weren't doing anything else.
2149 currentMove = forwardMostMove;
2150 ClearHighlights();/*!!could figure this out*/
2151 flipView = appData.flipView;
2152 DrawPosition(FALSE, boards[currentMove]);
2153 DisplayBothClocks();
2154 sprintf(str, "%s vs. %s",
2155 gameInfo.white, gameInfo.black);
2159 /* Moves were history of an active game */
2160 if (gameInfo.resultDetails != NULL) {
2161 free(gameInfo.resultDetails);
2162 gameInfo.resultDetails = NULL;
2165 HistorySet(parseList, backwardMostMove,
2166 forwardMostMove, currentMove-1);
2167 DisplayMove(currentMove - 1);
2168 if (started == STARTED_MOVES) next_out = i;
2169 started = STARTED_NONE;
2170 ics_getting_history = H_FALSE;
2173 case STARTED_OBSERVE:
2174 started = STARTED_NONE;
2175 SendToICS(ics_prefix);
2176 SendToICS("refresh\n");
2185 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2186 started == STARTED_HOLDINGS ||
2187 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2188 /* Accumulate characters in move list or board */
2189 parse[parse_pos++] = buf[i];
2192 /* Start of game messages. Mostly we detect start of game
2193 when the first board image arrives. On some versions
2194 of the ICS, though, we need to do a "refresh" after starting
2195 to observe in order to get the current board right away. */
2196 if (looking_at(buf, &i, "Adding game * to observation list")) {
2197 started = STARTED_OBSERVE;
2201 /* Handle auto-observe */
2202 if (appData.autoObserve &&
2203 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2204 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2206 /* Choose the player that was highlighted, if any. */
2207 if (star_match[0][0] == '\033' ||
2208 star_match[1][0] != '\033') {
2209 player = star_match[0];
2211 player = star_match[2];
2213 sprintf(str, "%sobserve %s\n",
2214 ics_prefix, StripHighlightAndTitle(player));
2217 /* Save ratings from notify string */
2218 strcpy(player1Name, star_match[0]);
2219 player1Rating = string_to_rating(star_match[1]);
2220 strcpy(player2Name, star_match[2]);
2221 player2Rating = string_to_rating(star_match[3]);
2223 if (appData.debugMode)
2225 "Ratings from 'Game notification:' %s %d, %s %d\n",
2226 player1Name, player1Rating,
2227 player2Name, player2Rating);
2232 /* Deal with automatic examine mode after a game,
2233 and with IcsObserving -> IcsExamining transition */
2234 if (looking_at(buf, &i, "Entering examine mode for game *") ||
2235 looking_at(buf, &i, "has made you an examiner of game *")) {
2237 int gamenum = atoi(star_match[0]);
2238 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2239 gamenum == ics_gamenum) {
2240 /* We were already playing or observing this game;
2241 no need to refetch history */
2242 gameMode = IcsExamining;
2244 pauseExamForwardMostMove = forwardMostMove;
2245 } else if (currentMove < forwardMostMove) {
2246 ForwardInner(forwardMostMove);
2249 /* I don't think this case really can happen */
2250 SendToICS(ics_prefix);
2251 SendToICS("refresh\n");
2256 /* Error messages */
2257 if (ics_user_moved) {
2258 if (looking_at(buf, &i, "Illegal move") ||
2259 looking_at(buf, &i, "Not a legal move") ||
2260 looking_at(buf, &i, "Your king is in check") ||
2261 looking_at(buf, &i, "It isn't your turn") ||
2262 looking_at(buf, &i, "It is not your move")) {
2265 if (forwardMostMove > backwardMostMove) {
2266 currentMove = --forwardMostMove;
2267 DisplayMove(currentMove - 1); /* before DMError */
2268 DisplayMoveError("Illegal move (rejected by ICS)");
2269 DrawPosition(FALSE, boards[currentMove]);
2271 DisplayBothClocks();
2277 if (looking_at(buf, &i, "still have time") ||
2278 looking_at(buf, &i, "not out of time") ||
2279 looking_at(buf, &i, "either player is out of time") ||
2280 looking_at(buf, &i, "has timeseal; checking")) {
2281 /* We must have called his flag a little too soon */
2282 whiteFlag = blackFlag = FALSE;
2286 if (looking_at(buf, &i, "added * seconds to") ||
2287 looking_at(buf, &i, "seconds were added to")) {
2288 /* Update the clocks */
2289 SendToICS(ics_prefix);
2290 SendToICS("refresh\n");
2294 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
2295 ics_clock_paused = TRUE;
2300 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
2301 ics_clock_paused = FALSE;
2306 /* Grab player ratings from the Creating: message.
2307 Note we have to check for the special case when
2308 the ICS inserts things like [white] or [black]. */
2309 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
2310 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
2312 0 player 1 name (not necessarily white)
2314 2 empty, white, or black (IGNORED)
2315 3 player 2 name (not necessarily black)
2318 The names/ratings are sorted out when the game
2319 actually starts (below).
2321 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
2322 player1Rating = string_to_rating(star_match[1]);
2323 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
2324 player2Rating = string_to_rating(star_match[4]);
2326 if (appData.debugMode)
2328 "Ratings from 'Creating:' %s %d, %s %d\n",
2329 player1Name, player1Rating,
2330 player2Name, player2Rating);
2335 /* Improved generic start/end-of-game messages */
2336 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
2337 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
2338 /* If tkind == 0: */
2339 /* star_match[0] is the game number */
2340 /* [1] is the white player's name */
2341 /* [2] is the black player's name */
2342 /* For end-of-game: */
2343 /* [3] is the reason for the game end */
2344 /* [4] is a PGN end game-token, preceded by " " */
2345 /* For start-of-game: */
2346 /* [3] begins with "Creating" or "Continuing" */
2347 /* [4] is " *" or empty (don't care). */
2348 int gamenum = atoi(star_match[0]);
2349 char *whitename, *blackname, *why, *endtoken;
2350 ChessMove endtype = (ChessMove) 0;
2353 whitename = star_match[1];
2354 blackname = star_match[2];
2355 why = star_match[3];
2356 endtoken = star_match[4];
2358 whitename = star_match[1];
2359 blackname = star_match[3];
2360 why = star_match[5];
2361 endtoken = star_match[6];
2364 /* Game start messages */
2365 if (strncmp(why, "Creating ", 9) == 0 ||
2366 strncmp(why, "Continuing ", 11) == 0) {
2367 gs_gamenum = gamenum;
2368 strcpy(gs_kind, strchr(why, ' ') + 1);
2370 if (appData.zippyPlay) {
2371 ZippyGameStart(whitename, blackname);
2377 /* Game end messages */
2378 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
2379 ics_gamenum != gamenum) {
2382 while (endtoken[0] == ' ') endtoken++;
2383 switch (endtoken[0]) {
2386 endtype = GameUnfinished;
2389 endtype = BlackWins;
2392 if (endtoken[1] == '/')
2393 endtype = GameIsDrawn;
2395 endtype = WhiteWins;
2398 GameEnds(endtype, why, GE_ICS);
2400 if (appData.zippyPlay && first.initDone) {
2401 ZippyGameEnd(endtype, why);
2402 if (first.pr == NULL) {
2403 /* Start the next process early so that we'll
2404 be ready for the next challenge */
2405 StartChessProgram(&first);
2407 /* Send "new" early, in case this command takes
2408 a long time to finish, so that we'll be ready
2409 for the next challenge. */
2416 if (looking_at(buf, &i, "Removing game * from observation") ||
2417 looking_at(buf, &i, "no longer observing game *") ||
2418 looking_at(buf, &i, "Game * (*) has no examiners")) {
2419 if (gameMode == IcsObserving &&
2420 atoi(star_match[0]) == ics_gamenum)
2425 ics_user_moved = FALSE;
2430 if (looking_at(buf, &i, "no longer examining game *")) {
2431 if (gameMode == IcsExamining &&
2432 atoi(star_match[0]) == ics_gamenum)
2436 ics_user_moved = FALSE;
2441 /* Advance leftover_start past any newlines we find,
2442 so only partial lines can get reparsed */
2443 if (looking_at(buf, &i, "\n")) {
2444 prevColor = curColor;
2445 if (curColor != ColorNormal) {
2446 if (oldi > next_out) {
2447 SendToPlayer(&buf[next_out], oldi - next_out);
2450 Colorize(ColorNormal, FALSE);
2451 curColor = ColorNormal;
2453 if (started == STARTED_BOARD) {
2454 started = STARTED_NONE;
2455 parse[parse_pos] = NULLCHAR;
2456 ParseBoard12(parse);
2459 /* Send premove here */
2460 if (appData.premove) {
2462 if (currentMove == 0 &&
2463 gameMode == IcsPlayingWhite &&
2464 appData.premoveWhite) {
2465 sprintf(str, "%s%s\n", ics_prefix,
2466 appData.premoveWhiteText);
2467 if (appData.debugMode)
2468 fprintf(debugFP, "Sending premove:\n");
2470 } else if (currentMove == 1 &&
2471 gameMode == IcsPlayingBlack &&
2472 appData.premoveBlack) {
2473 sprintf(str, "%s%s\n", ics_prefix,
2474 appData.premoveBlackText);
2475 if (appData.debugMode)
2476 fprintf(debugFP, "Sending premove:\n");
2478 } else if (gotPremove) {
2480 ClearPremoveHighlights();
2481 if (appData.debugMode)
2482 fprintf(debugFP, "Sending premove:\n");
2483 UserMoveEvent(premoveFromX, premoveFromY,
2484 premoveToX, premoveToY,
2489 /* Usually suppress following prompt */
2490 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
2491 if (looking_at(buf, &i, "*% ")) {
2492 savingComment = FALSE;
2496 } else if (started == STARTED_HOLDINGS) {
2498 char new_piece[MSG_SIZ];
2499 started = STARTED_NONE;
2500 parse[parse_pos] = NULLCHAR;
2501 if (appData.debugMode)
2502 fprintf(debugFP, "Parsing holdings: %s\n", parse);
2503 if (sscanf(parse, " game %d", &gamenum) == 1 &&
2504 gamenum == ics_gamenum) {
2505 if (gameInfo.variant == VariantNormal) {
2506 gameInfo.variant = VariantCrazyhouse; /*temp guess*/
2507 /* Get a move list just to see the header, which
2508 will tell us whether this is really bug or zh */
2509 if (ics_getting_history == H_FALSE) {
2510 ics_getting_history = H_REQUESTED;
2511 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
2515 new_piece[0] = NULLCHAR;
2516 sscanf(parse, "game %d white [%s black [%s <- %s",
2517 &gamenum, white_holding, black_holding,
2519 white_holding[strlen(white_holding)-1] = NULLCHAR;
2520 black_holding[strlen(black_holding)-1] = NULLCHAR;
2522 if (appData.zippyPlay && first.initDone) {
2523 ZippyHoldings(white_holding, black_holding,
2527 if (tinyLayout || smallLayout) {
2528 char wh[16], bh[16];
2529 PackHolding(wh, white_holding);
2530 PackHolding(bh, black_holding);
2531 sprintf(str, "[%s-%s] %s-%s", wh, bh,
2532 gameInfo.white, gameInfo.black);
2534 sprintf(str, "%s [%s] vs. %s [%s]",
2535 gameInfo.white, white_holding,
2536 gameInfo.black, black_holding);
2538 DrawPosition(FALSE, NULL);
2541 /* Suppress following prompt */
2542 if (looking_at(buf, &i, "*% ")) {
2543 savingComment = FALSE;
2550 i++; /* skip unparsed character and loop back */
2553 if (started != STARTED_MOVES && started != STARTED_BOARD &&
2554 started != STARTED_HOLDINGS && i > next_out) {
2555 SendToPlayer(&buf[next_out], i - next_out);
2559 leftover_len = buf_len - leftover_start;
2560 /* if buffer ends with something we couldn't parse,
2561 reparse it after appending the next read */
2563 } else if (count == 0) {
2564 RemoveInputSource(isr);
2565 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
2567 DisplayFatalError(_("Error reading from ICS"), error, 1);
2572 /* Board style 12 looks like this:
2574 <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
2576 * The "<12> " is stripped before it gets to this routine. The two
2577 * trailing 0's (flip state and clock ticking) are later addition, and
2578 * some chess servers may not have them, or may have only the first.
2579 * Additional trailing fields may be added in the future.
2582 #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"
2584 #define RELATION_OBSERVING_PLAYED 0
2585 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
2586 #define RELATION_PLAYING_MYMOVE 1
2587 #define RELATION_PLAYING_NOTMYMOVE -1
2588 #define RELATION_EXAMINING 2
2589 #define RELATION_ISOLATED_BOARD -3
2590 #define RELATION_STARTING_POSITION -4 /* FICS only */
2593 ParseBoard12(string)
2596 GameMode newGameMode;
2597 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
2598 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
2599 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
2600 char to_play, board_chars[72];
2601 char move_str[500], str[500], elapsed_time[500];
2602 char black[32], white[32];
2604 int prevMove = currentMove;
2607 int fromX, fromY, toX, toY;
2610 fromX = fromY = toX = toY = -1;
2614 if (appData.debugMode)
2615 fprintf(debugFP, _("Parsing board: %s\n"), string);
2617 move_str[0] = NULLCHAR;
2618 elapsed_time[0] = NULLCHAR;
2619 n = sscanf(string, PATTERN, board_chars, &to_play, &double_push,
2620 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
2621 &gamenum, white, black, &relation, &basetime, &increment,
2622 &white_stren, &black_stren, &white_time, &black_time,
2623 &moveNum, str, elapsed_time, move_str, &ics_flip,
2627 sprintf(str, _("Failed to parse board string:\n\"%s\""), string);
2628 DisplayError(str, 0);
2632 /* Convert the move number to internal form */
2633 moveNum = (moveNum - 1) * 2;
2634 if (to_play == 'B') moveNum++;
2635 if (moveNum >= MAX_MOVES) {
2636 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
2642 case RELATION_OBSERVING_PLAYED:
2643 case RELATION_OBSERVING_STATIC:
2644 if (gamenum == -1) {
2645 /* Old ICC buglet */
2646 relation = RELATION_OBSERVING_STATIC;
2648 newGameMode = IcsObserving;
2650 case RELATION_PLAYING_MYMOVE:
2651 case RELATION_PLAYING_NOTMYMOVE:
2653 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
2654 IcsPlayingWhite : IcsPlayingBlack;
2656 case RELATION_EXAMINING:
2657 newGameMode = IcsExamining;
2659 case RELATION_ISOLATED_BOARD:
2661 /* Just display this board. If user was doing something else,
2662 we will forget about it until the next board comes. */
2663 newGameMode = IcsIdle;
2665 case RELATION_STARTING_POSITION:
2666 newGameMode = gameMode;
2670 /* Modify behavior for initial board display on move listing
2673 switch (ics_getting_history) {
2677 case H_GOT_REQ_HEADER:
2678 case H_GOT_UNREQ_HEADER:
2679 /* This is the initial position of the current game */
2680 gamenum = ics_gamenum;
2681 moveNum = 0; /* old ICS bug workaround */
2682 if (to_play == 'B') {
2683 startedFromSetupPosition = TRUE;
2684 blackPlaysFirst = TRUE;
2686 if (forwardMostMove == 0) forwardMostMove = 1;
2687 if (backwardMostMove == 0) backwardMostMove = 1;
2688 if (currentMove == 0) currentMove = 1;
2690 newGameMode = gameMode;
2691 relation = RELATION_STARTING_POSITION; /* ICC needs this */
2693 case H_GOT_UNWANTED_HEADER:
2694 /* This is an initial board that we don't want */
2696 case H_GETTING_MOVES:
2697 /* Should not happen */
2698 DisplayError(_("Error gathering move list: extra board"), 0);
2699 ics_getting_history = H_FALSE;
2703 /* Take action if this is the first board of a new game, or of a
2704 different game than is currently being displayed. */
2705 if (gamenum != ics_gamenum || newGameMode != gameMode ||
2706 relation == RELATION_ISOLATED_BOARD) {
2708 /* Forget the old game and get the history (if any) of the new one */
2709 if (gameMode != BeginningOfGame) {
2713 if (appData.autoRaiseBoard) BoardToTop();
2715 if (gamenum == -1) {
2716 newGameMode = IcsIdle;
2717 } else if (moveNum > 0 && newGameMode != IcsIdle &&
2718 appData.getMoveList) {
2719 /* Need to get game history */
2720 ics_getting_history = H_REQUESTED;
2721 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
2725 /* Initially flip the board to have black on the bottom if playing
2726 black or if the ICS flip flag is set, but let the user change
2727 it with the Flip View button. */
2728 flipView = appData.autoFlipView ?
2729 (newGameMode == IcsPlayingBlack) || ics_flip :
2732 /* Done with values from previous mode; copy in new ones */
2733 gameMode = newGameMode;
2735 ics_gamenum = gamenum;
2736 if (gamenum == gs_gamenum) {
2737 int klen = strlen(gs_kind);
2738 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
2739 sprintf(str, "ICS %s", gs_kind);
2740 gameInfo.event = StrSave(str);
2742 gameInfo.event = StrSave("ICS game");
2744 gameInfo.site = StrSave(appData.icsHost);
2745 gameInfo.date = PGNDate();
2746 gameInfo.round = StrSave("-");
2747 gameInfo.white = StrSave(white);
2748 gameInfo.black = StrSave(black);
2749 timeControl = basetime * 60 * 1000;
2750 timeIncrement = increment * 1000;
2751 movesPerSession = 0;
2752 gameInfo.timeControl = TimeControlTagValue();
2753 gameInfo.variant = StringToVariant(gameInfo.event);
2755 /* Do we have the ratings? */
2756 if (strcmp(player1Name, white) == 0 &&
2757 strcmp(player2Name, black) == 0) {
2758 if (appData.debugMode)
2759 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
2760 player1Rating, player2Rating);
2761 gameInfo.whiteRating = player1Rating;
2762 gameInfo.blackRating = player2Rating;
2763 } else if (strcmp(player2Name, white) == 0 &&
2764 strcmp(player1Name, black) == 0) {
2765 if (appData.debugMode)
2766 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
2767 player2Rating, player1Rating);
2768 gameInfo.whiteRating = player2Rating;
2769 gameInfo.blackRating = player1Rating;
2771 player1Name[0] = player2Name[0] = NULLCHAR;
2773 /* Silence shouts if requested */
2774 if (appData.quietPlay &&
2775 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
2776 SendToICS(ics_prefix);
2777 SendToICS("set shout 0\n");
2781 /* Deal with midgame name changes */
2783 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
2784 if (gameInfo.white) free(gameInfo.white);
2785 gameInfo.white = StrSave(white);
2787 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
2788 if (gameInfo.black) free(gameInfo.black);
2789 gameInfo.black = StrSave(black);
2793 /* Throw away game result if anything actually changes in examine mode */
2794 if (gameMode == IcsExamining && !newGame) {
2795 gameInfo.result = GameUnfinished;
2796 if (gameInfo.resultDetails != NULL) {
2797 free(gameInfo.resultDetails);
2798 gameInfo.resultDetails = NULL;
2802 /* In pausing && IcsExamining mode, we ignore boards coming
2803 in if they are in a different variation than we are. */
2804 if (pauseExamInvalid) return;
2805 if (pausing && gameMode == IcsExamining) {
2806 if (moveNum <= pauseExamForwardMostMove) {
2807 pauseExamInvalid = TRUE;
2808 forwardMostMove = pauseExamForwardMostMove;
2813 /* Parse the board */
2814 for (k = 0; k < 8; k++)
2815 for (j = 0; j < 8; j++)
2816 board[k][j] = CharToPiece(board_chars[(7-k)*9 + j]);
2817 CopyBoard(boards[moveNum], board);
2819 startedFromSetupPosition =
2820 !CompareBoards(board, initialPosition);
2823 if (ics_getting_history == H_GOT_REQ_HEADER ||
2824 ics_getting_history == H_GOT_UNREQ_HEADER) {
2825 /* This was an initial position from a move list, not
2826 the current position */
2830 /* Update currentMove and known move number limits */
2831 newMove = newGame || moveNum > forwardMostMove;
2833 forwardMostMove = backwardMostMove = currentMove = moveNum;
2834 if (gameMode == IcsExamining && moveNum == 0) {
2835 /* Workaround for ICS limitation: we are not told the wild
2836 type when starting to examine a game. But if we ask for
2837 the move list, the move list header will tell us */
2838 ics_getting_history = H_REQUESTED;
2839 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
2842 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
2843 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
2844 forwardMostMove = moveNum;
2845 if (!pausing || currentMove > forwardMostMove)
2846 currentMove = forwardMostMove;
2848 /* New part of history that is not contiguous with old part */
2849 if (pausing && gameMode == IcsExamining) {
2850 pauseExamInvalid = TRUE;
2851 forwardMostMove = pauseExamForwardMostMove;
2854 forwardMostMove = backwardMostMove = currentMove = moveNum;
2855 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
2856 ics_getting_history = H_REQUESTED;
2857 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
2862 /* Update the clocks */
2863 if (strchr(elapsed_time, '.')) {
2865 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
2866 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
2868 /* Time is in seconds */
2869 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
2870 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
2875 if (appData.zippyPlay && newGame &&
2876 gameMode != IcsObserving && gameMode != IcsIdle &&
2877 gameMode != IcsExamining)
2878 ZippyFirstBoard(moveNum, basetime, increment);
2881 /* Put the move on the move list, first converting
2882 to canonical algebraic form. */
2884 if (moveNum <= backwardMostMove) {
2885 /* We don't know what the board looked like before
2887 strcpy(parseList[moveNum - 1], move_str);
2888 strcat(parseList[moveNum - 1], " ");
2889 strcat(parseList[moveNum - 1], elapsed_time);
2890 moveList[moveNum - 1][0] = NULLCHAR;
2891 } else if (ParseOneMove(move_str, moveNum - 1, &moveType,
2892 &fromX, &fromY, &toX, &toY, &promoChar)) {
2893 (void) CoordsToAlgebraic(boards[moveNum - 1],
2894 PosFlags(moveNum - 1), EP_UNKNOWN,
2895 fromY, fromX, toY, toX, promoChar,
2896 parseList[moveNum-1]);
2897 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN)){
2903 strcat(parseList[moveNum - 1], "+");
2906 strcat(parseList[moveNum - 1], "#");
2909 strcat(parseList[moveNum - 1], " ");
2910 strcat(parseList[moveNum - 1], elapsed_time);
2911 /* currentMoveString is set as a side-effect of ParseOneMove */
2912 strcpy(moveList[moveNum - 1], currentMoveString);
2913 strcat(moveList[moveNum - 1], "\n");
2914 } else if (strcmp(move_str, "none") == 0) {
2915 /* Again, we don't know what the board looked like;
2916 this is really the start of the game. */
2917 parseList[moveNum - 1][0] = NULLCHAR;
2918 moveList[moveNum - 1][0] = NULLCHAR;
2919 backwardMostMove = moveNum;
2920 startedFromSetupPosition = TRUE;
2921 fromX = fromY = toX = toY = -1;
2923 /* Move from ICS was illegal!? Punt. */
2925 if (appData.testLegality && appData.debugMode) {
2926 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
2927 DisplayError(str, 0);
2930 strcpy(parseList[moveNum - 1], move_str);
2931 strcat(parseList[moveNum - 1], " ");
2932 strcat(parseList[moveNum - 1], elapsed_time);
2933 moveList[moveNum - 1][0] = NULLCHAR;
2934 fromX = fromY = toX = toY = -1;
2938 /* Send move to chess program (BEFORE animating it). */
2939 if (appData.zippyPlay && !newGame && newMove &&
2940 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
2942 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
2943 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
2944 if (moveList[moveNum - 1][0] == NULLCHAR) {
2945 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
2947 DisplayError(str, 0);
2949 if (first.sendTime) {
2950 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
2952 SendMoveToProgram(moveNum - 1, &first);
2955 if (first.useColors) {
2956 SendToProgram(gameMode == IcsPlayingWhite ?
2958 "black\ngo\n", &first);
2960 SendToProgram("go\n", &first);
2962 first.maybeThinking = TRUE;
2965 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
2966 if (moveList[moveNum - 1][0] == NULLCHAR) {
2967 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
2968 DisplayError(str, 0);
2970 SendMoveToProgram(moveNum - 1, &first);
2977 if (moveNum > 0 && !gotPremove) {
2978 /* If move comes from a remote source, animate it. If it
2979 isn't remote, it will have already been animated. */
2980 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
2981 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
2983 if (!pausing && appData.highlightLastMove) {
2984 SetHighlights(fromX, fromY, toX, toY);
2988 /* Start the clocks */
2989 whiteFlag = blackFlag = FALSE;
2990 appData.clockMode = !(basetime == 0 && increment == 0);
2992 ics_clock_paused = TRUE;
2994 } else if (ticking == 1) {
2995 ics_clock_paused = FALSE;
2997 if (gameMode == IcsIdle ||
2998 relation == RELATION_OBSERVING_STATIC ||
2999 relation == RELATION_EXAMINING ||
3001 DisplayBothClocks();
3005 /* Display opponents and material strengths */
3006 if (gameInfo.variant != VariantBughouse &&
3007 gameInfo.variant != VariantCrazyhouse) {
3008 if (tinyLayout || smallLayout) {
3009 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3010 gameInfo.white, white_stren, gameInfo.black, black_stren,
3011 basetime, increment);
3013 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3014 gameInfo.white, white_stren, gameInfo.black, black_stren,
3015 basetime, increment);
3021 /* Display the board */
3024 if (appData.premove)
3026 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3027 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3028 ClearPremoveHighlights();
3030 DrawPosition(FALSE, boards[currentMove]);
3031 DisplayMove(moveNum - 1);
3032 if (appData.ringBellAfterMoves && !ics_user_moved)
3036 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3043 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3044 ics_getting_history = H_REQUESTED;
3045 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3051 AnalysisPeriodicEvent(force)
3054 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3055 && !force) || !appData.periodicUpdates)
3058 /* Send . command to Crafty to collect stats */
3059 SendToProgram(".\n", &first);
3061 /* Don't send another until we get a response (this makes
3062 us stop sending to old Crafty's which don't understand
3063 the "." command (sending illegal cmds resets node count & time,
3064 which looks bad)) */
3065 programStats.ok_to_send = 0;
3069 SendMoveToProgram(moveNum, cps)
3071 ChessProgramState *cps;
3074 if (cps->useUsermove) {
3075 SendToProgram("usermove ", cps);
3079 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
3080 int len = space - parseList[moveNum];
3081 memcpy(buf, parseList[moveNum], len);
3083 buf[len] = NULLCHAR;
3085 sprintf(buf, "%s\n", parseList[moveNum]);
3087 SendToProgram(buf, cps);
3089 SendToProgram(moveList[moveNum], cps);
3094 SendMoveToICS(moveType, fromX, fromY, toX, toY)
3096 int fromX, fromY, toX, toY;
3098 char user_move[MSG_SIZ];
3102 sprintf(user_move, "say Internal error; bad moveType %d (%d,%d-%d,%d)",
3103 (int)moveType, fromX, fromY, toX, toY);
3104 DisplayError(user_move + strlen("say "), 0);
3106 case WhiteKingSideCastle:
3107 case BlackKingSideCastle:
3108 case WhiteQueenSideCastleWild:
3109 case BlackQueenSideCastleWild:
3110 sprintf(user_move, "o-o\n");
3112 case WhiteQueenSideCastle:
3113 case BlackQueenSideCastle:
3114 case WhiteKingSideCastleWild:
3115 case BlackKingSideCastleWild:
3116 sprintf(user_move, "o-o-o\n");
3118 case WhitePromotionQueen:
3119 case BlackPromotionQueen:
3120 case WhitePromotionRook:
3121 case BlackPromotionRook:
3122 case WhitePromotionBishop:
3123 case BlackPromotionBishop:
3124 case WhitePromotionKnight:
3125 case BlackPromotionKnight:
3126 case WhitePromotionKing:
3127 case BlackPromotionKing:
3128 sprintf(user_move, "%c%c%c%c=%c\n",
3129 'a' + fromX, '1' + fromY, 'a' + toX, '1' + toY,
3130 PieceToChar(PromoPiece(moveType)));
3134 sprintf(user_move, "%c@%c%c\n",
3135 ToUpper(PieceToChar((ChessSquare) fromX)),
3136 'a' + toX, '1' + toY);
3139 case WhiteCapturesEnPassant:
3140 case BlackCapturesEnPassant:
3141 case IllegalMove: /* could be a variant we don't quite understand */
3142 sprintf(user_move, "%c%c%c%c\n",
3143 'a' + fromX, '1' + fromY, 'a' + toX, '1' + toY);
3146 SendToICS(user_move);
3150 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
3155 if (rf == DROP_RANK) {
3156 sprintf(move, "%c@%c%c\n",
3157 ToUpper(PieceToChar((ChessSquare) ff)), 'a' + ft, '1' + rt);
3159 if (promoChar == 'x' || promoChar == NULLCHAR) {
3160 sprintf(move, "%c%c%c%c\n",
3161 'a' + ff, '1' + rf, 'a' + ft, '1' + rt);
3163 sprintf(move, "%c%c%c%c%c\n",
3164 'a' + ff, '1' + rf, 'a' + ft, '1' + rt, promoChar);
3170 ProcessICSInitScript(f)
3175 while (fgets(buf, MSG_SIZ, f)) {
3176 SendToICSDelayed(buf,(long)appData.msLoginDelay);
3183 /* Parser for moves from gnuchess, ICS, or user typein box */
3185 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
3188 ChessMove *moveType;
3189 int *fromX, *fromY, *toX, *toY;
3192 *moveType = yylexstr(moveNum, move);
3193 switch (*moveType) {
3194 case WhitePromotionQueen:
3195 case BlackPromotionQueen:
3196 case WhitePromotionRook:
3197 case BlackPromotionRook:
3198 case WhitePromotionBishop:
3199 case BlackPromotionBishop:
3200 case WhitePromotionKnight:
3201 case BlackPromotionKnight:
3202 case WhitePromotionKing:
3203 case BlackPromotionKing:
3205 case WhiteCapturesEnPassant:
3206 case BlackCapturesEnPassant:
3207 case WhiteKingSideCastle:
3208 case WhiteQueenSideCastle:
3209 case BlackKingSideCastle:
3210 case BlackQueenSideCastle:
3211 case WhiteKingSideCastleWild:
3212 case WhiteQueenSideCastleWild:
3213 case BlackKingSideCastleWild:
3214 case BlackQueenSideCastleWild:
3215 case IllegalMove: /* bug or odd chess variant */
3216 *fromX = currentMoveString[0] - 'a';
3217 *fromY = currentMoveString[1] - '1';
3218 *toX = currentMoveString[2] - 'a';
3219 *toY = currentMoveString[3] - '1';
3220 *promoChar = currentMoveString[4];
3221 if (*fromX < 0 || *fromX > 7 || *fromY < 0 || *fromY > 7 ||
3222 *toX < 0 || *toX > 7 || *toY < 0 || *toY > 7) {
3223 *fromX = *fromY = *toX = *toY = 0;
3226 if (appData.testLegality) {
3227 return (*moveType != IllegalMove);
3229 return !(fromX == fromY && toX == toY);
3234 *fromX = *moveType == WhiteDrop ?
3235 (int) CharToPiece(ToUpper(currentMoveString[0])) :
3236 (int) CharToPiece(ToLower(currentMoveString[0]));
3238 *toX = currentMoveString[2] - 'a';
3239 *toY = currentMoveString[3] - '1';
3240 *promoChar = NULLCHAR;
3244 case ImpossibleMove:
3245 case (ChessMove) 0: /* end of file */
3255 *fromX = *fromY = *toX = *toY = 0;
3256 *promoChar = NULLCHAR;
3263 InitPosition(redraw)
3266 currentMove = forwardMostMove = backwardMostMove = 0;
3267 switch (gameInfo.variant) {
3269 CopyBoard(boards[0], initialPosition);
3271 case VariantTwoKings:
3272 CopyBoard(boards[0], twoKingsPosition);
3273 startedFromSetupPosition = TRUE;
3275 case VariantWildCastle:
3276 CopyBoard(boards[0], initialPosition);
3277 /* !!?shuffle with kings guaranteed to be on d or e file */
3279 case VariantNoCastle:
3280 CopyBoard(boards[0], initialPosition);
3281 /* !!?unconstrained back-rank shuffle */
3283 case VariantFischeRandom:
3284 CopyBoard(boards[0], initialPosition);
3285 /* !!shuffle according to FR rules */
3289 DrawPosition(FALSE, boards[currentMove]);
3293 SendBoard(cps, moveNum)
3294 ChessProgramState *cps;
3297 char message[MSG_SIZ];
3299 if (cps->useSetboard) {
3300 char* fen = PositionToFEN(moveNum);
3301 sprintf(message, "setboard %s\n", fen);
3302 SendToProgram(message, cps);
3308 /* Kludge to set black to move, avoiding the troublesome and now
3309 * deprecated "black" command.
3311 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
3313 SendToProgram("edit\n", cps);
3314 SendToProgram("#\n", cps);
3315 for (i = BOARD_SIZE - 1; i >= 0; i--) {
3316 bp = &boards[moveNum][i][0];
3317 for (j = 0; j < BOARD_SIZE; j++, bp++) {
3318 if ((int) *bp < (int) BlackPawn) {
3319 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
3321 SendToProgram(message, cps);
3326 SendToProgram("c\n", cps);
3327 for (i = BOARD_SIZE - 1; i >= 0; i--) {
3328 bp = &boards[moveNum][i][0];
3329 for (j = 0; j < BOARD_SIZE; j++, bp++) {
3330 if (((int) *bp != (int) EmptySquare)
3331 && ((int) *bp >= (int) BlackPawn)) {
3332 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
3334 SendToProgram(message, cps);
3339 SendToProgram(".\n", cps);
3344 IsPromotion(fromX, fromY, toX, toY)
3345 int fromX, fromY, toX, toY;
3347 return gameMode != EditPosition &&
3348 fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0 &&
3349 ((boards[currentMove][fromY][fromX] == WhitePawn && toY == 7) ||
3350 (boards[currentMove][fromY][fromX] == BlackPawn && toY == 0));
3355 PieceForSquare (x, y)
3359 if (x < 0 || x >= BOARD_SIZE || y < 0 || y >= BOARD_SIZE)
3362 return boards[currentMove][y][x];
3366 OKToStartUserMove(x, y)
3369 ChessSquare from_piece;
3372 if (matchMode) return FALSE;
3373 if (gameMode == EditPosition) return TRUE;
3375 if (x >= 0 && y >= 0)
3376 from_piece = boards[currentMove][y][x];
3378 from_piece = EmptySquare;
3380 if (from_piece == EmptySquare) return FALSE;
3382 white_piece = (int)from_piece >= (int)WhitePawn &&
3383 (int)from_piece <= (int)WhiteKing;
3386 case PlayFromGameFile:
3388 case TwoMachinesPlay:
3396 case MachinePlaysWhite:
3397 case IcsPlayingBlack:
3398 if (appData.zippyPlay) return FALSE;
3400 DisplayMoveError(_("You are playing Black"));
3405 case MachinePlaysBlack:
3406 case IcsPlayingWhite:
3407 if (appData.zippyPlay) return FALSE;
3409 DisplayMoveError(_("You are playing White"));
3415 if (!white_piece && WhiteOnMove(currentMove)) {
3416 DisplayMoveError(_("It is White's turn"));
3419 if (white_piece && !WhiteOnMove(currentMove)) {
3420 DisplayMoveError(_("It is Black's turn"));
3423 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
3424 /* Editing correspondence game history */
3425 /* Could disallow this or prompt for confirmation */
3428 if (currentMove < forwardMostMove) {
3429 /* Discarding moves */
3430 /* Could prompt for confirmation here,
3431 but I don't think that's such a good idea */
3432 forwardMostMove = currentMove;
3436 case BeginningOfGame:
3437 if (appData.icsActive) return FALSE;
3438 if (!appData.noChessProgram) {
3440 DisplayMoveError(_("You are playing White"));
3447 if (!white_piece && WhiteOnMove(currentMove)) {
3448 DisplayMoveError(_("It is White's turn"));
3451 if (white_piece && !WhiteOnMove(currentMove)) {
3452 DisplayMoveError(_("It is Black's turn"));
3461 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
3462 && gameMode != AnalyzeFile && gameMode != Training) {
3463 DisplayMoveError(_("Displayed position is not current"));
3469 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
3470 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
3471 int lastLoadGameUseList = FALSE;
3472 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
3473 ChessMove lastLoadGameStart = (ChessMove) 0;
3477 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
3478 int fromX, fromY, toX, toY;
3483 if (fromX < 0 || fromY < 0) return;
3484 if ((fromX == toX) && (fromY == toY)) {
3488 /* Check if the user is playing in turn. This is complicated because we
3489 let the user "pick up" a piece before it is his turn. So the piece he
3490 tried to pick up may have been captured by the time he puts it down!
3491 Therefore we use the color the user is supposed to be playing in this
3492 test, not the color of the piece that is currently on the starting
3493 square---except in EditGame mode, where the user is playing both
3494 sides; fortunately there the capture race can't happen. (It can
3495 now happen in IcsExamining mode, but that's just too bad. The user
3496 will get a somewhat confusing message in that case.)
3500 case PlayFromGameFile:
3502 case TwoMachinesPlay:
3506 /* We switched into a game mode where moves are not accepted,
3507 perhaps while the mouse button was down. */
3510 case MachinePlaysWhite:
3511 /* User is moving for Black */
3512 if (WhiteOnMove(currentMove)) {
3513 DisplayMoveError(_("It is White's turn"));
3518 case MachinePlaysBlack:
3519 /* User is moving for White */
3520 if (!WhiteOnMove(currentMove)) {
3521 DisplayMoveError(_("It is Black's turn"));
3528 case BeginningOfGame:
3531 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
3532 (int) boards[currentMove][fromY][fromX] <= (int) BlackKing) {
3533 /* User is moving for Black */
3534 if (WhiteOnMove(currentMove)) {
3535 DisplayMoveError(_("It is White's turn"));
3539 /* User is moving for White */
3540 if (!WhiteOnMove(currentMove)) {
3541 DisplayMoveError(_("It is Black's turn"));
3547 case IcsPlayingBlack:
3548 /* User is moving for Black */
3549 if (WhiteOnMove(currentMove)) {
3550 if (!appData.premove) {
3551 DisplayMoveError(_("It is White's turn"));
3552 } else if (toX >= 0 && toY >= 0) {
3555 premoveFromX = fromX;
3556 premoveFromY = fromY;
3557 premovePromoChar = promoChar;
3559 if (appData.debugMode)
3560 fprintf(debugFP, "Got premove: fromX %d,"
3561 "fromY %d, toX %d, toY %d\n",
3562 fromX, fromY, toX, toY);
3568 case IcsPlayingWhite:
3569 /* User is moving for White */
3570 if (!WhiteOnMove(currentMove)) {
3571 if (!appData.premove) {
3572 DisplayMoveError(_("It is Black's turn"));
3573 } else if (toX >= 0 && toY >= 0) {
3576 premoveFromX = fromX;
3577 premoveFromY = fromY;
3578 premovePromoChar = promoChar;
3580 if (appData.debugMode)
3581 fprintf(debugFP, "Got premove: fromX %d,"
3582 "fromY %d, toX %d, toY %d\n",
3583 fromX, fromY, toX, toY);
3593 if (toX == -2 || toY == -2) {
3594 boards[0][fromY][fromX] = EmptySquare;
3595 DrawPosition(FALSE, boards[currentMove]);
3596 } else if (toX >= 0 && toY >= 0) {
3597 boards[0][toY][toX] = boards[0][fromY][fromX];
3598 boards[0][fromY][fromX] = EmptySquare;
3599 DrawPosition(FALSE, boards[currentMove]);
3604 if (toX < 0 || toY < 0) return;
3605 userOfferedDraw = FALSE;
3607 if (appData.testLegality) {
3608 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
3609 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar);
3610 if (moveType == IllegalMove || moveType == ImpossibleMove) {
3611 DisplayMoveError(_("Illegal move"));
3615 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
3618 if (gameMode == Training) {
3619 /* compare the move played on the board to the next move in the
3620 * game. If they match, display the move and the opponent's response.
3621 * If they don't match, display an error message.
3625 CopyBoard(testBoard, boards[currentMove]);
3626 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
3628 if (CompareBoards(testBoard, boards[currentMove+1])) {
3629 ForwardInner(currentMove+1);
3631 /* Autoplay the opponent's response.
3632 * if appData.animate was TRUE when Training mode was entered,
3633 * the response will be animated.
3635 saveAnimate = appData.animate;
3636 appData.animate = animateTraining;
3637 ForwardInner(currentMove+1);
3638 appData.animate = saveAnimate;
3640 /* check for the end of the game */
3641 if (currentMove >= forwardMostMove) {
3642 gameMode = PlayFromGameFile;
3644 SetTrainingModeOff();
3645 DisplayInformation(_("End of game"));
3648 DisplayError(_("Incorrect move"), 0);
3653 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
3656 /* Common tail of UserMoveEvent and DropMenuEvent */
3658 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
3660 int fromX, fromY, toX, toY;
3661 /*char*/int promoChar;
3663 /* Ok, now we know that the move is good, so we can kill
3664 the previous line in Analysis Mode */
3665 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
3666 forwardMostMove = currentMove;
3669 /* If we need the chess program but it's dead, restart it */
3670 ResurrectChessProgram();
3672 /* A user move restarts a paused game*/
3676 thinkOutput[0] = NULLCHAR;
3678 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
3680 if (gameMode == BeginningOfGame) {
3681 if (appData.noChessProgram) {
3682 gameMode = EditGame;
3686 gameMode = MachinePlaysBlack;
3688 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
3690 if (first.sendName) {
3691 sprintf(buf, "name %s\n", gameInfo.white);
3692 SendToProgram(buf, &first);
3698 /* Relay move to ICS or chess engine */
3699 if (appData.icsActive) {
3700 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
3701 gameMode == IcsExamining) {
3702 SendMoveToICS(moveType, fromX, fromY, toX, toY);
3706 if (first.sendTime && (gameMode == BeginningOfGame ||
3707 gameMode == MachinePlaysWhite ||
3708 gameMode == MachinePlaysBlack)) {
3709 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
3711 SendMoveToProgram(forwardMostMove-1, &first);
3712 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
3713 first.maybeThinking = TRUE;
3715 if (currentMove == cmailOldMove + 1) {
3716 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
3720 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
3724 switch (MateTest(boards[currentMove], PosFlags(currentMove),
3730 if (WhiteOnMove(currentMove)) {
3731 GameEnds(BlackWins, "Black mates", GE_PLAYER);
3733 GameEnds(WhiteWins, "White mates", GE_PLAYER);
3737 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
3742 case MachinePlaysBlack:
3743 case MachinePlaysWhite:
3744 /* disable certain menu options while machine is thinking */
3745 SetMachineThinkingEnables();
3754 HandleMachineMove(message, cps)
3756 ChessProgramState *cps;
3758 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
3759 char realname[MSG_SIZ];
3760 int fromX, fromY, toX, toY;
3767 * Kludge to ignore BEL characters
3769 while (*message == '\007') message++;
3772 * Look for book output
3774 if (cps == &first && bookRequested) {
3775 if (message[0] == '\t' || message[0] == ' ') {
3776 /* Part of the book output is here; append it */
3777 strcat(bookOutput, message);
3778 strcat(bookOutput, " \n");
3780 } else if (bookOutput[0] != NULLCHAR) {
3781 /* All of book output has arrived; display it */
3782 char *p = bookOutput;
3783 while (*p != NULLCHAR) {
3784 if (*p == '\t') *p = ' ';
3787 DisplayInformation(bookOutput);
3788 bookRequested = FALSE;
3789 /* Fall through to parse the current output */
3794 * Look for machine move.
3796 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 &&
3797 strcmp(buf2, "...") == 0) ||
3798 (sscanf(message, "%s %s", buf1, machineMove) == 2 &&
3799 strcmp(buf1, "move") == 0)) {
3801 /* This method is only useful on engines that support ping */
3802 if (cps->lastPing != cps->lastPong) {
3803 if (gameMode == BeginningOfGame) {
3804 /* Extra move from before last new; ignore */
3805 if (appData.debugMode) {
3806 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
3809 if (appData.debugMode) {
3810 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
3811 cps->which, gameMode);
3813 SendToProgram("undo\n", cps);
3819 case BeginningOfGame:
3820 /* Extra move from before last reset; ignore */
3821 if (appData.debugMode) {
3822 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
3829 /* Extra move after we tried to stop. The mode test is
3830 not a reliable way of detecting this problem, but it's
3831 the best we can do on engines that don't support ping.
3833 if (appData.debugMode) {
3834 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
3835 cps->which, gameMode);
3837 SendToProgram("undo\n", cps);
3840 case MachinePlaysWhite:
3841 case IcsPlayingWhite:
3842 machineWhite = TRUE;
3845 case MachinePlaysBlack:
3846 case IcsPlayingBlack:
3847 machineWhite = FALSE;
3850 case TwoMachinesPlay:
3851 machineWhite = (cps->twoMachinesColor[0] == 'w');
3854 if (WhiteOnMove(forwardMostMove) != machineWhite) {
3855 if (appData.debugMode) {
3857 "Ignoring move out of turn by %s, gameMode %d"
3858 ", forwardMost %d\n",
3859 cps->which, gameMode, forwardMostMove);
3864 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
3865 &fromX, &fromY, &toX, &toY, &promoChar)) {
3866 /* Machine move could not be parsed; ignore it. */
3867 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
3868 machineMove, cps->which);
3869 DisplayError(buf1, 0);
3870 if (gameMode == TwoMachinesPlay) {
3871 GameEnds(machineWhite ? BlackWins : WhiteWins,
3872 "Forfeit due to illegal move", GE_XBOARD);
3877 hintRequested = FALSE;
3878 lastHint[0] = NULLCHAR;
3879 bookRequested = FALSE;
3880 /* Program may be pondering now */
3881 cps->maybeThinking = TRUE;
3882 if (cps->sendTime == 2) cps->sendTime = 1;
3883 if (cps->offeredDraw) cps->offeredDraw--;
3886 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
3888 SendMoveToICS(moveType, fromX, fromY, toX, toY);
3892 /* currentMoveString is set as a side-effect of ParseOneMove */
3893 strcpy(machineMove, currentMoveString);
3894 strcat(machineMove, "\n");
3895 strcpy(moveList[forwardMostMove], machineMove);
3897 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
3899 if (gameMode == TwoMachinesPlay) {
3900 if (cps->other->sendTime) {
3901 SendTimeRemaining(cps->other,
3902 cps->other->twoMachinesColor[0] == 'w');
3904 SendMoveToProgram(forwardMostMove-1, cps->other);
3907 if (cps->other->useColors) {
3908 SendToProgram(cps->other->twoMachinesColor, cps->other);
3910 SendToProgram("go\n", cps->other);
3912 cps->other->maybeThinking = TRUE;
3915 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
3916 if (!pausing && appData.ringBellAfterMoves) {
3920 * Reenable menu items that were disabled while
3921 * machine was thinking
3923 if (gameMode != TwoMachinesPlay)
3924 SetUserThinkingEnables();
3928 /* Set special modes for chess engines. Later something general
3929 * could be added here; for now there is just one kludge feature,
3930 * needed because Crafty 15.10 and earlier don't ignore SIGINT
3931 * when "xboard" is given as an interactive command.
3933 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
3934 cps->useSigint = FALSE;
3935 cps->useSigterm = FALSE;
3939 * Look for communication commands
3941 if (!strncmp(message, "telluser ", 9)) {
3942 DisplayNote(message + 9);
3945 if (!strncmp(message, "tellusererror ", 14)) {
3946 DisplayError(message + 14, 0);
3949 if (!strncmp(message, "tellopponent ", 13)) {
3950 if (appData.icsActive) {
3952 sprintf(buf1, "%ssay %s\n", ics_prefix, message + 13);
3956 DisplayNote(message + 13);
3960 if (!strncmp(message, "tellothers ", 11)) {
3961 if (appData.icsActive) {
3963 sprintf(buf1, "%swhisper %s\n", ics_prefix, message + 11);
3969 if (!strncmp(message, "tellall ", 8)) {
3970 if (appData.icsActive) {
3972 sprintf(buf1, "%skibitz %s\n", ics_prefix, message + 8);
3976 DisplayNote(message + 8);
3980 if (strncmp(message, "warning", 7) == 0) {
3981 /* Undocumented feature, use tellusererror in new code */
3982 DisplayError(message, 0);
3985 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
3986 strcpy(realname, cps->tidy);
3987 strcat(realname, " query");
3988 AskQuestion(realname, buf2, buf1, cps->pr);
3991 /* Commands from the engine directly to ICS. We don't allow these to be
3992 * sent until we are logged on. Crafty kibitzes have been known to
3993 * interfere with the login process.
3996 if (!strncmp(message, "tellics ", 8)) {
3997 SendToICS(message + 8);
4001 if (!strncmp(message, "tellicsnoalias ", 15)) {
4002 SendToICS(ics_prefix);
4003 SendToICS(message + 15);
4007 /* The following are for backward compatibility only */
4008 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
4009 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
4010 SendToICS(ics_prefix);
4016 if (strncmp(message, "feature ", 8) == 0) {
4017 ParseFeatures(message+8, cps);
4019 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
4023 * If the move is illegal, cancel it and redraw the board.
4024 * Also deal with other error cases. Matching is rather loose
4025 * here to accommodate engines written before the spec.
4027 if (strncmp(message + 1, "llegal move", 11) == 0 ||
4028 strncmp(message, "Error", 5) == 0) {
4029 if (StrStr(message, "name") ||
4030 StrStr(message, "rating") || StrStr(message, "?") ||
4031 StrStr(message, "result") || StrStr(message, "board") ||
4032 StrStr(message, "bk") || StrStr(message, "computer") ||
4033 StrStr(message, "variant") || StrStr(message, "hint") ||
4034 StrStr(message, "random") || StrStr(message, "depth") ||
4035 StrStr(message, "accepted")) {
4038 if (StrStr(message, "protover")) {
4039 /* Program is responding to input, so it's apparently done
4040 initializing, and this error message indicates it is
4041 protocol version 1. So we don't need to wait any longer
4042 for it to initialize and send feature commands. */
4043 FeatureDone(cps, 1);
4044 cps->protocolVersion = 1;
4047 cps->maybeThinking = FALSE;
4049 if (StrStr(message, "draw")) {
4050 /* Program doesn't have "draw" command */
4051 cps->sendDrawOffers = 0;
4054 if (cps->sendTime != 1 &&
4055 (StrStr(message, "time") || StrStr(message, "otim"))) {
4056 /* Program apparently doesn't have "time" or "otim" command */
4060 if (StrStr(message, "analyze")) {
4061 cps->analysisSupport = FALSE;
4062 cps->analyzing = FALSE;
4064 sprintf(buf2, "%s does not support analysis", cps->tidy);
4065 DisplayError(buf2, 0);
4068 if (StrStr(message, "(no matching move)st")) {
4069 /* Special kludge for GNU Chess 4 only */
4070 cps->stKludge = TRUE;
4071 SendTimeControl(cps, movesPerSession, timeControl,
4072 timeIncrement, appData.searchDepth,
4076 if (StrStr(message, "(no matching move)sd")) {
4077 /* Special kludge for GNU Chess 4 only */
4078 cps->sdKludge = TRUE;
4079 SendTimeControl(cps, movesPerSession, timeControl,
4080 timeIncrement, appData.searchDepth,
4084 if (!StrStr(message, "llegal")) return;
4085 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
4086 gameMode == IcsIdle) return;
4087 if (forwardMostMove <= backwardMostMove) return;
4089 /* Following removed: it caused a bug where a real illegal move
4090 message in analyze mored would be ignored. */
4091 if (cps == &first && programStats.ok_to_send == 0) {
4092 /* Bogus message from Crafty responding to "." This filtering
4093 can miss some of the bad messages, but fortunately the bug
4094 is fixed in current Crafty versions, so it doesn't matter. */
4098 if (pausing) PauseEvent();
4099 if (gameMode == PlayFromGameFile) {
4100 /* Stop reading this game file */
4101 gameMode = EditGame;
4104 currentMove = --forwardMostMove;
4105 DisplayMove(currentMove-1); /* before DisplayMoveError */
4107 DisplayBothClocks();
4108 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
4109 parseList[currentMove], cps->which);
4110 DisplayMoveError(buf1);
4111 DrawPosition(FALSE, boards[currentMove]);
4114 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
4115 /* Program has a broken "time" command that
4116 outputs a string not ending in newline.
4122 * If chess program startup fails, exit with an error message.
4123 * Attempts to recover here are futile.
4125 if ((StrStr(message, "unknown host") != NULL)
4126 || (StrStr(message, "No remote directory") != NULL)
4127 || (StrStr(message, "not found") != NULL)
4128 || (StrStr(message, "No such file") != NULL)
4129 || (StrStr(message, "can't alloc") != NULL)
4130 || (StrStr(message, "Permission denied") != NULL)) {
4132 cps->maybeThinking = FALSE;
4133 sprintf(buf1, _("Failed to start %s chess program %s on %s: %s\n"),
4134 cps->which, cps->program, cps->host, message);
4135 RemoveInputSource(cps->isr);
4136 DisplayFatalError(buf1, 0, 1);
4141 * Look for hint output
4143 if (sscanf(message, "Hint: %s", buf1) == 1) {
4144 if (cps == &first && hintRequested) {
4145 hintRequested = FALSE;
4146 if (ParseOneMove(buf1, forwardMostMove, &moveType,
4147 &fromX, &fromY, &toX, &toY, &promoChar)) {
4148 (void) CoordsToAlgebraic(boards[forwardMostMove],
4149 PosFlags(forwardMostMove), EP_UNKNOWN,
4150 fromY, fromX, toY, toX, promoChar, buf1);
4151 sprintf(buf2, "Hint: %s", buf1);
4152 DisplayInformation(buf2);
4154 /* Hint move could not be parsed!? */
4156 _("Illegal hint move \"%s\"\nfrom %s chess program"),
4158 DisplayError(buf2, 0);
4161 strcpy(lastHint, buf1);
4167 * Ignore other messages if game is not in progress
4169 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
4170 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
4173 * look for win, lose, draw, or draw offer
4175 if (strncmp(message, "1-0", 3) == 0) {
4176 char *p, *q, *r = "";
4177 p = strchr(message, '{');
4185 GameEnds(WhiteWins, r, GE_ENGINE);
4187 } else if (strncmp(message, "0-1", 3) == 0) {
4188 char *p, *q, *r = "";
4189 p = strchr(message, '{');
4197 /* Kludge for Arasan 4.1 bug */
4198 if (strcmp(r, "Black resigns") == 0) {
4199 GameEnds(WhiteWins, r, GE_ENGINE);
4202 GameEnds(BlackWins, r, GE_ENGINE);
4204 } else if (strncmp(message, "1/2", 3) == 0) {
4205 char *p, *q, *r = "";
4206 p = strchr(message, '{');
4214 GameEnds(GameIsDrawn, r, GE_ENGINE);
4217 } else if (strncmp(message, "White resign", 12) == 0) {
4218 GameEnds(BlackWins, "White resigns", GE_ENGINE);
4220 } else if (strncmp(message, "Black resign", 12) == 0) {
4221 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
4223 } else if (strncmp(message, "White", 5) == 0 &&
4224 message[5] != '(' &&
4225 StrStr(message, "Black") == NULL) {
4226 GameEnds(WhiteWins, "White mates", GE_ENGINE);
4228 } else if (strncmp(message, "Black", 5) == 0 &&
4229 message[5] != '(') {
4230 GameEnds(BlackWins, "Black mates", GE_ENGINE);
4232 } else if (strcmp(message, "resign") == 0 ||
4233 strcmp(message, "computer resigns") == 0) {
4235 case MachinePlaysBlack:
4236 case IcsPlayingBlack:
4237 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
4239 case MachinePlaysWhite:
4240 case IcsPlayingWhite:
4241 GameEnds(BlackWins, "White resigns", GE_ENGINE);
4243 case TwoMachinesPlay:
4244 if (cps->twoMachinesColor[0] == 'w')
4245 GameEnds(BlackWins, "White resigns", GE_ENGINE);
4247 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
4254 } else if (strncmp(message, "opponent mates", 14) == 0) {
4256 case MachinePlaysBlack:
4257 case IcsPlayingBlack:
4258 GameEnds(WhiteWins, "White mates", GE_ENGINE);
4260 case MachinePlaysWhite:
4261 case IcsPlayingWhite:
4262 GameEnds(BlackWins, "Black mates", GE_ENGINE);
4264 case TwoMachinesPlay:
4265 if (cps->twoMachinesColor[0] == 'w')
4266 GameEnds(BlackWins, "Black mates", GE_ENGINE);
4268 GameEnds(WhiteWins, "White mates", GE_ENGINE);
4275 } else if (strncmp(message, "computer mates", 14) == 0) {
4277 case MachinePlaysBlack:
4278 case IcsPlayingBlack:
4279 GameEnds(BlackWins, "Black mates", GE_ENGINE);
4281 case MachinePlaysWhite:
4282 case IcsPlayingWhite:
4283 GameEnds(WhiteWins, "White mates", GE_ENGINE);
4285 case TwoMachinesPlay:
4286 if (cps->twoMachinesColor[0] == 'w')
4287 GameEnds(WhiteWins, "White mates", GE_ENGINE);
4289 GameEnds(BlackWins, "Black mates", GE_ENGINE);
4296 } else if (strncmp(message, "checkmate", 9) == 0) {
4297 if (WhiteOnMove(forwardMostMove)) {
4298 GameEnds(BlackWins, "Black mates", GE_ENGINE);
4300 GameEnds(WhiteWins, "White mates", GE_ENGINE);
4303 } else if (strstr(message, "Draw") != NULL ||
4304 strstr(message, "game is a draw") != NULL) {
4305 GameEnds(GameIsDrawn, "Draw", GE_ENGINE);
4307 } else if (strstr(message, "offer") != NULL &&
4308 strstr(message, "draw") != NULL) {
4310 if (appData.zippyPlay && first.initDone) {
4311 /* Relay offer to ICS */
4312 SendToICS(ics_prefix);
4313 SendToICS("draw\n");
4316 cps->offeredDraw = 2; /* valid until this engine moves twice */
4317 if (gameMode == TwoMachinesPlay) {
4318 if (cps->other->offeredDraw) {
4319 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
4321 if (cps->other->sendDrawOffers) {
4322 SendToProgram("draw\n", cps->other);
4325 } else if (gameMode == MachinePlaysWhite ||
4326 gameMode == MachinePlaysBlack) {
4327 if (userOfferedDraw) {
4328 DisplayInformation(_("Machine accepts your draw offer"));
4329 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
4331 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
4338 * Look for thinking output
4340 if (appData.showThinking) {
4341 int plylev, mvleft, mvtot, curscore, time;
4342 char mvname[MOVE_LEN];
4343 unsigned long nodes;
4346 int prefixHint = FALSE;
4347 mvname[0] = NULLCHAR;
4350 case MachinePlaysBlack:
4351 case IcsPlayingBlack:
4352 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
4354 case MachinePlaysWhite:
4355 case IcsPlayingWhite:
4356 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
4361 case TwoMachinesPlay:
4362 if ((cps->twoMachinesColor[0] == 'w') !=
4363 WhiteOnMove(forwardMostMove)) {
4374 if (sscanf(message, "%d%c %d %d %lu %[^\n]\n",
4375 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
4377 if (plyext != ' ' && plyext != '\t') {
4380 programStats.depth = plylev;
4381 programStats.nodes = nodes;
4382 programStats.time = time;
4383 programStats.score = curscore;
4384 programStats.got_only_move = 0;
4386 /* Buffer overflow protection */
4387 if (buf1[0] != NULLCHAR) {
4388 if (strlen(buf1) >= sizeof(programStats.movelist)
4389 && appData.debugMode) {
4391 "PV is too long; using the first %d bytes.\n",
4392 sizeof(programStats.movelist) - 1);
4394 strncpy(programStats.movelist, buf1,
4395 sizeof(programStats.movelist));
4396 programStats.movelist[sizeof(programStats.movelist) - 1]
4399 sprintf(programStats.movelist, " no PV\n");
4402 if (programStats.seen_stat) {
4403 programStats.ok_to_send = 1;
4406 if (strchr(programStats.movelist, '(') != NULL) {
4407 programStats.line_is_book = 1;
4408 programStats.nr_moves = 0;
4409 programStats.moves_left = 0;
4411 programStats.line_is_book = 0;
4414 sprintf(thinkOutput, "[%d]%c%+.2f %s%s%s",
4416 (gameMode == TwoMachinesPlay ?
4417 ToUpper(cps->twoMachinesColor[0]) : ' '),
4418 ((double) curscore) / 100.0,
4419 prefixHint ? lastHint : "",
4420 prefixHint ? " " : "", buf1);
4422 if (currentMove == forwardMostMove ||
4423 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
4424 DisplayMove(currentMove - 1);
4429 } else if ((p=StrStr(message, "(only move)")) != NULL) {
4430 /* crafty (9.25+) says "(only move) <move>"
4431 * if there is only 1 legal move
4433 sscanf(p, "(only move) %s", buf1);
4434 sprintf(thinkOutput, "%s (only move)", buf1);
4435 sprintf(programStats.movelist, "%s (only move)", buf1);
4436 programStats.depth = 1;
4437 programStats.nr_moves = 1;
4438 programStats.moves_left = 1;
4439 programStats.nodes = 1;
4440 programStats.time = 1;
4441 programStats.got_only_move = 1;
4443 /* Not really, but we also use this member to
4444 mean "line isn't going to change" (Crafty
4445 isn't searching, so stats won't change) */
4446 programStats.line_is_book = 1;
4448 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
4449 gameMode == AnalyzeFile) {
4450 DisplayMove(currentMove - 1);
4454 } else if (sscanf(message,"stat01: %d %lu %d %d %d %s",
4455 &time, &nodes, &plylev, &mvleft,
4456 &mvtot, mvname) >= 5) {
4457 /* The stat01: line is from Crafty (9.29+) in response
4458 to the "." command */
4459 programStats.seen_stat = 1;
4460 cps->maybeThinking = TRUE;
4462 if (programStats.got_only_move || !appData.periodicUpdates)
4465 programStats.depth = plylev;
4466 programStats.time = time;
4467 programStats.nodes = nodes;
4468 programStats.moves_left = mvleft;
4469 programStats.nr_moves = mvtot;
4470 strcpy(programStats.move_name, mvname);
4471 programStats.ok_to_send = 1;
4475 } else if (strncmp(message,"++",2) == 0) {
4476 /* Crafty 9.29+ outputs this */
4477 programStats.got_fail = 2;
4480 } else if (strncmp(message,"--",2) == 0) {
4481 /* Crafty 9.29+ outputs this */
4482 programStats.got_fail = 1;
4485 } else if (thinkOutput[0] != NULLCHAR &&
4486 strncmp(message, " ", 4) == 0) {
4488 while (*p && *p == ' ') p++;
4489 strcat(thinkOutput, " ");
4490 strcat(thinkOutput, p);
4491 strcat(programStats.movelist, " ");
4492 strcat(programStats.movelist, p);
4493 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
4494 gameMode == AnalyzeFile) {
4495 DisplayMove(currentMove - 1);
4505 /* Parse a game score from the character string "game", and
4506 record it as the history of the current game. The game
4507 score is NOT assumed to start from the standard position.
4508 The display is not updated in any way.
4511 ParseGameHistory(game)
4515 int fromX, fromY, toX, toY, boardIndex;
4520 if (appData.debugMode)
4521 fprintf(debugFP, "Parsing game history: %s\n", game);
4523 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
4524 gameInfo.site = StrSave(appData.icsHost);
4525 gameInfo.date = PGNDate();
4526 gameInfo.round = StrSave("-");
4528 /* Parse out names of players */
4529 while (*game == ' ') game++;
4531 while (*game != ' ') *p++ = *game++;
4533 gameInfo.white = StrSave(buf);
4534 while (*game == ' ') game++;
4536 while (*game != ' ' && *game != '\n') *p++ = *game++;
4538 gameInfo.black = StrSave(buf);
4541 boardIndex = blackPlaysFirst ? 1 : 0;
4544 yyboardindex = boardIndex;
4545 moveType = (ChessMove) yylex();
4547 case WhitePromotionQueen:
4548 case BlackPromotionQueen:
4549 case WhitePromotionRook:
4550 case BlackPromotionRook:
4551 case WhitePromotionBishop:
4552 case BlackPromotionBishop:
4553 case WhitePromotionKnight:
4554 case BlackPromotionKnight:
4555 case WhitePromotionKing:
4556 case BlackPromotionKing:
4558 case WhiteCapturesEnPassant:
4559 case BlackCapturesEnPassant:
4560 case WhiteKingSideCastle:
4561 case WhiteQueenSideCastle:
4562 case BlackKingSideCastle:
4563 case BlackQueenSideCastle:
4564 case WhiteKingSideCastleWild:
4565 case WhiteQueenSideCastleWild:
4566 case BlackKingSideCastleWild:
4567 case BlackQueenSideCastleWild:
4568 case IllegalMove: /* maybe suicide chess, etc. */
4569 fromX = currentMoveString[0] - 'a';
4570 fromY = currentMoveString[1] - '1';
4571 toX = currentMoveString[2] - 'a';
4572 toY = currentMoveString[3] - '1';
4573 promoChar = currentMoveString[4];
4577 fromX = moveType == WhiteDrop ?
4578 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4579 (int) CharToPiece(ToLower(currentMoveString[0]));
4581 toX = currentMoveString[2] - 'a';
4582 toY = currentMoveString[3] - '1';
4583 promoChar = NULLCHAR;
4587 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
4588 DisplayError(buf, 0);
4590 case ImpossibleMove:
4592 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
4593 DisplayError(buf, 0);
4595 case (ChessMove) 0: /* end of file */
4596 if (boardIndex < backwardMostMove) {
4597 /* Oops, gap. How did that happen? */
4598 DisplayError(_("Gap in move list"), 0);
4601 backwardMostMove = blackPlaysFirst ? 1 : 0;
4602 if (boardIndex > forwardMostMove) {
4603 forwardMostMove = boardIndex;
4607 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
4608 strcat(parseList[boardIndex-1], " ");
4609 strcat(parseList[boardIndex-1], yy_text);
4621 case GameUnfinished:
4622 if (gameMode == IcsExamining) {
4623 if (boardIndex < backwardMostMove) {
4624 /* Oops, gap. How did that happen? */
4627 backwardMostMove = blackPlaysFirst ? 1 : 0;
4630 gameInfo.result = moveType;
4631 p = strchr(yy_text, '{');
4632 if (p == NULL) p = strchr(yy_text, '(');
4635 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
4637 q = strchr(p, *p == '{' ? '}' : ')');
4638 if (q != NULL) *q = NULLCHAR;
4641 gameInfo.resultDetails = StrSave(p);
4644 if (boardIndex >= forwardMostMove &&
4645 !(gameMode == IcsObserving && ics_gamenum == -1)) {
4646 backwardMostMove = blackPlaysFirst ? 1 : 0;
4649 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
4650 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
4651 parseList[boardIndex]);
4652 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
4653 /* currentMoveString is set as a side-effect of yylex */
4654 strcpy(moveList[boardIndex], currentMoveString);
4655 strcat(moveList[boardIndex], "\n");
4657 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
4658 switch (MateTest(boards[boardIndex],
4659 PosFlags(boardIndex), EP_UNKNOWN)) {
4665 strcat(parseList[boardIndex - 1], "+");
4668 strcat(parseList[boardIndex - 1], "#");
4675 /* Apply a move to the given board */
4677 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
4678 int fromX, fromY, toX, toY;
4682 ChessSquare captured = board[toY][toX];
4683 if (fromY == DROP_RANK) {
4685 board[toY][toX] = (ChessSquare) fromX;
4686 } else if (fromX == toX && fromY == toY) {
4688 } else if (fromY == 0 && fromX == 4
4689 && board[fromY][fromX] == WhiteKing
4690 && toY == 0 && toX == 6) {
4691 board[fromY][fromX] = EmptySquare;
4692 board[toY][toX] = WhiteKing;
4693 board[fromY][7] = EmptySquare;
4694 board[toY][5] = WhiteRook;
4695 } else if (fromY == 0 && fromX == 4
4696 && board[fromY][fromX] == WhiteKing
4697 && toY == 0 && toX == 2) {
4698 board[fromY][fromX] = EmptySquare;
4699 board[toY][toX] = WhiteKing;
4700 board[fromY][0] = EmptySquare;
4701 board[toY][3] = WhiteRook;
4702 } else if (fromY == 0 && fromX == 3
4703 && board[fromY][fromX] == WhiteKing
4704 && toY == 0 && toX == 5) {
4705 board[fromY][fromX] = EmptySquare;
4706 board[toY][toX] = WhiteKing;
4707 board[fromY][7] = EmptySquare;
4708 board[toY][4] = WhiteRook;
4709 } else if (fromY == 0 && fromX == 3
4710 && board[fromY][fromX] == WhiteKing
4711 && toY == 0 && toX == 1) {
4712 board[fromY][fromX] = EmptySquare;
4713 board[toY][toX] = WhiteKing;
4714 board[fromY][0] = EmptySquare;
4715 board[toY][2] = WhiteRook;
4716 } else if (board[fromY][fromX] == WhitePawn
4718 /* white pawn promotion */
4719 board[7][toX] = CharToPiece(ToUpper(promoChar));
4720 if (board[7][toX] == EmptySquare) {
4721 board[7][toX] = WhiteQueen;
4723 board[fromY][fromX] = EmptySquare;
4724 } else if ((fromY == 4)
4726 && (board[fromY][fromX] == WhitePawn)
4727 && (board[toY][toX] == EmptySquare)) {
4728 board[fromY][fromX] = EmptySquare;
4729 board[toY][toX] = WhitePawn;
4730 captured = board[toY - 1][toX];
4731 board[toY - 1][toX] = EmptySquare;
4732 } else if (fromY == 7 && fromX == 4
4733 && board[fromY][fromX] == BlackKing
4734 && toY == 7 && toX == 6) {
4735 board[fromY][fromX] = EmptySquare;
4736 board[toY][toX] = BlackKing;
4737 board[fromY][7] = EmptySquare;
4738 board[toY][5] = BlackRook;
4739 } else if (fromY == 7 && fromX == 4
4740 && board[fromY][fromX] == BlackKing
4741 && toY == 7 && toX == 2) {
4742 board[fromY][fromX] = EmptySquare;
4743 board[toY][toX] = BlackKing;
4744 board[fromY][0] = EmptySquare;
4745 board[toY][3] = BlackRook;
4746 } else if (fromY == 7 && fromX == 3
4747 && board[fromY][fromX] == BlackKing
4748 && toY == 7 && toX == 5) {
4749 board[fromY][fromX] = EmptySquare;
4750 board[toY][toX] = BlackKing;
4751 board[fromY][7] = EmptySquare;
4752 board[toY][4] = BlackRook;
4753 } else if (fromY == 7 && fromX == 3
4754 && board[fromY][fromX] == BlackKing
4755 && toY == 7 && toX == 1) {
4756 board[fromY][fromX] = EmptySquare;
4757 board[toY][toX] = BlackKing;
4758 board[fromY][0] = EmptySquare;
4759 board[toY][2] = BlackRook;
4760 } else if (board[fromY][fromX] == BlackPawn
4762 /* black pawn promotion */
4763 board[0][toX] = CharToPiece(ToLower(promoChar));
4764 if (board[0][toX] == EmptySquare) {
4765 board[0][toX] = BlackQueen;
4767 board[fromY][fromX] = EmptySquare;
4768 } else if ((fromY == 3)
4770 && (board[fromY][fromX] == BlackPawn)
4771 && (board[toY][toX] == EmptySquare)) {
4772 board[fromY][fromX] = EmptySquare;
4773 board[toY][toX] = BlackPawn;
4774 captured = board[toY + 1][toX];
4775 board[toY + 1][toX] = EmptySquare;
4777 board[toY][toX] = board[fromY][fromX];
4778 board[fromY][fromX] = EmptySquare;
4780 if (gameInfo.variant == VariantCrazyhouse) {
4782 /* !!A lot more code needs to be written to support holdings */
4783 if (fromY == DROP_RANK) {
4784 /* Delete from holdings */
4785 if (holdings[(int) fromX] > 0) holdings[(int) fromX]--;
4787 if (captured != EmptySquare) {
4788 /* Add to holdings */
4789 if (captured < BlackPawn) {
4790 holdings[(int)captured - (int)BlackPawn + (int)WhitePawn]++;
4792 holdings[(int)captured - (int)WhitePawn + (int)BlackPawn]++;
4796 } else if (gameInfo.variant == VariantAtomic) {
4797 if (captured != EmptySquare) {
4799 for (y = toY-1; y <= toY+1; y++) {
4800 for (x = toX-1; x <= toX+1; x++) {
4801 if (y >= 0 && y <= 7 && x >= 0 && x <= 7 &&
4802 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
4803 board[y][x] = EmptySquare;
4807 board[toY][toX] = EmptySquare;
4812 /* Updates forwardMostMove */
4814 MakeMove(fromX, fromY, toX, toY, promoChar)
4815 int fromX, fromY, toX, toY;
4819 if (forwardMostMove >= MAX_MOVES) {
4820 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4825 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
4826 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
4827 if (commentList[forwardMostMove] != NULL) {
4828 free(commentList[forwardMostMove]);
4829 commentList[forwardMostMove] = NULL;
4831 CopyBoard(boards[forwardMostMove], boards[forwardMostMove - 1]);
4832 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove]);
4833 gameInfo.result = GameUnfinished;
4834 if (gameInfo.resultDetails != NULL) {
4835 free(gameInfo.resultDetails);
4836 gameInfo.resultDetails = NULL;
4838 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
4839 moveList[forwardMostMove - 1]);
4840 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
4841 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
4842 fromY, fromX, toY, toX, promoChar,
4843 parseList[forwardMostMove - 1]);
4844 switch (MateTest(boards[forwardMostMove],
4845 PosFlags(forwardMostMove), EP_UNKNOWN)){
4851 strcat(parseList[forwardMostMove - 1], "+");
4854 strcat(parseList[forwardMostMove - 1], "#");
4859 /* Updates currentMove if not pausing */
4861 ShowMove(fromX, fromY, toX, toY)
4863 int instant = (gameMode == PlayFromGameFile) ?
4864 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
4865 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
4867 if (forwardMostMove == currentMove + 1) {
4868 AnimateMove(boards[forwardMostMove - 1],
4869 fromX, fromY, toX, toY);
4871 if (appData.highlightLastMove) {
4872 SetHighlights(fromX, fromY, toX, toY);
4875 currentMove = forwardMostMove;
4878 if (instant) return;
4879 DisplayMove(currentMove - 1);
4880 DrawPosition(FALSE, boards[currentMove]);
4881 DisplayBothClocks();
4882 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
4887 InitChessProgram(cps)
4888 ChessProgramState *cps;
4891 if (appData.noChessProgram) return;
4892 hintRequested = FALSE;
4893 bookRequested = FALSE;
4894 SendToProgram(cps->initString, cps);
4895 if (gameInfo.variant != VariantNormal &&
4896 gameInfo.variant != VariantLoadable) {
4897 char *v = VariantName(gameInfo.variant);
4898 if (StrStr(cps->variants, v) == NULL) {
4899 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
4900 DisplayFatalError(buf, 0, 1);
4903 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
4904 SendToProgram(buf, cps);
4907 sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-");
4908 SendToProgram(buf, cps);
4910 cps->maybeThinking = FALSE;
4911 cps->offeredDraw = 0;
4912 if (!appData.icsActive) {
4913 SendTimeControl(cps, movesPerSession, timeControl,
4914 timeIncrement, appData.searchDepth,
4917 if (appData.showThinking) {
4918 SendToProgram("post\n", cps);
4920 SendToProgram("hard\n", cps);
4921 if (!appData.ponderNextMove) {
4922 /* Warning: "easy" is a toggle in GNU Chess, so don't send
4923 it without being sure what state we are in first. "hard"
4924 is not a toggle, so that one is OK.
4926 SendToProgram("easy\n", cps);
4929 sprintf(buf, "ping %d\n", ++cps->lastPing);
4930 SendToProgram(buf, cps);
4932 cps->initDone = TRUE;
4937 StartChessProgram(cps)
4938 ChessProgramState *cps;
4943 if (appData.noChessProgram) return;
4944 cps->initDone = FALSE;
4946 if (strcmp(cps->host, "localhost") == 0) {
4947 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
4948 } else if (*appData.remoteShell == NULLCHAR) {
4949 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
4951 if (*appData.remoteUser == NULLCHAR) {
4952 sprintf(buf, "%s %s %s", appData.remoteShell, cps->host,
4955 sprintf(buf, "%s %s -l %s %s", appData.remoteShell,
4956 cps->host, appData.remoteUser, cps->program);
4958 err = StartChildProcess(buf, "", &cps->pr);
4962 sprintf(buf, "Startup failure on '%s'", cps->program);
4963 DisplayFatalError(buf, err, 1);
4969 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
4970 if (cps->protocolVersion > 1) {
4971 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
4972 SendToProgram(buf, cps);
4974 SendToProgram("xboard\n", cps);
4980 TwoMachinesEventIfReady P((void))
4982 if (first.lastPing != first.lastPong) {
4983 DisplayMessage("", "Waiting for first chess program");
4984 ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000);
4987 if (second.lastPing != second.lastPong) {
4988 DisplayMessage("", "Waiting for second chess program");
4989 ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000);
4997 NextMatchGame P((void))
5000 if (*appData.loadGameFile != NULLCHAR) {
5001 LoadGameFromFile(appData.loadGameFile,
5002 appData.loadGameIndex,
5003 appData.loadGameFile, FALSE);
5004 } else if (*appData.loadPositionFile != NULLCHAR) {
5005 LoadPositionFromFile(appData.loadPositionFile,
5006 appData.loadPositionIndex,
5007 appData.loadPositionFile);
5009 TwoMachinesEventIfReady();
5013 GameEnds(result, resultDetails, whosays)
5015 char *resultDetails;
5018 GameMode nextGameMode;
5021 if (appData.debugMode) {
5022 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
5023 result, resultDetails ? resultDetails : "(null)", whosays);
5026 if (appData.icsActive && whosays == GE_ENGINE) {
5027 /* If we are playing on ICS, the server decides when the
5028 game is over, but the engine can offer to draw, claim
5032 if (appData.zippyPlay && first.initDone) {
5033 if (result == GameIsDrawn) {
5034 /* In case draw still needs to be claimed */
5035 SendToICS(ics_prefix);
5036 SendToICS("draw\n");
5037 } else if (StrCaseStr(resultDetails, "resign")) {
5038 SendToICS(ics_prefix);
5039 SendToICS("resign\n");
5046 /* If we're loading the game from a file, stop */
5047 if (whosays == GE_FILE) {
5048 (void) StopLoadGameTimer();
5052 /* Cancel draw offers */
5053 first.offeredDraw = second.offeredDraw = 0;
5055 /* If this is an ICS game, only ICS can really say it's done;
5056 if not, anyone can. */
5057 isIcsGame = (gameMode == IcsPlayingWhite ||
5058 gameMode == IcsPlayingBlack ||
5059 gameMode == IcsObserving ||
5060 gameMode == IcsExamining);
5062 if (!isIcsGame || whosays == GE_ICS) {
5063 /* OK -- not an ICS game, or ICS said it was done */
5065 if (!isIcsGame && !appData.noChessProgram)
5066 SetUserThinkingEnables();
5068 if (resultDetails != NULL) {
5069 gameInfo.result = result;
5070 gameInfo.resultDetails = StrSave(resultDetails);
5072 /* Tell program how game ended in case it is learning */
5073 if (gameMode == MachinePlaysWhite ||
5074 gameMode == MachinePlaysBlack ||
5075 gameMode == TwoMachinesPlay ||
5076 gameMode == IcsPlayingWhite ||
5077 gameMode == IcsPlayingBlack ||
5078 gameMode == BeginningOfGame) {
5080 sprintf(buf, "result %s {%s}\n", PGNResult(result),
5082 if (first.pr != NoProc) {
5083 SendToProgram(buf, &first);
5085 if (second.pr != NoProc &&
5086 gameMode == TwoMachinesPlay) {
5087 SendToProgram(buf, &second);
5091 /* display last move only if game was not loaded from file */
5092 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
5093 DisplayMove(currentMove - 1);
5095 if (forwardMostMove != 0) {
5096 if (gameMode != PlayFromGameFile && gameMode != EditGame) {
5097 if (*appData.saveGameFile != NULLCHAR) {
5098 SaveGameToFile(appData.saveGameFile, TRUE);
5099 } else if (appData.autoSaveGames) {
5102 if (*appData.savePositionFile != NULLCHAR) {
5103 SavePositionToFile(appData.savePositionFile);
5109 if (appData.icsActive) {
5110 if (appData.quietPlay &&
5111 (gameMode == IcsPlayingWhite ||
5112 gameMode == IcsPlayingBlack)) {
5113 SendToICS(ics_prefix);
5114 SendToICS("set shout 1\n");
5116 nextGameMode = IcsIdle;
5117 ics_user_moved = FALSE;
5118 /* clean up premove. It's ugly when the game has ended and the
5119 * premove highlights are still on the board.
5123 ClearPremoveHighlights();
5124 DrawPosition(FALSE, boards[currentMove]);
5126 if (whosays == GE_ICS) {
5129 if (gameMode == IcsPlayingWhite)
5131 else if(gameMode == IcsPlayingBlack)
5135 if (gameMode == IcsPlayingBlack)
5137 else if(gameMode == IcsPlayingWhite)
5144 PlayIcsUnfinishedSound();
5147 } else if (gameMode == EditGame ||
5148 gameMode == PlayFromGameFile ||
5149 gameMode == AnalyzeMode ||
5150 gameMode == AnalyzeFile) {
5151 nextGameMode = gameMode;
5153 nextGameMode = EndOfGame;
5158 nextGameMode = gameMode;
5161 if (appData.noChessProgram) {
5162 gameMode = nextGameMode;
5168 /* Put first chess program into idle state */
5169 if (first.pr != NoProc &&
5170 (gameMode == MachinePlaysWhite ||
5171 gameMode == MachinePlaysBlack ||
5172 gameMode == TwoMachinesPlay ||
5173 gameMode == IcsPlayingWhite ||
5174 gameMode == IcsPlayingBlack ||
5175 gameMode == BeginningOfGame)) {
5176 SendToProgram("force\n", &first);
5177 if (first.usePing) {
5179 sprintf(buf, "ping %d\n", ++first.lastPing);
5180 SendToProgram(buf, &first);
5183 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
5184 /* Kill off first chess program */
5185 if (first.isr != NULL)
5186 RemoveInputSource(first.isr);
5189 if (first.pr != NoProc) {
5191 SendToProgram("quit\n", &first);
5192 DestroyChildProcess(first.pr, first.useSigterm);
5197 /* Put second chess program into idle state */
5198 if (second.pr != NoProc &&
5199 gameMode == TwoMachinesPlay) {
5200 SendToProgram("force\n", &second);
5201 if (second.usePing) {
5203 sprintf(buf, "ping %d\n", ++second.lastPing);
5204 SendToProgram(buf, &second);
5207 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
5208 /* Kill off second chess program */
5209 if (second.isr != NULL)
5210 RemoveInputSource(second.isr);
5213 if (second.pr != NoProc) {
5214 SendToProgram("quit\n", &second);
5215 DestroyChildProcess(second.pr, second.useSigterm);
5220 if (matchMode && gameMode == TwoMachinesPlay) {
5223 if (first.twoMachinesColor[0] == 'w') {
5230 if (first.twoMachinesColor[0] == 'b') {
5239 if (matchGame < appData.matchGames) {
5241 tmp = first.twoMachinesColor;
5242 first.twoMachinesColor = second.twoMachinesColor;
5243 second.twoMachinesColor = tmp;
5244 gameMode = nextGameMode;
5246 ScheduleDelayedEvent(NextMatchGame, 10000);
5250 gameMode = nextGameMode;
5251 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
5252 first.tidy, second.tidy,
5253 first.matchWins, second.matchWins,
5254 appData.matchGames - (first.matchWins + second.matchWins));
5255 DisplayFatalError(buf, 0, 0);
5258 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
5259 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
5261 gameMode = nextGameMode;
5265 /* Assumes program was just initialized (initString sent).
5266 Leaves program in force mode. */
5268 FeedMovesToProgram(cps, upto)
5269 ChessProgramState *cps;
5274 if (appData.debugMode)
5275 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
5276 startedFromSetupPosition ? "position and " : "",
5277 backwardMostMove, upto, cps->which);
5278 SendToProgram("force\n", cps);
5279 if (startedFromSetupPosition) {
5280 SendBoard(cps, backwardMostMove);
5282 for (i = backwardMostMove; i < upto; i++) {
5283 SendMoveToProgram(i, cps);
5289 ResurrectChessProgram()
5291 /* The chess program may have exited.
5292 If so, restart it and feed it all the moves made so far. */
5294 if (appData.noChessProgram || first.pr != NoProc) return;
5296 StartChessProgram(&first);
5297 InitChessProgram(&first);
5298 FeedMovesToProgram(&first, currentMove);
5300 if (!first.sendTime) {
5301 /* can't tell gnuchess what its clock should read,
5302 so we bow to its notion. */
5304 timeRemaining[0][currentMove] = whiteTimeRemaining;
5305 timeRemaining[1][currentMove] = blackTimeRemaining;
5308 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
5309 first.analysisSupport) {
5310 SendToProgram("analyze\n", &first);
5311 first.analyzing = TRUE;
5324 if (appData.debugMode) {
5325 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
5326 redraw, init, gameMode);
5329 pausing = pauseExamInvalid = FALSE;
5330 startedFromSetupPosition = blackPlaysFirst = FALSE;
5332 whiteFlag = blackFlag = FALSE;
5333 userOfferedDraw = FALSE;
5334 hintRequested = bookRequested = FALSE;
5335 first.maybeThinking = FALSE;
5336 second.maybeThinking = FALSE;
5337 thinkOutput[0] = NULLCHAR;
5338 lastHint[0] = NULLCHAR;
5339 ClearGameInfo(&gameInfo);
5340 gameInfo.variant = StringToVariant(appData.variant);
5341 ics_user_moved = ics_clock_paused = FALSE;
5342 ics_getting_history = H_FALSE;
5344 white_holding[0] = black_holding[0] = NULLCHAR;
5345 ClearProgramStats();
5349 flipView = appData.flipView;
5350 ClearPremoveHighlights();
5352 alarmSounded = FALSE;
5354 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
5356 gameMode = BeginningOfGame;
5358 InitPosition(redraw);
5359 for (i = 0; i < MAX_MOVES; i++) {
5360 if (commentList[i] != NULL) {
5361 free(commentList[i]);
5362 commentList[i] = NULL;
5366 timeRemaining[0][0] = whiteTimeRemaining;
5367 timeRemaining[1][0] = blackTimeRemaining;
5368 if (first.pr == NULL) {
5369 StartChessProgram(&first);
5371 if (init) InitChessProgram(&first);
5373 DisplayMessage("", "");
5374 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5381 if (!AutoPlayOneMove())
5383 if (matchMode || appData.timeDelay == 0)
5385 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
5387 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
5396 int fromX, fromY, toX, toY;
5398 if (appData.debugMode) {
5399 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
5402 if (gameMode != PlayFromGameFile)
5405 if (currentMove >= forwardMostMove) {
5406 gameMode = EditGame;
5411 toX = moveList[currentMove][2] - 'a';
5412 toY = moveList[currentMove][3] - '1';
5414 if (moveList[currentMove][1] == '@') {
5415 if (appData.highlightLastMove) {
5416 SetHighlights(-1, -1, toX, toY);
5419 fromX = moveList[currentMove][0] - 'a';
5420 fromY = moveList[currentMove][1] - '1';
5421 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
5423 if (appData.highlightLastMove) {
5424 SetHighlights(fromX, fromY, toX, toY);
5427 DisplayMove(currentMove);
5428 SendMoveToProgram(currentMove++, &first);
5429 DisplayBothClocks();
5430 DrawPosition(FALSE, boards[currentMove]);
5431 if (commentList[currentMove] != NULL) {
5432 DisplayComment(currentMove - 1, commentList[currentMove]);
5439 LoadGameOneMove(readAhead)
5440 ChessMove readAhead;
5442 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
5443 char promoChar = NULLCHAR;
5448 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
5449 gameMode != AnalyzeMode && gameMode != Training) {
5454 yyboardindex = forwardMostMove;
5455 if (readAhead != (ChessMove)0) {
5456 moveType = readAhead;
5458 if (gameFileFP == NULL)
5460 moveType = (ChessMove) yylex();
5466 if (appData.debugMode)
5467 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
5469 if (*p == '{' || *p == '[' || *p == '(') {
5470 p[strlen(p) - 1] = NULLCHAR;
5474 /* append the comment but don't display it */
5475 while (*p == '\n') p++;
5476 AppendComment(currentMove, p);
5479 case WhiteCapturesEnPassant:
5480 case BlackCapturesEnPassant:
5481 case WhitePromotionQueen:
5482 case BlackPromotionQueen:
5483 case WhitePromotionRook:
5484 case BlackPromotionRook:
5485 case WhitePromotionBishop:
5486 case BlackPromotionBishop:
5487 case WhitePromotionKnight:
5488 case BlackPromotionKnight:
5489 case WhitePromotionKing:
5490 case BlackPromotionKing:
5492 case WhiteKingSideCastle:
5493 case WhiteQueenSideCastle:
5494 case BlackKingSideCastle:
5495 case BlackQueenSideCastle:
5496 case WhiteKingSideCastleWild:
5497 case WhiteQueenSideCastleWild:
5498 case BlackKingSideCastleWild:
5499 case BlackQueenSideCastleWild:
5500 if (appData.debugMode)
5501 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
5502 fromX = currentMoveString[0] - 'a';
5503 fromY = currentMoveString[1] - '1';
5504 toX = currentMoveString[2] - 'a';
5505 toY = currentMoveString[3] - '1';
5506 promoChar = currentMoveString[4];
5511 if (appData.debugMode)
5512 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
5513 fromX = moveType == WhiteDrop ?
5514 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5515 (int) CharToPiece(ToLower(currentMoveString[0]));
5517 toX = currentMoveString[2] - 'a';
5518 toY = currentMoveString[3] - '1';
5524 case GameUnfinished:
5525 if (appData.debugMode)
5526 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
5527 p = strchr(yy_text, '{');
5528 if (p == NULL) p = strchr(yy_text, '(');
5531 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
5533 q = strchr(p, *p == '{' ? '}' : ')');
5534 if (q != NULL) *q = NULLCHAR;
5537 GameEnds(moveType, p, GE_FILE);
5539 if (cmailMsgLoaded) {
5541 flipView = WhiteOnMove(currentMove);
5542 if (moveType == GameUnfinished) flipView = !flipView;
5543 if (appData.debugMode)
5544 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
5548 case (ChessMove) 0: /* end of file */
5549 if (appData.debugMode)
5550 fprintf(debugFP, "Parser hit end of file\n");
5551 switch (MateTest(boards[currentMove], PosFlags(currentMove),
5557 if (WhiteOnMove(currentMove)) {
5558 GameEnds(BlackWins, "Black mates", GE_FILE);
5560 GameEnds(WhiteWins, "White mates", GE_FILE);
5564 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
5571 if (lastLoadGameStart == GNUChessGame) {
5572 /* GNUChessGames have numbers, but they aren't move numbers */
5573 if (appData.debugMode)
5574 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
5575 yy_text, (int) moveType);
5576 return LoadGameOneMove((ChessMove)0); /* tail recursion */
5578 /* else fall thru */
5583 /* Reached start of next game in file */
5584 if (appData.debugMode)
5585 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
5586 switch (MateTest(boards[currentMove], PosFlags(currentMove),
5592 if (WhiteOnMove(currentMove)) {
5593 GameEnds(BlackWins, "Black mates", GE_FILE);
5595 GameEnds(WhiteWins, "White mates", GE_FILE);
5599 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
5605 case PositionDiagram: /* should not happen; ignore */
5606 case ElapsedTime: /* ignore */
5607 case NAG: /* ignore */
5608 if (appData.debugMode)
5609 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
5610 yy_text, (int) moveType);
5611 return LoadGameOneMove((ChessMove)0); /* tail recursion */
5614 if (appData.testLegality) {
5615 if (appData.debugMode)
5616 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
5617 sprintf(move, _("Illegal move: %d.%s%s"),
5618 (forwardMostMove / 2) + 1,
5619 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
5620 DisplayError(move, 0);
5623 if (appData.debugMode)
5624 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
5625 yy_text, currentMoveString);
5626 fromX = currentMoveString[0] - 'a';
5627 fromY = currentMoveString[1] - '1';
5628 toX = currentMoveString[2] - 'a';
5629 toY = currentMoveString[3] - '1';
5630 promoChar = currentMoveString[4];
5635 if (appData.debugMode)
5636 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
5637 sprintf(move, _("Ambiguous move: %d.%s%s"),
5638 (forwardMostMove / 2) + 1,
5639 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
5640 DisplayError(move, 0);
5645 case ImpossibleMove:
5646 if (appData.debugMode)
5647 fprintf(debugFP, "Parsed ImpossibleMove: %s\n", yy_text);
5648 sprintf(move, _("Illegal move: %d.%s%s"),
5649 (forwardMostMove / 2) + 1,
5650 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
5651 DisplayError(move, 0);
5657 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
5658 DrawPosition(FALSE, boards[currentMove]);
5659 DisplayBothClocks();
5660 if (!appData.matchMode && commentList[currentMove] != NULL)
5661 DisplayComment(currentMove - 1, commentList[currentMove]);
5663 (void) StopLoadGameTimer();
5665 cmailOldMove = forwardMostMove;
5668 /* currentMoveString is set as a side-effect of yylex */
5669 strcat(currentMoveString, "\n");
5670 strcpy(moveList[forwardMostMove], currentMoveString);
5672 thinkOutput[0] = NULLCHAR;
5673 MakeMove(fromX, fromY, toX, toY, promoChar);
5674 currentMove = forwardMostMove;
5679 /* Load the nth game from the given file */
5681 LoadGameFromFile(filename, n, title, useList)
5685 /*Boolean*/ int useList;
5690 if (strcmp(filename, "-") == 0) {
5694 f = fopen(filename, "rb");
5696 sprintf(buf, _("Can't open \"%s\""), filename);
5697 DisplayError(buf, errno);
5701 if (fseek(f, 0, 0) == -1) {
5702 /* f is not seekable; probably a pipe */
5705 if (useList && n == 0) {
5706 int error = GameListBuild(f);
5708 DisplayError(_("Cannot build game list"), error);
5709 } else if (!ListEmpty(&gameList) &&
5710 ((ListGame *) gameList.tailPred)->number > 1) {
5711 GameListPopUp(f, title);
5718 return LoadGame(f, n, title, FALSE);
5723 MakeRegisteredMove()
5725 int fromX, fromY, toX, toY;
5727 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
5728 switch (cmailMoveType[lastLoadGameNumber - 1]) {
5731 if (appData.debugMode)
5732 fprintf(debugFP, "Restoring %s for game %d\n",
5733 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
5735 thinkOutput[0] = NULLCHAR;
5736 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
5737 fromX = cmailMove[lastLoadGameNumber - 1][0] - 'a';
5738 fromY = cmailMove[lastLoadGameNumber - 1][1] - '1';
5739 toX = cmailMove[lastLoadGameNumber - 1][2] - 'a';
5740 toY = cmailMove[lastLoadGameNumber - 1][3] - '1';
5741 promoChar = cmailMove[lastLoadGameNumber - 1][4];
5742 MakeMove(fromX, fromY, toX, toY, promoChar);
5743 ShowMove(fromX, fromY, toX, toY);
5745 switch (MateTest(boards[currentMove], PosFlags(currentMove),
5752 if (WhiteOnMove(currentMove)) {
5753 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5755 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5760 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5767 if (WhiteOnMove(currentMove)) {
5768 GameEnds(BlackWins, "White resigns", GE_PLAYER);
5770 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
5775 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
5786 /* Wrapper around LoadGame for use when a Cmail message is loaded */
5788 CmailLoadGame(f, gameNumber, title, useList)
5796 if (gameNumber > nCmailGames) {
5797 DisplayError(_("No more games in this message"), 0);
5800 if (f == lastLoadGameFP) {
5801 int offset = gameNumber - lastLoadGameNumber;
5803 cmailMsg[0] = NULLCHAR;
5804 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
5805 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
5806 nCmailMovesRegistered--;
5808 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5809 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
5810 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
5813 if (! RegisterMove()) return FALSE;
5817 retVal = LoadGame(f, gameNumber, title, useList);
5819 /* Make move registered during previous look at this game, if any */
5820 MakeRegisteredMove();
5822 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
5823 commentList[currentMove]
5824 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
5825 DisplayComment(currentMove - 1, commentList[currentMove]);
5831 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
5836 int gameNumber = lastLoadGameNumber + offset;
5837 if (lastLoadGameFP == NULL) {
5838 DisplayError(_("No game has been loaded yet"), 0);
5841 if (gameNumber <= 0) {
5842 DisplayError(_("Can't back up any further"), 0);
5845 if (cmailMsgLoaded) {
5846 return CmailLoadGame(lastLoadGameFP, gameNumber,
5847 lastLoadGameTitle, lastLoadGameUseList);
5849 return LoadGame(lastLoadGameFP, gameNumber,
5850 lastLoadGameTitle, lastLoadGameUseList);
5856 /* Load the nth game from open file f */
5858 LoadGame(f, gameNumber, title, useList)
5866 int gn = gameNumber;
5867 ListGame *lg = NULL;
5870 GameMode oldGameMode;
5872 if (appData.debugMode)
5873 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
5875 if (gameMode == Training )
5876 SetTrainingModeOff();
5878 oldGameMode = gameMode;
5879 if (gameMode != BeginningOfGame) {
5884 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
5885 fclose(lastLoadGameFP);
5889 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
5892 fseek(f, lg->offset, 0);
5893 GameListHighlight(gameNumber);
5897 DisplayError(_("Game number out of range"), 0);
5902 if (fseek(f, 0, 0) == -1) {
5903 if (f == lastLoadGameFP ?
5904 gameNumber == lastLoadGameNumber + 1 :
5908 DisplayError(_("Can't seek on game file"), 0);
5914 lastLoadGameNumber = gameNumber;
5915 strcpy(lastLoadGameTitle, title);
5916 lastLoadGameUseList = useList;
5921 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
5922 sprintf(buf, "%s vs. %s", lg->gameInfo.white,
5923 lg->gameInfo.black);
5925 } else if (*title != NULLCHAR) {
5926 if (gameNumber > 1) {
5927 sprintf(buf, "%s %d", title, gameNumber);
5930 DisplayTitle(title);
5934 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
5935 gameMode = PlayFromGameFile;
5939 currentMove = forwardMostMove = backwardMostMove = 0;
5940 CopyBoard(boards[0], initialPosition);
5944 * Skip the first gn-1 games in the file.
5945 * Also skip over anything that precedes an identifiable
5946 * start of game marker, to avoid being confused by
5947 * garbage at the start of the file. Currently
5948 * recognized start of game markers are the move number "1",
5949 * the pattern "gnuchess .* game", the pattern
5950 * "^[#;%] [^ ]* game file", and a PGN tag block.
5951 * A game that starts with one of the latter two patterns
5952 * will also have a move number 1, possibly
5953 * following a position diagram.
5954 * 5-4-02: Let's try being more lenient and allowing a game to
5955 * start with an unnumbered move. Does that break anything?
5957 cm = lastLoadGameStart = (ChessMove) 0;
5959 yyboardindex = forwardMostMove;
5960 cm = (ChessMove) yylex();
5963 if (cmailMsgLoaded) {
5964 nCmailGames = CMAIL_MAX_GAMES - gn;
5967 DisplayError(_("Game not found in file"), 0);
5974 lastLoadGameStart = cm;
5978 switch (lastLoadGameStart) {
5985 gn--; /* count this game */
5986 lastLoadGameStart = cm;
5995 switch (lastLoadGameStart) {
6000 gn--; /* count this game */
6001 lastLoadGameStart = cm;
6004 lastLoadGameStart = cm; /* game counted already */
6012 yyboardindex = forwardMostMove;
6013 cm = (ChessMove) yylex();
6014 } while (cm == PGNTag || cm == Comment);
6021 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
6022 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
6023 != CMAIL_OLD_RESULT) {
6025 cmailResult[ CMAIL_MAX_GAMES
6026 - gn - 1] = CMAIL_OLD_RESULT;
6032 /* Only a NormalMove can be at the start of a game
6033 * without a position diagram. */
6034 if (lastLoadGameStart == (ChessMove) 0) {
6036 lastLoadGameStart = MoveNumberOne;
6045 if (appData.debugMode)
6046 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
6048 if (cm == XBoardGame) {
6049 /* Skip any header junk before position diagram and/or move 1 */
6051 yyboardindex = forwardMostMove;
6052 cm = (ChessMove) yylex();
6054 if (cm == (ChessMove) 0 ||
6055 cm == GNUChessGame || cm == XBoardGame) {
6056 /* Empty game; pretend end-of-file and handle later */
6061 if (cm == MoveNumberOne || cm == PositionDiagram ||
6062 cm == PGNTag || cm == Comment)
6065 } else if (cm == GNUChessGame) {
6066 if (gameInfo.event != NULL) {
6067 free(gameInfo.event);
6069 gameInfo.event = StrSave(yy_text);
6072 startedFromSetupPosition = FALSE;
6073 while (cm == PGNTag) {
6074 if (appData.debugMode)
6075 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
6076 err = ParsePGNTag(yy_text, &gameInfo);
6077 if (!err) numPGNTags++;
6079 if (gameInfo.fen != NULL) {
6080 Board initial_position;
6081 startedFromSetupPosition = TRUE;
6082 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
6084 DisplayError(_("Bad FEN position in file"), 0);
6087 CopyBoard(boards[0], initial_position);
6088 if (blackPlaysFirst) {
6089 currentMove = forwardMostMove = backwardMostMove = 1;
6090 CopyBoard(boards[1], initial_position);
6091 strcpy(moveList[0], "");
6092 strcpy(parseList[0], "");
6093 timeRemaining[0][1] = whiteTimeRemaining;
6094 timeRemaining[1][1] = blackTimeRemaining;
6095 if (commentList[0] != NULL) {
6096 commentList[1] = commentList[0];
6097 commentList[0] = NULL;
6100 currentMove = forwardMostMove = backwardMostMove = 0;
6102 yyboardindex = forwardMostMove;
6104 gameInfo.fen = NULL;
6107 yyboardindex = forwardMostMove;
6108 cm = (ChessMove) yylex();
6110 /* Handle comments interspersed among the tags */
6111 while (cm == Comment) {
6113 if (appData.debugMode)
6114 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
6116 if (*p == '{' || *p == '[' || *p == '(') {
6117 p[strlen(p) - 1] = NULLCHAR;
6120 while (*p == '\n') p++;
6121 AppendComment(currentMove, p);
6122 yyboardindex = forwardMostMove;
6123 cm = (ChessMove) yylex();
6127 /* don't rely on existence of Event tag since if game was
6128 * pasted from clipboard the Event tag may not exist
6130 if (numPGNTags > 0){
6132 if (gameInfo.variant == VariantNormal) {
6133 gameInfo.variant = StringToVariant(gameInfo.event);
6136 tags = PGNTags(&gameInfo);
6137 TagsPopUp(tags, CmailMsg());
6141 /* Make something up, but don't display it now */
6146 if (cm == PositionDiagram) {
6149 Board initial_position;
6151 if (appData.debugMode)
6152 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
6154 if (!startedFromSetupPosition) {
6156 for (i = BOARD_SIZE - 1; i >= 0; i--)
6157 for (j = 0; j < BOARD_SIZE; p++)
6167 initial_position[i][j++] = CharToPiece(*p);
6170 while (*p == ' ' || *p == '\t' ||
6171 *p == '\n' || *p == '\r') p++;
6173 if (strncmp(p, "black", strlen("black"))==0)
6174 blackPlaysFirst = TRUE;
6176 blackPlaysFirst = FALSE;
6177 startedFromSetupPosition = TRUE;
6179 CopyBoard(boards[0], initial_position);
6180 if (blackPlaysFirst) {
6181 currentMove = forwardMostMove = backwardMostMove = 1;
6182 CopyBoard(boards[1], initial_position);
6183 strcpy(moveList[0], "");
6184 strcpy(parseList[0], "");
6185 timeRemaining[0][1] = whiteTimeRemaining;
6186 timeRemaining[1][1] = blackTimeRemaining;
6187 if (commentList[0] != NULL) {
6188 commentList[1] = commentList[0];
6189 commentList[0] = NULL;
6192 currentMove = forwardMostMove = backwardMostMove = 0;
6195 yyboardindex = forwardMostMove;
6196 cm = (ChessMove) yylex();
6199 if (first.pr == NoProc) {
6200 StartChessProgram(&first);
6202 InitChessProgram(&first);
6203 SendToProgram("force\n", &first);
6204 if (startedFromSetupPosition) {
6205 SendBoard(&first, forwardMostMove);
6206 DisplayBothClocks();
6209 while (cm == Comment) {
6211 if (appData.debugMode)
6212 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
6214 if (*p == '{' || *p == '[' || *p == '(') {
6215 p[strlen(p) - 1] = NULLCHAR;
6218 while (*p == '\n') p++;
6219 AppendComment(currentMove, p);
6220 yyboardindex = forwardMostMove;
6221 cm = (ChessMove) yylex();
6224 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
6225 cm == WhiteWins || cm == BlackWins ||
6226 cm == GameIsDrawn || cm == GameUnfinished) {
6227 DisplayMessage("", _("No moves in game"));
6228 if (cmailMsgLoaded) {
6229 if (appData.debugMode)
6230 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
6234 DrawPosition(FALSE, boards[currentMove]);
6235 DisplayBothClocks();
6236 gameMode = EditGame;
6243 if (commentList[currentMove] != NULL) {
6244 if (!matchMode && (pausing || appData.timeDelay != 0)) {
6245 DisplayComment(currentMove - 1, commentList[currentMove]);
6248 if (!matchMode && appData.timeDelay != 0)
6249 DrawPosition(FALSE, boards[currentMove]);
6251 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
6252 programStats.ok_to_send = 1;
6255 /* if the first token after the PGN tags is a move
6256 * and not move number 1, retrieve it from the parser
6258 if (cm != MoveNumberOne)
6259 LoadGameOneMove(cm);
6261 /* load the remaining moves from the file */
6262 while (LoadGameOneMove((ChessMove)0)) {
6263 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
6264 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
6267 /* rewind to the start of the game */
6268 currentMove = backwardMostMove;
6270 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
6272 if (oldGameMode == AnalyzeFile ||
6273 oldGameMode == AnalyzeMode) {
6277 if (matchMode || appData.timeDelay == 0) {
6279 gameMode = EditGame;
6281 } else if (appData.timeDelay > 0) {
6285 if (appData.debugMode)
6286 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
6290 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
6292 ReloadPosition(offset)
6295 int positionNumber = lastLoadPositionNumber + offset;
6296 if (lastLoadPositionFP == NULL) {
6297 DisplayError(_("No position has been loaded yet"), 0);
6300 if (positionNumber <= 0) {
6301 DisplayError(_("Can't back up any further"), 0);
6304 return LoadPosition(lastLoadPositionFP, positionNumber,
6305 lastLoadPositionTitle);
6308 /* Load the nth position from the given file */
6310 LoadPositionFromFile(filename, n, title)
6318 if (strcmp(filename, "-") == 0) {
6319 return LoadPosition(stdin, n, "stdin");
6321 f = fopen(filename, "rb");
6323 sprintf(buf, _("Can't open \"%s\""), filename);
6324 DisplayError(buf, errno);
6327 return LoadPosition(f, n, title);
6332 /* Load the nth position from the given open file, and close it */
6334 LoadPosition(f, positionNumber, title)
6339 char *p, line[MSG_SIZ];
6340 Board initial_position;
6341 int i, j, fenMode, pn;
6343 if (gameMode == Training )
6344 SetTrainingModeOff();
6346 if (gameMode != BeginningOfGame) {
6349 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
6350 fclose(lastLoadPositionFP);
6352 if (positionNumber == 0) positionNumber = 1;
6353 lastLoadPositionFP = f;
6354 lastLoadPositionNumber = positionNumber;
6355 strcpy(lastLoadPositionTitle, title);
6356 if (first.pr == NoProc) {
6357 StartChessProgram(&first);
6358 InitChessProgram(&first);
6360 pn = positionNumber;
6361 if (positionNumber < 0) {
6362 /* Negative position number means to seek to that byte offset */
6363 if (fseek(f, -positionNumber, 0) == -1) {
6364 DisplayError(_("Can't seek on position file"), 0);
6369 if (fseek(f, 0, 0) == -1) {
6370 if (f == lastLoadPositionFP ?
6371 positionNumber == lastLoadPositionNumber + 1 :
6372 positionNumber == 1) {
6375 DisplayError(_("Can't seek on position file"), 0);
6380 /* See if this file is FEN or old-style xboard */
6381 if (fgets(line, MSG_SIZ, f) == NULL) {
6382 DisplayError(_("Position not found in file"), 0);
6390 case 'p': case 'n': case 'b': case 'r': case 'q': case 'k':
6391 case 'P': case 'N': case 'B': case 'R': case 'Q': case 'K':
6392 case '1': case '2': case '3': case '4': case '5': case '6':
6399 if (fenMode || line[0] == '#') pn--;
6401 /* skip postions before number pn */
6402 if (fgets(line, MSG_SIZ, f) == NULL) {
6404 DisplayError(_("Position not found in file"), 0);
6407 if (fenMode || line[0] == '#') pn--;
6412 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
6413 DisplayError(_("Bad FEN position in file"), 0);
6417 (void) fgets(line, MSG_SIZ, f);
6418 (void) fgets(line, MSG_SIZ, f);
6420 for (i = BOARD_SIZE - 1; i >= 0; i--) {
6421 (void) fgets(line, MSG_SIZ, f);
6422 for (p = line, j = 0; j < BOARD_SIZE; p++) {
6425 initial_position[i][j++] = CharToPiece(*p);
6429 blackPlaysFirst = FALSE;
6431 (void) fgets(line, MSG_SIZ, f);
6432 if (strncmp(line, "black", strlen("black"))==0)
6433 blackPlaysFirst = TRUE;
6436 startedFromSetupPosition = TRUE;
6438 SendToProgram("force\n", &first);
6439 CopyBoard(boards[0], initial_position);
6440 if (blackPlaysFirst) {
6441 currentMove = forwardMostMove = backwardMostMove = 1;
6442 strcpy(moveList[0], "");
6443 strcpy(parseList[0], "");
6444 CopyBoard(boards[1], initial_position);
6445 DisplayMessage("", _("Black to play"));
6447 currentMove = forwardMostMove = backwardMostMove = 0;
6448 DisplayMessage("", _("White to play"));
6450 SendBoard(&first, forwardMostMove);
6452 if (positionNumber > 1) {
6453 sprintf(line, "%s %d", title, positionNumber);
6456 DisplayTitle(title);
6458 gameMode = EditGame;
6461 timeRemaining[0][1] = whiteTimeRemaining;
6462 timeRemaining[1][1] = blackTimeRemaining;
6463 DrawPosition(FALSE, boards[currentMove]);
6470 CopyPlayerNameIntoFileName(dest, src)
6473 while (*src != NULLCHAR && *src != ',') {
6478 *(*dest)++ = *src++;
6483 char *DefaultFileName(ext)
6486 static char def[MSG_SIZ];
6489 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
6491 CopyPlayerNameIntoFileName(&p, gameInfo.white);
6493 CopyPlayerNameIntoFileName(&p, gameInfo.black);
6502 /* Save the current game to the given file */
6504 SaveGameToFile(filename, append)
6511 if (strcmp(filename, "-") == 0) {
6512 return SaveGame(stdout, 0, NULL);
6514 f = fopen(filename, append ? "a" : "w");
6516 sprintf(buf, _("Can't open \"%s\""), filename);
6517 DisplayError(buf, errno);
6520 return SaveGame(f, 0, NULL);
6529 static char buf[MSG_SIZ];
6532 p = strchr(str, ' ');
6533 if (p == NULL) return str;
6534 strncpy(buf, str, p - str);
6535 buf[p - str] = NULLCHAR;
6539 #define PGN_MAX_LINE 75
6541 /* Save game in PGN style and close the file */
6546 int i, offset, linelen, newblock;
6550 int movelen, numlen, blank;
6552 tm = time((time_t *) NULL);
6554 PrintPGNTags(f, &gameInfo);
6556 if (backwardMostMove > 0 || startedFromSetupPosition) {
6557 char *fen = PositionToFEN(backwardMostMove);
6558 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
6559 fprintf(f, "\n{--------------\n");
6560 PrintPosition(f, backwardMostMove);
6561 fprintf(f, "--------------}\n");
6567 i = backwardMostMove;
6568 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
6572 while (i < forwardMostMove) {
6573 /* Print comments preceding this move */
6574 if (commentList[i] != NULL) {
6575 if (linelen > 0) fprintf(f, "\n");
6576 fprintf(f, "{\n%s}\n", commentList[i]);
6581 /* Format move number */
6583 sprintf(numtext, "%d.", (i - offset)/2 + 1);
6586 sprintf(numtext, "%d...", (i - offset)/2 + 1);
6588 numtext[0] = NULLCHAR;
6591 numlen = strlen(numtext);
6594 /* Print move number */
6595 blank = linelen > 0 && numlen > 0;
6596 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
6605 fprintf(f, numtext);
6609 movetext = SavePart(parseList[i]);
6610 movelen = strlen(movetext);
6613 blank = linelen > 0 && movelen > 0;
6614 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
6623 fprintf(f, movetext);
6629 /* Start a new line */
6630 if (linelen > 0) fprintf(f, "\n");
6632 /* Print comments after last move */
6633 if (commentList[i] != NULL) {
6634 fprintf(f, "{\n%s}\n", commentList[i]);
6638 if (gameInfo.resultDetails != NULL &&
6639 gameInfo.resultDetails[0] != NULLCHAR) {
6640 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
6641 PGNResult(gameInfo.result));
6643 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
6650 /* Save game in old style and close the file */
6658 tm = time((time_t *) NULL);
6660 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
6663 if (backwardMostMove > 0 || startedFromSetupPosition) {
6664 fprintf(f, "\n[--------------\n");
6665 PrintPosition(f, backwardMostMove);
6666 fprintf(f, "--------------]\n");
6671 i = backwardMostMove;
6672 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
6674 while (i < forwardMostMove) {
6675 if (commentList[i] != NULL) {
6676 fprintf(f, "[%s]\n", commentList[i]);
6680 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
6683 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
6685 if (commentList[i] != NULL) {
6689 if (i >= forwardMostMove) {
6693 fprintf(f, "%s\n", parseList[i]);
6698 if (commentList[i] != NULL) {
6699 fprintf(f, "[%s]\n", commentList[i]);
6702 /* This isn't really the old style, but it's close enough */
6703 if (gameInfo.resultDetails != NULL &&
6704 gameInfo.resultDetails[0] != NULLCHAR) {
6705 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
6706 gameInfo.resultDetails);
6708 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
6715 /* Save the current game to open file f and close the file */
6717 SaveGame(f, dummy, dummy2)
6722 if (gameMode == EditPosition) EditPositionDone();
6723 if (appData.oldSaveStyle)
6724 return SaveGameOldStyle(f);
6726 return SaveGamePGN(f);
6729 /* Save the current position to the given file */
6731 SavePositionToFile(filename)
6737 if (strcmp(filename, "-") == 0) {
6738 return SavePosition(stdout, 0, NULL);
6740 f = fopen(filename, "a");
6742 sprintf(buf, _("Can't open \"%s\""), filename);
6743 DisplayError(buf, errno);
6746 SavePosition(f, 0, NULL);
6752 /* Save the current position to the given open file and close the file */
6754 SavePosition(f, dummy, dummy2)
6762 if (appData.oldSaveStyle) {
6763 tm = time((time_t *) NULL);
6765 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
6767 fprintf(f, "[--------------\n");
6768 PrintPosition(f, currentMove);
6769 fprintf(f, "--------------]\n");
6771 fen = PositionToFEN(currentMove);
6772 fprintf(f, "%s\n", fen);
6780 ReloadCmailMsgEvent(unregister)
6784 static char *inFilename = NULL;
6785 static char *outFilename;
6787 struct stat inbuf, outbuf;
6790 /* Any registered moves are unregistered if unregister is set, */
6791 /* i.e. invoked by the signal handler */
6793 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
6794 cmailMoveRegistered[i] = FALSE;
6795 if (cmailCommentList[i] != NULL) {
6796 free(cmailCommentList[i]);
6797 cmailCommentList[i] = NULL;
6800 nCmailMovesRegistered = 0;
6803 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
6804 cmailResult[i] = CMAIL_NOT_RESULT;
6808 if (inFilename == NULL) {
6809 /* Because the filenames are static they only get malloced once */
6810 /* and they never get freed */
6811 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
6812 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
6814 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
6815 sprintf(outFilename, "%s.out", appData.cmailGameName);
6818 status = stat(outFilename, &outbuf);
6820 cmailMailedMove = FALSE;
6822 status = stat(inFilename, &inbuf);
6823 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
6826 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
6827 counts the games, notes how each one terminated, etc.
6829 It would be nice to remove this kludge and instead gather all
6830 the information while building the game list. (And to keep it
6831 in the game list nodes instead of having a bunch of fixed-size
6832 parallel arrays.) Note this will require getting each game's
6833 termination from the PGN tags, as the game list builder does
6834 not process the game moves. --mann
6836 cmailMsgLoaded = TRUE;
6837 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
6839 /* Load first game in the file or popup game menu */
6840 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
6850 char string[MSG_SIZ];
6852 if ( cmailMailedMove
6853 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
6854 return TRUE; /* Allow free viewing */
6857 /* Unregister move to ensure that we don't leave RegisterMove */
6858 /* with the move registered when the conditions for registering no */
6860 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
6861 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
6862 nCmailMovesRegistered --;
6864 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
6866 free(cmailCommentList[lastLoadGameNumber - 1]);
6867 cmailCommentList[lastLoadGameNumber - 1] = NULL;
6871 if (cmailOldMove == -1) {
6872 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
6876 if (currentMove > cmailOldMove + 1) {
6877 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
6881 if (currentMove < cmailOldMove) {
6882 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
6886 if (forwardMostMove > currentMove) {
6887 /* Silently truncate extra moves */
6891 if ( (currentMove == cmailOldMove + 1)
6892 || ( (currentMove == cmailOldMove)
6893 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
6894 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
6895 if (gameInfo.result != GameUnfinished) {
6896 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
6899 if (commentList[currentMove] != NULL) {
6900 cmailCommentList[lastLoadGameNumber - 1]
6901 = StrSave(commentList[currentMove]);
6903 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
6905 if (appData.debugMode)
6906 fprintf(debugFP, "Saving %s for game %d\n",
6907 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
6910 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
6912 f = fopen(string, "w");
6913 if (appData.oldSaveStyle) {
6914 SaveGameOldStyle(f); /* also closes the file */
6916 sprintf(string, "%s.pos.out", appData.cmailGameName);
6917 f = fopen(string, "w");
6918 SavePosition(f, 0, NULL); /* also closes the file */
6920 fprintf(f, "{--------------\n");
6921 PrintPosition(f, currentMove);
6922 fprintf(f, "--------------}\n\n");
6924 SaveGame(f, 0, NULL); /* also closes the file*/
6927 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
6928 nCmailMovesRegistered ++;
6929 } else if (nCmailGames == 1) {
6930 DisplayError(_("You have not made a move yet"), 0);
6941 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
6942 FILE *commandOutput;
6943 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
6944 int nBytes = 0; /* Suppress warnings on uninitialized variables */
6950 if (! cmailMsgLoaded) {
6951 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
6955 if (nCmailGames == nCmailResults) {
6956 DisplayError(_("No unfinished games"), 0);
6960 #if CMAIL_PROHIBIT_REMAIL
6961 if (cmailMailedMove) {
6962 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);
6963 DisplayError(msg, 0);
6968 if (! (cmailMailedMove || RegisterMove())) return;
6970 if ( cmailMailedMove
6971 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
6972 sprintf(string, partCommandString,
6973 appData.debugMode ? " -v" : "", appData.cmailGameName);
6974 commandOutput = popen(string, "rb");
6976 if (commandOutput == NULL) {
6977 DisplayError(_("Failed to invoke cmail"), 0);
6979 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
6980 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
6983 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
6984 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
6985 nBytes = MSG_SIZ - 1;
6987 (void) memcpy(msg, buffer, nBytes);
6989 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
6991 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
6992 cmailMailedMove = TRUE; /* Prevent >1 moves */
6995 for (i = 0; i < nCmailGames; i ++) {
6996 if (cmailResult[i] == CMAIL_NOT_RESULT) {
7001 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
7003 sprintf(buffer, "%s/%s.%s.archive",
7005 appData.cmailGameName,
7007 LoadGameFromFile(buffer, 1, buffer, FALSE);
7008 cmailMsgLoaded = FALSE;
7012 DisplayInformation(msg);
7013 pclose(commandOutput);
7016 if ((*cmailMsg) != '\0') {
7017 DisplayInformation(cmailMsg);
7031 int prependComma = 0;
7033 char string[MSG_SIZ]; /* Space for game-list */
7036 if (!cmailMsgLoaded) return "";
7038 if (cmailMailedMove) {
7039 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
7041 /* Create a list of games left */
7042 sprintf(string, "[");
7043 for (i = 0; i < nCmailGames; i ++) {
7044 if (! ( cmailMoveRegistered[i]
7045 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
7047 sprintf(number, ",%d", i + 1);
7049 sprintf(number, "%d", i + 1);
7053 strcat(string, number);
7056 strcat(string, "]");
7058 if (nCmailMovesRegistered + nCmailResults == 0) {
7059 switch (nCmailGames) {
7062 _("Still need to make move for game\n"));
7067 _("Still need to make moves for both games\n"));
7072 _("Still need to make moves for all %d games\n"),
7077 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
7080 _("Still need to make a move for game %s\n"),
7085 if (nCmailResults == nCmailGames) {
7086 sprintf(cmailMsg, _("No unfinished games\n"));
7088 sprintf(cmailMsg, _("Ready to send mail\n"));
7094 _("Still need to make moves for games %s\n"),
7106 if (gameMode == Training)
7107 SetTrainingModeOff();
7110 cmailMsgLoaded = FALSE;
7111 if (appData.icsActive) {
7112 SendToICS(ics_prefix);
7113 SendToICS("refresh\n");
7117 static int exiting = 0;
7125 /* Give up on clean exit */
7129 /* Keep trying for clean exit */
7133 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
7135 if (telnetISR != NULL) {
7136 RemoveInputSource(telnetISR);
7138 if (icsPR != NoProc) {
7139 DestroyChildProcess(icsPR, TRUE);
7141 /* Save game if resource set and not already saved by GameEnds() */
7142 if (gameInfo.resultDetails == NULL && forwardMostMove > 0) {
7143 if (*appData.saveGameFile != NULLCHAR) {
7144 SaveGameToFile(appData.saveGameFile, TRUE);
7145 } else if (appData.autoSaveGames) {
7148 if (*appData.savePositionFile != NULLCHAR) {
7149 SavePositionToFile(appData.savePositionFile);
7152 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
7154 /* Kill off chess programs */
7155 if (first.pr != NoProc) {
7157 SendToProgram("quit\n", &first);
7158 DestroyChildProcess(first.pr, first.useSigterm);
7160 if (second.pr != NoProc) {
7161 SendToProgram("quit\n", &second);
7162 DestroyChildProcess(second.pr, second.useSigterm);
7164 if (first.isr != NULL) {
7165 RemoveInputSource(first.isr);
7167 if (second.isr != NULL) {
7168 RemoveInputSource(second.isr);
7178 if (appData.debugMode)
7179 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
7183 if (gameMode == MachinePlaysWhite ||
7184 gameMode == MachinePlaysBlack) {
7187 DisplayBothClocks();
7189 if (gameMode == PlayFromGameFile) {
7190 if (appData.timeDelay >= 0)
7192 } else if (gameMode == IcsExamining && pauseExamInvalid) {
7194 SendToICS(ics_prefix);
7195 SendToICS("refresh\n");
7196 } else if (currentMove < forwardMostMove) {
7197 ForwardInner(forwardMostMove);
7199 pauseExamInvalid = FALSE;
7205 pauseExamForwardMostMove = forwardMostMove;
7206 pauseExamInvalid = FALSE;
7209 case IcsPlayingWhite:
7210 case IcsPlayingBlack:
7214 case PlayFromGameFile:
7215 (void) StopLoadGameTimer();
7219 case BeginningOfGame:
7220 if (appData.icsActive) return;
7221 /* else fall through */
7222 case MachinePlaysWhite:
7223 case MachinePlaysBlack:
7224 case TwoMachinesPlay:
7225 if (forwardMostMove == 0)
7226 return; /* don't pause if no one has moved */
7227 if ((gameMode == MachinePlaysWhite &&
7228 !WhiteOnMove(forwardMostMove)) ||
7229 (gameMode == MachinePlaysBlack &&
7230 WhiteOnMove(forwardMostMove))) {
7243 char title[MSG_SIZ];
7245 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
7246 strcpy(title, _("Edit comment"));
7248 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
7249 WhiteOnMove(currentMove - 1) ? " " : ".. ",
7250 parseList[currentMove - 1]);
7253 EditCommentPopUp(currentMove, title, commentList[currentMove]);
7260 char *tags = PGNTags(&gameInfo);
7261 EditTagsPopUp(tags);
7268 if (appData.noChessProgram || gameMode == AnalyzeMode)
7271 if (gameMode != AnalyzeFile) {
7273 if (gameMode != EditGame) return;
7274 ResurrectChessProgram();
7275 SendToProgram("analyze\n", &first);
7276 first.analyzing = TRUE;
7277 /*first.maybeThinking = TRUE;*/
7278 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
7279 AnalysisPopUp(_("Analysis"),
7280 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
7282 gameMode = AnalyzeMode;
7287 StartAnalysisClock();
7288 GetTimeMark(&lastNodeCountTime);
7295 if (appData.noChessProgram || gameMode == AnalyzeFile)
7298 if (gameMode != AnalyzeMode) {
7300 if (gameMode != EditGame) return;
7301 ResurrectChessProgram();
7302 SendToProgram("analyze\n", &first);
7303 first.analyzing = TRUE;
7304 /*first.maybeThinking = TRUE;*/
7305 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
7306 AnalysisPopUp(_("Analysis"),
7307 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
7309 gameMode = AnalyzeFile;
7314 StartAnalysisClock();
7315 GetTimeMark(&lastNodeCountTime);
7324 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
7328 if (gameMode == PlayFromGameFile ||
7329 gameMode == TwoMachinesPlay ||
7330 gameMode == Training ||
7331 gameMode == AnalyzeMode ||
7332 gameMode == EndOfGame)
7335 if (gameMode == EditPosition)
7338 if (!WhiteOnMove(currentMove)) {
7339 DisplayError(_("It is not White's turn"), 0);
7343 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
7346 if (gameMode == EditGame || gameMode == AnalyzeMode ||
7347 gameMode == AnalyzeFile)
7350 ResurrectChessProgram(); /* in case it isn't running */
7351 gameMode = MachinePlaysWhite;
7355 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
7357 if (first.sendName) {
7358 sprintf(buf, "name %s\n", gameInfo.black);
7359 SendToProgram(buf, &first);
7361 if (first.sendTime) {
7362 if (first.useColors) {
7363 SendToProgram("black\n", &first); /*gnu kludge*/
7365 SendTimeRemaining(&first, TRUE);
7367 if (first.useColors) {
7368 SendToProgram("white\ngo\n", &first);
7370 SendToProgram("go\n", &first);
7372 SetMachineThinkingEnables();
7373 first.maybeThinking = TRUE;
7376 if (appData.autoFlipView && !flipView) {
7377 flipView = !flipView;
7378 DrawPosition(FALSE, NULL);
7387 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
7391 if (gameMode == PlayFromGameFile ||
7392 gameMode == TwoMachinesPlay ||
7393 gameMode == Training ||
7394 gameMode == AnalyzeMode ||
7395 gameMode == EndOfGame)
7398 if (gameMode == EditPosition)
7401 if (WhiteOnMove(currentMove)) {
7402 DisplayError(_("It is not Black's turn"), 0);
7406 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
7409 if (gameMode == EditGame || gameMode == AnalyzeMode ||
7410 gameMode == AnalyzeFile)
7413 ResurrectChessProgram(); /* in case it isn't running */
7414 gameMode = MachinePlaysBlack;
7418 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
7420 if (first.sendName) {
7421 sprintf(buf, "name %s\n", gameInfo.white);
7422 SendToProgram(buf, &first);
7424 if (first.sendTime) {
7425 if (first.useColors) {
7426 SendToProgram("white\n", &first); /*gnu kludge*/
7428 SendTimeRemaining(&first, FALSE);
7430 if (first.useColors) {
7431 SendToProgram("black\ngo\n", &first);
7433 SendToProgram("go\n", &first);
7435 SetMachineThinkingEnables();
7436 first.maybeThinking = TRUE;
7439 if (appData.autoFlipView && flipView) {
7440 flipView = !flipView;
7441 DrawPosition(FALSE, NULL);
7447 DisplayTwoMachinesTitle()
7450 if (appData.matchGames > 0) {
7451 if (first.twoMachinesColor[0] == 'w') {
7452 sprintf(buf, "%s vs. %s (%d-%d-%d)",
7453 gameInfo.white, gameInfo.black,
7454 first.matchWins, second.matchWins,
7455 matchGame - 1 - (first.matchWins + second.matchWins));
7457 sprintf(buf, "%s vs. %s (%d-%d-%d)",
7458 gameInfo.white, gameInfo.black,
7459 second.matchWins, first.matchWins,
7460 matchGame - 1 - (first.matchWins + second.matchWins));
7463 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
7469 TwoMachinesEvent P((void))
7473 ChessProgramState *onmove;
7475 if (appData.noChessProgram) return;
7478 case TwoMachinesPlay:
7480 case MachinePlaysWhite:
7481 case MachinePlaysBlack:
7482 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
7483 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
7487 case BeginningOfGame:
7488 case PlayFromGameFile:
7491 if (gameMode != EditGame) return;
7505 forwardMostMove = currentMove;
7506 ResurrectChessProgram(); /* in case first program isn't running */
7508 if (second.pr == NULL) {
7509 StartChessProgram(&second);
7510 if (second.protocolVersion == 1) {
7511 TwoMachinesEventIfReady();
7513 /* kludge: allow timeout for initial "feature" command */
7515 DisplayMessage("", _("Starting second chess program"));
7516 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
7520 DisplayMessage("", "");
7521 InitChessProgram(&second);
7522 SendToProgram("force\n", &second);
7523 if (startedFromSetupPosition) {
7524 SendBoard(&second, backwardMostMove);
7526 for (i = backwardMostMove; i < forwardMostMove; i++) {
7527 SendMoveToProgram(i, &second);
7530 gameMode = TwoMachinesPlay;
7534 DisplayTwoMachinesTitle();
7536 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
7542 SendToProgram(first.computerString, &first);
7543 if (first.sendName) {
7544 sprintf(buf, "name %s\n", second.tidy);
7545 SendToProgram(buf, &first);
7547 SendToProgram(second.computerString, &second);
7548 if (second.sendName) {
7549 sprintf(buf, "name %s\n", first.tidy);
7550 SendToProgram(buf, &second);
7553 if (!first.sendTime || !second.sendTime) {
7555 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7556 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7558 if (onmove->sendTime) {
7559 if (onmove->useColors) {
7560 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
7562 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
7564 if (onmove->useColors) {
7565 SendToProgram(onmove->twoMachinesColor, onmove);
7567 SendToProgram("go\n", onmove);
7568 onmove->maybeThinking = TRUE;
7569 SetMachineThinkingEnables();
7577 if (gameMode == Training) {
7578 SetTrainingModeOff();
7579 gameMode = PlayFromGameFile;
7580 DisplayMessage("", _("Training mode off"));
7582 gameMode = Training;
7583 animateTraining = appData.animate;
7585 /* make sure we are not already at the end of the game */
7586 if (currentMove < forwardMostMove) {
7587 SetTrainingModeOn();
7588 DisplayMessage("", _("Training mode on"));
7590 gameMode = PlayFromGameFile;
7591 DisplayError(_("Already at end of game"), 0);
7600 if (!appData.icsActive) return;
7602 case IcsPlayingWhite:
7603 case IcsPlayingBlack:
7606 case BeginningOfGame:
7640 SetTrainingModeOff();
7642 case MachinePlaysWhite:
7643 case MachinePlaysBlack:
7644 case BeginningOfGame:
7645 SendToProgram("force\n", &first);
7646 SetUserThinkingEnables();
7648 case PlayFromGameFile:
7649 (void) StopLoadGameTimer();
7650 if (gameFileFP != NULL) {
7660 SendToProgram("force\n", &first);
7662 case TwoMachinesPlay:
7663 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
7664 ResurrectChessProgram();
7665 SetUserThinkingEnables();
7668 ResurrectChessProgram();
7670 case IcsPlayingBlack:
7671 case IcsPlayingWhite:
7672 DisplayError(_("Warning: You are still playing a game"), 0);
7675 DisplayError(_("Warning: You are still observing a game"), 0);
7678 DisplayError(_("Warning: You are still examining a game"), 0);
7689 first.offeredDraw = second.offeredDraw = 0;
7691 if (gameMode == PlayFromGameFile) {
7692 whiteTimeRemaining = timeRemaining[0][currentMove];
7693 blackTimeRemaining = timeRemaining[1][currentMove];
7697 if (gameMode == MachinePlaysWhite ||
7698 gameMode == MachinePlaysBlack ||
7699 gameMode == TwoMachinesPlay ||
7700 gameMode == EndOfGame) {
7701 i = forwardMostMove;
7702 while (i > currentMove) {
7703 SendToProgram("undo\n", &first);
7706 whiteTimeRemaining = timeRemaining[0][currentMove];
7707 blackTimeRemaining = timeRemaining[1][currentMove];
7708 DisplayBothClocks();
7709 if (whiteFlag || blackFlag) {
7710 whiteFlag = blackFlag = 0;
7715 gameMode = EditGame;
7724 if (gameMode == EditPosition) {
7730 if (gameMode != EditGame) return;
7732 gameMode = EditPosition;
7735 if (currentMove > 0)
7736 CopyBoard(boards[0], boards[currentMove]);
7738 blackPlaysFirst = !WhiteOnMove(currentMove);
7740 currentMove = forwardMostMove = backwardMostMove = 0;
7741 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
7748 if (first.analysisSupport && first.analyzing) {
7749 SendToProgram("exit\n", &first);
7750 first.analyzing = FALSE;
7753 thinkOutput[0] = NULLCHAR;
7759 startedFromSetupPosition = TRUE;
7760 InitChessProgram(&first);
7761 SendToProgram("force\n", &first);
7762 if (blackPlaysFirst) {
7763 strcpy(moveList[0], "");
7764 strcpy(parseList[0], "");
7765 currentMove = forwardMostMove = backwardMostMove = 1;
7766 CopyBoard(boards[1], boards[0]);
7768 currentMove = forwardMostMove = backwardMostMove = 0;
7770 SendBoard(&first, forwardMostMove);
7772 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7773 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7774 gameMode = EditGame;
7776 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
7779 /* Pause for `ms' milliseconds */
7780 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
7790 } while (SubtractTimeMarks(&m2, &m1) < ms);
7793 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
7795 SendMultiLineToICS(buf)
7798 char temp[MSG_SIZ+1], *p;
7805 strncpy(temp, buf, len);
7810 if (*p == '\n' || *p == '\r')
7817 SendToPlayer(temp, strlen(temp));
7821 SetWhiteToPlayEvent()
7823 if (gameMode == EditPosition) {
7824 blackPlaysFirst = FALSE;
7825 DisplayBothClocks(); /* works because currentMove is 0 */
7826 } else if (gameMode == IcsExamining) {
7827 SendToICS(ics_prefix);
7828 SendToICS("tomove white\n");
7833 SetBlackToPlayEvent()
7835 if (gameMode == EditPosition) {
7836 blackPlaysFirst = TRUE;
7837 currentMove = 1; /* kludge */
7838 DisplayBothClocks();
7840 } else if (gameMode == IcsExamining) {
7841 SendToICS(ics_prefix);
7842 SendToICS("tomove black\n");
7847 EditPositionMenuEvent(selection, x, y)
7848 ChessSquare selection;
7853 if (gameMode != EditPosition && gameMode != IcsExamining) return;
7855 switch (selection) {
7857 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
7858 SendToICS(ics_prefix);
7859 SendToICS("bsetup clear\n");
7860 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
7861 SendToICS(ics_prefix);
7862 SendToICS("clearboard\n");
7864 for (x = 0; x < BOARD_SIZE; x++) {
7865 for (y = 0; y < BOARD_SIZE; y++) {
7866 if (gameMode == IcsExamining) {
7867 if (boards[currentMove][y][x] != EmptySquare) {
7868 sprintf(buf, "%sx@%c%c\n", ics_prefix,
7873 boards[0][y][x] = EmptySquare;
7878 if (gameMode == EditPosition) {
7879 DrawPosition(FALSE, boards[0]);
7884 SetWhiteToPlayEvent();
7888 SetBlackToPlayEvent();
7892 if (gameMode == IcsExamining) {
7893 sprintf(buf, "%sx@%c%c\n", ics_prefix, 'a' + x, '1' + y);
7896 boards[0][y][x] = EmptySquare;
7897 DrawPosition(FALSE, boards[0]);
7902 if (gameMode == IcsExamining) {
7903 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
7904 PieceToChar(selection), 'a' + x, '1' + y);
7907 boards[0][y][x] = selection;
7908 DrawPosition(FALSE, boards[0]);
7916 DropMenuEvent(selection, x, y)
7917 ChessSquare selection;
7923 case IcsPlayingWhite:
7924 case MachinePlaysBlack:
7925 if (!WhiteOnMove(currentMove)) {
7926 DisplayMoveError(_("It is Black's turn"));
7929 moveType = WhiteDrop;
7931 case IcsPlayingBlack:
7932 case MachinePlaysWhite:
7933 if (WhiteOnMove(currentMove)) {
7934 DisplayMoveError(_("It is White's turn"));
7937 moveType = BlackDrop;
7940 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7946 if (moveType == BlackDrop && selection < BlackPawn) {
7947 selection = (ChessSquare) ((int) selection
7948 + (int) BlackPawn - (int) WhitePawn);
7950 if (boards[currentMove][y][x] != EmptySquare) {
7951 DisplayMoveError(_("That square is occupied"));
7955 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
7961 /* Accept a pending offer of any kind from opponent */
7963 if (appData.icsActive) {
7964 SendToICS(ics_prefix);
7965 SendToICS("accept\n");
7966 } else if (cmailMsgLoaded) {
7967 if (currentMove == cmailOldMove &&
7968 commentList[cmailOldMove] != NULL &&
7969 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
7970 "Black offers a draw" : "White offers a draw")) {
7972 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
7973 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
7975 DisplayError(_("There is no pending offer on this move"), 0);
7976 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7979 /* Not used for offers from chess program */
7986 /* Decline a pending offer of any kind from opponent */
7988 if (appData.icsActive) {
7989 SendToICS(ics_prefix);
7990 SendToICS("decline\n");
7991 } else if (cmailMsgLoaded) {
7992 if (currentMove == cmailOldMove &&
7993 commentList[cmailOldMove] != NULL &&
7994 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
7995 "Black offers a draw" : "White offers a draw")) {
7997 AppendComment(cmailOldMove, "Draw declined");
7998 DisplayComment(cmailOldMove - 1, "Draw declined");
8001 DisplayError(_("There is no pending offer on this move"), 0);
8004 /* Not used for offers from chess program */
8011 /* Issue ICS rematch command */
8012 if (appData.icsActive) {
8013 SendToICS(ics_prefix);
8014 SendToICS("rematch\n");
8021 /* Call your opponent's flag (claim a win on time) */
8022 if (appData.icsActive) {
8023 SendToICS(ics_prefix);
8024 SendToICS("flag\n");
8029 case MachinePlaysWhite:
8032 GameEnds(GameIsDrawn, "Both players ran out of time",
8035 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
8037 DisplayError(_("Your opponent is not out of time"), 0);
8040 case MachinePlaysBlack:
8043 GameEnds(GameIsDrawn, "Both players ran out of time",
8046 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
8048 DisplayError(_("Your opponent is not out of time"), 0);
8058 /* Offer draw or accept pending draw offer from opponent */
8060 if (appData.icsActive) {
8061 /* Note: tournament rules require draw offers to be
8062 made after you make your move but before you punch
8063 your clock. Currently ICS doesn't let you do that;
8064 instead, you immediately punch your clock after making
8065 a move, but you can offer a draw at any time. */
8067 SendToICS(ics_prefix);
8068 SendToICS("draw\n");
8069 } else if (cmailMsgLoaded) {
8070 if (currentMove == cmailOldMove &&
8071 commentList[cmailOldMove] != NULL &&
8072 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
8073 "Black offers a draw" : "White offers a draw")) {
8074 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8075 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
8076 } else if (currentMove == cmailOldMove + 1) {
8077 char *offer = WhiteOnMove(cmailOldMove) ?
8078 "White offers a draw" : "Black offers a draw";
8079 AppendComment(currentMove, offer);
8080 DisplayComment(currentMove - 1, offer);
8081 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
8083 DisplayError(_("You must make your move before offering a draw"), 0);
8084 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8086 } else if (first.offeredDraw) {
8087 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8089 if (first.sendDrawOffers) {
8090 SendToProgram("draw\n", &first);
8091 userOfferedDraw = TRUE;
8099 /* Offer Adjourn or accept pending Adjourn offer from opponent */
8101 if (appData.icsActive) {
8102 SendToICS(ics_prefix);
8103 SendToICS("adjourn\n");
8105 /* Currently GNU Chess doesn't offer or accept Adjourns */
8113 /* Offer Abort or accept pending Abort offer from opponent */
8115 if (appData.icsActive) {
8116 SendToICS(ics_prefix);
8117 SendToICS("abort\n");
8119 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
8126 /* Resign. You can do this even if it's not your turn. */
8128 if (appData.icsActive) {
8129 SendToICS(ics_prefix);
8130 SendToICS("resign\n");
8133 case MachinePlaysWhite:
8134 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8136 case MachinePlaysBlack:
8137 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8140 if (cmailMsgLoaded) {
8142 if (WhiteOnMove(cmailOldMove)) {
8143 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8145 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8147 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
8158 StopObservingEvent()
8160 /* Stop observing current games */
8161 SendToICS(ics_prefix);
8162 SendToICS("unobserve\n");
8166 StopExaminingEvent()
8168 /* Stop observing current game */
8169 SendToICS(ics_prefix);
8170 SendToICS("unexamine\n");
8174 ForwardInner(target)
8179 if (appData.debugMode)
8180 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
8181 target, currentMove, forwardMostMove);
8183 if (gameMode == EditPosition)
8186 if (gameMode == PlayFromGameFile && !pausing)
8189 if (gameMode == IcsExamining && pausing)
8190 limit = pauseExamForwardMostMove;
8192 limit = forwardMostMove;
8194 if (target > limit) target = limit;
8196 if (target > 0 && moveList[target - 1][0]) {
8197 int fromX, fromY, toX, toY;
8198 toX = moveList[target - 1][2] - 'a';
8199 toY = moveList[target - 1][3] - '1';
8200 if (moveList[target - 1][1] == '@') {
8201 if (appData.highlightLastMove) {
8202 SetHighlights(-1, -1, toX, toY);
8205 fromX = moveList[target - 1][0] - 'a';
8206 fromY = moveList[target - 1][1] - '1';
8207 if (target == currentMove + 1) {
8208 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8210 if (appData.highlightLastMove) {
8211 SetHighlights(fromX, fromY, toX, toY);
8215 if (gameMode == EditGame || gameMode == AnalyzeMode ||
8216 gameMode == Training || gameMode == PlayFromGameFile ||
8217 gameMode == AnalyzeFile) {
8218 while (currentMove < target) {
8219 SendMoveToProgram(currentMove++, &first);
8222 currentMove = target;
8225 if (gameMode == EditGame || gameMode == EndOfGame) {
8226 whiteTimeRemaining = timeRemaining[0][currentMove];
8227 blackTimeRemaining = timeRemaining[1][currentMove];
8229 DisplayBothClocks();
8230 DisplayMove(currentMove - 1);
8231 DrawPosition(FALSE, boards[currentMove]);
8232 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8233 if (commentList[currentMove] && !matchMode && gameMode != Training) {
8234 DisplayComment(currentMove - 1, commentList[currentMove]);
8242 if (gameMode == IcsExamining && !pausing) {
8243 SendToICS(ics_prefix);
8244 SendToICS("forward\n");
8246 ForwardInner(currentMove + 1);
8253 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
8254 /* to optimze, we temporarily turn off analysis mode while we feed
8255 * the remaining moves to the engine. Otherwise we get analysis output
8258 if (first.analysisSupport) {
8259 SendToProgram("exit\nforce\n", &first);
8260 first.analyzing = FALSE;
8264 if (gameMode == IcsExamining && !pausing) {
8265 SendToICS(ics_prefix);
8266 SendToICS("forward 999999\n");
8268 ForwardInner(forwardMostMove);
8271 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
8272 /* we have fed all the moves, so reactivate analysis mode */
8273 SendToProgram("analyze\n", &first);
8274 first.analyzing = TRUE;
8275 /*first.maybeThinking = TRUE;*/
8276 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
8281 BackwardInner(target)
8284 if (appData.debugMode)
8285 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
8286 target, currentMove, forwardMostMove);
8288 if (gameMode == EditPosition) return;
8289 if (currentMove <= backwardMostMove) {
8291 DrawPosition(FALSE, boards[currentMove]);
8294 if (gameMode == PlayFromGameFile && !pausing)
8297 if (moveList[target][0]) {
8298 int fromX, fromY, toX, toY;
8299 toX = moveList[target][2] - 'a';
8300 toY = moveList[target][3] - '1';
8301 if (moveList[target][1] == '@') {
8302 if (appData.highlightLastMove) {
8303 SetHighlights(-1, -1, toX, toY);
8306 fromX = moveList[target][0] - 'a';
8307 fromY = moveList[target][1] - '1';
8308 if (target == currentMove - 1) {
8309 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
8311 if (appData.highlightLastMove) {
8312 SetHighlights(fromX, fromY, toX, toY);
8316 if (gameMode == EditGame || gameMode==AnalyzeMode ||
8317 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8318 while (currentMove > target) {
8319 SendToProgram("undo\n", &first);
8323 currentMove = target;
8326 if (gameMode == EditGame || gameMode == EndOfGame) {
8327 whiteTimeRemaining = timeRemaining[0][currentMove];
8328 blackTimeRemaining = timeRemaining[1][currentMove];
8330 DisplayBothClocks();
8331 DisplayMove(currentMove - 1);
8332 DrawPosition(FALSE, boards[currentMove]);
8333 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8334 if (commentList[currentMove] != NULL) {
8335 DisplayComment(currentMove - 1, commentList[currentMove]);
8342 if (gameMode == IcsExamining && !pausing) {
8343 SendToICS(ics_prefix);
8344 SendToICS("backward\n");
8346 BackwardInner(currentMove - 1);
8353 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
8354 /* to optimze, we temporarily turn off analysis mode while we undo
8355 * all the moves. Otherwise we get analysis output after each undo.
8357 if (first.analysisSupport) {
8358 SendToProgram("exit\nforce\n", &first);
8359 first.analyzing = FALSE;
8363 if (gameMode == IcsExamining && !pausing) {
8364 SendToICS(ics_prefix);
8365 SendToICS("backward 999999\n");
8367 BackwardInner(backwardMostMove);
8370 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
8371 /* we have fed all the moves, so reactivate analysis mode */
8372 SendToProgram("analyze\n", &first);
8373 first.analyzing = TRUE;
8374 /*first.maybeThinking = TRUE;*/
8375 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
8382 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
8383 if (to >= forwardMostMove) to = forwardMostMove;
8384 if (to <= backwardMostMove) to = backwardMostMove;
8385 if (to < currentMove) {
8395 if (gameMode != IcsExamining) {
8396 DisplayError(_("You are not examining a game"), 0);
8400 DisplayError(_("You can't revert while pausing"), 0);
8403 SendToICS(ics_prefix);
8404 SendToICS("revert\n");
8411 case MachinePlaysWhite:
8412 case MachinePlaysBlack:
8413 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
8414 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
8417 if (forwardMostMove < 2) return;
8418 currentMove = forwardMostMove = forwardMostMove - 2;
8419 whiteTimeRemaining = timeRemaining[0][currentMove];
8420 blackTimeRemaining = timeRemaining[1][currentMove];
8421 DisplayBothClocks();
8422 DisplayMove(currentMove - 1);
8423 ClearHighlights();/*!! could figure this out*/
8424 DrawPosition(FALSE, boards[currentMove]);
8425 SendToProgram("remove\n", &first);
8426 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
8429 case BeginningOfGame:
8433 case IcsPlayingWhite:
8434 case IcsPlayingBlack:
8435 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
8436 SendToICS(ics_prefix);
8437 SendToICS("takeback 2\n");
8439 SendToICS(ics_prefix);
8440 SendToICS("takeback 1\n");
8449 ChessProgramState *cps;
8452 case MachinePlaysWhite:
8453 if (!WhiteOnMove(forwardMostMove)) {
8454 DisplayError(_("It is your turn"), 0);
8459 case MachinePlaysBlack:
8460 if (WhiteOnMove(forwardMostMove)) {
8461 DisplayError(_("It is your turn"), 0);
8466 case TwoMachinesPlay:
8467 if (WhiteOnMove(forwardMostMove) ==
8468 (first.twoMachinesColor[0] == 'w')) {
8474 case BeginningOfGame:
8478 SendToProgram("?\n", cps);
8485 if (gameMode != EditGame) return;
8492 if (forwardMostMove > currentMove) {
8493 if (gameInfo.resultDetails != NULL) {
8494 free(gameInfo.resultDetails);
8495 gameInfo.resultDetails = NULL;
8496 gameInfo.result = GameUnfinished;
8498 forwardMostMove = currentMove;
8499 HistorySet(parseList, backwardMostMove, forwardMostMove,
8507 if (appData.noChessProgram) return;
8509 case MachinePlaysWhite:
8510 if (WhiteOnMove(forwardMostMove)) {
8511 DisplayError(_("Wait until your turn"), 0);
8515 case BeginningOfGame:
8516 case MachinePlaysBlack:
8517 if (!WhiteOnMove(forwardMostMove)) {
8518 DisplayError(_("Wait until your turn"), 0);
8523 DisplayError(_("No hint available"), 0);
8526 SendToProgram("hint\n", &first);
8527 hintRequested = TRUE;
8533 if (appData.noChessProgram) return;
8535 case MachinePlaysWhite:
8536 if (WhiteOnMove(forwardMostMove)) {
8537 DisplayError(_("Wait until your turn"), 0);
8541 case BeginningOfGame:
8542 case MachinePlaysBlack:
8543 if (!WhiteOnMove(forwardMostMove)) {
8544 DisplayError(_("Wait until your turn"), 0);
8551 case TwoMachinesPlay:
8556 SendToProgram("bk\n", &first);
8557 bookOutput[0] = NULLCHAR;
8558 bookRequested = TRUE;
8564 char *tags = PGNTags(&gameInfo);
8565 TagsPopUp(tags, CmailMsg());
8569 /* end button procedures */
8572 PrintPosition(fp, move)
8578 for (i = BOARD_SIZE - 1; i >= 0; i--) {
8579 for (j = 0; j < BOARD_SIZE; j++) {
8580 char c = PieceToChar(boards[move][i][j]);
8581 fputc(c == 'x' ? '.' : c, fp);
8582 fputc(j == BOARD_SIZE - 1 ? '\n' : ' ', fp);
8585 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
8586 fprintf(fp, "white to play\n");
8588 fprintf(fp, "black to play\n");
8595 if (gameInfo.white != NULL) {
8596 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
8602 /* Find last component of program's own name, using some heuristics */
8604 TidyProgramName(prog, host, buf)
8605 char *prog, *host, buf[MSG_SIZ];
8608 int local = (strcmp(host, "localhost") == 0);
8609 while (!local && (p = strchr(prog, ';')) != NULL) {
8611 while (*p == ' ') p++;
8614 if (*prog == '"' || *prog == '\'') {
8615 q = strchr(prog + 1, *prog);
8617 q = strchr(prog, ' ');
8619 if (q == NULL) q = prog + strlen(prog);
8621 while (p >= prog && *p != '/' && *p != '\\') p--;
8623 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
8624 memcpy(buf, p, q - p);
8625 buf[q - p] = NULLCHAR;
8633 TimeControlTagValue()
8636 if (!appData.clockMode) {
8638 } else if (movesPerSession > 0) {
8639 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
8640 } else if (timeIncrement == 0) {
8641 sprintf(buf, "%ld", timeControl/1000);
8643 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
8645 return StrSave(buf);
8651 /* This routine is used only for certain modes */
8652 VariantClass v = gameInfo.variant;
8653 ClearGameInfo(&gameInfo);
8654 gameInfo.variant = v;
8657 case MachinePlaysWhite:
8658 gameInfo.event = StrSave("Computer chess game");
8659 gameInfo.site = StrSave(HostName());
8660 gameInfo.date = PGNDate();
8661 gameInfo.round = StrSave("-");
8662 gameInfo.white = StrSave(first.tidy);
8663 gameInfo.black = StrSave(UserName());
8664 gameInfo.timeControl = TimeControlTagValue();
8667 case MachinePlaysBlack:
8668 gameInfo.event = StrSave("Computer chess game");
8669 gameInfo.site = StrSave(HostName());
8670 gameInfo.date = PGNDate();
8671 gameInfo.round = StrSave("-");
8672 gameInfo.white = StrSave(UserName());
8673 gameInfo.black = StrSave(first.tidy);
8674 gameInfo.timeControl = TimeControlTagValue();
8677 case TwoMachinesPlay:
8678 gameInfo.event = StrSave("Computer chess game");
8679 gameInfo.site = StrSave(HostName());
8680 gameInfo.date = PGNDate();
8681 if (matchGame > 0) {
8683 sprintf(buf, "%d", matchGame);
8684 gameInfo.round = StrSave(buf);
8686 gameInfo.round = StrSave("-");
8688 if (first.twoMachinesColor[0] == 'w') {
8689 gameInfo.white = StrSave(first.tidy);
8690 gameInfo.black = StrSave(second.tidy);
8692 gameInfo.white = StrSave(second.tidy);
8693 gameInfo.black = StrSave(first.tidy);
8695 gameInfo.timeControl = TimeControlTagValue();
8699 gameInfo.event = StrSave("Edited game");
8700 gameInfo.site = StrSave(HostName());
8701 gameInfo.date = PGNDate();
8702 gameInfo.round = StrSave("-");
8703 gameInfo.white = StrSave("-");
8704 gameInfo.black = StrSave("-");
8708 gameInfo.event = StrSave("Edited position");
8709 gameInfo.site = StrSave(HostName());
8710 gameInfo.date = PGNDate();
8711 gameInfo.round = StrSave("-");
8712 gameInfo.white = StrSave("-");
8713 gameInfo.black = StrSave("-");
8716 case IcsPlayingWhite:
8717 case IcsPlayingBlack:
8722 case PlayFromGameFile:
8723 gameInfo.event = StrSave("Game from non-PGN file");
8724 gameInfo.site = StrSave(HostName());
8725 gameInfo.date = PGNDate();
8726 gameInfo.round = StrSave("-");
8727 gameInfo.white = StrSave("?");
8728 gameInfo.black = StrSave("?");
8737 ReplaceComment(index, text)
8743 while (*text == '\n') text++;
8745 while (len > 0 && text[len - 1] == '\n') len--;
8747 if (commentList[index] != NULL)
8748 free(commentList[index]);
8751 commentList[index] = NULL;
8754 commentList[index] = (char *) malloc(len + 2);
8755 strncpy(commentList[index], text, len);
8756 commentList[index][len] = '\n';
8757 commentList[index][len + 1] = NULLCHAR;
8770 if (ch == '\r') continue;
8772 } while (ch != '\0');
8776 AppendComment(index, text)
8784 while (*text == '\n') text++;
8786 while (len > 0 && text[len - 1] == '\n') len--;
8788 if (len == 0) return;
8790 if (commentList[index] != NULL) {
8791 old = commentList[index];
8792 oldlen = strlen(old);
8793 commentList[index] = (char *) malloc(oldlen + len + 2);
8794 strcpy(commentList[index], old);
8796 strncpy(&commentList[index][oldlen], text, len);
8797 commentList[index][oldlen + len] = '\n';
8798 commentList[index][oldlen + len + 1] = NULLCHAR;
8800 commentList[index] = (char *) malloc(len + 2);
8801 strncpy(commentList[index], text, len);
8802 commentList[index][len] = '\n';
8803 commentList[index][len + 1] = NULLCHAR;
8808 SendToProgram(message, cps)
8810 ChessProgramState *cps;
8812 int count, outCount, error;
8815 if (cps->pr == NULL) return;
8818 if (appData.debugMode) {
8821 fprintf(debugFP, "%ld >%-6s: %s",
8822 SubtractTimeMarks(&now, &programStartTime),
8823 cps->which, message);
8826 count = strlen(message);
8827 outCount = OutputToProcess(cps->pr, message, count, &error);
8828 if (outCount < count && !exiting) {
8829 sprintf(buf, _("Error writing to %s chess program"), cps->which);
8830 DisplayFatalError(buf, error, 1);
8835 ReceiveFromProgram(isr, closure, message, count, error)
8844 ChessProgramState *cps = (ChessProgramState *)closure;
8846 if (isr != cps->isr) return; /* Killed intentionally */
8850 _("Error: %s chess program (%s) exited unexpectedly"),
8851 cps->which, cps->program);
8852 RemoveInputSource(cps->isr);
8853 DisplayFatalError(buf, 0, 1);
8856 _("Error reading from %s chess program (%s)"),
8857 cps->which, cps->program);
8858 RemoveInputSource(cps->isr);
8859 DisplayFatalError(buf, error, 1);
8861 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8865 if ((end_str = strchr(message, '\r')) != NULL)
8866 *end_str = NULLCHAR;
8867 if ((end_str = strchr(message, '\n')) != NULL)
8868 *end_str = NULLCHAR;
8870 if (appData.debugMode) {
8873 fprintf(debugFP, "%ld <%-6s: %s\n",
8874 SubtractTimeMarks(&now, &programStartTime),
8875 cps->which, message);
8877 HandleMachineMove(message, cps);
8882 SendTimeControl(cps, mps, tc, inc, sd, st)
8883 ChessProgramState *cps;
8884 int mps, inc, sd, st;
8888 int seconds = (tc / 1000) % 60;
8891 /* Set exact time per move, normally using st command */
8892 if (cps->stKludge) {
8893 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
8896 sprintf(buf, "level 1 %d\n", st/60);
8898 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
8901 sprintf(buf, "st %d\n", st);
8904 /* Set conventional or incremental time control, using level command */
8906 /* Note old gnuchess bug -- minutes:seconds used to not work.
8907 Fixed in later versions, but still avoid :seconds
8908 when seconds is 0. */
8909 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
8911 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
8915 SendToProgram(buf, cps);
8917 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
8918 /* Orthogonally, limit search to given depth */
8920 if (cps->sdKludge) {
8921 sprintf(buf, "depth\n%d\n", sd);
8923 sprintf(buf, "sd %d\n", sd);
8925 SendToProgram(buf, cps);
8930 SendTimeRemaining(cps, machineWhite)
8931 ChessProgramState *cps;
8932 int /*boolean*/ machineWhite;
8934 char message[MSG_SIZ];
8937 /* Note: this routine must be called when the clocks are stopped
8938 or when they have *just* been set or switched; otherwise
8939 it will be off by the time since the current tick started.
8942 time = whiteTimeRemaining / 10;
8943 otime = blackTimeRemaining / 10;
8945 time = blackTimeRemaining / 10;
8946 otime = whiteTimeRemaining / 10;
8948 if (time <= 0) time = 1;
8949 if (otime <= 0) otime = 1;
8951 sprintf(message, "time %ld\notim %ld\n", time, otime);
8952 SendToProgram(message, cps);
8956 BoolFeature(p, name, loc, cps)
8960 ChessProgramState *cps;
8963 int len = strlen(name);
8965 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
8967 sscanf(*p, "%d", &val);
8969 while (**p && **p != ' ') (*p)++;
8970 sprintf(buf, "accepted %s\n", name);
8971 SendToProgram(buf, cps);
8978 IntFeature(p, name, loc, cps)
8982 ChessProgramState *cps;
8985 int len = strlen(name);
8986 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
8988 sscanf(*p, "%d", loc);
8989 while (**p && **p != ' ') (*p)++;
8990 sprintf(buf, "accepted %s\n", name);
8991 SendToProgram(buf, cps);
8998 StringFeature(p, name, loc, cps)
9002 ChessProgramState *cps;
9005 int len = strlen(name);
9006 if (strncmp((*p), name, len) == 0
9007 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
9009 sscanf(*p, "%[^\"]", loc);
9010 while (**p && **p != '\"') (*p)++;
9011 if (**p == '\"') (*p)++;
9012 sprintf(buf, "accepted %s\n", name);
9013 SendToProgram(buf, cps);
9020 FeatureDone(cps, val)
9021 ChessProgramState* cps;
9024 DelayedEventCallback cb = GetDelayedEvent();
9025 if ((cb == InitBackEnd3 && cps == &first) ||
9026 (cb == TwoMachinesEventIfReady && cps == &second)) {
9027 CancelDelayedEvent();
9028 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
9030 cps->initDone = val;
9033 /* Parse feature command from engine */
9035 ParseFeatures(args, cps)
9037 ChessProgramState *cps;
9045 while (*p == ' ') p++;
9046 if (*p == NULLCHAR) return;
9048 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
9049 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
9050 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
9051 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
9052 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
9053 if (BoolFeature(&p, "reuse", &val, cps)) {
9054 /* Engine can disable reuse, but can't enable it if user said no */
9055 if (!val) cps->reuse = FALSE;
9058 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
9059 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
9060 if (gameMode == TwoMachinesPlay) {
9061 DisplayTwoMachinesTitle();
9067 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
9068 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
9069 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
9070 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
9071 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
9072 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
9073 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
9074 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
9075 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
9076 if (IntFeature(&p, "done", &val, cps)) {
9077 FeatureDone(cps, val);
9081 /* unknown feature: complain and skip */
9083 while (*q && *q != '=') q++;
9084 sprintf(buf, "rejected %.*s\n", q-p, p);
9085 SendToProgram(buf, cps);
9091 while (*p && *p != '\"') p++;
9092 if (*p == '\"') p++;
9094 while (*p && *p != ' ') p++;
9102 PeriodicUpdatesEvent(newState)
9105 if (newState == appData.periodicUpdates)
9108 appData.periodicUpdates=newState;
9110 /* Display type changes, so update it now */
9113 /* Get the ball rolling again... */
9115 AnalysisPeriodicEvent(1);
9116 StartAnalysisClock();
9121 PonderNextMoveEvent(newState)
9124 if (newState == appData.ponderNextMove) return;
9125 if (gameMode == EditPosition) EditPositionDone();
9127 SendToProgram("hard\n", &first);
9128 if (gameMode == TwoMachinesPlay) {
9129 SendToProgram("hard\n", &second);
9132 SendToProgram("easy\n", &first);
9133 thinkOutput[0] = NULLCHAR;
9134 if (gameMode == TwoMachinesPlay) {
9135 SendToProgram("easy\n", &second);
9138 appData.ponderNextMove = newState;
9142 ShowThinkingEvent(newState)
9145 if (newState == appData.showThinking) return;
9146 if (gameMode == EditPosition) EditPositionDone();
9148 SendToProgram("post\n", &first);
9149 if (gameMode == TwoMachinesPlay) {
9150 SendToProgram("post\n", &second);
9153 SendToProgram("nopost\n", &first);
9154 thinkOutput[0] = NULLCHAR;
9155 if (gameMode == TwoMachinesPlay) {
9156 SendToProgram("nopost\n", &second);
9159 appData.showThinking = newState;
9163 AskQuestionEvent(title, question, replyPrefix, which)
9164 char *title; char *question; char *replyPrefix; char *which;
9166 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
9167 if (pr == NoProc) return;
9168 AskQuestion(title, question, replyPrefix, pr);
9172 DisplayMove(moveNumber)
9175 char message[MSG_SIZ];
9177 char cpThinkOutput[MSG_SIZ];
9179 if (moveNumber == forwardMostMove - 1 ||
9180 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
9182 strcpy(cpThinkOutput, thinkOutput);
9183 if (strchr(cpThinkOutput, '\n'))
9184 *strchr(cpThinkOutput, '\n') = NULLCHAR;
9186 *cpThinkOutput = NULLCHAR;
9189 if (moveNumber == forwardMostMove - 1 &&
9190 gameInfo.resultDetails != NULL) {
9191 if (gameInfo.resultDetails[0] == NULLCHAR) {
9192 sprintf(res, " %s", PGNResult(gameInfo.result));
9194 sprintf(res, " {%s} %s",
9195 gameInfo.resultDetails, PGNResult(gameInfo.result));
9201 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
9202 DisplayMessage(res, cpThinkOutput);
9204 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
9205 WhiteOnMove(moveNumber) ? " " : ".. ",
9206 parseList[moveNumber], res);
9207 DisplayMessage(message, cpThinkOutput);
9212 DisplayAnalysisText(text)
9217 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
9218 sprintf(buf, "Analysis (%s)", first.tidy);
9219 AnalysisPopUp(buf, text);
9227 while (*str && isspace(*str)) ++str;
9228 while (*str && !isspace(*str)) ++str;
9229 if (!*str) return 1;
9230 while (*str && isspace(*str)) ++str;
9231 if (!*str) return 1;
9240 static char *xtra[] = { "", " (--)", " (++)" };
9243 if (programStats.time == 0) {
9244 programStats.time = 1;
9247 if (programStats.got_only_move) {
9248 strcpy(buf, programStats.movelist);
9250 nps = (((double)programStats.nodes) /
9251 (((double)programStats.time)/100.0));
9253 cs = programStats.time % 100;
9254 s = programStats.time / 100;
9260 if (programStats.moves_left > 0 && appData.periodicUpdates) {
9261 if (programStats.move_name[0] != NULLCHAR) {
9262 sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",
9264 programStats.nr_moves-programStats.moves_left,
9265 programStats.nr_moves, programStats.move_name,
9266 ((float)programStats.score)/100.0, programStats.movelist,
9267 only_one_move(programStats.movelist)?
9268 xtra[programStats.got_fail] : "",
9269 programStats.nodes, (int)nps, h, m, s, cs);
9271 sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",
9273 programStats.nr_moves-programStats.moves_left,
9274 programStats.nr_moves, ((float)programStats.score)/100.0,
9275 programStats.movelist,
9276 only_one_move(programStats.movelist)?
9277 xtra[programStats.got_fail] : "",
9278 programStats.nodes, (int)nps, h, m, s, cs);
9281 sprintf(buf, "depth=%d %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",
9283 ((float)programStats.score)/100.0,
9284 programStats.movelist,
9285 only_one_move(programStats.movelist)?
9286 xtra[programStats.got_fail] : "",
9287 programStats.nodes, (int)nps, h, m, s, cs);
9290 DisplayAnalysisText(buf);
9294 DisplayComment(moveNumber, text)
9298 char title[MSG_SIZ];
9300 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
9301 strcpy(title, "Comment");
9303 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
9304 WhiteOnMove(moveNumber) ? " " : ".. ",
9305 parseList[moveNumber]);
9308 CommentPopUp(title, text);
9311 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
9312 * might be busy thinking or pondering. It can be omitted if your
9313 * gnuchess is configured to stop thinking immediately on any user
9314 * input. However, that gnuchess feature depends on the FIONREAD
9315 * ioctl, which does not work properly on some flavors of Unix.
9319 ChessProgramState *cps;
9322 if (!cps->useSigint) return;
9323 if (appData.noChessProgram || (cps->pr == NoProc)) return;
9325 case MachinePlaysWhite:
9326 case MachinePlaysBlack:
9327 case TwoMachinesPlay:
9328 case IcsPlayingWhite:
9329 case IcsPlayingBlack:
9332 /* Skip if we know it isn't thinking */
9333 if (!cps->maybeThinking) return;
9334 if (appData.debugMode)
9335 fprintf(debugFP, "Interrupting %s\n", cps->which);
9336 InterruptChildProcess(cps->pr);
9337 cps->maybeThinking = FALSE;
9342 #endif /*ATTENTION*/
9348 if (whiteTimeRemaining <= 0) {
9351 if (appData.icsActive) {
9352 if (appData.autoCallFlag &&
9353 gameMode == IcsPlayingBlack && !blackFlag) {
9354 SendToICS(ics_prefix);
9355 SendToICS("flag\n");
9359 DisplayTitle(_("Both flags fell"));
9361 DisplayTitle(_("White's flag fell"));
9362 if (appData.autoCallFlag) {
9363 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
9370 if (blackTimeRemaining <= 0) {
9373 if (appData.icsActive) {
9374 if (appData.autoCallFlag &&
9375 gameMode == IcsPlayingWhite && !whiteFlag) {
9376 SendToICS(ics_prefix);
9377 SendToICS("flag\n");
9381 DisplayTitle(_("Both flags fell"));
9383 DisplayTitle(_("Black's flag fell"));
9384 if (appData.autoCallFlag) {
9385 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
9398 if (!appData.clockMode || appData.icsActive ||
9399 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
9401 if (timeIncrement >= 0) {
9402 if (WhiteOnMove(forwardMostMove)) {
9403 blackTimeRemaining += timeIncrement;
9405 whiteTimeRemaining += timeIncrement;
9409 * add time to clocks when time control is achieved
9411 if (movesPerSession) {
9412 switch ((forwardMostMove + 1) % (movesPerSession * 2)) {
9414 /* White made time control */
9415 whiteTimeRemaining += timeControl;
9418 /* Black made time control */
9419 blackTimeRemaining += timeControl;
9430 int wom = gameMode == EditPosition ?
9431 !blackPlaysFirst : WhiteOnMove(currentMove);
9432 DisplayWhiteClock(whiteTimeRemaining, wom);
9433 DisplayBlackClock(blackTimeRemaining, !wom);
9437 /* Timekeeping seems to be a portability nightmare. I think everyone
9438 has ftime(), but I'm really not sure, so I'm including some ifdefs
9439 to use other calls if you don't. Clocks will be less accurate if
9440 you have neither ftime nor gettimeofday.
9443 /* Get the current time as a TimeMark */
9448 #if HAVE_GETTIMEOFDAY
9450 struct timeval timeVal;
9451 struct timezone timeZone;
9453 gettimeofday(&timeVal, &timeZone);
9454 tm->sec = (long) timeVal.tv_sec;
9455 tm->ms = (int) (timeVal.tv_usec / 1000L);
9457 #else /*!HAVE_GETTIMEOFDAY*/
9460 #include <sys/timeb.h>
9464 tm->sec = (long) timeB.time;
9465 tm->ms = (int) timeB.millitm;
9467 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
9468 tm->sec = (long) time(NULL);
9474 /* Return the difference in milliseconds between two
9475 time marks. We assume the difference will fit in a long!
9478 SubtractTimeMarks(tm2, tm1)
9479 TimeMark *tm2, *tm1;
9481 return 1000L*(tm2->sec - tm1->sec) +
9482 (long) (tm2->ms - tm1->ms);
9487 * Code to manage the game clocks.
9489 * In tournament play, black starts the clock and then white makes a move.
9490 * We give the human user a slight advantage if he is playing white---the
9491 * clocks don't run until he makes his first move, so it takes zero time.
9492 * Also, we don't account for network lag, so we could get out of sync
9493 * with GNU Chess's clock -- but then, referees are always right.
9496 static TimeMark tickStartTM;
9497 static long intendedTickLength;
9500 NextTickLength(timeRemaining)
9503 long nominalTickLength, nextTickLength;
9505 if (timeRemaining > 0L && timeRemaining <= 10000L)
9506 nominalTickLength = 100L;
9508 nominalTickLength = 1000L;
9509 nextTickLength = timeRemaining % nominalTickLength;
9510 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
9512 return nextTickLength;
9515 /* Stop clocks and reset to a fresh time control */
9519 (void) StopClockTimer();
9520 if (appData.icsActive) {
9521 whiteTimeRemaining = blackTimeRemaining = 0;
9523 whiteTimeRemaining = blackTimeRemaining = timeControl;
9525 if (whiteFlag || blackFlag) {
9527 whiteFlag = blackFlag = FALSE;
9529 DisplayBothClocks();
9532 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
9534 /* Decrement running clock by amount of time that has passed */
9539 long lastTickLength, fudge;
9542 if (!appData.clockMode) return;
9543 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
9547 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
9549 /* Fudge if we woke up a little too soon */
9550 fudge = intendedTickLength - lastTickLength;
9551 if (fudge < 0 || fudge > FUDGE) fudge = 0;
9553 if (WhiteOnMove(forwardMostMove)) {
9554 timeRemaining = whiteTimeRemaining -= lastTickLength;
9555 DisplayWhiteClock(whiteTimeRemaining - fudge,
9556 WhiteOnMove(currentMove));
9558 timeRemaining = blackTimeRemaining -= lastTickLength;
9559 DisplayBlackClock(blackTimeRemaining - fudge,
9560 !WhiteOnMove(currentMove));
9563 if (CheckFlags()) return;
9566 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
9567 StartClockTimer(intendedTickLength);
9569 /* if the time remaining has fallen below the alarm threshold, sound the
9570 * alarm. if the alarm has sounded and (due to a takeback or time control
9571 * with increment) the time remaining has increased to a level above the
9572 * threshold, reset the alarm so it can sound again.
9575 if (appData.icsActive && appData.icsAlarm) {
9577 /* make sure we are dealing with the user's clock */
9578 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
9579 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
9582 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
9583 alarmSounded = FALSE;
9584 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
9586 alarmSounded = TRUE;
9592 /* A player has just moved, so stop the previously running
9593 clock and (if in clock mode) start the other one.
9594 We redisplay both clocks in case we're in ICS mode, because
9595 ICS gives us an update to both clocks after every move.
9596 Note that this routine is called *after* forwardMostMove
9597 is updated, so the last fractional tick must be subtracted
9598 from the color that is *not* on move now.
9603 long lastTickLength;
9605 int flagged = FALSE;
9609 if (StopClockTimer() && appData.clockMode) {
9610 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
9611 if (WhiteOnMove(forwardMostMove)) {
9612 blackTimeRemaining -= lastTickLength;
9614 whiteTimeRemaining -= lastTickLength;
9616 flagged = CheckFlags();
9620 if (flagged || !appData.clockMode) return;
9623 case MachinePlaysBlack:
9624 case MachinePlaysWhite:
9625 case BeginningOfGame:
9626 if (pausing) return;
9630 case PlayFromGameFile:
9639 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
9640 whiteTimeRemaining : blackTimeRemaining);
9641 StartClockTimer(intendedTickLength);
9645 /* Stop both clocks */
9649 long lastTickLength;
9652 if (!StopClockTimer()) return;
9653 if (!appData.clockMode) return;
9657 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
9658 if (WhiteOnMove(forwardMostMove)) {
9659 whiteTimeRemaining -= lastTickLength;
9660 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
9662 blackTimeRemaining -= lastTickLength;
9663 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
9668 /* Start clock of player on move. Time may have been reset, so
9669 if clock is already running, stop and restart it. */
9673 (void) StopClockTimer(); /* in case it was running already */
9674 DisplayBothClocks();
9675 if (CheckFlags()) return;
9677 if (!appData.clockMode) return;
9678 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
9680 GetTimeMark(&tickStartTM);
9681 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
9682 whiteTimeRemaining : blackTimeRemaining);
9683 StartClockTimer(intendedTickLength);
9690 long second, minute, hour, day;
9692 static char buf[32];
9694 if (ms > 0 && ms <= 9900) {
9695 /* convert milliseconds to tenths, rounding up */
9696 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
9698 sprintf(buf, " %03.1f ", tenths/10.0);
9702 /* convert milliseconds to seconds, rounding up */
9703 /* use floating point to avoid strangeness of integer division
9704 with negative dividends on many machines */
9705 second = (long) floor(((double) (ms + 999L)) / 1000.0);
9712 day = second / (60 * 60 * 24);
9713 second = second % (60 * 60 * 24);
9714 hour = second / (60 * 60);
9715 second = second % (60 * 60);
9716 minute = second / 60;
9717 second = second % 60;
9720 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
9721 sign, day, hour, minute, second);
9723 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
9725 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
9732 * This is necessary because some C libraries aren't ANSI C compliant yet.
9735 StrStr(string, match)
9736 char *string, *match;
9740 length = strlen(match);
9742 for (i = strlen(string) - length; i >= 0; i--, string++)
9743 if (!strncmp(match, string, length))
9750 StrCaseStr(string, match)
9751 char *string, *match;
9755 length = strlen(match);
9757 for (i = strlen(string) - length; i >= 0; i--, string++) {
9758 for (j = 0; j < length; j++) {
9759 if (ToLower(match[j]) != ToLower(string[j]))
9762 if (j == length) return string;
9776 c1 = ToLower(*s1++);
9777 c2 = ToLower(*s2++);
9778 if (c1 > c2) return 1;
9779 if (c1 < c2) return -1;
9780 if (c1 == NULLCHAR) return 0;
9789 return isupper(c) ? tolower(c) : c;
9797 return islower(c) ? toupper(c) : c;
9799 #endif /* !_amigados */
9807 if ((ret = (char *) malloc(strlen(s) + 1))) {
9814 StrSavePtr(s, savePtr)
9820 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
9821 strcpy(*savePtr, s);
9833 clock = time((time_t *)NULL);
9834 tm = localtime(&clock);
9835 sprintf(buf, "%04d.%02d.%02d",
9836 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
9837 return StrSave(buf);
9845 int i, j, fromX, fromY, toX, toY;
9851 whiteToPlay = (gameMode == EditPosition) ?
9852 !blackPlaysFirst : (move % 2 == 0);
9855 /* Piece placement data */
9856 for (i = BOARD_SIZE - 1; i >= 0; i--) {
9858 for (j = 0; j < BOARD_SIZE; j++) {
9859 if (boards[move][i][j] == EmptySquare) {
9862 if (emptycount > 0) {
9863 *p++ = '0' + emptycount;
9866 *p++ = PieceToChar(boards[move][i][j]);
9869 if (emptycount > 0) {
9870 *p++ = '0' + emptycount;
9878 *p++ = whiteToPlay ? 'w' : 'b';
9881 /* !!We don't keep track of castling availability, so fake it */
9883 if (boards[move][0][4] == WhiteKing) {
9884 if (boards[move][0][7] == WhiteRook) *p++ = 'K';
9885 if (boards[move][0][0] == WhiteRook) *p++ = 'Q';
9887 if (boards[move][7][4] == BlackKing) {
9888 if (boards[move][7][7] == BlackRook) *p++ = 'k';
9889 if (boards[move][7][0] == BlackRook) *p++ = 'q';
9891 if (q == p) *p++ = '-';
9894 /* En passant target square */
9895 if (move > backwardMostMove) {
9896 fromX = moveList[move - 1][0] - 'a';
9897 fromY = moveList[move - 1][1] - '1';
9898 toX = moveList[move - 1][2] - 'a';
9899 toY = moveList[move - 1][3] - '1';
9900 if (fromY == (whiteToPlay ? 6 : 1) &&
9901 toY == (whiteToPlay ? 4 : 3) &&
9902 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
9904 /* 2-square pawn move just happened */
9906 *p++ = whiteToPlay ? '6' : '3';
9914 /* !!We don't keep track of halfmove clock for 50-move rule */
9918 /* Fullmove number */
9919 sprintf(p, "%d", (move / 2) + 1);
9921 return StrSave(buf);
9925 ParseFEN(board, blackPlaysFirst, fen)
9927 int *blackPlaysFirst;
9936 /* Piece placement data */
9937 for (i = BOARD_SIZE - 1; i >= 0; i--) {
9940 if (*p == '/' || *p == ' ') {
9942 emptycount = BOARD_SIZE - j;
9943 while (emptycount--) board[i][j++] = EmptySquare;
9945 } else if (isdigit(*p)) {
9946 emptycount = *p++ - '0';
9947 if (j + emptycount > BOARD_SIZE) return FALSE;
9948 while (emptycount--) board[i][j++] = EmptySquare;
9949 } else if (isalpha(*p)) {
9950 if (j >= BOARD_SIZE) return FALSE;
9951 board[i][j++] = CharToPiece(*p++);
9957 while (*p == '/' || *p == ' ') p++;
9962 *blackPlaysFirst = FALSE;
9965 *blackPlaysFirst = TRUE;
9970 /* !!We ignore the rest of the FEN notation */
9975 EditPositionPasteFEN(char *fen)
9978 Board initial_position;
9980 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
9981 DisplayError(_("Bad FEN position in clipboard"), 0);
9984 int savedBlackPlaysFirst = blackPlaysFirst;
9985 EditPositionEvent();
9986 blackPlaysFirst = savedBlackPlaysFirst;
9987 CopyBoard(boards[0], initial_position);
9989 DisplayBothClocks();
9990 DrawPosition(FALSE, boards[currentMove]);